Release Candidate: Production Release with Multi-Tenant & Onboarding Enhancements

This commit is contained in:
ajaysi
2026-02-28 20:06:26 +05:30
parent 08a1f4a1d8
commit 4828274cbf
162 changed files with 19489 additions and 4300 deletions

View File

@@ -0,0 +1,65 @@
"""
Podcast Bible Models
Pydantic models for the structured Podcast Bible, used for hyper-personalization.
"""
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any
class HostPersona(BaseModel):
"""Details about the podcast host persona."""
name: str = Field(..., description="Name of the podcast host")
background: str = Field(..., description="Professional background and expertise")
expertise_level: str = Field(..., description="Level of expertise (e.g., Expert, Practitioner, Enthusiast)")
personality_traits: List[str] = Field(default_factory=list, description="Personality traits (e.g., Witty, Authoritative, Empathetic)")
vocal_style: str = Field(..., description="Description of the vocal style and delivery")
vocal_characteristics: List[str] = Field(default_factory=list, description="Specific vocal traits (e.g., Deep, Raspy, Energetic, Calm)")
look: Optional[str] = Field(None, description="Visual description of the host (for avatar generation)")
catchphrases: List[str] = Field(default_factory=list, description="Commonly used phrases or sign-offs")
class VisualStyle(BaseModel):
"""Visual aesthetic for the podcast videos and avatars."""
style_preset: str = Field(default="Professional Studio", description="Visual style (e.g., 3D Cartoon, Cinematic, Minimalist)")
environment: str = Field(..., description="The studio or setting where the podcast takes place")
lighting: str = Field(default="Soft Studio Lighting", description="Lighting mood and setup")
color_palette: List[str] = Field(default_factory=list, description="Primary brand colors for the visual elements")
camera_style: str = Field(default="Static Mid-shot", description="Preferred camera framing and movement")
class AudioEnvironment(BaseModel):
"""The soundscape and audio characteristics of the podcast."""
soundscape: str = Field(default="Quiet Studio", description="Acoustics and ambient noise level")
music_mood: str = Field(default="Professional & Subtle", description="Genre and mood of background music")
sfx_style: str = Field(default="Minimalist", description="Style of sound effects used (e.g., tech-inspired, natural)")
class ShowRules(BaseModel):
"""Consistency rules for the podcast narrative and structure."""
intro_format: str = Field(..., description="Standard way to start the episode")
outro_format: str = Field(..., description="Standard way to end the episode")
interaction_tone: str = Field(default="Conversational", description="Tone between hosts or with audience")
constraints: List[str] = Field(default_factory=list, description="Specific things to always do or avoid")
class AudienceDNA(BaseModel):
"""Details about the target audience."""
expertise_level: str = Field(..., description="Target audience expertise level (Beginner, Intermediate, Expert)")
interests: List[str] = Field(default_factory=list, description="Primary interests of the audience")
pain_points: List[str] = Field(default_factory=list, description="Common challenges or problems the audience faces")
demographics: Optional[str] = Field(None, description="General demographic information")
class BrandDNA(BaseModel):
"""Details about the brand and industry context."""
industry: str = Field(..., description="Primary industry or niche")
tone: str = Field(..., description="Overall brand tone (e.g., Professional, Casual, Inspirational)")
communication_style: str = Field(..., description="Preferred communication style (e.g., Socratic, Storytelling, Analytical)")
key_messages: List[str] = Field(default_factory=list, description="Core messages the brand wants to convey")
competitor_context: Optional[str] = Field(None, description="Context on how to differentiate from competitors")
class PodcastBible(BaseModel):
"""The complete structured Podcast Bible SSOT."""
project_id: Optional[str] = Field(default=None, description="Associated project ID")
host: HostPersona = Field(..., description="Host persona details")
audience: AudienceDNA = Field(..., description="Target audience details")
brand: BrandDNA = Field(..., description="Brand and industry context")
visual_style: VisualStyle = Field(..., description="Visual aesthetic and environment")
audio_environment: AudioEnvironment = Field(..., description="Soundscape and music details")
show_rules: ShowRules = Field(..., description="Consistency and structural rules")

