vimalk78's picture
Add complete Python backend with AI-powered crossword generation
38c016b
raw
history blame
5.92 kB
"""
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))