File size: 6,982 Bytes
792ad00
b6e32c9
792ad00
 
b6e32c9
 
792ad00
 
 
 
b6e32c9
 
792ad00
 
 
 
 
 
b6e32c9
 
 
 
792ad00
b6e32c9
 
 
 
792ad00
 
 
 
 
 
b6e32c9
 
 
 
792ad00
 
 
b6e32c9
792ad00
b6e32c9
792ad00
 
 
 
 
 
 
 
b6e32c9
792ad00
 
b6e32c9
 
 
4064f62
b6e32c9
 
 
 
792ad00
b6e32c9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77f281e
 
 
 
 
 
 
 
 
b6e32c9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
792ad00
 
 
 
 
 
 
 
 
 
 
 
 
b6e32c9
792ad00
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b6e32c9
792ad00
401a9dd
792ad00
 
 
 
401a9dd
792ad00
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
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

        # Call AI service
        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")

        # Save individual cards
        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()

        # Notify via WebSocket
        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

    # Create initial processing record
    file_base = request.file_key.split('/')[-1].rsplit('.', 1)[0] if request.file_key else None
    
    # Priority: 1. File-based name, 2. User Topic (if not default 'string'), 3. Default timestamp
    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)

    # Offload to background task
    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"}