sunbal7's picture
Update app.py
a6cbda1 verified
raw
history blame
30.3 kB
# app.py - Final Version with Enhanced Animations
import streamlit as st
import os
import time
import random
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from gtts import gTTS
import base64
from PIL import Image
import io
import matplotlib.pyplot as plt
import requests
# Configure Streamlit page
st.set_page_config(
page_title="StoryCoder - Learn Python Through Stories",
page_icon="πŸ§™β€β™‚οΈ",
layout="wide",
initial_sidebar_state="expanded"
)
# Custom CSS for colorful UI
st.markdown("""
<style>
@import url('https://fonts.googleapis.com/css2?family=Comic+Neue:wght@700&display=swap');
:root {
--primary: #FF6B6B;
--secondary: #4ECDC4;
--accent: #FFD166;
--dark: #1A535C;
--light: #F7FFF7;
}
body {
background: linear-gradient(135deg, var(--light) 0%, #E8F4F8 100%);
font-family: 'Comic Neue', cursive;
}
.stApp {
background: url('https://www.transparenttextures.com/patterns/cartographer.png');
}
.story-box {
background-color: white;
border-radius: 20px;
padding: 25px;
box-shadow: 0 8px 16px rgba(26, 83, 92, 0.15);
border: 3px solid var(--accent);
margin-bottom: 25px;
}
.header {
color: var(--dark);
text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
}
.concept-card {
background: linear-gradient(145deg, #ffffff, #f0f0f0);
border-radius: 15px;
padding: 15px;
margin: 10px 0;
border-left: 5px solid var(--secondary);
box-shadow: 0 4px 8px rgba(0,0,0,0.05);
}
.stButton>button {
background: linear-gradient(45deg, var(--primary), var(--secondary));
color: white;
border-radius: 12px;
padding: 10px 24px;
font-weight: bold;
font-size: 18px;
border: none;
transition: all 0.3s;
}
.stButton>button:hover {
transform: scale(1.05);
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
}
.stTextInput>div>div>input {
border-radius: 12px;
padding: 12px;
border: 2px solid var(--accent);
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
overflow-x: auto;
}
.tab {
padding: 10px 20px;
background-color: var(--accent);
border-radius: 10px;
cursor: pointer;
font-weight: bold;
white-space: nowrap;
}
.tab.active {
background-color: var(--secondary);
color: white;
}
@media (max-width: 768px) {
.tabs {
flex-wrap: wrap;
}
}
.animation-container {
background-color: #1a1a2e;
border-radius: 15px;
padding: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
margin-bottom: 25px;
position: relative;
overflow: hidden;
}
.animation-canvas {
border-radius: 10px;
overflow: hidden;
margin: 0 auto;
display: block;
max-width: 100%;
}
.character {
font-size: 48px;
text-align: center;
margin: 10px 0;
}
.audio-player {
width: 100%;
margin: 20px 0;
border-radius: 20px;
background: #f0f8ff;
padding: 15px;
}
</style>
""", unsafe_allow_html=True)
# Concept database
CONCEPTS = {
"loop": {
"name": "Loop",
"emoji": "πŸ”„",
"description": "Loops repeat actions multiple times",
"example": "for i in range(5):\n print('Hello!')",
"color": "#FF9E6D"
},
"conditional": {
"name": "Conditional",
"emoji": "❓",
"description": "Conditionals make decisions in code",
"example": "if sunny:\n go_outside()\nelse:\n stay_inside()",
"color": "#4ECDC4"
},
"function": {
"name": "Function",
"emoji": "✨",
"description": "Functions are reusable blocks of code",
"example": "def greet(name):\n print(f'Hello {name}!')",
"color": "#FFD166"
},
"variable": {
"name": "Variable",
"emoji": "πŸ“¦",
"description": "Variables store information",
"example": "score = 10\nplayer = 'Alex'",
"color": "#FF6B6B"
},
"list": {
"name": "List",
"emoji": "πŸ“",
"description": "Lists store collections of items",
"example": "fruits = ['apple', 'banana', 'orange']",
"color": "#1A535C"
}
}
# Character database with enhanced visuals
CHARACTERS = [
{"name": "rabbit", "emoji": "🐰", "color": "#FFB6C1", "speed": 1.5},
{"name": "dragon", "emoji": "πŸ‰", "color": "#FF6347", "speed": 0.8},
{"name": "cat", "emoji": "🐱", "color": "#DDA0DD", "speed": 2.0},
{"name": "dog", "emoji": "🐢", "color": "#FFD700", "speed": 2.5},
{"name": "knight", "emoji": "🀺", "color": "#87CEEB", "speed": 1.2},
{"name": "wizard", "emoji": "πŸ§™", "color": "#98FB98", "speed": 1.0},
{"name": "scientist", "emoji": "πŸ”¬", "color": "#20B2AA", "speed": 1.3},
{"name": "pirate", "emoji": "πŸ΄β€β˜ οΈ", "color": "#FFA500", "speed": 1.4}
]
# Object database for stories
STORY_OBJECTS = [
{"name": "carrot", "emoji": "πŸ₯•", "color": "#FF8C00"},
{"name": "treasure", "emoji": "πŸ’°", "color": "#FFD700"},
{"name": "castle", "emoji": "🏰", "color": "#A9A9A9"},
{"name": "flower", "emoji": "🌸", "color": "#FF69B4"},
{"name": "book", "emoji": "πŸ“š", "color": "#8B4513"},
{"name": "star", "emoji": "⭐", "color": "#FFFF00"},
{"name": "key", "emoji": "πŸ”‘", "color": "#DAA520"},
{"name": "apple", "emoji": "🍎", "color": "#FF0000"}
]
# Animation templates with visual cues
ANIMATION_TEMPLATES = {
"loop": {
"description": "Character moving to a target multiple times",
"visual_cue": "πŸ”„ Repeating action multiple times",
"code": """
# Loop Animation
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(figsize=(10, 6))
ax.set_title('Your Story: {story}')
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
character = ax.text(1, 5, '{emoji}', fontsize=48, color='{color}')
target = ax.text(9, 5, '{target_emoji}', fontsize=48)
info = ax.text(5, 8, 'Loop: Repeating {count} times',
fontsize=16, ha='center', color='{concept_color}')
for i in range({count}):
# Update position
for pos in np.linspace(1, 9, 20):
character.set_position((pos, 5))
plt.pause(0.05)
# Show loop counter
info.set_text(f'Loop: Completed {{i+1}}/{count}')
plt.pause(0.2)
# Return to start
for pos in np.linspace(9, 1, 20):
character.set_position((pos, 5))
plt.pause(0.05)
plt.show()
"""
},
"conditional": {
"description": "Character making a decision based on a condition",
"visual_cue": "❓ Making a decision based on a condition",
"code": """
# Conditional Animation
import matplotlib.pyplot as plt
import numpy as np
import random
fig, ax = plt.subplots(figsize=(10, 6))
ax.set_title('Your Story: {story}')
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
character = ax.text(5, 5, '{emoji}', fontsize=48, color='{color}')
condition = random.choice([True, False])
info = ax.text(5, 8, 'Condition: {condition_desc}',
fontsize=16, ha='center', color='{concept_color}')
if condition:
# Path for True condition
decision = ax.text(8, 7, 'βœ… True Path', fontsize=20, color='green')
path = np.linspace(5, 8, 20)
else:
# Path for False condition
decision = ax.text(2, 7, '❌ False Path', fontsize=20, color='red')
path = np.linspace(5, 2, 20)
for pos in path:
character.set_position((pos, 5))
plt.pause(0.05)
plt.show()
"""
},
"function": {
"description": "Character performing an action multiple times",
"visual_cue": "✨ Performing action with a function",
"code": """
# Function Animation
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(figsize=(10, 6))
ax.set_title('Your Story: {story}')
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
character = ax.text(5, 5, '{emoji}', fontsize=48, color='{color}')
info = ax.text(5, 8, 'Function: Performing action {count} times',
fontsize=16, ha='center', color='{concept_color}')
def perform_action(position):
# Action animation
character.set_fontsize(60)
plt.pause(0.1)
character.set_fontsize(48)
plt.pause(0.1)
# Move to new position
character.set_position((position, 5))
for i in range({count}):
perform_action(5 + i)
info.set_text(f'Function: Action {{i+1}}/{count}')
plt.pause(0.3)
plt.show()
"""
}
}
def analyze_story(story):
"""Analyze story and identify programming concepts"""
story_lower = story.lower()
detected_concepts = []
# Check for loops
if any(word in story_lower for word in ["times", "repeat", "again", "multiple"]):
detected_concepts.append("loop")
# Check for conditionals
if any(word in story_lower for word in ["if", "when", "unless", "whether"]):
detected_concepts.append("conditional")
# Check for functions
if any(word in story_lower for word in ["make", "create", "do", "perform", "cast"]):
detected_concepts.append("function")
# Check for variables
if any(word in story_lower for word in ["is", "has", "set to", "value"]):
detected_concepts.append("variable")
# Check for lists
if any(word in story_lower for word in ["and", "many", "several", "collection", "items"]):
detected_concepts.append("list")
return list(set(detected_concepts))
def extract_count_from_story(story):
"""Extract a number from the story to use in animations"""
for word in story.split():
if word.isdigit():
return min(int(word), 10)
return 3 # Default value
def extract_story_elements(story):
"""Extract characters and objects from the story"""
# Choose a random character
character = random.choice(CHARACTERS)
# Choose a random object
story_object = random.choice(STORY_OBJECTS)
# Try to find character in story
for char in CHARACTERS:
if char["name"] in story.lower():
character = char
break
# Try to find object in story
for obj in STORY_OBJECTS:
if obj["name"] in story.lower():
story_object = obj
break
return character, story_object
def create_visualization(story, concepts):
"""Create a visualization image based on the story and concepts"""
try:
# Get story elements
character, story_object = extract_story_elements(story)
count = extract_count_from_story(story)
concept = concepts[0] if concepts else "variable"
concept_color = CONCEPTS[concept]["color"]
# Create figure
fig, ax = plt.subplots(figsize=(10, 6))
ax.set_facecolor('#f0f8ff')
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.axis('off')
# Add title
ax.text(5, 9, '✨ Your Story Visualization ✨',
fontsize=20, ha='center', color='purple', fontweight='bold')
# Add story text
story_text = story[:80] + ('...' if len(story) > 80 else '')
ax.text(5, 8, f'"{story_text}"',
fontsize=14, ha='center', color='#333')
# Add character
ax.text(3, 5, character["emoji"],
fontsize=100, ha='center', color=character["color"])
# Add object
ax.text(7, 5, story_object["emoji"],
fontsize=100, ha='center', color=story_object["color"])
# Add connection arrow
ax.annotate('', xy=(7, 5), xytext=(3, 5),
arrowprops=dict(arrowstyle='->', lw=3, color=concept_color))
# Add concept visualization
concept_text = CONCEPTS[concept]["emoji"] + " " + CONCEPTS[concept]["name"]
ax.text(5, 3, concept_text,
fontsize=24, ha='center', color=concept_color, fontweight='bold')
# Add action text
action = f"Action: {character['name'].title()} moves toward {story_object['name']}"
ax.text(5, 2.5, action,
fontsize=16, ha='center', color='#333')
# Add footer
ax.text(5, 1, "Created with StoryCoder",
fontsize=12, ha='center', style='italic', color='gray')
# Save to buffer
buf = io.BytesIO()
plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0.5, dpi=150)
buf.seek(0)
plt.close()
return buf
except Exception as e:
st.error(f"Visualization error: {str(e)}")
return None
def create_interactive_animation(story, concepts):
"""Create an interactive animation using Plotly"""
try:
# Get story elements
character, story_object = extract_story_elements(story)
count = extract_count_from_story(story)
concept = concepts[0] if concepts else "variable"
concept_color = CONCEPTS[concept]["color"]
# Create animation data
frames = []
for i in range(count):
# Create a path for the character
path_length = 20
for step in range(path_length):
progress = (i * path_length + step) / (count * path_length)
frames.append({
"frame": i * path_length + step,
"x": 3 + 4 * progress,
"y": 5 + np.sin(progress * np.pi * 2) * 2, # Wave motion
"size": 20 + np.sin(step * 0.3) * 10, # Pulsing effect
"character": character["emoji"],
"color": character["color"],
"info": f"{character['name'].title()} moving to {story_object['name']}",
"step": f"Action {i+1}/{count}"
})
df = pd.DataFrame(frames)
# Create the target object (static)
target_df = pd.DataFrame([{
"x": 7,
"y": 5,
"character": story_object["emoji"],
"color": story_object["color"],
"size": 30,
"info": f"Target: {story_object['name']}",
"step": "Target"
}])
# Create animated scatter plot
fig = px.scatter(
df,
x="x",
y="y",
animation_frame="frame",
text="character",
size="size",
size_max=45,
color_discrete_sequence=[character["color"]],
range_x=[0, 10],
range_y=[0, 10],
hover_name="info",
hover_data={"step": True}
)
# Add the target object
fig.add_trace(go.Scatter(
x=target_df["x"],
y=target_df["y"],
text=target_df["character"],
mode="text",
textfont=dict(size=40, color=story_object["color"]),
hoverinfo="text",
hovertext=target_df["info"],
showlegend=False
))
# Add concept indicator
fig.add_annotation(
x=0.5,
y=1.05,
xref="paper",
yref="paper",
text=f"{CONCEPTS[concept]['emoji']} {CONCEPTS[concept]['name']}",
showarrow=False,
font=dict(size=24, color=concept_color)
)
# Customize layout
fig.update_layout(
title=f'Animation: {story[:60]}{"..." if len(story) > 60 else ""}',
showlegend=False,
plot_bgcolor='rgba(240,248,255,1)',
paper_bgcolor='rgba(240,248,255,1)',
width=800,
height=600,
xaxis=dict(showgrid=False, zeroline=False, visible=False),
yaxis=dict(showgrid=False, zeroline=False, visible=False),
updatemenus=[dict(
type="buttons",
buttons=[dict(
label="Play",
method="animate",
args=[None, {"frame": {"duration": 100, "redraw": True}}]
)]
)])
fig.update_traces(
textfont_size=40,
textposition='middle center',
hoverlabel=dict(bgcolor="white", font_size=16)
)
return fig
except Exception as e:
st.error(f"Interactive animation error: {str(e)}")
return None
def text_to_speech(text, filename="story_audio.mp3"):
"""Convert text to speech using gTTS"""
try:
tts = gTTS(text=text, lang='en')
tts.save(filename)
return filename
except Exception as e:
st.error(f"Audio creation error: {str(e)}")
return None
def autoplay_audio(file_path):
"""Autoplay audio in Streamlit"""
with open(file_path, "rb") as f:
data = f.read()
b64 = base64.b64encode(data).decode()
md = f"""
<audio autoplay>
<source src="data:audio/mp3;base64,{b64}" type="audio/mp3">
</audio>
"""
st.markdown(md, unsafe_allow_html=True)
def generate_animation_code(story, concepts):
"""Generate Python animation code based on story"""
try:
# Get story elements
character, story_object = extract_story_elements(story)
count = extract_count_from_story(story)
concept = concepts[0] if concepts else "loop"
# Get the appropriate template
template = ANIMATION_TEMPLATES.get(concept, ANIMATION_TEMPLATES["loop"])
# Fill in the template
condition_desc = random.choice(["sunny", "raining", "dark"])
code = template["code"].format(
emoji=character["emoji"],
color=character["color"],
story=story[:80] + ('...' if len(story) > 80 else ''),
count=count,
condition_desc=condition_desc,
target_emoji=story_object["emoji"],
concept_color=CONCEPTS[concept]["color"]
)
return code, template["description"], template["visual_cue"]
except Exception as e:
return f"# Error generating code\nprint('Could not generate code: {str(e)}')", "", ""
def create_story_image(story):
"""Create a story image with Matplotlib"""
try:
# Create figure
fig, ax = plt.subplots(figsize=(10, 6))
ax.set_facecolor('#f0f8ff')
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.axis('off')
# Add title
ax.text(5, 8, '✨ Your Story ✨', fontsize=24,
ha='center', color='purple', fontweight='bold')
# Add story text (wrapped)
words = story.split()
lines = []
current_line = []
for word in words:
if len(' '.join(current_line + [word])) < 60:
current_line.append(word)
else:
lines.append(' '.join(current_line))
current_line = [word]
if current_line:
lines.append(' '.join(current_line))
for i, line in enumerate(lines):
ax.text(5, 6.5 - i*0.7, line, fontsize=14, ha='center', color='#333')
# Add decoration
ax.text(2, 2, '🐰', fontsize=40, ha='center')
ax.text(8, 2, 'πŸ‰', fontsize=40, ha='center')
ax.text(5, 1, 'Created with StoryCoder', fontsize=12,
ha='center', style='italic', color='gray')
# Save to buffer
buf = io.BytesIO()
plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0.5, dpi=150)
buf.seek(0)
plt.close()
return buf
except Exception as e:
st.error(f"Image generation error: {str(e)}")
return None
def main():
"""Main application function"""
st.title("πŸ§™β€β™‚οΈ StoryCoder - Learn Python Through Stories!")
st.subheader("Turn your story into an animation and discover coding secrets!")
# Initialize session state
if 'story' not in st.session_state:
st.session_state.story = ""
if 'concepts' not in st.session_state:
st.session_state.concepts = []
if 'visualization' not in st.session_state:
st.session_state.visualization = None
if 'interactive_animation' not in st.session_state:
st.session_state.interactive_animation = None
if 'animation_code' not in st.session_state:
st.session_state.animation_code = ""
if 'code_description' not in st.session_state:
st.session_state.code_description = ""
if 'visual_cue' not in st.session_state:
st.session_state.visual_cue = ""
if 'audio_file' not in st.session_state:
st.session_state.audio_file = None
if 'active_tab' not in st.session_state:
st.session_state.active_tab = "story"
# Create tabs
tabs = st.empty()
tab_cols = st.columns(5)
with tab_cols[0]:
if st.button("πŸ“– Create Story"):
st.session_state.active_tab = "story"
with tab_cols[1]:
if st.button("🎬 Animation"):
st.session_state.active_tab = "animation"
with tab_cols[2]:
if st.button("πŸ” Concepts"):
st.session_state.active_tab = "concepts"
with tab_cols[3]:
if st.button("πŸ’» Code"):
st.session_state.active_tab = "code"
with tab_cols[4]:
if st.button("πŸ”„ Reset"):
st.session_state.story = ""
st.session_state.concepts = []
st.session_state.visualization = None
st.session_state.interactive_animation = None
st.session_state.animation_code = ""
st.session_state.code_description = ""
st.session_state.visual_cue = ""
st.session_state.audio_file = None
st.session_state.active_tab = "story"
# Story creation tab
if st.session_state.active_tab == "story":
with st.container():
st.header("πŸ“– Create Your Story")
st.write("Write a short story (2-5 sentences) and I'll turn it into an animation!")
story = st.text_area(
"Your story:",
height=200,
placeholder="Once upon a time, a rabbit hopped 3 times to reach a carrot...",
value=st.session_state.story,
key="story_input"
)
if st.button("Create Animation!", use_container_width=True):
if len(story) < 10:
st.error("Your story needs to be at least 10 characters long!")
else:
st.session_state.story = story
with st.spinner("🧠 Analyzing your story for coding concepts..."):
st.session_state.concepts = analyze_story(story)
with st.spinner("🎨 Creating your visualization..."):
st.session_state.visualization = create_visualization(
story, st.session_state.concepts
)
with st.spinner("πŸš€ Creating interactive animation..."):
st.session_state.interactive_animation = create_interactive_animation(
story, st.session_state.concepts
)
with st.spinner("πŸ’» Generating animation code..."):
st.session_state.animation_code, st.session_state.code_description, st.session_state.visual_cue = generate_animation_code(
story, st.session_state.concepts
)
with st.spinner("πŸ”Š Creating audio narration..."):
audio_text = f"Your story: {story}. This demonstrates the programming concept: {st.session_state.code_description}"
st.session_state.audio_file = text_to_speech(audio_text)
st.session_state.active_tab = "animation"
st.rerun()
# Show examples
st.subheader("✨ Story Examples")
col1, col2, col3 = st.columns(3)
with col1:
st.caption("Loop Example")
st.code('"A dragon breathes fire 5 times at the castle"', language="text")
st.image("https://i.imgur.com/7zQY1eE.png", width=200)
with col2:
st.caption("Conditional Example")
st.code('"If it rains, the cat stays inside, else it goes out"', language="text")
st.image("https://i.imgur.com/5X8jYAy.png", width=200)
with col3:
st.caption("Function Example")
st.code('"A wizard casts a spell to make flowers grow"', language="text")
st.image("https://i.imgur.com/9zJkQ7P.png", width=200)
# Animation tab
elif st.session_state.active_tab == "animation":
st.header("🎬 Your Story Animation")
if not st.session_state.story:
st.warning("Please create a story first!")
st.session_state.active_tab = "story"
st.rerun()
# Display visualization
st.subheader("🎨 Story Visualization")
if st.session_state.visualization:
st.image(st.session_state.visualization, use_container_width=True)
else:
st.warning("Visualization couldn't be generated. Showing story image instead.")
story_img = create_story_image(st.session_state.story)
if story_img:
st.image(story_img, use_container_width=True)
# Display interactive animation
st.subheader("πŸš€ Interactive Animation")
if st.session_state.interactive_animation:
st.plotly_chart(st.session_state.interactive_animation, use_container_width=True)
else:
st.info("Interactive animation preview. The real animation would run on your computer!")
st.image("https://i.imgur.com/8LzQY7a.gif", use_container_width=True)
# Play audio narration
st.subheader("πŸ”Š Story Narration")
if st.session_state.audio_file:
audio_bytes = open(st.session_state.audio_file, 'rb').read()
st.audio(audio_bytes, format='audio/mp3')
if st.button("▢️ Play Automatically"):
autoplay_audio(st.session_state.audio_file)
else:
st.info("Audio narration would play automatically here")
st.success("✨ Animation created successfully!")
st.caption("This animation was generated with Python code based on your story!")
if st.button("Reveal Coding Secrets!", use_container_width=True):
st.session_state.active_tab = "concepts"
st.rerun()
# Concepts tab
elif st.session_state.active_tab == "concepts":
st.header("πŸ” Coding Concepts in Your Story")
st.subheader("We secretly used these programming concepts:")
if not st.session_state.concepts:
st.warning("No concepts detected in your story! Try adding words like '3 times', 'if', or 'make'.")
else:
for concept in st.session_state.concepts:
if concept in CONCEPTS:
details = CONCEPTS[concept]
st.markdown(f"""
<div class="concept-card" style="border-left: 5px solid {details['color']};">
<div style="display:flex; align-items:center; gap:15px;">
<span style="font-size:36px;">{details['emoji']}</span>
<h3 style="color:{details['color']};">{details['name']}</h3>
</div>
<p>{details['description']}</p>
<pre style="background:#f0f0f0; padding:10px; border-radius:8px;">{details['example']}</pre>
</div>
""", unsafe_allow_html=True)
if st.button("See the Magic Code!", use_container_width=True):
st.session_state.active_tab = "code"
st.rerun()
# Code tab
elif st.session_state.active_tab == "code":
st.header("πŸ’» The Magic Code Behind Your Animation")
st.write("Here's the Python code that would create your animation:")
if st.session_state.animation_code:
st.subheader(st.session_state.code_description)
st.markdown(f"**Visual Cue:** {st.session_state.visual_cue}")
st.code(st.session_state.animation_code, language="python")
# Download button
st.download_button(
label="Download Animation Code",
data=st.session_state.animation_code,
file_name="story_animation.py",
mime="text/python",
use_container_width=True
)
st.info("πŸ’‘ To run this animation on your computer:")
st.markdown("""
1. Install Python from [python.org](https://python.org)
2. Install required libraries: `pip install matplotlib numpy`
3. Copy the code above into a file named `animation.py`
4. Run it with: `python animation.py`
""")
st.success("πŸŽ‰ When you run this code, you'll see your story come to life!")
else:
st.warning("No code generated yet!")
if st.button("Create Another Story!", use_container_width=True):
st.session_state.active_tab = "story"
st.session_state.story = ""
st.rerun()
if __name__ == "__main__":
main()