View File

@@ -33,6 +33,7 @@ class PodcastProject(Base):
# Project state (stored as JSON)
# This mirrors the PodcastProjectState interface from frontend
bible = Column(JSON, nullable=True) # PodcastBible structured data
analysis = Column(JSON, nullable=True) # PodcastAnalysis
queries = Column(JSON, nullable=True) # List[Query]
selected_queries = Column(JSON, nullable=True) # Array of query IDs
@@ -56,6 +57,11 @@ class PodcastProject(Base):
# Final combined video URL (persisted for reloads)
final_video_url = Column(String(1000), nullable=True) # URL to final combined podcast video
# Avatar details
avatar_url = Column(String(1000), nullable=True)
avatar_prompt = Column(Text, nullable=True)
avatar_persona_id = Column(String(255), nullable=True)
# Timestamps
created_at = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False, index=True)

View File

@@ -40,11 +40,53 @@ class StoryGenerationRequest(BaseModel):
audio_lang: str = Field(default="en", description="Language code for TTS")
audio_slow: bool = Field(default=False, description="Whether to speak slowly (gTTS only)")
audio_rate: int = Field(default=150, description="Speech rate (pyttsx3 only)")
anime_bible: Optional[Dict[str, Any]] = Field(
default=None,
description="Optional structured anime story bible for anime fiction templates",
)
class StorySetupGenerationRequest(BaseModel):
"""Request model for AI story setup generation."""
story_idea: str = Field(..., description="Basic story idea or information from the user")
story_mode: Optional[str] = Field(
default=None,
description="Story mode (marketing or pure) if provided by the UI",
)
story_template: Optional[str] = Field(
default=None,
description="Optional story template identifier (e.g. product_story, brand_manifesto)",
)
brand_context: Optional[Dict[str, Any]] = Field(
default=None,
description="Optional high-signal brand context derived from onboarding",
)
class StoryIdeaEnhanceRequest(BaseModel):
"""Request model for AI story idea enhancement."""
story_idea: str = Field(..., description="Original story idea or concept text from the user")
story_mode: Optional[str] = Field(
default=None,
description="Story mode (marketing or pure) if provided by the UI",
)
story_template: Optional[str] = Field(
default=None,
description="Optional story template identifier (e.g. product_story, brand_manifesto)",
)
brand_context: Optional[Dict[str, Any]] = Field(
default=None,
description="Optional high-signal brand context derived from onboarding",
)
fiction_variant: Optional[str] = Field(
default=None,
description="Optional fiction-specific focus label (e.g. high-concept twist, shonen action)",
)
narrative_energy: Optional[str] = Field(
default=None,
description="Optional narrative energy or pacing hint (e.g. grounded, balanced, cinematic)",
)
class StorySetupOption(BaseModel):
@@ -78,6 +120,43 @@ class StorySetupOption(BaseModel):
audio_lang: str = Field(default="en", description="Language code for TTS")
audio_slow: bool = Field(default=False, description="Whether to speak slowly (gTTS only)")
audio_rate: int = Field(default=150, description="Speech rate (pyttsx3 only)")
anime_bible: Optional["AnimeStoryBible"] = Field(
default=None,
description="Optional structured anime story bible for anime fiction templates",
)
class AnimeCharacter(BaseModel):
id: str = Field(..., description="Stable identifier for the character (snake_case)")
name: str = Field(..., description="Character name")
age_range: str = Field(..., description="Approximate age range (e.g., 'late teens', '30s')")
role: str = Field(..., description="Narrative role (protagonist, antagonist, mentor, etc.)")
look: str = Field(..., description="Key visual details (hair, build, notable traits)")
outfit_palette: str = Field(..., description="Main outfit colors and style")
personality_tags: List[str] = Field(default_factory=list, description="Short tags describing personality")
class AnimeWorld(BaseModel):
setting: str = Field(..., description="World description and primary locations")
era: str = Field(..., description="Time period (near-future, far future, alt 1990s, etc.)")
tech_or_magic_level: str = Field(..., description="Technology or magic sophistication level")
core_rules: List[str] = Field(default_factory=list, description="Key world rules and constraints")
class AnimeVisualStyle(BaseModel):
style_preset: str = Field(..., description="High level style preset (anime_manga, cinematic_anime, cozy_slice_of_life)")
camera_style: str = Field(..., description="Typical camera behaviour and framing")
color_mood: str = Field(..., description="Dominant color palette and contrast")
lighting: str = Field(..., description="Lighting style")
line_style: str = Field(..., description="Line art style (thick, thin, rough, etc.)")
extra_tags: List[str] = Field(default_factory=list, description="Additional style tags")
class AnimeStoryBible(BaseModel):
story_id: Optional[str] = Field(default=None, description="Optional story identifier")
main_cast: List[AnimeCharacter] = Field(default_factory=list, description="Main cast of characters")
world: AnimeWorld = Field(..., description="World and rules description")
visual_style: AnimeVisualStyle = Field(..., description="Visual style anchors for images and video")
class StorySetupGenerationResponse(BaseModel):
@@ -86,8 +165,28 @@ class StorySetupGenerationResponse(BaseModel):
success: bool = Field(default=True, description="Whether the generation was successful")
class StoryIdeaEnhanceSuggestion(BaseModel):
"""A single enhanced story idea suggestion."""
idea: str = Field(..., description="AI-enhanced story idea text")
whats_missing: str = Field(
...,
description="Concise explanation of missing or underspecified plot/context elements",
)
why_choose: str = Field(
...,
description="Why this idea is a strong direction based on the original input",
)
class StoryIdeaEnhanceResponse(BaseModel):
"""Response model for story idea enhancement."""
suggestions: List[StoryIdeaEnhanceSuggestion] = Field(
..., description="List of enhanced story idea suggestions"
)
success: bool = Field(default=True, description="Whether the enhancement was successful")
class StoryScene(BaseModel):
"""Model for a story scene."""
scene_number: int = Field(..., description="Scene number")
title: str = Field(..., description="Scene title")
description: str = Field(..., description="Scene description")
@@ -97,6 +196,58 @@ class StoryScene(BaseModel):
key_events: List[str] = Field(default_factory=list, description="Key events in the scene")
class AnimeSceneTextRequest(BaseModel):
scene: StoryScene = Field(..., description="Scene to refine using the anime bible")
persona: str = Field(..., description="Persona context for the scene")
story_setting: str = Field(..., description="Story setting")
character_input: str = Field(..., description="Characters description from story setup")
plot_elements: str = Field(..., description="Plot elements from story setup")
writing_style: str = Field(..., description="Writing style")
story_tone: str = Field(..., description="Story tone")
narrative_pov: str = Field(..., description="Narrative point of view")
audience_age_group: str = Field(..., description="Audience age group")
content_rating: str = Field(..., description="Content rating")
anime_bible: Optional[Dict[str, Any]] = Field(
default=None,
description="Optional anime story bible used to refine the scene",
)
class AnimeSceneTextResponse(BaseModel):
scene: StoryScene = Field(..., description="Refined scene with bible-aware text and prompts")
success: bool = Field(default=True, description="Whether the refinement was successful")
class AnimeSceneGenerateRequest(BaseModel):
premise: str = Field(..., description="Overall story premise for context")
persona: str = Field(..., description="Persona context for the scene")
story_setting: str = Field(..., description="Story setting")
character_input: str = Field(..., description="Characters description from story setup")
plot_elements: str = Field(..., description="Plot elements from story setup")
writing_style: str = Field(..., description="Writing style")
story_tone: str = Field(..., description="Story tone")
narrative_pov: str = Field(..., description="Narrative point of view")
audience_age_group: str = Field(..., description="Audience age group")
content_rating: str = Field(..., description="Content rating")
anime_bible: Dict[str, Any] = Field(
...,
description="Anime story bible used as a hard constraint for generation",
)
previous_scenes: Optional[List[StoryScene]] = Field(
default=None,
description="Optional list of previous scenes for continuity context",
)
target_scene_number: Optional[int] = Field(
default=None,
description="Optional target scene number for the new scene",
)
class AnimeSceneGenerateResponse(BaseModel):
scene: StoryScene = Field(..., description="Newly generated anime scene based on the bible")
success: bool = Field(default=True, description="Whether the scene generation was successful")
class StoryStartRequest(StoryGenerationRequest):
"""Request model for story start generation."""
premise: str = Field(..., description="The story premise")
@@ -116,6 +267,10 @@ class StoryOutlineResponse(BaseModel):
success: bool = Field(default=True, description="Whether the generation was successful")
task_id: Optional[str] = Field(None, description="Task ID for async operations")
is_structured: bool = Field(default=False, description="Whether the outline is structured (scenes) or plain text")
anime_bible: Optional[AnimeStoryBible] = Field(
default=None,
description="Optional structured anime story bible generated from final story setup",
)
class StoryContentResponse(BaseModel):
@@ -156,6 +311,10 @@ class StoryContinueRequest(BaseModel):
content_rating: str = Field(..., description="The content rating")
ending_preference: str = Field(..., description="The preferred ending")
story_length: str = Field(default="Medium", description="Target story length (Short: >1000 words, Medium: >5000 words, Long: >10000 words)")
anime_bible: Optional[Dict[str, Any]] = Field(
default=None,
description="Optional structured anime story bible for anime fiction templates",
)
class StoryContinueResponse(BaseModel):

