| 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"} |
|
|