andevs commited on
Commit
b73c039
·
verified ·
1 Parent(s): 024e641

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +201 -114
app.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- StudyFlow AI Backend - Lightweight Version for Hugging Face Spaces
3
  """
4
  import os
5
  import json
@@ -12,7 +12,6 @@ from pathlib import Path
12
  from fastapi import FastAPI, UploadFile, File, Form, HTTPException
13
  from fastapi.responses import JSONResponse, FileResponse, HTMLResponse
14
  from fastapi.middleware.cors import CORSMiddleware
15
- from fastapi.staticfiles import StaticFiles
16
  import PyPDF2
17
  from youtube_transcript_api import YouTubeTranscriptApi
18
  import requests
@@ -143,81 +142,140 @@ def extract_text_from_pdf(file_path: str) -> str:
143
  pdf_reader = PyPDF2.PdfReader(file)
144
  text = ""
145
  for page in pdf_reader.pages:
146
- text += page.extract_text() + "\n"
147
- return text
 
 
148
  except Exception as e:
149
- return f"Error extracting PDF: {str(e)}"
150
 
151
  def extract_text_from_youtube(url: str) -> str:
152
  """Extract transcript from YouTube video"""
153
  try:
154
- video_id = url.split("v=")[-1].split("&")[0]
 
 
 
 
 
 
 
155
  transcript = YouTubeTranscriptApi.get_transcript(video_id)
156
  text = " ".join([entry['text'] for entry in transcript])
157
- return text
158
  except Exception as e:
159
- return f"Error extracting YouTube transcript: {str(e)}"
160
 
161
  def generate_questions(text: str, difficulty: str, count: int = 5) -> list:
162
- """Generate questions from text (simplified for demo)"""
163
- sentences = [s.strip() for s in text.split('.') if len(s.strip()) > 50]
164
- sentences = sentences[:10] # Take first 10 meaningful sentences
 
 
 
 
 
165
 
166
  questions = []
167
  for i, sentence in enumerate(sentences[:count]):
 
 
 
168
  qid = generate_id(f"q_{i}_{sentence[:20]}")
169
 
170
- # Simple question generation logic
171
- words = sentence.split()
172
- if len(words) > 8:
173
- # Create a fill-in-the-blank question
174
- blank_word = words[len(words)//2]
175
- question_text = sentence.replace(blank_word, "_____")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  questions.append({
177
  "id": qid,
178
  "question_text": question_text,
179
- "question_type": "fill_blank",
180
- "correct_answer": blank_word,
181
  "difficulty": difficulty,
182
- "explanation": f"The missing word is '{blank_word}', which fits grammatically and contextually."
183
  })
184
- else:
185
- # Create a multiple choice question
186
- question_text = f"What is the main idea of: '{sentence[:100]}...'?"
 
187
  options = [
188
- "It describes a process",
189
- "It states a fact",
190
- "It presents an argument",
191
- "It asks a question"
192
  ]
193
  questions.append({
194
  "id": qid,
195
  "question_text": question_text,
196
  "question_type": "multiple_choice",
197
  "options": json.dumps(options),
198
- "correct_answer": "0", # Default to first option
199
  "difficulty": difficulty,
200
- "explanation": "This sentence appears to describe a process or sequence of events."
201
  })
202
 
203
  return questions
204
 
205
  def generate_flashcards(text: str, count: int = 3) -> list:
206
  """Generate flashcards from text"""
207
- sentences = [s.strip() for s in text.split('.') if len(s.strip()) > 30]
208
- sentences = sentences[:count * 2] # Take pairs for front/back
 
209
 
210
  flashcards = []
211
- for i in range(0, min(len(sentences), count * 2), 2):
212
- if i + 1 < len(sentences):
213
- fcid = generate_id(f"fc_{i}")
214
- flashcards.append({
215
- "id": fcid,
216
- "front": sentences[i][:100],
217
- "back": sentences[i + 1][:200],
218
- "category": "General",
219
- "difficulty": "medium"
220
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
  return flashcards
223
 
@@ -236,28 +294,23 @@ async def process_content(
236
  # Extract text based on content type
237
  text_content = ""
238
  if content_type == "text" and content:
239
- text_content = content
240
  elif content_type == "pdf" and file:
241
  # Save uploaded file temporarily
242
- temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf")
243
- content_bytes = await file.read()
244
- temp_file.write(content_bytes)
245
- temp_file.close()
246
- text_content = extract_text_from_pdf(temp_file.name)
247
- os.unlink(temp_file.name)
 
248
  elif content_type == "youtube" and youtube_url:
249
  text_content = extract_text_from_youtube(youtube_url)
250
  else:
251
- return JSONResponse(
252
- status_code=400,
253
- content={"error": "Invalid content type or missing content"}
254
- )
255
 
256
- if len(text_content) < 100:
257
- return JSONResponse(
258
- status_code=400,
259
- content={"error": "Content too short (minimum 100 characters)"}
260
- )
261
 
262
  # Generate content hash
263
  content_hash = hashlib.md5(text_content.encode()).hexdigest()
@@ -300,7 +353,7 @@ async def process_content(
300
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
301
  ''', (
302
  q["id"], session_id, q["question_text"], q["question_type"],
303
- q.get("options"), q["correct_answer"], q["difficulty"], q.get("explanation")
304
  ))
305
 
306
  # Save flashcards
@@ -313,15 +366,16 @@ async def process_content(
313
  fc["category"], fc["difficulty"]
314
  ))
315
 
316
- # Create initial notes
 
317
  cursor.execute('''
318
  INSERT INTO notes (id, session_id, title, content)
319
  VALUES (?, ?, ?, ?)
320
  ''', (
321
- generate_id("note1"),
322
  session_id,
323
- "Key Concepts",
324
- "Here are the key concepts from your study material..."
325
  ))
326
 
327
  conn.commit()
@@ -350,6 +404,12 @@ async def get_session(session_id: str):
350
  conn.close()
351
  raise HTTPException(status_code=404, detail="Session not found")
352
 
 
 
 
 
 
 
353
  # Get questions
354
  cursor.execute("SELECT * FROM questions WHERE session_id = ?", (session_id,))
355
  questions = [dict(row) for row in cursor.fetchall()]
@@ -372,9 +432,10 @@ async def get_session(session_id: str):
372
  accuracy = round((correct_answers / total_questions * 100) if total_questions > 0 else 0, 1)
373
  avg_time_spent = round(sum(q.get("time_spent", 0) for q in questions) / total_questions if total_questions > 0 else 0, 1)
374
 
 
375
  conn.close()
376
 
377
- return JSONResponse({
378
  "session": dict(session),
379
  "materials": {
380
  "questions": questions,
@@ -394,7 +455,7 @@ async def get_session(session_id: str):
394
  "accuracy": accuracy,
395
  "avg_time_spent": avg_time_spent
396
  }
397
- })
398
 
399
  @app.get("/api/user/sessions")
400
  async def get_user_sessions():
@@ -425,7 +486,7 @@ async def get_user_sessions():
425
 
426
  conn.close()
427
 
428
- return JSONResponse({"sessions": sessions})
429
 
430
  @app.post("/api/submit-answer")
431
  async def submit_answer(
@@ -450,7 +511,16 @@ async def submit_answer(
450
  raise HTTPException(status_code=404, detail="Question not found")
451
 
452
  correct_answer = result[0]
453
- is_correct = 1 if str(user_answer).strip().lower() == str(correct_answer).strip().lower() else 0
 
 
 
 
 
 
 
 
 
454
 
455
  # Update question with user answer
456
  cursor.execute('''
@@ -461,51 +531,59 @@ async def submit_answer(
461
 
462
  # Update user profile stats
463
  cursor.execute('''
464
- INSERT OR REPLACE INTO user_profile (id, total_questions, correct_answers, updated_at)
465
- VALUES (
466
- 1,
467
- COALESCE((SELECT total_questions FROM user_profile WHERE id = 1), 0) + 1,
468
- COALESCE((SELECT correct_answers FROM user_profile WHERE id = 1), 0) + ?,
469
- CURRENT_TIMESTAMP
470
- )
 
 
471
  ''', (is_correct,))
472
 
473
  conn.commit()
474
  conn.close()
475
 
476
- return JSONResponse({
477
  "is_correct": bool(is_correct),
478
  "correct_answer": correct_answer,
479
- "message": "Correct!" if is_correct else "Incorrect, try again!"
480
- })
481
 
482
  @app.post("/api/analyze-text")
483
  async def analyze_text(text: str = Form(...), session_id: str = Form(...)):
484
- """Analyze selected text (simplified version)"""
 
 
 
485
 
486
  # Simple text analysis
487
  words = text.split()
488
  definitions = []
489
 
490
- # Simple "definition" generation for demo
491
- for i, word in enumerate(words[:5]): # Analyze first 5 words
492
- if len(word) > 4: # Only "define" longer words
493
  definitions.append({
494
  "word": word,
495
- "definition": f"A term used in this context meaning: '{word}'",
496
- "source": "StudyFlow AI",
497
- "example": f"In the sentence: '{' '.join(words[max(0,i-2):min(len(words),i+3)])}'"
498
  })
499
 
500
- # Generate simple explanation
501
  word_count = len(words)
502
- explanation = f"This text contains {word_count} words. "
503
- if word_count < 10:
504
- explanation += "It's a short phrase or sentence fragment."
505
- elif word_count < 25:
506
- explanation += "It's a sentence or two expressing a complete thought."
507
  else:
508
- explanation += "It's a paragraph expressing multiple related ideas."
 
 
 
 
509
 
510
  # Save as highlight
511
  conn = sqlite3.connect(DB_PATH)
@@ -520,16 +598,16 @@ async def analyze_text(text: str = Form(...), session_id: str = Form(...)):
520
  conn.commit()
521
  conn.close()
522
 
523
- return JSONResponse({
524
  "text": text,
525
  "definitions": definitions,
526
  "explanation": explanation,
527
  "suggested_actions": [
528
- "Add to flashcards",
529
- "Create a practice question",
530
- "Make a note about this concept"
531
  ]
532
- })
533
 
534
  @app.get("/api/user/profile")
535
  async def get_user_profile():
@@ -543,8 +621,7 @@ async def get_user_profile():
543
  if not profile:
544
  # Create default profile
545
  cursor.execute('''
546
- INSERT INTO user_profile (id, total_questions, correct_answers, total_study_time)
547
- VALUES (1, 0, 0, 0)
548
  ''')
549
  conn.commit()
550
  cursor.execute("SELECT * FROM user_profile WHERE id = 1")
@@ -573,14 +650,20 @@ async def get_user_profile():
573
  elif accuracy > 0.8:
574
  strengths.append(f"{diff} difficulty questions")
575
 
 
 
 
 
 
 
576
  conn.close()
577
 
578
- return JSONResponse({
579
  "profile": {
580
- "total_questions": profile[1],
581
- "correct_answers": profile[2],
582
- "total_study_time": profile[3],
583
- "accuracy": round((profile[2] / profile[1] * 100) if profile[1] > 0 else 0, 1)
584
  },
585
  "insights": {
586
  "weaknesses": weak_areas,
@@ -588,12 +671,12 @@ async def get_user_profile():
588
  "recent_performance": [
589
  {
590
  "date": datetime.now().strftime("%Y-%m-%d"),
591
- "total": profile[1],
592
- "correct": profile[2]
593
  }
594
  ]
595
  }
596
- })
597
 
598
  @app.delete("/api/session/{session_id}")
599
  async def delete_session(session_id: str):
@@ -610,7 +693,7 @@ async def delete_session(session_id: str):
610
  if affected == 0:
611
  raise HTTPException(status_code=404, detail="Session not found")
612
 
613
- return JSONResponse({"message": "Session deleted successfully"})
614
 
615
  @app.post("/api/update-study-time")
616
  async def update_study_time(session_id: str = Form(...), study_time: int = Form(...)):
@@ -624,6 +707,10 @@ async def update_study_time(session_id: str = Form(...), study_time: int = Form(
624
  WHERE id = ?
625
  ''', (session_id,))
626
 
 
 
 
 
627
  cursor.execute('''
628
  UPDATE user_profile
629
  SET total_study_time = COALESCE(total_study_time, 0) + ?
@@ -633,9 +720,15 @@ async def update_study_time(session_id: str = Form(...), study_time: int = Form(
633
  conn.commit()
634
  conn.close()
635
 
636
- return JSONResponse({"message": "Study time updated"})
637
 
638
- # Serve static files
 
 
 
 
 
 
639
  @app.get("/")
640
  async def serve_frontend():
641
  """Serve the main frontend page"""
@@ -656,12 +749,6 @@ async def serve_js():
656
  except FileNotFoundError:
657
  return HTMLResponse(content="console.error('app.js not found');", media_type="application/javascript")
658
 
659
- # Health check endpoint
660
- @app.get("/health")
661
- async def health_check():
662
- """Health check endpoint"""
663
- return JSONResponse({"status": "healthy", "timestamp": datetime.now().isoformat()})
664
-
665
  if __name__ == "__main__":
666
  import uvicorn
667
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
  """
