Implement script generation endpoint (Task 18)
- Added ScriptRequest, ScriptResponse, and ScriptSegment Pydantic models - Created /api/tts/script POST endpoint in main.py - Integrated with TTSService to generate script segments using AI - Added generate_script_segments method to TTSService - Returns segments with unique IDs for frontend tracking - Handles errors gracefully when AI segmentation fails - Falls back to pattern-based segmentation when Ollama unavailable - Added comprehensive tests for the endpoint - Validates: Requirements 15.1, 15.2
This commit is contained in:
104
backend/main.py
104
backend/main.py
@@ -34,6 +34,21 @@ class RolesResponse(BaseModel):
|
||||
"""Response model for role detection endpoint."""
|
||||
roles: List[RoleInfo] = Field(..., description="List of detected roles")
|
||||
|
||||
class ScriptSegment(BaseModel):
|
||||
"""Model representing a script segment with speaker assignment."""
|
||||
id: str = Field(..., description="Unique segment identifier")
|
||||
speaker: str = Field(..., description="Assigned speaker/role")
|
||||
text: str = Field(..., description="Text content for this segment")
|
||||
|
||||
class ScriptRequest(BaseModel):
|
||||
"""Request model for script generation endpoint."""
|
||||
text: str = Field(..., description="Text to segment and analyze")
|
||||
roles: List[str] = Field(..., description="List of detected role IDs")
|
||||
|
||||
class ScriptResponse(BaseModel):
|
||||
"""Response model for script generation endpoint."""
|
||||
segments: List[ScriptSegment] = Field(..., description="List of script segments with speaker assignments")
|
||||
|
||||
class RoleVoiceMapping(BaseModel):
|
||||
"""Model for role-to-voice mapping."""
|
||||
id: str = Field(..., description="Role identifier")
|
||||
@@ -129,6 +144,95 @@ async def detect_roles(request: RoleDetectionRequest):
|
||||
logger.error(f"Error during role detection: {str(e)}", exc_info=True)
|
||||
raise
|
||||
|
||||
@app.post("/api/tts/script", response_model=ScriptResponse)
|
||||
async def generate_script(request: ScriptRequest):
|
||||
"""
|
||||
Generates script preview with AI-segmented text and speaker assignments.
|
||||
|
||||
Uses AI (Ollama) to intelligently segment the text and assign speakers to each segment.
|
||||
Falls back to pattern-based segmentation if AI is unavailable.
|
||||
|
||||
Args:
|
||||
request: ScriptRequest containing text and list of detected roles
|
||||
|
||||
Returns:
|
||||
ScriptResponse with list of segments, each containing id, speaker, and text
|
||||
|
||||
Raises:
|
||||
400: If validation fails (empty text or roles)
|
||||
500: If script generation fails
|
||||
"""
|
||||
logger.info(
|
||||
f"Script generation requested for text of length {len(request.text)} "
|
||||
f"with {len(request.roles)} roles"
|
||||
)
|
||||
|
||||
# Validate text is not empty
|
||||
if not request.text or not request.text.strip():
|
||||
logger.error("Empty text provided for script generation")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content={
|
||||
"error": "ValidationError",
|
||||
"message": "Text field cannot be empty",
|
||||
"details": {"field": "text"}
|
||||
}
|
||||
)
|
||||
|
||||
# Validate roles list is not empty
|
||||
if not request.roles:
|
||||
logger.error("No roles provided for script generation")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content={
|
||||
"error": "ValidationError",
|
||||
"message": "At least one role is required",
|
||||
"details": {"field": "roles"}
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
# Generate script segments using TTS service
|
||||
segments = await tts_service.generate_script_segments(
|
||||
text=request.text,
|
||||
roles=request.roles
|
||||
)
|
||||
|
||||
if not segments:
|
||||
logger.warning("Script generation produced no segments")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content={
|
||||
"error": "ScriptGenerationError",
|
||||
"message": "Failed to generate script segments",
|
||||
"details": {"reason": "No segments produced"}
|
||||
}
|
||||
)
|
||||
|
||||
# Convert to response format with unique IDs
|
||||
script_segments = [
|
||||
ScriptSegment(
|
||||
id=f"seg_{i}",
|
||||
speaker=speaker,
|
||||
text=text
|
||||
)
|
||||
for i, (speaker, text) in enumerate(segments)
|
||||
]
|
||||
|
||||
logger.info(f"Generated script with {len(script_segments)} segments")
|
||||
return ScriptResponse(segments=script_segments)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during script generation: {str(e)}", exc_info=True)
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content={
|
||||
"error": "ScriptGenerationError",
|
||||
"message": "Failed to generate script",
|
||||
"details": {"error": str(e)}
|
||||
}
|
||||
)
|
||||
|
||||
@app.post("/api/tts")
|
||||
async def generate_audiobook(request: TTSRequest):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user