from fastapi import APIRouter, Depends, HTTPException from fastapi.responses import Response from pydantic import BaseModel from sqlalchemy.orm import Session from app.config import settings from app.database import get_db from app.models.user import User from app.models.ai_model_config import AIModelConfig from app.services import ai_service from app.utils.auth import get_current_user, check_rate_limit router = APIRouter() class TTSRequest(BaseModel): text: str voice: str | None = None # model_id override def _polly_enabled() -> bool: try: import redis as redis_lib r = redis_lib.from_url(settings.REDIS_URL, decode_responses=True) val = r.get("settings:polly_enabled") return val != "false" except Exception: return True @router.get("/voices") def get_voices( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """Return available TTS voices from DB, excluding Polly if disabled.""" query = db.query(AIModelConfig).filter( AIModelConfig.task == "tts", AIModelConfig.is_active == True, ) if not _polly_enabled(): query = query.filter(~AIModelConfig.model_id.like("polly/%")) db_models = query.order_by(AIModelConfig.is_default.desc(), AIModelConfig.name).all() return [{"id": m.model_id, "name": m.name, "is_default": m.is_default} for m in db_models] @router.post("/speak") def text_to_speech( request: TTSRequest, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """Convert text to speech using configured or user-selected TTS model.""" # Rate limit: 60 TTS requests per user per hour (admins/unthrottled users exempt) check_rate_limit( key=f"tts_speak:{current_user.id}", max_calls=60, window_seconds=3600, detail="You've reached the audio limit (60 clips/hour). The limit resets automatically — try again shortly. Contact an admin if you need this raised.", user=current_user, ) if not request.text.strip(): raise HTTPException(status_code=400, detail="Text cannot be empty") text = request.text[:2000] if request.voice: if request.voice.startswith("polly/") and not _polly_enabled(): raise HTTPException(status_code=400, detail="AWS Polly is currently disabled") config = db.query(AIModelConfig).filter(AIModelConfig.model_id == request.voice).first() model_id = config.model_id if config else request.voice api_key = config.api_key if (config and config.api_key) else None else: config = db.query(AIModelConfig).filter( AIModelConfig.task == "tts", AIModelConfig.is_active == True, AIModelConfig.is_default == True, ).first() model_id = config.model_id if config else "tts-1:alloy" api_key = config.api_key if (config and config.api_key) else None audio = ai_service.generate_tts_audio(text, model_id=model_id, api_key=api_key) if audio is None: raise HTTPException(status_code=500, detail="TTS generation failed. Check model configuration.") return Response(content=audio, media_type="audio/mpeg")