2
+ StudyFlow AI Backend - Complete Working Version for Hugging Face Spaces
3
  """
4
  import os
5
  import json
 
12
  from fastapi import FastAPI, UploadFile, File, Form, HTTPException
13
  from fastapi.responses import JSONResponse, FileResponse, HTMLResponse
14
  from fastapi.middleware.cors import CORSMiddleware
 
15
  import PyPDF2
16
  from youtube_transcript_api import YouTubeTranscriptApi
17
  import requests
 
142
  pdf_reader = PyPDF2.PdfReader(file)
143
  text = ""
144
  for page in pdf_reader.pages:
145
+ page_text = page.extract_text()
146
+ if page_text:
147
+ text += page_text + "\n"
148
+ return text[:5000] # Limit text length
149
  except Exception as e:
150
+ return f"PDF text extraction error: {str(e)}"
151
 
152
  def extract_text_from_youtube(url: str) -> str:
153
  """Extract transcript from YouTube video"""
154
  try:
155
+ # Extract video ID from URL
156
+ if "youtube.com/watch?v=" in url:
157
+ video_id = url.split("v=")[-1].split("&")[0]
158
+ elif "youtu.be/" in url:
159
+ video_id = url.split("/")[-1].split("?")[0]
160
+ else:
161
+ return "Invalid YouTube URL"
162
+
163
  transcript = YouTubeTranscriptApi.get_transcript(video_id)
164
  text = " ".join([entry['text'] for entry in transcript])
165
+ return text[:5000] # Limit text length
166
  except Exception as e:
167
+ return f"YouTube transcript error: {str(e)}"
168
 
169
  def generate_questions(text: str, difficulty: str, count: int = 5) -> list:
170
+ """Generate questions from text"""
171
+ # Split text into sentences
172
+ import re
173
+ sentences = re.split(r'[.!?]+', text)
174
+ sentences = [s.strip() for s in sentences if len(s.strip()) > 30]
175
+
176
+ if not sentences:
177
+ sentences = [text[:200]] # Use first 200 chars as fallback
178
 
179
  questions = []
180
  for i, sentence in enumerate(sentences[:count]):
181
+ if not sentence:
182
+ continue
183
+
184
  qid = generate_id(f"q_{i}_{sentence[:20]}")
185
 
186
+ # Simple question generation
187
+ if difficulty == "easy":
188
+ # Fill in the blank
189
+ words = sentence.split()
190
+ if len(words) > 3:
191
+ blank_index = len(words) // 2
192
+ blank_word = words[blank_index]
193
+ question_text = sentence.replace(blank_word, "_____", 1)
194
+ questions.append({
195
+ "id": qid,
196
+ "question_text": question_text,
197
+ "question_type": "fill_blank",
198
+ "correct_answer": blank_word,
199
+ "difficulty": difficulty,
200
+ "explanation": f"The missing word is '{blank_word}' which completes the sentence."
201
+ })
202
+ else:
203
+ # Multiple choice for short sentences
204
+ question_text = f"What is the meaning of: '{sentence}'?"
205
+ options = ["A factual statement", "An opinion", "A question", "A command"]
206
+ questions.append({
207
+ "id": qid,
208
+ "question_text": question_text,
209
+ "question_type": "multiple_choice",
210
+ "options": json.dumps(options),
211
+ "correct_answer": "0",
212
+ "difficulty": difficulty,
213
+ "explanation": "This appears to be a factual statement."
214
+ })
215
+
216
+ elif difficulty == "medium":
217
+ # True/False question
218
+ question_text = f"Is the following statement true? '{sentence}'"
219
  questions.append({
220
  "id": qid,
221
  "question_text": question_text,
222
+ "question_type": "true_false",
223
+ "correct_answer": "0", # True
224
  "difficulty": difficulty,
225
+ "explanation": "This statement appears to be true based on the context."
226
  })
227
+
228
+ else: # hard
229
+ # Multiple choice with context
230
+ question_text = f"What is the main idea of: '{sentence[:150]}...'?"
231
  options = [
232
+ "It describes a specific detail",
233
+ "It presents a general concept",
234
+ "It asks a rhetorical question",
235
+ "It provides an example"
236
  ]
237
  questions.append({
238
  "id": qid,
239
  "question_text": question_text,
240
  "question_type": "multiple_choice",
241
  "options": json.dumps(options),
242
+ "correct_answer": "1",
243
  "difficulty": difficulty,
244
+ "explanation": "This sentence appears to present a general concept rather than specific details."
245
  })
246
 
247
  return questions
248
 
249
  def generate_flashcards(text: str, count: int = 3) -> list:
250
  """Generate flashcards from text"""
251
+ import re
252
+ sentences = re.split(r'[.!?]+', text)
253
+ sentences = [s.strip() for s in sentences if len(s.strip()) > 20]
254
 
255
  flashcards = []
256
+ for i in range(min(len(sentences), count)):
257
+ sentence = sentences[i]
258
+ if not sentence:
259
+ continue
260
+
261
+ fcid = generate_id(f"fc_{i}_{sentence[:20]}")
262
+
263
+ # Create flashcard with term and definition
264
+ words = sentence.split()
265
+ if len(words) > 5:
266
+ term = " ".join(words[:3]) + "..."
267
+ definition = sentence
268
+ else:
269
+ term = sentence
270
+ definition = "Key concept from the study material"
271
+
272
+ flashcards.append({
273
+ "id": fcid,
274
+ "front": term[:100],
275
+ "back": definition[:200],
276
+ "category": "Study Material",
277
+ "difficulty": "medium"
278
+ })
279
 
280
  return flashcards
281
 
 
294
  # Extract text based on content type
295
  text_content = ""
296
  if content_type == "text" and content:
297
+ text_content = content[:10000] # Limit length
298
  elif content_type == "pdf" and file:
299
  # Save uploaded file temporarily
300
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as temp_file:
301
+ content_bytes = await file.read()
302
+ temp_file.write(content_bytes)
303
+ temp_file_path = temp_file.name
304
+
305
+ text_content = extract_text_from_pdf(temp_file_path)
306
+ os.unlink(temp_file_path)
307
  elif content_type == "youtube" and youtube_url:
308
  text_content = extract_text_from_youtube(youtube_url)
309
  else:
310
+ raise HTTPException(status_code=400, detail="Invalid content type or missing content")
 
 
 
311
 
312
+ if len(text_content) < 50:
313
+ raise HTTPException(status_code=400, detail="Content too short (minimum 50 characters)")
 
 
 
314
 
315
  # Generate content hash
316
  content_hash = hashlib.md5(text_content.encode()).hexdigest()
 
353
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
354
  ''', (
355
  q["id"], session_id, q["question_text"], q["question_type"],
356
+ q.get("options"), q["correct_answer"], q["difficulty"], q.get("explanation", "")
357
  ))
358
 
359
  # Save flashcards
 
366
  fc["category"], fc["difficulty"]
367
  ))
368
 
369
+ # Create initial note
370
+ note_id = generate_id(f"note_{session_id}")
371
  cursor.execute('''
372
  INSERT INTO notes (id, session_id, title, content)
373
  VALUES (?, ?, ?, ?)
374
  ''', (
375
+ note_id,
376
  session_id,
377
+ "Study Notes",
378
+ f"Key points from: {title}\n\nSummary: {text_content[:500]}..."
379
  ))
380
 
381
  conn.commit()
 
404
  conn.close()
405
  raise HTTPException(status_code=404, detail="Session not found")
406
 
407
+ # Update last accessed
408
+ cursor.execute(
409
+ "UPDATE sessions SET last_accessed = CURRENT_TIMESTAMP WHERE id = ?",
410
+ (session_id,)
411
+ )
412
+
413
  # Get questions
414
  cursor.execute("SELECT * FROM questions WHERE session_id = ?", (session_id,))
415
  questions = [dict(row) for row in cursor.fetchall()]
 
432
  accuracy = round((correct_answers / total_questions * 100) if total_questions > 0 else 0, 1)
433
  avg_time_spent = round(sum(q.get("time_spent", 0) for q in questions) / total_questions if total_questions > 0 else 0, 1)
434
 
435
+ conn.commit()
436
  conn.close()
437
 
438
+ return {
439
  "session": dict(session),
440
  "materials": {
441
  "questions": questions,
 
455
  "accuracy": accuracy,
456
  "avg_time_spent": avg_time_spent
457
  }
458
+ }
459
 
460
  @app.get("/api/user/sessions")
461
  async def get_user_sessions():
 
486
 
487
  conn.close()
488
 
489
+ return {"sessions": sessions}
490
 
491
  @app.post("/api/submit-answer")
492
  async def submit_answer(
 
511
  raise HTTPException(status_code=404, detail="Question not found")
512
 
513
  correct_answer = result[0]
514
+
515
+ # Simple answer checking
516
+ if isinstance(correct_answer, str) and correct_answer.isdigit():
517
+ # Multiple choice or true/false
518
+ is_correct = 1 if str(user_answer).strip() == str(correct_answer).strip() else 0
519
+ else:
520
+ # Fill in the blank - case insensitive partial match
521
+ user_ans = str(user_answer).strip().lower()
522
+ correct_ans = str(correct_answer).strip().lower()
523
+ is_correct = 1 if user_ans == correct_ans or correct_ans in user_ans or user_ans in correct_ans else 0
524
 
525
  # Update question with user answer
526
  cursor.execute('''
 
531
 
532
  # Update user profile stats
533
  cursor.execute('''
534
+ INSERT OR IGNORE INTO user_profile (id) VALUES (1)
535
+ ''')
536
+
537
+ cursor.execute('''
538
+ UPDATE user_profile
539
+ SET total_questions = total_questions + 1,
540
+ correct_answers = correct_answers + ?,
541
+ updated_at = CURRENT_TIMESTAMP
542
+ WHERE id = 1
543
  ''', (is_correct,))
544
 
545
  conn.commit()
546
  conn.close()
547
 
548
+ return {
549
  "is_correct": bool(is_correct),
550
  "correct_answer": correct_answer,
551
+ "message": "Correct! Well done!" if is_correct else "Incorrect. The correct answer was: " + str(correct_answer)
552
+ }
553
 
554
  @app.post("/api/analyze-text")
555
  async def analyze_text(text: str = Form(...), session_id: str = Form(...)):
556
+ """Analyze selected text"""
557
+
558
+ if len(text) < 2 or len(text) > 1000:
559
+ raise HTTPException(status_code=400, detail="Text must be between 2 and 1000 characters")
560
 
561
  # Simple text analysis
562
  words = text.split()
563
  definitions = []
564
 
565
+ # Create simple definitions for longer words
566
+ for i, word in enumerate(words[:min(5, len(words))]):
567
+ if len(word) > 4 and word.isalpha():
568
  definitions.append({
569
  "word": word,
570
+ "definition": f"'{word}' appears in this context. It's likely a key term in the study material.",
571
+ "source": "StudyFlow AI Analysis",
572
+ "example": f"Used in: '{' '.join(words[max(0,i-2):min(len(words),i+3)])}'"
573
  })
574
 
575
+ # Generate explanation based on text length
576
  word_count = len(words)
577
+ if word_count < 5:
578
+ explanation = "This is a short phrase or term."
579
+ elif word_count < 15:
580
+ explanation = "This appears to be a complete sentence expressing a single idea."
 
581
  else:
582
+ explanation = "This text contains multiple ideas or detailed information."
583
+
584
+ # Count unique words
585
+ unique_words = len(set([w.lower() for w in words if w.isalpha()]))
586
+ explanation += f"\n\nIt contains {word_count} words with {unique_words} unique terms."
587
 
588
  # Save as highlight
589
  conn = sqlite3.connect(DB_PATH)
 
598
  conn.commit()
599
  conn.close()
600
 
601
+ return {
602
  "text": text,
603
  "definitions": definitions,
604
  "explanation": explanation,
605
  "suggested_actions": [
606
+ "Create a flashcard for this concept",
607
+ "Generate a practice question",
608
+ "Add to study notes"
609
  ]
610
+ }
611
 
612
  @app.get("/api/user/profile")
613
  async def get_user_profile():
 
621
  if not profile:
622
  # Create default profile
623
  cursor.execute('''
624
+ INSERT INTO user_profile (id) VALUES (1)
 
625
  ''')
626
  conn.commit()
627
  cursor.execute("SELECT * FROM user_profile WHERE id = 1")
 
650
  elif accuracy > 0.8:
651
  strengths.append(f"{diff} difficulty questions")
652
 
653
+ # If no data yet
654
+ if not weak_areas:
655
+ weak_areas = ["Concepts will appear as you answer questions"]
656
+ if not strengths:
657
+ strengths = ["Keep practicing to build your strengths!"]
658
+
659
  conn.close()
660
 
661
+ return {
662
  "profile": {
663
+ "total_questions": profile[1] or 0,
664
+ "correct_answers": profile[2] or 0,
665
+ "total_study_time": profile[3] or 0,
666
+ "accuracy": round((profile[2] / profile[1] * 100) if profile[1] and profile[1] > 0 else 0, 1)
667
  },
668
  "insights": {
669
  "weaknesses": weak_areas,
 
671
  "recent_performance": [
672
  {
673
  "date": datetime.now().strftime("%Y-%m-%d"),
674
+ "total": profile[1] or 0,
675
+ "correct": profile[2] or 0
676
  }
677
  ]
678
  }
679
+ }
680
 
681
  @app.delete("/api/session/{session_id}")
682
  async def delete_session(session_id: str):
 
693
  if affected == 0:
694
  raise HTTPException(status_code=404, detail="Session not found")
695
 
696
+ return {"message": "Session deleted successfully"}
697
 
698
  @app.post("/api/update-study-time")
699
  async def update_study_time(session_id: str = Form(...), study_time: int = Form(...)):
 
707
  WHERE id = ?
708
  ''', (session_id,))
709
 
710
+ cursor.execute('''
711
+ INSERT OR IGNORE INTO user_profile (id) VALUES (1)
712
+ ''')
713
+
714
  cursor.execute('''
715
  UPDATE user_profile
716
  SET total_study_time = COALESCE(total_study_time, 0) + ?
 
720
  conn.commit()
721
  conn.close()
722
 
723
+ return {"message": "Study time updated"}
724
 
725
+ # Health check endpoint
726
+ @app.get("/health")
727
+ async def health_check():
728
+ """Health check endpoint"""
729
+ return {"status": "healthy", "timestamp": datetime.now().isoformat()}
730
+
731
+ # Serve frontend files
732
  @app.get("/")
733
  async def serve_frontend():
734
  """Serve the main frontend page"""
 
749
  except FileNotFoundError:
750
  return HTMLResponse(content="console.error('app.js not found');", media_type="application/javascript")
751
 
 
 
 
 
 
 
752
  if __name__ == "__main__":
753
  import uvicorn
754
  uvicorn.run(app, host="0.0.0.0", port=7860)