|
import streamlit as st |
|
import anthropic |
|
import requests |
|
import json |
|
import base64 |
|
import plotly.graph_objects as go |
|
from typing import Dict, Any |
|
import time |
|
import random |
|
|
|
|
|
st.set_page_config( |
|
page_title="NarrativeCraft: Immersive Story & Character Design Studio", |
|
layout="wide", |
|
initial_sidebar_state="expanded", |
|
page_icon="π" |
|
) |
|
|
|
|
|
st.markdown(""" |
|
<style> |
|
.main {background-color: #0e1117; color: #ffffff;} |
|
.stProgress > div > div > div > div {background-color: #1f77b4;} |
|
.character-card { |
|
background-color: #1e1e1e; |
|
padding: 1.5rem; |
|
border-radius: 0.5rem; |
|
margin: 1rem 0; |
|
border: 1px solid #2e2e2e; |
|
} |
|
.section-title { |
|
font-size: 1.5rem; |
|
font-weight: bold; |
|
margin: 1.5rem 0 1rem 0; |
|
padding-bottom: 0.5rem; |
|
border-bottom: 2px solid #2e2e2e; |
|
} |
|
.subsection-title { |
|
font-size: 1.2rem; |
|
font-weight: bold; |
|
margin: 1rem 0; |
|
color: #4e8cff; |
|
} |
|
.story-section { |
|
background-color: #1e1e1e; |
|
padding: 2rem; |
|
border-radius: 0.5rem; |
|
margin: 1.5rem 0; |
|
border: 1px solid #2e2e2e; |
|
} |
|
.audio-player { |
|
margin: 1rem 0; |
|
padding: 1rem; |
|
background-color: #2e2e2e; |
|
border-radius: 0.5rem; |
|
} |
|
.analysis-card { |
|
background-color: #2e2e2e; |
|
padding: 1rem; |
|
border-radius: 0.5rem; |
|
margin: 0.5rem 0; |
|
} |
|
</style> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
if 'character' not in st.session_state: |
|
st.session_state.character = None |
|
if 'story_params' not in st.session_state: |
|
st.session_state.story_params = None |
|
if 'generated_content' not in st.session_state: |
|
st.session_state.generated_content = None |
|
if 'voice_data' not in st.session_state: |
|
st.session_state.voice_data = None |
|
|
|
|
|
ARCHETYPES = { |
|
"The Hero": "Brave, determined protagonist driven by a noble cause", |
|
"The Mentor": "Wise guide with deep knowledge and experience", |
|
"The Trickster": "Clever, unpredictable character who challenges conventions", |
|
"The Sage": "Philosophical, thoughtful character seeking truth", |
|
"The Rebel": "Independent spirit fighting against the system", |
|
"The Caregiver": "Nurturing, protective character driven by compassion", |
|
"The Creator": "Innovative, artistic character driven by vision", |
|
"The Explorer": "Adventure-seeking character driven by curiosity", |
|
"The Ruler": "Leadership-focused character seeking control", |
|
"The Innocent": "Pure-hearted character maintaining optimism" |
|
} |
|
|
|
GENRES = [ |
|
"Epic Fantasy", |
|
"Science Fiction", |
|
"Dark Fantasy", |
|
"Historical Fiction", |
|
"Magical Realism", |
|
"Contemporary Drama", |
|
"Mystery/Thriller", |
|
"Romance", |
|
"Political Intrigue", |
|
"Cyberpunk", |
|
"Space Opera", |
|
"Urban Fantasy", |
|
"Gothic Horror", |
|
"Adventure", |
|
"Literary Fiction" |
|
] |
|
|
|
WRITING_STYLES = { |
|
"Classical": "Elegant, formal prose with rich descriptions", |
|
"Modern Minimalist": "Clean, precise language with impact", |
|
"Lyrical": "Poetic, flowing prose with metaphorical depth", |
|
"Gritty Realism": "Raw, direct style with stark honesty", |
|
"Experimental": "Innovative structure and unique voice", |
|
"Journalistic": "Clear, factual style with objectivity", |
|
"Stream of Consciousness": "Free-flowing, internal narrative", |
|
"Epic": "Grand, sweeping style with historical weight" |
|
} |
|
def create_character_section(): |
|
st.markdown('<p class="section-title">Character Design</p>', unsafe_allow_html=True) |
|
|
|
col1, col2 = st.columns([2, 1]) |
|
|
|
with col1: |
|
selected_archetype = st.selectbox( |
|
"Choose Character Archetype", |
|
list(ARCHETYPES.keys()) |
|
) |
|
|
|
st.info(ARCHETYPES[selected_archetype]) |
|
|
|
age_range = st.select_slider( |
|
"Age Range", |
|
options=["Child", "Teen", "Young Adult", "Adult", "Middle-aged", "Elderly"], |
|
value="Adult" |
|
) |
|
|
|
with col2: |
|
st.markdown('<p class="subsection-title">Character Preview</p>', unsafe_allow_html=True) |
|
st.markdown(f"**Archetype:** {selected_archetype}") |
|
st.markdown(f"**Age:** {age_range}") |
|
|
|
st.markdown('<p class="subsection-title">Personality Traits</p>', unsafe_allow_html=True) |
|
trait_col1, trait_col2, trait_col3 = st.columns(3) |
|
|
|
with trait_col1: |
|
confidence = st.slider("Confidence", 1, 10, 5, help="Character's self-assurance level") |
|
empathy = st.slider("Empathy", 1, 10, 5, help="Ability to understand others") |
|
intelligence = st.slider("Intelligence", 1, 10, 5, help="Mental capability and wit") |
|
|
|
with trait_col2: |
|
courage = st.slider("Courage", 1, 10, 5, help="Bravery in face of adversity") |
|
ambition = st.slider("Ambition", 1, 10, 5, help="Drive to achieve goals") |
|
loyalty = st.slider("Loyalty", 1, 10, 5, help="Faithfulness to causes/people") |
|
|
|
with trait_col3: |
|
humor = st.slider("Humor", 1, 10, 5, help="Sense of humor and wit") |
|
creativity = st.slider("Creativity", 1, 10, 5, help="Imaginative capability") |
|
wisdom = st.slider("Wisdom", 1, 10, 5, help="Depth of understanding") |
|
|
|
st.markdown('<p class="subsection-title">Voice & Speech</p>', unsafe_allow_html=True) |
|
voice_col1, voice_col2 = st.columns(2) |
|
|
|
with voice_col1: |
|
voice_type = st.selectbox( |
|
"Voice Quality", |
|
["Deep", "Melodious", "Rough", "Soft", "Commanding", "Gentle", "Energetic"], |
|
help="Primary characteristic of the character's voice" |
|
) |
|
|
|
accent = st.selectbox( |
|
"Accent", |
|
["Standard", "Regional", "Foreign", "Cultured", "Rustic"], |
|
help="Character's accent or dialect" |
|
) |
|
|
|
with voice_col2: |
|
speech_pattern = st.selectbox( |
|
"Speech Pattern", |
|
["Formal", "Casual", "Educated", "Street-wise", "Poetic", "Technical", "Noble"], |
|
help="How the character typically speaks" |
|
) |
|
|
|
speech_pacing = st.select_slider( |
|
"Speech Pace", |
|
options=["Very Slow", "Slow", "Moderate", "Quick", "Very Quick"], |
|
value="Moderate", |
|
help="Speed and rhythm of speech" |
|
) |
|
|
|
st.markdown('<p class="subsection-title">Emotional Profile</p>', unsafe_allow_html=True) |
|
emo_col1, emo_col2 = st.columns(2) |
|
|
|
with emo_col1: |
|
primary_emotion = st.selectbox( |
|
"Primary Emotional State", |
|
["Determined", "Serene", "Passionate", "Calculating", "Troubled", "Optimistic", "Reserved"] |
|
) |
|
emotional_stability = st.slider("Emotional Stability", 1, 10, 5) |
|
|
|
with emo_col2: |
|
emotional_expression = st.selectbox( |
|
"Emotional Expression Style", |
|
["Open", "Guarded", "Volatile", "Controlled", "Subtle", "Dramatic"] |
|
) |
|
emotional_depth = st.slider("Emotional Depth", 1, 10, 5) |
|
|
|
return { |
|
"archetype": selected_archetype, |
|
"age_range": age_range, |
|
"traits": { |
|
"confidence": confidence, |
|
"empathy": empathy, |
|
"intelligence": intelligence, |
|
"courage": courage, |
|
"ambition": ambition, |
|
"loyalty": loyalty, |
|
"humor": humor, |
|
"creativity": creativity, |
|
"wisdom": wisdom |
|
}, |
|
"voice": { |
|
"type": voice_type, |
|
"accent": accent, |
|
"pattern": speech_pattern, |
|
"pacing": speech_pacing |
|
}, |
|
"emotional_profile": { |
|
"primary_emotion": primary_emotion, |
|
"stability": emotional_stability, |
|
"expression": emotional_expression, |
|
"depth": emotional_depth |
|
} |
|
} |
|
|
|
def create_voice_preview(description: str, text: str) -> Dict: |
|
"""Create voice preview using ElevenLabs API""" |
|
url = 'https://api.elevenlabs.io/v1/text-to-voice/create-previews' |
|
|
|
headers = { |
|
'xi-api-key': st.secrets["ELEVENLABS_API_KEY"], |
|
'Content-Type': 'application/json' |
|
} |
|
|
|
payload = { |
|
'voice_description': description, |
|
'text': text |
|
} |
|
|
|
try: |
|
response = requests.post(url, headers=headers, json=payload) |
|
if response.status_code == 200: |
|
return response.json() |
|
else: |
|
st.error(f"Voice generation error: {response.status_code}") |
|
st.write("Error details:", response.json()) |
|
return None |
|
except Exception as e: |
|
st.error(f"Error: {str(e)}") |
|
return None |
|
|
|
def display_character_profile(character: Dict, voice_data: Dict = None): |
|
"""Display character profile with voice playback""" |
|
if not character: |
|
st.warning("No character data available") |
|
return |
|
|
|
try: |
|
with st.container(): |
|
st.markdown('<div class="character-card">', unsafe_allow_html=True) |
|
|
|
|
|
if character.get("archetype"): |
|
st.markdown(f'<p class="section-title">{character["archetype"]}</p>', |
|
unsafe_allow_html=True) |
|
|
|
col1, col2 = st.columns([2, 1]) |
|
|
|
with col1: |
|
if character.get("traits"): |
|
st.markdown("### Personality Profile") |
|
|
|
|
|
traits = character['traits'] |
|
fig = go.Figure(data=go.Scatterpolar( |
|
r=[traits[key] for key in traits.keys()], |
|
theta=list(traits.keys()), |
|
fill='toself', |
|
line=dict(color='#4e8cff') |
|
)) |
|
|
|
fig.update_layout( |
|
polar=dict( |
|
radialaxis=dict(visible=True, range=[0, 10])), |
|
showlegend=False, |
|
paper_bgcolor='rgba(0,0,0,0)', |
|
plot_bgcolor='rgba(0,0,0,0)', |
|
font=dict(color='white') |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
with col2: |
|
st.markdown('<p class="subsection-title">Voice Sample</p>', |
|
unsafe_allow_html=True) |
|
if voice_data and 'previews' in voice_data and voice_data['previews']: |
|
st.markdown('<div class="audio-player">', unsafe_allow_html=True) |
|
audio_data = base64.b64decode(voice_data['previews'][0]['audio_base_64']) |
|
st.audio(audio_data, format='audio/mp3') |
|
st.markdown('</div>', unsafe_allow_html=True) |
|
|
|
if character.get("voice"): |
|
st.markdown('<p class="subsection-title">Core Traits</p>', |
|
unsafe_allow_html=True) |
|
st.markdown(f"**Age Range:** {character.get('age_range', 'N/A')}") |
|
st.markdown(f"**Voice Type:** {character['voice'].get('type', 'N/A')}") |
|
st.markdown(f"**Speech Pattern:** {character['voice'].get('pattern', 'N/A')}") |
|
|
|
st.markdown('</div>', unsafe_allow_html=True) |
|
|
|
except Exception as e: |
|
st.error(f"Error displaying character profile: {str(e)}") |
|
|
|
def create_story_parameters_section(): |
|
st.markdown('<p class="section-title">Story Configuration</p>', unsafe_allow_html=True) |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
genre = st.selectbox("Genre", GENRES) |
|
|
|
writing_style = st.selectbox( |
|
"Writing Style", |
|
list(WRITING_STYLES.keys()) |
|
) |
|
st.info(WRITING_STYLES[writing_style]) |
|
|
|
tone = st.select_slider( |
|
"Story Tone", |
|
options=["Light", "Hopeful", "Neutral", "Dark", "Gritty"], |
|
value="Neutral" |
|
) |
|
|
|
with col2: |
|
pacing = st.select_slider( |
|
"Story Pacing", |
|
options=["Slow & Thoughtful", "Balanced", "Fast & Intense"], |
|
value="Balanced" |
|
) |
|
|
|
plot_complexity = st.slider("Plot Complexity", 1, 10, 5) |
|
|
|
dialogue_focus = st.slider("Dialogue Emphasis", 1, 10, 5) |
|
|
|
|
|
st.markdown('<p class="subsection-title">Setting & Atmosphere</p>', unsafe_allow_html=True) |
|
set_col1, set_col2 = st.columns(2) |
|
|
|
with set_col1: |
|
time_period = st.selectbox( |
|
"Time Period", |
|
["Ancient", "Medieval", "Renaissance", "Industrial", "Modern", |
|
"Contemporary", "Near Future", "Far Future", "Multiple Eras"] |
|
) |
|
|
|
setting_type = st.selectbox( |
|
"Setting Type", |
|
["Urban", "Rural", "Wilderness", "Fantasy World", "Space", |
|
"Underground", "Ocean", "Multiple Locations"] |
|
) |
|
|
|
with set_col2: |
|
atmosphere = st.select_slider( |
|
"Atmosphere", |
|
options=["Mysterious", "Whimsical", "Tense", "Peaceful", "Epic", "Intimate"], |
|
value="Tense" |
|
) |
|
|
|
realism_level = st.select_slider( |
|
"Realism Level", |
|
options=["Highly Realistic", "Balanced", "Fantastical"], |
|
value="Balanced" |
|
) |
|
|
|
|
|
st.markdown('<p class="subsection-title">Themes & Conflicts</p>', unsafe_allow_html=True) |
|
theme_col1, theme_col2 = st.columns(2) |
|
|
|
with theme_col1: |
|
themes = st.multiselect( |
|
"Main Themes", |
|
["Redemption", "Power", "Love", "Justice", "Identity", "Change", |
|
"Good vs Evil", "Order vs Chaos", "Faith", "Family"], |
|
default=["Identity"] |
|
) |
|
|
|
conflict_type = st.selectbox( |
|
"Primary Conflict", |
|
["Person vs Person", "Person vs Nature", "Person vs Society", |
|
"Person vs Self", "Person vs Technology", "Person vs Fate"] |
|
) |
|
|
|
with theme_col2: |
|
moral_ambiguity = st.select_slider( |
|
"Moral Ambiguity", |
|
options=["Clear Good/Evil", "Some Gray Areas", "Morally Complex"], |
|
value="Some Gray Areas" |
|
) |
|
|
|
theme_exploration = st.select_slider( |
|
"Theme Exploration", |
|
options=["Subtle", "Balanced", "Overt"], |
|
value="Balanced" |
|
) |
|
|
|
return { |
|
"main_params": { |
|
"genre": genre, |
|
"writing_style": writing_style, |
|
"tone": tone, |
|
"pacing": pacing, |
|
"plot_complexity": plot_complexity, |
|
"dialogue_focus": dialogue_focus |
|
}, |
|
"setting": { |
|
"time_period": time_period, |
|
"type": setting_type, |
|
"atmosphere": atmosphere, |
|
"realism": realism_level |
|
}, |
|
"themes": { |
|
"main_themes": themes, |
|
"conflict": conflict_type, |
|
"moral_ambiguity": moral_ambiguity, |
|
"exploration": theme_exploration |
|
} |
|
} |
|
|
|
def display_story_preview(story_params: Dict, generated_content: Dict = None): |
|
"""Display generated story content""" |
|
if not generated_content: |
|
st.info("Generate preview to see content") |
|
return |
|
|
|
st.markdown('<div class="story-section">', unsafe_allow_html=True) |
|
|
|
|
|
tabs = st.tabs(["Story", "Analysis"]) |
|
|
|
with tabs[0]: |
|
|
|
st.markdown('<p class="subsection-title" style="color: #4e8cff;">Prologue</p>', |
|
unsafe_allow_html=True) |
|
st.markdown(generated_content.get('prologue', '')) |
|
|
|
|
|
if generated_content.get('character_analysis'): |
|
st.markdown('<p class="subsection-title" style="color: #4e8cff;">Character Analysis</p>', |
|
unsafe_allow_html=True) |
|
st.markdown(generated_content['character_analysis']) |
|
|
|
|
|
if generated_content.get('scene_preview'): |
|
st.markdown('<p class="subsection-title" style="color: #4e8cff;">Scene Preview</p>', |
|
unsafe_allow_html=True) |
|
st.markdown(generated_content['scene_preview']) |
|
|
|
with tabs[1]: |
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
|
|
st.markdown('<p class="subsection-title" style="color: #4e8cff;">Themes & Motifs</p>', |
|
unsafe_allow_html=True) |
|
themes = generated_content.get('analysis', {}).get('themes', []) |
|
if themes: |
|
for theme in themes: |
|
st.markdown(f"β’ {theme}") |
|
else: |
|
st.info("No themes specified") |
|
|
|
with col2: |
|
|
|
st.markdown('<p class="subsection-title" style="color: #4e8cff;">Style Analysis</p>', |
|
unsafe_allow_html=True) |
|
style = generated_content.get('analysis', {}).get('style', {}) |
|
if style: |
|
for key, value in style.items(): |
|
st.markdown(f"**{key}:**") |
|
st.markdown(value) |
|
else: |
|
st.info("No style analysis available") |
|
|
|
st.markdown('</div>', unsafe_allow_html=True) |
|
|
|
def generate_story_content(character: Dict, story_params: Dict) -> Dict: |
|
"""Generate story content using Claude""" |
|
client = anthropic.Anthropic(api_key=st.secrets["ANTHROPIC_API_KEY"]) |
|
|
|
prompt = f"""Create a compelling and human-like story preview based on the following character and parameters: |
|
|
|
Character Details: |
|
{json.dumps(character, indent=2)} |
|
|
|
Story Parameters: |
|
{json.dumps(story_params, indent=2)} |
|
|
|
Please provide: |
|
|
|
1. Prologue (2-3 engaging paragraphs) |
|
- Set the tone and atmosphere |
|
- Introduce the world/setting |
|
- Create intrigue |
|
|
|
2. Character Analysis |
|
- Deep psychological insights |
|
- Key motivations and conflicts |
|
- Unique traits and quirks |
|
|
|
3. Sample Scene |
|
- Show character in action |
|
- Demonstrate personality |
|
- Include meaningful dialogue |
|
|
|
4. Thematic Analysis |
|
- Provide 3-4 major themes |
|
- Explain their significance |
|
- Show their manifestation |
|
|
|
5. Style Elements |
|
- Discuss narrative approach |
|
- Note key stylistic choices |
|
- Highlight unique features |
|
|
|
Make the writing feel natural and professional, avoiding any AI-like patterns. Focus on depth and authenticity.""" |
|
|
|
try: |
|
response = client.messages.create( |
|
max_tokens=4096, |
|
model="claude-3-5-sonnet-latest", |
|
messages=[{"role": "user", "content": prompt}] |
|
) |
|
|
|
content = response.content[0].text |
|
|
|
|
|
sections = parse_generated_content(content) |
|
|
|
return sections |
|
except Exception as e: |
|
st.error(f"Error generating content: {str(e)}") |
|
return None |
|
|
|
def parse_generated_content(content: str) -> Dict: |
|
"""Parse Claude's response into structured sections""" |
|
sections = { |
|
"prologue": "", |
|
"character_analysis": "", |
|
"scene_preview": "", |
|
"analysis": { |
|
"themes": [], |
|
"style": {} |
|
} |
|
} |
|
|
|
try: |
|
current_section = None |
|
buffer = [] |
|
|
|
for line in content.split('\n'): |
|
line = line.strip() |
|
if not line: |
|
continue |
|
|
|
lower_line = line.lower() |
|
|
|
|
|
if "prologue" in lower_line and len(line) < 30: |
|
if buffer and current_section: |
|
sections[current_section] = '\n'.join(buffer).strip() |
|
current_section = "prologue" |
|
buffer = [] |
|
continue |
|
|
|
elif "character analysis" in lower_line and len(line) < 30: |
|
if buffer and current_section: |
|
sections[current_section] = '\n'.join(buffer).strip() |
|
current_section = "character_analysis" |
|
buffer = [] |
|
continue |
|
|
|
elif "sample scene" in lower_line and len(line) < 30: |
|
if buffer and current_section: |
|
sections[current_section] = '\n'.join(buffer).strip() |
|
current_section = "scene_preview" |
|
buffer = [] |
|
continue |
|
|
|
elif ("theme" in lower_line or "themes" in lower_line) and len(line) < 30: |
|
if buffer and current_section: |
|
sections[current_section] = '\n'.join(buffer).strip() |
|
current_section = "themes" |
|
buffer = [] |
|
continue |
|
|
|
elif "style" in lower_line and len(line) < 30: |
|
if buffer and current_section: |
|
sections[current_section] = '\n'.join(buffer).strip() |
|
current_section = "style" |
|
buffer = [] |
|
continue |
|
|
|
|
|
if current_section: |
|
if current_section == "themes": |
|
if line.startswith(('β’', '-', '*')): |
|
clean_line = line.lstrip('β’-* ').strip() |
|
sections['analysis']['themes'].append(clean_line) |
|
elif line: |
|
buffer.append(line) |
|
|
|
elif current_section == "style": |
|
if ':' in line: |
|
key, value = line.split(':', 1) |
|
key = key.strip().strip('0123456789.- ').strip() |
|
sections['analysis']['style'][key] = value.strip() |
|
elif line: |
|
buffer.append(line) |
|
|
|
else: |
|
buffer.append(line) |
|
|
|
|
|
if buffer and current_section: |
|
if current_section in ["themes", "style"]: |
|
|
|
pass |
|
else: |
|
sections[current_section] = '\n'.join(buffer).strip() |
|
|
|
return sections |
|
|
|
except Exception as e: |
|
st.error(f"Error parsing content: {str(e)}") |
|
return None |
|
|
|
def main(): |
|
st.title("NarrativeCraft: Immersive Story & Character Design Studio") |
|
st.markdown("#### Created by Oussama Naji") |
|
|
|
with st.sidebar: |
|
st.markdown("### Creation Process") |
|
current_step = st.radio( |
|
"Select Step:", |
|
["Character Creation", "Story Configuration", "Generate Preview"] |
|
) |
|
|
|
try: |
|
if current_step == "Character Creation": |
|
character = create_character_section() |
|
if st.button("Save Character", type="primary"): |
|
st.session_state.character = character |
|
st.success("Character saved! Proceed to Story Configuration.") |
|
|
|
elif current_step == "Story Configuration": |
|
if 'character' not in st.session_state: |
|
st.warning("Please create a character first!") |
|
return |
|
|
|
story_params = create_story_parameters_section() |
|
if st.button("Save Story Parameters", type="primary"): |
|
st.session_state.story_params = story_params |
|
st.success("Story parameters saved! Proceed to Preview Generation.") |
|
|
|
else: |
|
if 'character' not in st.session_state or 'story_params' not in st.session_state: |
|
st.warning("Please complete character and story configuration first!") |
|
return |
|
|
|
if st.button("Generate Preview", type="primary"): |
|
with st.spinner("π¨ Crafting your story..."): |
|
progress_bar = st.progress(0) |
|
|
|
try: |
|
|
|
progress_bar.progress(25) |
|
st.info("π€ Generating story content...") |
|
st.session_state.generated_content = generate_story_content( |
|
st.session_state.character, |
|
st.session_state.story_params |
|
) |
|
|
|
if not st.session_state.generated_content: |
|
st.error("Failed to generate story content") |
|
return |
|
|
|
|
|
progress_bar.progress(50) |
|
st.info("π€ Creating character voice...") |
|
|
|
voice_desc = f"A {st.session_state.character['voice']['type']} voice with {st.session_state.character['voice']['pattern']} speech pattern" |
|
sample_text = "I stand here before you, ready to share my story. Each word carries the weight of my experiences, the essence of who I am." |
|
|
|
st.session_state.voice_data = create_voice_preview(voice_desc, sample_text) |
|
|
|
progress_bar.progress(75) |
|
st.info("β¨ Finalizing preview...") |
|
|
|
|
|
if st.session_state.character: |
|
display_character_profile( |
|
st.session_state.character, |
|
st.session_state.voice_data |
|
) |
|
|
|
if st.session_state.story_params and st.session_state.generated_content: |
|
display_story_preview( |
|
st.session_state.story_params, |
|
st.session_state.generated_content |
|
) |
|
|
|
progress_bar.progress(100) |
|
st.success("β
Preview generated successfully!") |
|
|
|
except Exception as e: |
|
st.error(f"An error occurred: {str(e)}") |
|
return |
|
|
|
|
|
elif all(key in st.session_state for key in ['character', 'generated_content']): |
|
display_character_profile( |
|
st.session_state.character, |
|
st.session_state.voice_data |
|
) |
|
display_story_preview( |
|
st.session_state.story_params, |
|
st.session_state.generated_content |
|
) |
|
|
|
|
|
if 'generated_content' in st.session_state: |
|
if st.button("Start Over"): |
|
for key in ['character', 'story_params', 'generated_content', 'voice_data']: |
|
if key in st.session_state: |
|
del st.session_state[key] |
|
st.rerun() |
|
|
|
except Exception as e: |
|
st.error(f"An error occurred in main: {str(e)}") |
|
|
|
if __name__ == "__main__": |
|
main() |