Spaces:
Sleeping
Sleeping
| # src/expon/presentation/interfaces/rest/controllers/multimodal_controller.py | |
| from fastapi import APIRouter, UploadFile, File, Form, HTTPException, Depends | |
| from pydantic import BaseModel | |
| from typing import Optional, Dict | |
| import os | |
| from tempfile import NamedTemporaryFile | |
| from src.expon.presentation.domain.services.multimodal_service import MultimodalService | |
| # 🔐 Usa la MISMA dependencia de auth del backend | |
| from src.expon.iam.infrastructure.authorization.sfs.auth_bearer import get_current_user | |
| router = APIRouter() | |
| _service = MultimodalService() | |
| class PredictResponse(BaseModel): | |
| dominant_emotion: str | |
| emotion_probabilities: Dict[str, float] | |
| transcript: Optional[str] # ← puede no haber texto | |
| alpha: float | |
| temp_text: float | |
| provider: str | |
| async def predict_multimodal( | |
| audio: UploadFile = File(...), | |
| text: Optional[str] = Form( | |
| None, | |
| description="Texto opcional para fusión. Déjalo vacío para usar solo audio.", | |
| example=None, | |
| ), | |
| user=Depends(get_current_user), # ← exige login con tu flujo real | |
| ): | |
| # 🔹 Sanitiza el placeholder de Swagger y vacíos | |
| if text is not None: | |
| t = text.strip() | |
| if not t or t.lower() == "string": | |
| text = None | |
| ext = (os.path.splitext(audio.filename or "")[-1] or "").lower() | |
| if ext not in [".wav", ".mp3", ".m4a"]: | |
| raise HTTPException(status_code=400, detail="Formato no soportado (.wav, .mp3, .m4a)") | |
| with NamedTemporaryFile(delete=False, suffix=ext) as tmp: | |
| tmp.write(await audio.read()) | |
| tmp_path = tmp.name | |
| try: | |
| result = _service.predict(tmp_path, text) | |
| return PredictResponse(**result) | |
| finally: | |
| try: | |
| os.remove(tmp_path) | |
| except Exception: | |
| pass | |