feat: improve voice studio alpha recovery flow

This commit is contained in:
2026-04-19 23:25:41 +08:00
parent 46d6201529
commit 4ecf0c09c0
9 changed files with 657 additions and 14 deletions

View File

@@ -12,6 +12,7 @@ from fastapi import (
)
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.config import settings
from app.core.deps import require_user
from app.core.rate_limiter import check_rate_limit
from app.db.database import get_db
@@ -34,11 +35,14 @@ from app.services.voice_session_service import (
create_voice_turn_from_text_service,
create_voice_turn_from_upload_service,
finalize_voice_session_service,
get_latest_active_voice_session_service,
get_voice_session_detail_service,
get_voice_turn_audio_service,
get_voice_turn_service,
get_voice_turn_user_audio_service,
list_voice_sessions_service,
retry_voice_turn_audio_service,
retry_voice_turn_service,
)
router = APIRouter()
@@ -68,8 +72,13 @@ async def create_voice_session(
@router.get("/voice-sessions", response_model=list[VoiceSessionSummaryResponse])
async def list_voice_sessions(
limit: int = Query(default=8, ge=1, le=20),
limit: int = Query(
default=settings.voice_session_default_list_limit,
ge=1,
le=settings.voice_session_max_list_limit,
),
active_only: bool = Query(default=False),
active_first: bool = Query(default=True),
user: User = Depends(require_user),
db: AsyncSession = Depends(get_db),
):
@@ -79,9 +88,19 @@ async def list_voice_sessions(
db,
limit=limit,
active_only=active_only,
active_first=active_first,
)
@router.get("/voice-sessions/active", response_model=VoiceSessionSummaryResponse | None)
async def get_latest_active_voice_session(
user: User = Depends(require_user),
db: AsyncSession = Depends(get_db),
):
"""Get the latest active voice session for quick resume behavior."""
return await get_latest_active_voice_session_service(user.id, db)
@router.get("/voice-sessions/{session_id}", response_model=VoiceSessionDetailResponse)
async def get_voice_session(
session_id: str,
@@ -158,6 +177,21 @@ async def get_voice_turn(
return await get_voice_turn_service(session_id, turn_id, user.id, db)
@router.post(
"/voice-sessions/{session_id}/turns/{turn_id}/retry",
response_model=VoiceTurnAcceptedResponse,
status_code=status.HTTP_202_ACCEPTED,
)
async def retry_voice_turn(
session_id: str,
turn_id: str,
user: User = Depends(require_user),
db: AsyncSession = Depends(get_db),
):
"""Retry one failed voice turn using its saved transcript."""
return await retry_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,
@@ -170,6 +204,20 @@ async def get_voice_turn_audio(
return Response(content=audio_bytes, media_type="audio/mpeg")
@router.post(
"/voice-sessions/{session_id}/turns/{turn_id}/retry-audio",
response_model=VoiceTurnSummaryResponse,
)
async def retry_voice_turn_audio(
session_id: str,
turn_id: str,
user: User = Depends(require_user),
db: AsyncSession = Depends(get_db),
):
"""Retry assistant audio synthesis when one turn only has text output."""
return await retry_voice_turn_audio_service(session_id, turn_id, user.id, db)
@router.get("/voice-sessions/{session_id}/turns/{turn_id}/user-audio")
async def get_voice_turn_user_audio(
session_id: str,