Upload folder using huggingface_hub
Browse files- api/flashcards.py +31 -4
- api/mindmaps.py +8 -0
- api/podcast.py +7 -1
- api/quizzes.py +30 -3
- api/reports.py +26 -3
- api/video_generator.py +4 -0
- models/db_models.py +15 -0
- models/schemas.py +12 -0
- scripts/migrate_all.py +44 -0
- services/flashcard_service.py +8 -14
api/flashcards.py
CHANGED
|
@@ -54,7 +54,7 @@ async def generate_flashcards(
|
|
| 54 |
)
|
| 55 |
|
| 56 |
if not cards_data:
|
| 57 |
-
raise HTTPException(status_code=500, detail="
|
| 58 |
|
| 59 |
# 2. Save Flashcard Set to DB
|
| 60 |
title = request.topic if request.topic else f"Flashcards {len(cards_data)}"
|
|
@@ -80,7 +80,15 @@ async def generate_flashcards(
|
|
| 80 |
db.commit()
|
| 81 |
db.refresh(db_set)
|
| 82 |
|
| 83 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
|
| 85 |
except HTTPException:
|
| 86 |
raise
|
|
@@ -100,7 +108,18 @@ async def list_flashcard_sets(
|
|
| 100 |
sets = db.query(db_models.FlashcardSet).filter(
|
| 101 |
db_models.FlashcardSet.user_id == current_user.id
|
| 102 |
).order_by(db_models.FlashcardSet.created_at.desc()).all()
|
| 103 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
except Exception as e:
|
| 105 |
raise HTTPException(status_code=500, detail=str(e))
|
| 106 |
|
|
@@ -121,7 +140,15 @@ async def get_flashcard_set(
|
|
| 121 |
if not db_set:
|
| 122 |
raise HTTPException(status_code=404, detail="Flashcard set not found")
|
| 123 |
|
| 124 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
@router.post("/explain")
|
| 127 |
async def explain_flashcard(
|
|
|
|
| 54 |
)
|
| 55 |
|
| 56 |
if not cards_data:
|
| 57 |
+
raise HTTPException(status_code=500, detail="AI returned an empty response")
|
| 58 |
|
| 59 |
# 2. Save Flashcard Set to DB
|
| 60 |
title = request.topic if request.topic else f"Flashcards {len(cards_data)}"
|
|
|
|
| 80 |
db.commit()
|
| 81 |
db.refresh(db_set)
|
| 82 |
|
| 83 |
+
return {
|
| 84 |
+
"id": db_set.id,
|
| 85 |
+
"title": db_set.title,
|
| 86 |
+
"difficulty": db_set.difficulty,
|
| 87 |
+
"created_at": db_set.created_at,
|
| 88 |
+
"parent_file_id": db_set.source_id,
|
| 89 |
+
"parent_file_key": source.s3_key if source else None,
|
| 90 |
+
"flashcards": db_set.flashcards
|
| 91 |
+
}
|
| 92 |
|
| 93 |
except HTTPException:
|
| 94 |
raise
|
|
|
|
| 108 |
sets = db.query(db_models.FlashcardSet).filter(
|
| 109 |
db_models.FlashcardSet.user_id == current_user.id
|
| 110 |
).order_by(db_models.FlashcardSet.created_at.desc()).all()
|
| 111 |
+
return [
|
| 112 |
+
{
|
| 113 |
+
"id": s.id,
|
| 114 |
+
"title": s.title,
|
| 115 |
+
"difficulty": s.difficulty,
|
| 116 |
+
"created_at": s.created_at,
|
| 117 |
+
"parent_file_id": s.source_id,
|
| 118 |
+
"parent_file_key": s.source.s3_key if s.source else None,
|
| 119 |
+
"flashcards": s.flashcards
|
| 120 |
+
}
|
| 121 |
+
for s in sets
|
| 122 |
+
]
|
| 123 |
except Exception as e:
|
| 124 |
raise HTTPException(status_code=500, detail=str(e))
|
| 125 |
|
|
|
|
| 140 |
if not db_set:
|
| 141 |
raise HTTPException(status_code=404, detail="Flashcard set not found")
|
| 142 |
|
| 143 |
+
return {
|
| 144 |
+
"id": db_set.id,
|
| 145 |
+
"title": db_set.title,
|
| 146 |
+
"difficulty": db_set.difficulty,
|
| 147 |
+
"created_at": db_set.created_at,
|
| 148 |
+
"parent_file_id": db_set.source_id,
|
| 149 |
+
"parent_file_key": db_set.source.s3_key if db_set.source else None,
|
| 150 |
+
"flashcards": db_set.flashcards
|
| 151 |
+
}
|
| 152 |
|
| 153 |
@router.post("/explain")
|
| 154 |
async def explain_flashcard(
|
api/mindmaps.py
CHANGED
|
@@ -55,8 +55,12 @@ async def generate_mindmap(
|
|
| 55 |
db.refresh(db_mindmap)
|
| 56 |
|
| 57 |
return MindMapResponse(
|
|
|
|
| 58 |
title=db_mindmap.title,
|
| 59 |
mermaid_code=db_mindmap.mermaid_code,
|
|
|
|
|
|
|
|
|
|
| 60 |
message="Mind map generated successfully"
|
| 61 |
)
|
| 62 |
|
|
@@ -81,8 +85,12 @@ async def list_mindmaps(
|
|
| 81 |
|
| 82 |
return [
|
| 83 |
MindMapResponse(
|
|
|
|
| 84 |
title=m.title,
|
| 85 |
mermaid_code=m.mermaid_code,
|
|
|
|
|
|
|
|
|
|
| 86 |
message="Retrieved successfully"
|
| 87 |
) for m in mindmaps
|
| 88 |
]
|
|
|
|
| 55 |
db.refresh(db_mindmap)
|
| 56 |
|
| 57 |
return MindMapResponse(
|
| 58 |
+
id=db_mindmap.id,
|
| 59 |
title=db_mindmap.title,
|
| 60 |
mermaid_code=db_mindmap.mermaid_code,
|
| 61 |
+
parent_file_id=db_mindmap.source_id,
|
| 62 |
+
parent_file_key=source.s3_key if source else None,
|
| 63 |
+
created_at=db_mindmap.created_at,
|
| 64 |
message="Mind map generated successfully"
|
| 65 |
)
|
| 66 |
|
|
|
|
| 85 |
|
| 86 |
return [
|
| 87 |
MindMapResponse(
|
| 88 |
+
id=m.id,
|
| 89 |
title=m.title,
|
| 90 |
mermaid_code=m.mermaid_code,
|
| 91 |
+
parent_file_id=m.source_id,
|
| 92 |
+
parent_file_key=m.source.s3_key if m.source else None,
|
| 93 |
+
created_at=m.created_at,
|
| 94 |
message="Retrieved successfully"
|
| 95 |
) for m in mindmaps
|
| 96 |
]
|
api/podcast.py
CHANGED
|
@@ -75,6 +75,9 @@ async def generate_podcast(
|
|
| 75 |
).first()
|
| 76 |
if not source:
|
| 77 |
raise HTTPException(status_code=403, detail="Not authorized to access this file")
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
# 2. Generate Script
|
| 80 |
script = await podcast_service.generate_script(
|
|
@@ -126,7 +129,8 @@ async def generate_podcast(
|
|
| 126 |
s3_key=s3_key,
|
| 127 |
s3_url=public_url,
|
| 128 |
script=script,
|
| 129 |
-
user_id=current_user.id
|
|
|
|
| 130 |
)
|
| 131 |
db.add(db_podcast)
|
| 132 |
db.commit()
|
|
@@ -170,6 +174,8 @@ async def list_podcasts(
|
|
| 170 |
"public_url": p.s3_url,
|
| 171 |
"private_url": s3_service.get_presigned_url(p.s3_key),
|
| 172 |
"script_preview": (p.script[:200] + "...") if p.script else "",
|
|
|
|
|
|
|
| 173 |
"created_at": p.created_at
|
| 174 |
}
|
| 175 |
for p in podcasts
|
|
|
|
| 75 |
).first()
|
| 76 |
if not source:
|
| 77 |
raise HTTPException(status_code=403, detail="Not authorized to access this file")
|
| 78 |
+
source_id = source.id
|
| 79 |
+
else:
|
| 80 |
+
source_id = None
|
| 81 |
|
| 82 |
# 2. Generate Script
|
| 83 |
script = await podcast_service.generate_script(
|
|
|
|
| 129 |
s3_key=s3_key,
|
| 130 |
s3_url=public_url,
|
| 131 |
script=script,
|
| 132 |
+
user_id=current_user.id,
|
| 133 |
+
source_id=source_id
|
| 134 |
)
|
| 135 |
db.add(db_podcast)
|
| 136 |
db.commit()
|
|
|
|
| 174 |
"public_url": p.s3_url,
|
| 175 |
"private_url": s3_service.get_presigned_url(p.s3_key),
|
| 176 |
"script_preview": (p.script[:200] + "...") if p.script else "",
|
| 177 |
+
"parent_file_id": p.source_id,
|
| 178 |
+
"parent_file_key": p.source.s3_key if p.source else None,
|
| 179 |
"created_at": p.created_at
|
| 180 |
}
|
| 181 |
for p in podcasts
|
api/quizzes.py
CHANGED
|
@@ -82,7 +82,15 @@ async def generate_quiz(
|
|
| 82 |
db.commit()
|
| 83 |
db.refresh(db_set)
|
| 84 |
|
| 85 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
except HTTPException:
|
| 88 |
raise
|
|
@@ -102,7 +110,18 @@ async def list_quiz_sets(
|
|
| 102 |
sets = db.query(db_models.QuizSet).filter(
|
| 103 |
db_models.QuizSet.user_id == current_user.id
|
| 104 |
).order_by(db_models.QuizSet.created_at.desc()).all()
|
| 105 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
except Exception as e:
|
| 107 |
raise HTTPException(status_code=500, detail=str(e))
|
| 108 |
|
|
@@ -123,7 +142,15 @@ async def get_quiz_set(
|
|
| 123 |
if not db_set:
|
| 124 |
raise HTTPException(status_code=404, detail="Quiz set not found")
|
| 125 |
|
| 126 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
|
| 128 |
@router.delete("/set/{set_id}")
|
| 129 |
async def delete_quiz_set(
|
|
|
|
| 82 |
db.commit()
|
| 83 |
db.refresh(db_set)
|
| 84 |
|
| 85 |
+
return {
|
| 86 |
+
"id": db_set.id,
|
| 87 |
+
"title": db_set.title,
|
| 88 |
+
"difficulty": db_set.difficulty,
|
| 89 |
+
"created_at": db_set.created_at,
|
| 90 |
+
"parent_file_id": db_set.source_id,
|
| 91 |
+
"parent_file_key": source.s3_key if source else None,
|
| 92 |
+
"questions": db_set.questions
|
| 93 |
+
}
|
| 94 |
|
| 95 |
except HTTPException:
|
| 96 |
raise
|
|
|
|
| 110 |
sets = db.query(db_models.QuizSet).filter(
|
| 111 |
db_models.QuizSet.user_id == current_user.id
|
| 112 |
).order_by(db_models.QuizSet.created_at.desc()).all()
|
| 113 |
+
return [
|
| 114 |
+
{
|
| 115 |
+
"id": s.id,
|
| 116 |
+
"title": s.title,
|
| 117 |
+
"difficulty": s.difficulty,
|
| 118 |
+
"created_at": s.created_at,
|
| 119 |
+
"parent_file_id": s.source_id,
|
| 120 |
+
"parent_file_key": s.source.s3_key if s.source else None,
|
| 121 |
+
"questions": s.questions
|
| 122 |
+
}
|
| 123 |
+
for s in sets
|
| 124 |
+
]
|
| 125 |
except Exception as e:
|
| 126 |
raise HTTPException(status_code=500, detail=str(e))
|
| 127 |
|
|
|
|
| 142 |
if not db_set:
|
| 143 |
raise HTTPException(status_code=404, detail="Quiz set not found")
|
| 144 |
|
| 145 |
+
return {
|
| 146 |
+
"id": db_set.id,
|
| 147 |
+
"title": db_set.title,
|
| 148 |
+
"difficulty": db_set.difficulty,
|
| 149 |
+
"created_at": db_set.created_at,
|
| 150 |
+
"parent_file_id": db_set.source_id,
|
| 151 |
+
"parent_file_key": db_set.source.s3_key if db_set.source else None,
|
| 152 |
+
"questions": db_set.questions
|
| 153 |
+
}
|
| 154 |
|
| 155 |
@router.delete("/set/{set_id}")
|
| 156 |
async def delete_quiz_set(
|
api/reports.py
CHANGED
|
@@ -87,7 +87,11 @@ async def generate_report(
|
|
| 87 |
db.commit()
|
| 88 |
db.refresh(db_report)
|
| 89 |
|
| 90 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
|
| 92 |
except HTTPException:
|
| 93 |
raise
|
|
@@ -107,7 +111,18 @@ async def list_reports(
|
|
| 107 |
reports = db.query(db_models.Report).filter(
|
| 108 |
db_models.Report.user_id == current_user.id
|
| 109 |
).order_by(db_models.Report.created_at.desc()).all()
|
| 110 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
except Exception as e:
|
| 112 |
raise HTTPException(status_code=500, detail=str(e))
|
| 113 |
|
|
@@ -128,7 +143,15 @@ async def get_report(
|
|
| 128 |
if not report:
|
| 129 |
raise HTTPException(status_code=404, detail="Report not found")
|
| 130 |
|
| 131 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
@router.delete("/{report_id}")
|
| 134 |
async def delete_report(
|
|
|
|
| 87 |
db.commit()
|
| 88 |
db.refresh(db_report)
|
| 89 |
|
| 90 |
+
return {
|
| 91 |
+
**db_report.__dict__,
|
| 92 |
+
"parent_file_id": db_report.source_id,
|
| 93 |
+
"parent_file_key": source.s3_key if source else None
|
| 94 |
+
}
|
| 95 |
|
| 96 |
except HTTPException:
|
| 97 |
raise
|
|
|
|
| 111 |
reports = db.query(db_models.Report).filter(
|
| 112 |
db_models.Report.user_id == current_user.id
|
| 113 |
).order_by(db_models.Report.created_at.desc()).all()
|
| 114 |
+
return [
|
| 115 |
+
{
|
| 116 |
+
"id": r.id,
|
| 117 |
+
"title": r.title,
|
| 118 |
+
"content": r.content,
|
| 119 |
+
"format_key": r.format_key,
|
| 120 |
+
"parent_file_id": r.source_id,
|
| 121 |
+
"parent_file_key": r.source.s3_key if r.source else None,
|
| 122 |
+
"created_at": r.created_at
|
| 123 |
+
}
|
| 124 |
+
for r in reports
|
| 125 |
+
]
|
| 126 |
except Exception as e:
|
| 127 |
raise HTTPException(status_code=500, detail=str(e))
|
| 128 |
|
|
|
|
| 143 |
if not report:
|
| 144 |
raise HTTPException(status_code=404, detail="Report not found")
|
| 145 |
|
| 146 |
+
return {
|
| 147 |
+
"id": report.id,
|
| 148 |
+
"title": report.title,
|
| 149 |
+
"content": report.content,
|
| 150 |
+
"format_key": report.format_key,
|
| 151 |
+
"parent_file_id": report.source_id,
|
| 152 |
+
"parent_file_key": report.source.s3_key if report.source else None,
|
| 153 |
+
"created_at": report.created_at
|
| 154 |
+
}
|
| 155 |
|
| 156 |
@router.delete("/{report_id}")
|
| 157 |
async def delete_report(
|
api/video_generator.py
CHANGED
|
@@ -67,6 +67,8 @@ async def generate_video_summary(
|
|
| 67 |
"s3_key": db_summary.s3_key,
|
| 68 |
"public_url": db_summary.s3_url,
|
| 69 |
"private_url": s3_service.get_presigned_url(db_summary.s3_key),
|
|
|
|
|
|
|
| 70 |
"created_at": db_summary.created_at
|
| 71 |
}
|
| 72 |
|
|
@@ -94,6 +96,8 @@ async def list_video_summaries(
|
|
| 94 |
"s3_key": s.s3_key,
|
| 95 |
"public_url": s.s3_url,
|
| 96 |
"private_url": s3_service.get_presigned_url(s.s3_key),
|
|
|
|
|
|
|
| 97 |
"created_at": s.created_at
|
| 98 |
}
|
| 99 |
for s in summaries
|
|
|
|
| 67 |
"s3_key": db_summary.s3_key,
|
| 68 |
"public_url": db_summary.s3_url,
|
| 69 |
"private_url": s3_service.get_presigned_url(db_summary.s3_key),
|
| 70 |
+
"parent_file_id": db_summary.source_id,
|
| 71 |
+
"parent_file_key": db_summary.source.s3_key if db_summary.source else None,
|
| 72 |
"created_at": db_summary.created_at
|
| 73 |
}
|
| 74 |
|
|
|
|
| 96 |
"s3_key": s.s3_key,
|
| 97 |
"public_url": s.s3_url,
|
| 98 |
"private_url": s3_service.get_presigned_url(s.s3_key),
|
| 99 |
+
"parent_file_id": s.source_id,
|
| 100 |
+
"parent_file_key": s.source.s3_key if s.source else None,
|
| 101 |
"created_at": s.created_at
|
| 102 |
}
|
| 103 |
for s in summaries
|
models/db_models.py
CHANGED
|
@@ -34,6 +34,13 @@ class Source(Base):
|
|
| 34 |
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 35 |
|
| 36 |
owner = relationship("User", back_populates="sources")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
class Podcast(Base):
|
| 39 |
__tablename__ = "podcasts"
|
|
@@ -44,9 +51,11 @@ class Podcast(Base):
|
|
| 44 |
s3_url = Column(String(1024), nullable=False)
|
| 45 |
script = Column(UnicodeText)
|
| 46 |
user_id = Column(Integer, ForeignKey("users.id"))
|
|
|
|
| 47 |
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 48 |
|
| 49 |
owner = relationship("User", back_populates="podcasts")
|
|
|
|
| 50 |
|
| 51 |
class FlashcardSet(Base):
|
| 52 |
__tablename__ = "flashcard_sets"
|
|
@@ -59,6 +68,7 @@ class FlashcardSet(Base):
|
|
| 59 |
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 60 |
|
| 61 |
owner = relationship("User", back_populates="flashcard_sets")
|
|
|
|
| 62 |
flashcards = relationship("Flashcard", back_populates="flashcard_set", cascade="all, delete-orphan")
|
| 63 |
|
| 64 |
class MindMap(Base):
|
|
@@ -72,6 +82,7 @@ class MindMap(Base):
|
|
| 72 |
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 73 |
|
| 74 |
owner = relationship("User", back_populates="mind_maps")
|
|
|
|
| 75 |
|
| 76 |
class QuizSet(Base):
|
| 77 |
__tablename__ = "quiz_sets"
|
|
@@ -84,6 +95,7 @@ class QuizSet(Base):
|
|
| 84 |
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 85 |
|
| 86 |
owner = relationship("User", back_populates="quiz_sets")
|
|
|
|
| 87 |
questions = relationship("QuizQuestion", back_populates="quiz_set", cascade="all, delete-orphan")
|
| 88 |
|
| 89 |
class QuizQuestion(Base):
|
|
@@ -111,6 +123,7 @@ class Report(Base):
|
|
| 111 |
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 112 |
|
| 113 |
owner = relationship("User", back_populates="reports")
|
|
|
|
| 114 |
|
| 115 |
class VideoSummary(Base):
|
| 116 |
__tablename__ = "video_summaries"
|
|
@@ -124,6 +137,7 @@ class VideoSummary(Base):
|
|
| 124 |
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 125 |
|
| 126 |
owner = relationship("User", back_populates="video_summaries")
|
|
|
|
| 127 |
|
| 128 |
class Flashcard(Base):
|
| 129 |
__tablename__ = "flashcards"
|
|
@@ -147,6 +161,7 @@ class RAGDocument(Base):
|
|
| 147 |
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 148 |
|
| 149 |
owner = relationship("User", back_populates="rag_documents")
|
|
|
|
| 150 |
|
| 151 |
class ChatMessage(Base):
|
| 152 |
__tablename__ = "chat_messages"
|
|
|
|
| 34 |
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 35 |
|
| 36 |
owner = relationship("User", back_populates="sources")
|
| 37 |
+
podcasts = relationship("Podcast", back_populates="source")
|
| 38 |
+
flashcard_sets = relationship("FlashcardSet", back_populates="source")
|
| 39 |
+
mind_maps = relationship("MindMap", back_populates="source")
|
| 40 |
+
quiz_sets = relationship("QuizSet", back_populates="source")
|
| 41 |
+
reports = relationship("Report", back_populates="source")
|
| 42 |
+
video_summaries = relationship("VideoSummary", back_populates="source")
|
| 43 |
+
rag_documents = relationship("RAGDocument", back_populates="source")
|
| 44 |
|
| 45 |
class Podcast(Base):
|
| 46 |
__tablename__ = "podcasts"
|
|
|
|
| 51 |
s3_url = Column(String(1024), nullable=False)
|
| 52 |
script = Column(UnicodeText)
|
| 53 |
user_id = Column(Integer, ForeignKey("users.id"))
|
| 54 |
+
source_id = Column(Integer, ForeignKey("sources.id"), nullable=True)
|
| 55 |
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 56 |
|
| 57 |
owner = relationship("User", back_populates="podcasts")
|
| 58 |
+
source = relationship("Source", back_populates="podcasts")
|
| 59 |
|
| 60 |
class FlashcardSet(Base):
|
| 61 |
__tablename__ = "flashcard_sets"
|
|
|
|
| 68 |
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 69 |
|
| 70 |
owner = relationship("User", back_populates="flashcard_sets")
|
| 71 |
+
source = relationship("Source", back_populates="flashcard_sets")
|
| 72 |
flashcards = relationship("Flashcard", back_populates="flashcard_set", cascade="all, delete-orphan")
|
| 73 |
|
| 74 |
class MindMap(Base):
|
|
|
|
| 82 |
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 83 |
|
| 84 |
owner = relationship("User", back_populates="mind_maps")
|
| 85 |
+
source = relationship("Source", back_populates="mind_maps")
|
| 86 |
|
| 87 |
class QuizSet(Base):
|
| 88 |
__tablename__ = "quiz_sets"
|
|
|
|
| 95 |
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 96 |
|
| 97 |
owner = relationship("User", back_populates="quiz_sets")
|
| 98 |
+
source = relationship("Source", back_populates="quiz_sets")
|
| 99 |
questions = relationship("QuizQuestion", back_populates="quiz_set", cascade="all, delete-orphan")
|
| 100 |
|
| 101 |
class QuizQuestion(Base):
|
|
|
|
| 123 |
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 124 |
|
| 125 |
owner = relationship("User", back_populates="reports")
|
| 126 |
+
source = relationship("Source", back_populates="reports")
|
| 127 |
|
| 128 |
class VideoSummary(Base):
|
| 129 |
__tablename__ = "video_summaries"
|
|
|
|
| 137 |
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 138 |
|
| 139 |
owner = relationship("User", back_populates="video_summaries")
|
| 140 |
+
source = relationship("Source", back_populates="video_summaries")
|
| 141 |
|
| 142 |
class Flashcard(Base):
|
| 143 |
__tablename__ = "flashcards"
|
|
|
|
| 161 |
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
| 162 |
|
| 163 |
owner = relationship("User", back_populates="rag_documents")
|
| 164 |
+
source = relationship("Source", back_populates="rag_documents")
|
| 165 |
|
| 166 |
class ChatMessage(Base):
|
| 167 |
__tablename__ = "chat_messages"
|
models/schemas.py
CHANGED
|
@@ -84,6 +84,8 @@ class FlashcardSetResponse(BaseModel):
|
|
| 84 |
title: Optional[str]
|
| 85 |
difficulty: str
|
| 86 |
created_at: datetime
|
|
|
|
|
|
|
| 87 |
flashcards: List[FlashcardResponse]
|
| 88 |
|
| 89 |
class Config:
|
|
@@ -96,8 +98,12 @@ class MindMapGenerateRequest(BaseModel):
|
|
| 96 |
title: Optional[str] = None
|
| 97 |
|
| 98 |
class MindMapResponse(BaseModel):
|
|
|
|
| 99 |
title: str
|
| 100 |
mermaid_code: str
|
|
|
|
|
|
|
|
|
|
| 101 |
message: str
|
| 102 |
|
| 103 |
# Quiz Schemas
|
|
@@ -122,6 +128,8 @@ class QuizSetResponse(BaseModel):
|
|
| 122 |
title: Optional[str]
|
| 123 |
difficulty: str
|
| 124 |
created_at: datetime
|
|
|
|
|
|
|
| 125 |
questions: List[QuizQuestionResponse]
|
| 126 |
|
| 127 |
class Config:
|
|
@@ -148,6 +156,8 @@ class ReportResponse(BaseModel):
|
|
| 148 |
title: str
|
| 149 |
content: str
|
| 150 |
format_key: str
|
|
|
|
|
|
|
| 151 |
created_at: datetime
|
| 152 |
|
| 153 |
class Config:
|
|
@@ -167,6 +177,8 @@ class VideoSummaryResponse(BaseModel):
|
|
| 167 |
s3_key: str
|
| 168 |
public_url: str
|
| 169 |
private_url: Optional[str] = None
|
|
|
|
|
|
|
| 170 |
created_at: datetime
|
| 171 |
|
| 172 |
class Config:
|
|
|
|
| 84 |
title: Optional[str]
|
| 85 |
difficulty: str
|
| 86 |
created_at: datetime
|
| 87 |
+
parent_file_id: Optional[int] = None
|
| 88 |
+
parent_file_key: Optional[str] = None
|
| 89 |
flashcards: List[FlashcardResponse]
|
| 90 |
|
| 91 |
class Config:
|
|
|
|
| 98 |
title: Optional[str] = None
|
| 99 |
|
| 100 |
class MindMapResponse(BaseModel):
|
| 101 |
+
id: Optional[int] = None
|
| 102 |
title: str
|
| 103 |
mermaid_code: str
|
| 104 |
+
parent_file_id: Optional[int] = None
|
| 105 |
+
parent_file_key: Optional[str] = None
|
| 106 |
+
created_at: Optional[datetime] = None
|
| 107 |
message: str
|
| 108 |
|
| 109 |
# Quiz Schemas
|
|
|
|
| 128 |
title: Optional[str]
|
| 129 |
difficulty: str
|
| 130 |
created_at: datetime
|
| 131 |
+
parent_file_id: Optional[int] = None
|
| 132 |
+
parent_file_key: Optional[str] = None
|
| 133 |
questions: List[QuizQuestionResponse]
|
| 134 |
|
| 135 |
class Config:
|
|
|
|
| 156 |
title: str
|
| 157 |
content: str
|
| 158 |
format_key: str
|
| 159 |
+
parent_file_id: Optional[int] = None
|
| 160 |
+
parent_file_key: Optional[str] = None
|
| 161 |
created_at: datetime
|
| 162 |
|
| 163 |
class Config:
|
|
|
|
| 177 |
s3_key: str
|
| 178 |
public_url: str
|
| 179 |
private_url: Optional[str] = None
|
| 180 |
+
parent_file_id: Optional[int] = None
|
| 181 |
+
parent_file_key: Optional[str] = None
|
| 182 |
created_at: datetime
|
| 183 |
|
| 184 |
class Config:
|
scripts/migrate_all.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
# Add the project root to the python path
|
| 5 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 6 |
+
|
| 7 |
+
from sqlalchemy import text
|
| 8 |
+
from core.database import engine
|
| 9 |
+
|
| 10 |
+
def migrate():
|
| 11 |
+
print("Starting migration: Ensuring source_id exists in all output tables...")
|
| 12 |
+
|
| 13 |
+
tables = [
|
| 14 |
+
"podcasts",
|
| 15 |
+
"video_summaries",
|
| 16 |
+
"mind_maps",
|
| 17 |
+
"flashcard_sets",
|
| 18 |
+
"quiz_sets",
|
| 19 |
+
"reports"
|
| 20 |
+
]
|
| 21 |
+
|
| 22 |
+
try:
|
| 23 |
+
with engine.connect() as connection:
|
| 24 |
+
for table in tables:
|
| 25 |
+
print(f"Processing table: {table}...")
|
| 26 |
+
|
| 27 |
+
# Check and add column/constraint
|
| 28 |
+
sql = f"""
|
| 29 |
+
IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[{table}]') AND name = 'source_id')
|
| 30 |
+
BEGIN
|
| 31 |
+
ALTER TABLE [dbo].[{table}] ADD [source_id] INT NULL;
|
| 32 |
+
ALTER TABLE [dbo].[{table}] ADD CONSTRAINT [FK_{table}_sources]
|
| 33 |
+
FOREIGN KEY ([source_id]) REFERENCES [dbo].[sources] ([id]);
|
| 34 |
+
END
|
| 35 |
+
"""
|
| 36 |
+
connection.execute(text(sql))
|
| 37 |
+
|
| 38 |
+
connection.commit()
|
| 39 |
+
print("Migration completed successfully!")
|
| 40 |
+
except Exception as e:
|
| 41 |
+
print(f"Error during migration: {e}")
|
| 42 |
+
|
| 43 |
+
if __name__ == "__main__":
|
| 44 |
+
migrate()
|
services/flashcard_service.py
CHANGED
|
@@ -26,20 +26,18 @@ class FlashcardService:
|
|
| 26 |
language: str = "English"
|
| 27 |
) -> List[Dict[str, str]]:
|
| 28 |
"""
|
| 29 |
-
Generates flashcards from either an S3 PDF or direct text input.
|
| 30 |
"""
|
| 31 |
try:
|
| 32 |
system_prompt = get_flashcard_system_prompt(difficulty, quantity, language)
|
| 33 |
if topic:
|
| 34 |
system_prompt += get_flashcard_topic_prompt(topic)
|
| 35 |
|
| 36 |
-
content_to_analyze = ""
|
| 37 |
-
|
| 38 |
if file_key:
|
| 39 |
# Download PDF from S3
|
| 40 |
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf")
|
| 41 |
tmp_path = tmp.name
|
| 42 |
-
tmp.close()
|
| 43 |
|
| 44 |
try:
|
| 45 |
s3_service.s3_client.download_file(
|
|
@@ -68,14 +66,13 @@ class FlashcardService:
|
|
| 68 |
]
|
| 69 |
|
| 70 |
response = self.openai_client.chat.completions.create(
|
| 71 |
-
model="gpt-4o-mini",
|
| 72 |
messages=messages,
|
| 73 |
temperature=0.7
|
| 74 |
)
|
| 75 |
|
| 76 |
# Clean up OpenAI file
|
| 77 |
self.openai_client.files.delete(uploaded_file.id)
|
| 78 |
-
|
| 79 |
raw_content = response.choices[0].message.content
|
| 80 |
|
| 81 |
finally:
|
|
@@ -98,18 +95,16 @@ class FlashcardService:
|
|
| 98 |
raise ValueError("Either file_key or text_input must be provided")
|
| 99 |
|
| 100 |
# Parse JSON
|
| 101 |
-
# Remove markdown code blocks if present
|
| 102 |
if "```json" in raw_content:
|
| 103 |
raw_content = raw_content.split("```json")[1].split("```")[0].strip()
|
| 104 |
elif "```" in raw_content:
|
| 105 |
raw_content = raw_content.split("```")[1].split("```")[0].strip()
|
| 106 |
|
| 107 |
-
|
| 108 |
-
return flashcards
|
| 109 |
|
| 110 |
except Exception as e:
|
| 111 |
-
logger.error(f"Flashcard generation failed: {e}")
|
| 112 |
-
raise
|
| 113 |
|
| 114 |
async def generate_explanation(self, question: str, file_key: Optional[str] = None, language: str = "English") -> str:
|
| 115 |
"""
|
|
@@ -119,7 +114,6 @@ class FlashcardService:
|
|
| 119 |
explanation_prompt = get_flashcard_explanation_prompt(question, language)
|
| 120 |
|
| 121 |
if file_key:
|
| 122 |
-
# Similar logic to generation if PDF context is needed
|
| 123 |
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf")
|
| 124 |
tmp_path = tmp.name
|
| 125 |
tmp.close()
|
|
@@ -158,7 +152,7 @@ class FlashcardService:
|
|
| 158 |
return response.choices[0].message.content
|
| 159 |
|
| 160 |
except Exception as e:
|
| 161 |
-
logger.error(f"Explanation generation failed: {e}")
|
| 162 |
-
raise
|
| 163 |
|
| 164 |
flashcard_service = FlashcardService()
|
|
|
|
| 26 |
language: str = "English"
|
| 27 |
) -> List[Dict[str, str]]:
|
| 28 |
"""
|
| 29 |
+
Generates flashcards from either an S3 PDF or direct text input (Original File-ID Method).
|
| 30 |
"""
|
| 31 |
try:
|
| 32 |
system_prompt = get_flashcard_system_prompt(difficulty, quantity, language)
|
| 33 |
if topic:
|
| 34 |
system_prompt += get_flashcard_topic_prompt(topic)
|
| 35 |
|
|
|
|
|
|
|
| 36 |
if file_key:
|
| 37 |
# Download PDF from S3
|
| 38 |
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf")
|
| 39 |
tmp_path = tmp.name
|
| 40 |
+
tmp.close()
|
| 41 |
|
| 42 |
try:
|
| 43 |
s3_service.s3_client.download_file(
|
|
|
|
| 66 |
]
|
| 67 |
|
| 68 |
response = self.openai_client.chat.completions.create(
|
| 69 |
+
model="gpt-4o-mini",
|
| 70 |
messages=messages,
|
| 71 |
temperature=0.7
|
| 72 |
)
|
| 73 |
|
| 74 |
# Clean up OpenAI file
|
| 75 |
self.openai_client.files.delete(uploaded_file.id)
|
|
|
|
| 76 |
raw_content = response.choices[0].message.content
|
| 77 |
|
| 78 |
finally:
|
|
|
|
| 95 |
raise ValueError("Either file_key or text_input must be provided")
|
| 96 |
|
| 97 |
# Parse JSON
|
|
|
|
| 98 |
if "```json" in raw_content:
|
| 99 |
raw_content = raw_content.split("```json")[1].split("```")[0].strip()
|
| 100 |
elif "```" in raw_content:
|
| 101 |
raw_content = raw_content.split("```")[1].split("```")[0].strip()
|
| 102 |
|
| 103 |
+
return json.loads(raw_content)
|
|
|
|
| 104 |
|
| 105 |
except Exception as e:
|
| 106 |
+
logger.error(f"Flashcard generation failed: {str(e)}")
|
| 107 |
+
raise e
|
| 108 |
|
| 109 |
async def generate_explanation(self, question: str, file_key: Optional[str] = None, language: str = "English") -> str:
|
| 110 |
"""
|
|
|
|
| 114 |
explanation_prompt = get_flashcard_explanation_prompt(question, language)
|
| 115 |
|
| 116 |
if file_key:
|
|
|
|
| 117 |
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf")
|
| 118 |
tmp_path = tmp.name
|
| 119 |
tmp.close()
|
|
|
|
| 152 |
return response.choices[0].message.content
|
| 153 |
|
| 154 |
except Exception as e:
|
| 155 |
+
logger.error(f"Explanation generation failed: {str(e)}")
|
| 156 |
+
raise e
|
| 157 |
|
| 158 |
flashcard_service = FlashcardService()
|