View File

@@ -0,0 +1,55 @@
"""
Story Project Models
Database models for Story Studio project persistence and state management.
Modeled after PodcastProject and ResearchProject for cross-device resume.
"""
from datetime import datetime
from sqlalchemy import Column, Integer, String, DateTime, Boolean, JSON, Index
from models.subscription_models import Base
class StoryProject(Base):
"""
Database model for Story Studio project state.
Stores complete story project state to enable cross-device resume.
"""
__tablename__ = "story_projects"
# Primary fields
id = Column(Integer, primary_key=True, autoincrement=True)
project_id = Column(String(255), unique=True, nullable=False, index=True)
user_id = Column(String(255), nullable=False, index=True)
# Project metadata
title = Column(String(500), nullable=True)
story_mode = Column(String(50), nullable=True)
story_template = Column(String(100), nullable=True)
# Story state (stored as JSON)
setup = Column(JSON, nullable=True)
outline = Column(JSON, nullable=True)
scenes = Column(JSON, nullable=True)
story_content = Column(JSON, nullable=True)
anime_bible = Column(JSON, nullable=True)
media_state = Column(JSON, nullable=True)
# UI/progress state
current_phase = Column(String(50), nullable=True)
status = Column(String(50), default="draft", nullable=False, index=True)
is_favorite = Column(Boolean, default=False, index=True)
is_complete = Column(Boolean, default=False)
# Timestamps
created_at = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False, index=True)
__table_args__ = (
Index("idx_story_user_status_created", "user_id", "status", "created_at"),
Index("idx_story_user_favorite_updated", "user_id", "is_favorite", "updated_at"),
)

View File

@@ -26,6 +26,8 @@ class UsageStatus(enum.Enum):
WARNING = "warning" # 80% usage
LIMIT_REACHED = "limit_reached" # 100% usage
SUSPENDED = "suspended"
CANCELLED = "cancelled"
PAST_DUE = "past_due"
class APIProvider(enum.Enum):
GEMINI = "gemini"
@@ -389,4 +391,20 @@ class SubscriptionRenewalHistory(Base):
# Indexes for performance
__table_args__ = (
{'mysql_engine': 'InnoDB'},
)
)
class FraudWarning(Base):
__tablename__ = "fraud_warnings"
id = Column(String(100), primary_key=True)
charge_id = Column(String(100), nullable=False)
payment_intent_id = Column(String(100), nullable=True)
user_id = Column(String(100), nullable=True)
amount = Column(Integer, nullable=False, default=0)
currency = Column(String(10), nullable=False, default="")
status = Column(String(20), nullable=False, default="open")
action = Column(String(20), nullable=False, default="none")
action_at = Column(DateTime, nullable=True)
reason_notes = Column(Text, nullable=True)
meta_info = Column(JSON, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)