178 lines
5.4 KiB
Python
178 lines
5.4 KiB
Python
"""Pydantic schemas for voice co-creation sessions."""
|
|
|
|
from datetime import datetime
|
|
from typing import Any, Literal
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
MAX_VOICE_TRANSCRIPT_LENGTH = 1000
|
|
MAX_VOICE_ABORT_REASON_LENGTH = 200
|
|
MAX_VOICE_TURN_DURATION_MS = 90_000
|
|
|
|
|
|
class VoiceSessionCreateRequest(BaseModel):
|
|
"""Create one draft voice co-creation session."""
|
|
|
|
child_profile_id: str | None = None
|
|
universe_id: str | None = None
|
|
target_mode: Literal["story"] = Field(default="story")
|
|
|
|
|
|
class VoiceTurnCreateFallbackRequest(BaseModel):
|
|
"""Create one voice turn using text fallback instead of uploaded audio."""
|
|
|
|
transcript_text: str = Field(..., min_length=1, max_length=MAX_VOICE_TRANSCRIPT_LENGTH)
|
|
duration_ms: int | None = Field(default=None, ge=1, le=MAX_VOICE_TURN_DURATION_MS)
|
|
|
|
|
|
class VoiceTurnUploadAcceptedResponse(BaseModel):
|
|
"""Accepted response for one uploaded-audio voice turn."""
|
|
|
|
turn_id: str
|
|
session_id: str
|
|
status: str
|
|
transcription_provider: str | None = None
|
|
|
|
|
|
class VoiceSessionFinalizeRequest(BaseModel):
|
|
"""Finalize one voice session into a persisted story."""
|
|
|
|
save_story: bool = True
|
|
generate_cover: bool = True
|
|
generate_final_audio: bool = False
|
|
|
|
|
|
class VoiceTurnConfirmRequest(BaseModel):
|
|
"""Resolve one pending confirmation before the story continues."""
|
|
|
|
action: Literal["accept", "retry_recording", "switch_to_text"]
|
|
|
|
|
|
class VoiceSessionAbandonRequest(BaseModel):
|
|
"""Explicitly abandon one in-progress session."""
|
|
|
|
reason: str | None = Field(default=None, max_length=MAX_VOICE_ABORT_REASON_LENGTH)
|
|
|
|
|
|
class VoiceSessionEventResponse(BaseModel):
|
|
"""One persisted session event."""
|
|
|
|
id: int
|
|
session_id: str
|
|
turn_id: str | None = None
|
|
event_type: str
|
|
status: str
|
|
message: str | None = None
|
|
event_metadata: dict[str, Any] = Field(default_factory=dict)
|
|
created_at: datetime
|
|
|
|
|
|
class VoiceTurnSummaryResponse(BaseModel):
|
|
"""One summarized voice session turn."""
|
|
|
|
id: str
|
|
session_id: str
|
|
turn_index: int
|
|
status: str
|
|
user_transcript: str | None = None
|
|
transcript_confidence: float | None = None
|
|
transcription_provider: str | None = None
|
|
detected_intent: str
|
|
intent_confidence: float | None = None
|
|
understanding_summary: str | None = None
|
|
requires_confirmation: bool = False
|
|
confirmation_state: str = "not_needed"
|
|
confirmation_reason: str | None = None
|
|
confirmation_message: str | None = None
|
|
safety_flags: list[str] = Field(default_factory=list)
|
|
safety_blocked: bool = False
|
|
safety_message: str | None = None
|
|
assistant_text: str | None = None
|
|
assistant_audio_ready: bool = False
|
|
assistant_audio_url: str | None = None
|
|
user_audio_ready: bool = False
|
|
user_audio_url: str | None = None
|
|
error_message: str | None = None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
class VoiceSessionSummaryResponse(BaseModel):
|
|
"""One summarized voice co-creation session."""
|
|
|
|
id: str
|
|
child_profile_id: str | None = None
|
|
universe_id: str | None = None
|
|
final_story_id: int | None = None
|
|
target_mode: str
|
|
status: str
|
|
current_turn_index: int
|
|
total_turns: int = 0
|
|
working_title: str | None = None
|
|
story_state: dict[str, Any] = Field(default_factory=dict)
|
|
latest_user_transcript: str | None = None
|
|
latest_assistant_text: str | None = None
|
|
latest_detected_intent: str | None = None
|
|
latest_understanding_summary: str | None = None
|
|
latest_requires_confirmation: bool = False
|
|
latest_confirmation_state: str | None = None
|
|
latest_confirmation_message: str | None = None
|
|
latest_safety_flags: list[str] = Field(default_factory=list)
|
|
latest_safety_message: str | None = None
|
|
latest_assistant_audio_ready: bool = False
|
|
last_turn_status: str | None = None
|
|
attention_reasons: list[str] = Field(default_factory=list)
|
|
transcription_mode_hint: str | None = None
|
|
can_continue: bool = False
|
|
can_finalize: bool = False
|
|
last_error: str | None = None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
class VoiceSessionDetailResponse(VoiceSessionSummaryResponse):
|
|
"""Detailed voice session payload with recent turns and events."""
|
|
|
|
recent_turns: list[VoiceTurnSummaryResponse] = Field(default_factory=list)
|
|
events: list[VoiceSessionEventResponse] = Field(default_factory=list)
|
|
|
|
|
|
class VoiceTurnAcceptedResponse(BaseModel):
|
|
"""Accepted response for one asynchronously processed turn."""
|
|
|
|
turn_id: str
|
|
session_id: str
|
|
status: str
|
|
|
|
|
|
class VoiceSessionAnalyticsResponse(BaseModel):
|
|
"""Aggregated voice co-creation analytics for one user."""
|
|
|
|
window_days: int | None = None
|
|
total_sessions: int = 0
|
|
attention_sessions: int = 0
|
|
confirmation_attention_sessions: int = 0
|
|
safety_attention_sessions: int = 0
|
|
failed_attention_sessions: int = 0
|
|
active_sessions: int = 0
|
|
finalized_sessions: int = 0
|
|
abandoned_sessions: int = 0
|
|
total_turns: int = 0
|
|
successful_turns: int = 0
|
|
failed_turns: int = 0
|
|
asr_failures: int = 0
|
|
tts_failures: int = 0
|
|
low_confidence_turns: int = 0
|
|
safety_interventions: int = 0
|
|
turn_success_rate: float = 0.0
|
|
finalize_conversion_rate: float = 0.0
|
|
|
|
|
|
class VoiceSessionFinalizeResponse(BaseModel):
|
|
"""Finalize response after a session is converted into a story."""
|
|
|
|
session_id: str
|
|
status: str
|
|
story_id: int | None = None
|
|
generation_job_id: str | None = None
|