""" API routes for crossword puzzle generator. Matches the existing JavaScript API for frontend compatibility. """ import logging from typing import List, Dict, Any, Optional from datetime import datetime from fastapi import APIRouter, HTTPException, Request, Depends from pydantic import BaseModel, Field from ..services.crossword_generator_wrapper import CrosswordGenerator logger = logging.getLogger(__name__) router = APIRouter() # Request/Response models class GeneratePuzzleRequest(BaseModel): topics: List[str] = Field(..., description="List of topics for the puzzle") difficulty: str = Field(default="medium", description="Difficulty level: easy, medium, hard") useAI: bool = Field(default=False, description="Use AI vector search for word generation") class WordInfo(BaseModel): word: str clue: str similarity: Optional[float] = None source: Optional[str] = None class ClueInfo(BaseModel): number: int word: str text: str direction: str # "across" or "down" position: Dict[str, int] # {"row": int, "col": int} class PuzzleMetadata(BaseModel): topics: List[str] difficulty: str wordCount: int size: int aiGenerated: bool class PuzzleResponse(BaseModel): grid: List[List[str]] clues: List[ClueInfo] metadata: PuzzleMetadata class TopicInfo(BaseModel): id: str name: str # Global crossword generator instance (will be initialized in lifespan) generator = None def get_crossword_generator(request: Request) -> CrosswordGenerator: """Dependency to get the crossword generator with vector search service.""" global generator if generator is None: vector_service = getattr(request.app.state, 'vector_service', None) generator = CrosswordGenerator(vector_service) return generator @router.get("/topics", response_model=List[TopicInfo]) async def get_topics(): """Get available topics for puzzle generation.""" # Return the same topics as JavaScript backend for consistency topics = [ {"id": "animals", "name": "Animals"}, {"id": "geography", "name": "Geography"}, {"id": "science", "name": "Science"}, {"id": "technology", "name": "Technology"} ] return topics @router.post("/generate", response_model=PuzzleResponse) async def generate_puzzle( request: GeneratePuzzleRequest, crossword_gen: CrosswordGenerator = Depends(get_crossword_generator) ): """ Generate a crossword puzzle with optional AI vector search. This endpoint matches the JavaScript API exactly for frontend compatibility. """ try: logger.info(f"🎯 Generating puzzle for topics: {request.topics}, difficulty: {request.difficulty}, useAI: {request.useAI}") # Validate topics if not request.topics: raise HTTPException(status_code=400, detail="At least one topic is required") valid_difficulties = ["easy", "medium", "hard"] if request.difficulty not in valid_difficulties: raise HTTPException( status_code=400, detail=f"Invalid difficulty. Must be one of: {valid_difficulties}" ) # Generate puzzle puzzle_data = await crossword_gen.generate_puzzle( topics=request.topics, difficulty=request.difficulty, use_ai=request.useAI ) if not puzzle_data: raise HTTPException(status_code=500, detail="Failed to generate puzzle") logger.info(f"✅ Generated puzzle with {puzzle_data['metadata']['wordCount']} words") return puzzle_data except HTTPException: raise except Exception as e: logger.error(f"❌ Error generating puzzle: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.post("/words") async def generate_words( request: GeneratePuzzleRequest, crossword_gen: CrosswordGenerator = Depends(get_crossword_generator) ): """ Generate words for given topics (debug endpoint). This endpoint allows testing word generation without full puzzle creation. """ try: words = await crossword_gen.generate_words_for_topics( topics=request.topics, difficulty=request.difficulty, use_ai=request.useAI ) return { "topics": request.topics, "difficulty": request.difficulty, "useAI": request.useAI, "wordCount": len(words), "words": words } except Exception as e: logger.error(f"❌ Error generating words: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/health") async def api_health(): """API health check.""" return { "status": "healthy", "timestamp": datetime.utcnow().isoformat(), "backend": "python", "version": "2.0.0" } @router.get("/debug/vector-search") async def debug_vector_search( topic: str, difficulty: str = "medium", max_words: int = 10, request: Request = None ): """ Debug endpoint to test vector search directly. """ try: vector_service = getattr(request.app.state, 'vector_service', None) if not vector_service or not vector_service.is_initialized: raise HTTPException(status_code=503, detail="Vector search service not available") words = await vector_service.find_similar_words(topic, difficulty, max_words) return { "topic": topic, "difficulty": difficulty, "max_words": max_words, "found_words": len(words), "words": words } except Exception as e: logger.error(f"❌ Vector search debug failed: {e}") raise HTTPException(status_code=500, detail=str(e))