"""Voice co-creation session APIs.""" from fastapi import APIRouter, Depends, Response, status from sqlalchemy.ext.asyncio import AsyncSession from app.core.deps import require_user from app.core.rate_limiter import check_rate_limit from app.db.database import get_db from app.db.models import User from app.schemas.voice_session_schemas import ( VoiceSessionAbandonRequest, VoiceSessionCreateRequest, VoiceSessionDetailResponse, VoiceSessionFinalizeRequest, VoiceSessionFinalizeResponse, VoiceSessionSummaryResponse, VoiceTurnAcceptedResponse, VoiceTurnCreateFallbackRequest, VoiceTurnSummaryResponse, ) from app.services.voice_session_service import ( abandon_voice_session_service, create_voice_session_service, create_voice_turn_from_text_service, finalize_voice_session_service, get_voice_session_detail_service, get_voice_turn_audio_service, get_voice_turn_service, ) router = APIRouter() VOICE_SESSION_RATE_LIMIT_WINDOW = 60 VOICE_SESSION_RATE_LIMIT_REQUESTS = 20 @router.post( "/voice-sessions", response_model=VoiceSessionSummaryResponse, status_code=status.HTTP_201_CREATED, ) async def create_voice_session( request: VoiceSessionCreateRequest, user: User = Depends(require_user), db: AsyncSession = Depends(get_db), ): """Create one draft voice co-creation session.""" await check_rate_limit( f"voice-session:{user.id}", VOICE_SESSION_RATE_LIMIT_REQUESTS, VOICE_SESSION_RATE_LIMIT_WINDOW, ) return await create_voice_session_service(request, user.id, db) @router.get("/voice-sessions/{session_id}", response_model=VoiceSessionDetailResponse) async def get_voice_session( session_id: str, user: User = Depends(require_user), db: AsyncSession = Depends(get_db), ): """Get one voice co-creation session with recent turns and events.""" return await get_voice_session_detail_service(session_id, user.id, db) @router.post( "/voice-sessions/{session_id}/turns/fallback", response_model=VoiceTurnAcceptedResponse, status_code=status.HTTP_202_ACCEPTED, ) async def create_voice_turn_from_text( session_id: str, request: VoiceTurnCreateFallbackRequest, user: User = Depends(require_user), db: AsyncSession = Depends(get_db), ): """Create one turn using text fallback before real audio upload is added.""" await check_rate_limit( f"voice-turn:{user.id}", VOICE_SESSION_RATE_LIMIT_REQUESTS, VOICE_SESSION_RATE_LIMIT_WINDOW, ) return await create_voice_turn_from_text_service(session_id, request, user.id, db) @router.get( "/voice-sessions/{session_id}/turns/{turn_id}", response_model=VoiceTurnSummaryResponse, ) async def get_voice_turn( session_id: str, turn_id: str, user: User = Depends(require_user), db: AsyncSession = Depends(get_db), ): """Get one processed turn within a voice session.""" return await get_voice_turn_service(session_id, turn_id, user.id, db) @router.get("/voice-sessions/{session_id}/turns/{turn_id}/audio") async def get_voice_turn_audio( session_id: str, turn_id: str, user: User = Depends(require_user), db: AsyncSession = Depends(get_db), ): """Get synthesized assistant audio for one completed voice turn.""" audio_bytes = await get_voice_turn_audio_service(session_id, turn_id, user.id, db) return Response(content=audio_bytes, media_type="audio/mpeg") @router.post( "/voice-sessions/{session_id}/finalize", response_model=VoiceSessionFinalizeResponse, ) async def finalize_voice_session( session_id: str, request: VoiceSessionFinalizeRequest, user: User = Depends(require_user), db: AsyncSession = Depends(get_db), ): """Finalize one voice session into a persisted story.""" return await finalize_voice_session_service(session_id, request, user.id, db) @router.post( "/voice-sessions/{session_id}/abandon", response_model=VoiceSessionSummaryResponse, ) async def abandon_voice_session( session_id: str, request: VoiceSessionAbandonRequest, user: User = Depends(require_user), db: AsyncSession = Depends(get_db), ): """Abandon one in-progress voice session without saving it as a story.""" return await abandon_voice_session_service(session_id, request, user.id, db)