173 lines
7.1 KiB
Python
173 lines
7.1 KiB
Python
"""Story outline generation helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Any, Dict
|
|
|
|
from fastapi import HTTPException
|
|
from loguru import logger
|
|
|
|
from services.llm_providers.main_text_generation import llm_text_gen
|
|
|
|
from .base import StoryServiceBase
|
|
|
|
|
|
class StoryOutlineMixin(StoryServiceBase):
|
|
"""Provides outline generation behaviour."""
|
|
|
|
def _get_outline_schema(self) -> Dict[str, Any]:
|
|
"""Return JSON schema for structured story outlines."""
|
|
return {
|
|
"type": "object",
|
|
"properties": {
|
|
"scenes": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {
|
|
"scene_number": {"type": "integer"},
|
|
"title": {"type": "string"},
|
|
"description": {"type": "string"},
|
|
"image_prompt": {"type": "string"},
|
|
"audio_narration": {"type": "string"},
|
|
"character_descriptions": {"type": "array", "items": {"type": "string"}},
|
|
"key_events": {"type": "array", "items": {"type": "string"}},
|
|
},
|
|
"required": ["scene_number", "title", "description", "image_prompt", "audio_narration"],
|
|
},
|
|
}
|
|
},
|
|
"required": ["scenes"],
|
|
}
|
|
|
|
def generate_outline(
|
|
self,
|
|
*,
|
|
premise: str,
|
|
persona: str,
|
|
story_setting: str,
|
|
character_input: str,
|
|
plot_elements: str,
|
|
writing_style: str,
|
|
story_tone: str,
|
|
narrative_pov: str,
|
|
audience_age_group: str,
|
|
content_rating: str,
|
|
ending_preference: str,
|
|
user_id: str,
|
|
use_structured_output: bool = True,
|
|
include_anime_bible: bool = False,
|
|
) -> Any:
|
|
"""Generate a story outline with optional structured JSON output."""
|
|
persona_prompt = self.build_persona_prompt(
|
|
persona,
|
|
story_setting,
|
|
character_input,
|
|
plot_elements,
|
|
writing_style,
|
|
story_tone,
|
|
narrative_pov,
|
|
audience_age_group,
|
|
content_rating,
|
|
ending_preference,
|
|
)
|
|
|
|
parameter_guidance = self._get_parameter_interaction_guidance(
|
|
writing_style, story_tone, audience_age_group, content_rating
|
|
)
|
|
|
|
outline_prompt = f"""\
|
|
{persona_prompt}
|
|
|
|
**PREMISE:**
|
|
{premise}
|
|
|
|
{parameter_guidance}
|
|
|
|
**YOUR TASK:**
|
|
Create a detailed story outline with multiple scenes that brings this premise to life. The outline must perfectly align with ALL of the story setup parameters provided above.
|
|
|
|
**SCENE PROGRESSION STRUCTURE:**
|
|
|
|
**Scene 1-2 (Opening):**
|
|
- Introduce the setting ({story_setting}) and main characters ({character_input})
|
|
- Establish the {story_tone} tone from the beginning
|
|
- Set up the main conflict or adventure based on the plot elements ({plot_elements})
|
|
- Hook the audience with an engaging opening that matches {writing_style} style
|
|
- Use the {narrative_pov} perspective to establish the story world
|
|
- Create intrigue and interest appropriate for {audience_age_group}
|
|
- Respect the {content_rating} content rating from the start
|
|
|
|
**Scene 3-7 (Development):**
|
|
- Develop the plot elements ({plot_elements}) in detail
|
|
- Build character relationships and growth using the specified characters ({character_input})
|
|
- Create tension, obstacles, or challenges that advance the story
|
|
- Maintain the {writing_style} style consistently throughout
|
|
- Progress toward the {ending_preference} ending
|
|
- Explore the setting ({story_setting}) more deeply
|
|
- Ensure all content is age-appropriate for {audience_age_group}
|
|
- Maintain the {story_tone} tone while developing the plot
|
|
- Respect the {content_rating} content rating in all scenes
|
|
- Use the {narrative_pov} perspective consistently
|
|
|
|
**Final Scenes (Resolution):**
|
|
- Resolve the main conflict established in the plot elements ({plot_elements})
|
|
- Deliver the {ending_preference} ending
|
|
- Tie together all plot elements and character arcs
|
|
- Provide satisfying closure appropriate for {audience_age_group}
|
|
- Maintain the {writing_style} style and {story_tone} tone until the end
|
|
- Ensure the ending respects the {content_rating} content rating
|
|
- Use the {narrative_pov} perspective to conclude the story
|
|
|
|
**OUTLINE STRUCTURE:**
|
|
For each scene, provide:
|
|
1. **Scene Number and Title**
|
|
2. **Description** (written in {writing_style}, maintaining {story_tone}, and age-appropriate for {audience_age_group})
|
|
3. **Image Prompt** (vivid, visually descriptive, includes setting/characters, age-appropriate)
|
|
4. **Audio Narration** (2-3 sentences, engaging, maintains style/tone, suitable for narration)
|
|
5. **Character Descriptions** (for characters appearing in the scene)
|
|
6. **Key Events** (bullet list of important happenings)
|
|
|
|
**CONTEXT INTEGRATION REQUIREMENTS:**
|
|
- Ensure every scene reflects the setting ({story_setting})
|
|
- Keep characters consistent with ({character_input})
|
|
- Integrate plot elements ({plot_elements}) logically
|
|
- Maintain persona voice ({persona})
|
|
- Respect audience age group ({audience_age_group}) and content rating ({content_rating})
|
|
|
|
Before finalizing, verify that every scene adheres to the writing style, tone, age appropriateness, content rating, and narrative POV. Create 5-10 scenes that tell a complete, engaging story with clear progression and satisfying resolution.
|
|
"""
|
|
|
|
try:
|
|
if use_structured_output:
|
|
outline_schema = self._get_outline_schema()
|
|
try:
|
|
response = self.load_json_response(
|
|
llm_text_gen(prompt=outline_prompt, json_struct=outline_schema, user_id=user_id)
|
|
)
|
|
scenes = response.get("scenes", [])
|
|
if scenes:
|
|
logger.info(f"[StoryWriter] Generated {len(scenes)} structured scenes for user {user_id}")
|
|
logger.info(
|
|
"[StoryWriter] Outline generated with parameters: "
|
|
f"audience={audience_age_group}, style={writing_style}, tone={story_tone}"
|
|
)
|
|
return scenes
|
|
logger.warning("[StoryWriter] No scenes found in structured output, falling back to text parsing")
|
|
raise ValueError("No scenes found in structured output")
|
|
except (json.JSONDecodeError, ValueError, KeyError) as exc:
|
|
logger.warning(
|
|
f"[StoryWriter] Failed to parse structured JSON outline ({exc}), falling back to text parsing"
|
|
)
|
|
return self._parse_text_outline(outline_prompt, user_id)
|
|
|
|
outline = self.generate_with_retry(outline_prompt, user_id=user_id)
|
|
return outline.strip()
|
|
except HTTPException:
|
|
raise
|
|
except Exception as exc:
|
|
logger.error(f"Outline Generation Error: {exc}")
|
|
raise RuntimeError(f"Failed to generate outline: {exc}") from exc
|
|
|