feat: add voice co-creation session skeleton
This commit is contained in:
137
backend/app/api/voice_sessions.py
Normal file
137
backend/app/api/voice_sessions.py
Normal file
@@ -0,0 +1,137 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user