| | import logging |
| | from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks |
| | from sqlalchemy.orm import Session |
| | from typing import List, Dict |
| | import asyncio |
| | from datetime import datetime |
| |
|
| | from api.auth import get_current_user |
| | from models import db_models |
| | from models.schemas import FlashcardGenerateRequest, FlashcardSetResponse, FlashcardResponse |
| | from core.database import get_db, SessionLocal |
| | from api.websocket_routes import manager |
| | from services.flashcard_service import flashcard_service |
| | from core import constants |
| |
|
| | router = APIRouter(prefix="/api/flashcards", tags=["flashcards"]) |
| | logger = logging.getLogger(__name__) |
| |
|
| | async def run_flashcard_generation(set_id: int, request: FlashcardGenerateRequest, user_id: int): |
| | """Background task for flashcard generation""" |
| | db = SessionLocal() |
| | connection_id = f"user_{user_id}" |
| | try: |
| | db_set = db.query(db_models.FlashcardSet).filter(db_models.FlashcardSet.id == set_id).first() |
| | if not db_set: return |
| |
|
| | |
| | cards_data = await flashcard_service.generate_flashcards( |
| | file_key=request.file_key, |
| | text_input=request.text_input, |
| | difficulty=request.difficulty, |
| | quantity=request.quantity, |
| | topic=request.topic, |
| | language=request.language, |
| | progress_callback=lambda p, m: asyncio.create_task( |
| | manager.send_progress(connection_id, p, "processing", m) |
| | ) |
| | ) |
| |
|
| | if not cards_data: |
| | raise Exception("AI returned empty flashcards data") |
| |
|
| | |
| | for item in cards_data: |
| | db_card = db_models.Flashcard( |
| | flashcard_set_id=db_set.id, |
| | question=item.get("question", ""), |
| | answer=item.get("answer", "") |
| | ) |
| | db.add(db_card) |
| | |
| | db_set.status = "completed" |
| | db.commit() |
| |
|
| | |
| | await manager.send_result(connection_id, { |
| | "type": "flashcards", |
| | "id": db_set.id, |
| | "status": "completed", |
| | "title": db_set.title |
| | }) |
| |
|
| | except Exception as e: |
| | logger.error(f"Background flashcard generation failed: {e}") |
| | db_set = db.query(db_models.FlashcardSet).filter(db_models.FlashcardSet.id == set_id).first() |
| | if db_set: |
| | db_set.status = "failed" |
| | db_set.error_message = str(e) |
| | db.commit() |
| | await manager.send_error(connection_id, f"Flashcard generation failed: {str(e)}") |
| | finally: |
| | db.close() |
| |
|
| | @router.get("/config") |
| | async def get_flashcard_config(): |
| | """Returns available difficulties, quantities, and languages for flashcards.""" |
| | return { |
| | "difficulties": constants.DIFFICULTIES, |
| | "quantities": constants.FLASHCARD_QUANTITIES, |
| | "languages": constants.LANGUAGES |
| | } |
| |
|
| | @router.post("/generate", response_model=FlashcardSetResponse) |
| | async def generate_flashcards( |
| | request: FlashcardGenerateRequest, |
| | background_tasks: BackgroundTasks, |
| | current_user: db_models.User = Depends(get_current_user), |
| | db: Session = Depends(get_db) |
| | ): |
| | """ |
| | Initiates flashcard generation in the background. |
| | """ |
| | source_id = None |
| | if request.file_key: |
| | source = db.query(db_models.Source).filter( |
| | db_models.Source.s3_key == request.file_key, |
| | db_models.Source.user_id == current_user.id |
| | ).first() |
| | if not source: |
| | raise HTTPException(status_code=403, detail="Not authorized to access this file") |
| | source_id = source.id |
| |
|
| | |
| | file_base = request.file_key.split('/')[-1].rsplit('.', 1)[0] if request.file_key else None |
| | |
| | |
| | if file_base: |
| | title = f"Flashcard-{file_base}" |
| | elif request.topic and request.topic != "string": |
| | title = request.topic |
| | else: |
| | title = f"Flashcards {datetime.utcnow().strftime('%Y-%m-%d %H:%M')}" |
| | db_set = db_models.FlashcardSet( |
| | title=title, |
| | difficulty=request.difficulty, |
| | user_id=current_user.id, |
| | source_id=source_id, |
| | status="processing" |
| | ) |
| | db.add(db_set) |
| | db.commit() |
| | db.refresh(db_set) |
| |
|
| | |
| | background_tasks.add_task(run_flashcard_generation, db_set.id, request, current_user.id) |
| |
|
| | return db_set |
| |
|
| | @router.get("/sets", response_model=List[FlashcardSetResponse]) |
| | async def list_flashcard_sets( |
| | current_user: db_models.User = Depends(get_current_user), |
| | db: Session = Depends(get_db) |
| | ): |
| | """ |
| | Lists all flashcard sets for the current user. |
| | """ |
| | try: |
| | sets = db.query(db_models.FlashcardSet).filter( |
| | db_models.FlashcardSet.user_id == current_user.id |
| | ).order_by(db_models.FlashcardSet.created_at.desc()).all() |
| | return [FlashcardSetResponse.model_validate(s) for s in sets] |
| | except Exception as e: |
| | raise HTTPException(status_code=500, detail=str(e)) |
| |
|
| | @router.get("/set/{set_id}", response_model=FlashcardSetResponse) |
| | async def get_flashcard_set( |
| | set_id: int, |
| | current_user: db_models.User = Depends(get_current_user), |
| | db: Session = Depends(get_db) |
| | ): |
| | """ |
| | Retrieves a specific flashcard set. |
| | """ |
| | db_set = db.query(db_models.FlashcardSet).filter( |
| | db_models.FlashcardSet.id == set_id, |
| | db_models.FlashcardSet.user_id == current_user.id |
| | ).first() |
| | |
| | if not db_set: |
| | raise HTTPException(status_code=404, detail="Flashcard set not found") |
| | |
| | return FlashcardSetResponse.model_validate(db_set) |
| |
|
| | @router.get("/explain") |
| | async def explain_flashcard( |
| | question: str, |
| | file_key: str = None, |
| | language: str = "English", |
| | current_user: db_models.User = Depends(get_current_user)): |
| | """ |
| | Provides a detailed explanation for a specific question. |
| | """ |
| | try: |
| | explanation = await flashcard_service.generate_explanation( |
| | question=question, |
| | file_key=file_key, |
| | language=language |
| | ) |
| | return {"explanation": explanation} |
| | except Exception as e: |
| | logger.error(f"Explanation failed: {e}") |
| | raise HTTPException(status_code=500, detail=str(e)) |
| |
|
| | @router.delete("/set/{set_id}") |
| | async def delete_flashcard_set( |
| | set_id: int, |
| | current_user: db_models.User = Depends(get_current_user), |
| | db: Session = Depends(get_db) |
| | ): |
| | """ |
| | Deletes a specific flashcard set and all its cards. |
| | """ |
| | db_set = db.query(db_models.FlashcardSet).filter( |
| | db_models.FlashcardSet.id == set_id, |
| | db_models.FlashcardSet.user_id == current_user.id |
| | ).first() |
| | |
| | if not db_set: |
| | raise HTTPException(status_code=404, detail="Flashcard set not found") |
| | |
| | db.delete(db_set) |
| | db.commit() |
| | return {"message": "Flashcard set and all associated cards deleted successfully"} |
| |
|