Spaces:
Sleeping
Sleeping
# 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() |