Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,15 +1,18 @@
|
|
1 |
-
# app.py - Final
|
2 |
import streamlit as st
|
3 |
import os
|
4 |
import time
|
5 |
import random
|
6 |
import numpy as np
|
7 |
-
import
|
|
|
|
|
|
|
8 |
import base64
|
9 |
from PIL import Image
|
10 |
import io
|
11 |
-
import
|
12 |
-
import
|
13 |
|
14 |
# Configure Streamlit page
|
15 |
st.set_page_config(
|
@@ -136,6 +139,14 @@ st.markdown("""
|
|
136 |
text-align: center;
|
137 |
margin: 10px 0;
|
138 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
139 |
</style>
|
140 |
""", unsafe_allow_html=True)
|
141 |
|
@@ -178,64 +189,93 @@ CONCEPTS = {
|
|
178 |
}
|
179 |
}
|
180 |
|
181 |
-
# Character database
|
182 |
CHARACTERS = [
|
183 |
-
{"name": "rabbit", "emoji": "π°", "color": "#FFB6C1"},
|
184 |
-
{"name": "dragon", "emoji": "π", "color": "#FF6347"},
|
185 |
-
{"name": "cat", "emoji": "π±", "color": "#DDA0DD"},
|
186 |
-
{"name": "dog", "emoji": "πΆ", "color": "#FFD700"},
|
187 |
-
{"name": "knight", "emoji": "π€Ί", "color": "#87CEEB"},
|
188 |
-
{"name": "wizard", "emoji": "π§", "color": "#98FB98"},
|
189 |
-
{"name": "scientist", "emoji": "π¬", "color": "#20B2AA"},
|
190 |
-
{"name": "pirate", "emoji": "π΄ββ οΈ", "color": "#FFA500"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
191 |
]
|
192 |
|
193 |
-
# Animation templates
|
194 |
ANIMATION_TEMPLATES = {
|
195 |
"loop": {
|
196 |
"description": "Character moving to a target multiple times",
|
|
|
197 |
"code": """
|
198 |
# Loop Animation
|
199 |
import matplotlib.pyplot as plt
|
200 |
import numpy as np
|
201 |
|
202 |
fig, ax = plt.subplots(figsize=(10, 6))
|
|
|
203 |
ax.set_xlim(0, 10)
|
204 |
ax.set_ylim(0, 10)
|
205 |
-
ax.axis('off')
|
206 |
|
207 |
-
character = ax.text(1, 5,
|
208 |
-
target = ax.text(9, 5,
|
209 |
-
ax.text(5,
|
|
|
210 |
|
211 |
for i in range({count}):
|
|
|
212 |
for pos in np.linspace(1, 9, 20):
|
213 |
character.set_position((pos, 5))
|
214 |
plt.pause(0.05)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
215 |
plt.show()
|
216 |
"""
|
217 |
},
|
218 |
"conditional": {
|
219 |
"description": "Character making a decision based on a condition",
|
|
|
220 |
"code": """
|
221 |
# Conditional Animation
|
222 |
import matplotlib.pyplot as plt
|
223 |
import numpy as np
|
|
|
224 |
|
225 |
fig, ax = plt.subplots(figsize=(10, 6))
|
|
|
226 |
ax.set_xlim(0, 10)
|
227 |
ax.set_ylim(0, 10)
|
228 |
-
ax.axis('off')
|
229 |
|
230 |
-
character = ax.text(5, 5,
|
231 |
-
|
|
|
|
|
232 |
|
233 |
-
|
234 |
-
|
235 |
-
decision = ax.text(
|
236 |
path = np.linspace(5, 8, 20)
|
237 |
else:
|
238 |
-
|
|
|
239 |
path = np.linspace(5, 2, 20)
|
240 |
|
241 |
for pos in path:
|
@@ -246,29 +286,34 @@ plt.show()
|
|
246 |
},
|
247 |
"function": {
|
248 |
"description": "Character performing an action multiple times",
|
|
|
249 |
"code": """
|
250 |
# Function Animation
|
251 |
import matplotlib.pyplot as plt
|
252 |
import numpy as np
|
253 |
|
254 |
fig, ax = plt.subplots(figsize=(10, 6))
|
|
|
255 |
ax.set_xlim(0, 10)
|
256 |
ax.set_ylim(0, 10)
|
257 |
-
ax.axis('off')
|
258 |
|
259 |
-
character = ax.text(5, 5,
|
260 |
-
ax.text(5,
|
|
|
261 |
|
262 |
-
def perform_action():
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
|
|
|
|
268 |
|
269 |
for i in range({count}):
|
270 |
-
perform_action()
|
271 |
-
|
|
|
272 |
plt.show()
|
273 |
"""
|
274 |
}
|
@@ -308,12 +353,36 @@ def extract_count_from_story(story):
|
|
308 |
return min(int(word), 10)
|
309 |
return 3 # Default value
|
310 |
|
311 |
-
def
|
312 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
313 |
try:
|
314 |
-
#
|
315 |
-
character =
|
316 |
count = extract_count_from_story(story)
|
|
|
|
|
317 |
|
318 |
# Create figure
|
319 |
fig, ax = plt.subplots(figsize=(10, 6))
|
@@ -323,7 +392,7 @@ def create_animation(story, concepts):
|
|
323 |
ax.axis('off')
|
324 |
|
325 |
# Add title
|
326 |
-
ax.text(5, 9, 'β¨ Your Story
|
327 |
fontsize=20, ha='center', color='purple', fontweight='bold')
|
328 |
|
329 |
# Add story text
|
@@ -332,23 +401,26 @@ def create_animation(story, concepts):
|
|
332 |
fontsize=14, ha='center', color='#333')
|
333 |
|
334 |
# Add character
|
335 |
-
ax.text(
|
336 |
fontsize=100, ha='center', color=character["color"])
|
337 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
338 |
# Add concept visualization
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
ax.text(5, 3, f"β¨ Performing action {count} times",
|
348 |
-
fontsize=18, ha='center', color=CONCEPTS["function"]["color"])
|
349 |
-
else:
|
350 |
-
ax.text(5, 3, "π Creating your story visualization",
|
351 |
-
fontsize=18, ha='center', color=CONCEPTS["variable"]["color"])
|
352 |
|
353 |
# Add footer
|
354 |
ax.text(5, 1, "Created with StoryCoder",
|
@@ -362,29 +434,49 @@ def create_animation(story, concepts):
|
|
362 |
return buf
|
363 |
|
364 |
except Exception as e:
|
365 |
-
st.error(f"
|
366 |
return None
|
367 |
|
368 |
def create_interactive_animation(story, concepts):
|
369 |
"""Create an interactive animation using Plotly"""
|
370 |
try:
|
371 |
-
#
|
372 |
-
character =
|
373 |
count = extract_count_from_story(story)
|
|
|
|
|
374 |
|
375 |
# Create animation data
|
376 |
frames = []
|
377 |
for i in range(count):
|
378 |
-
|
379 |
-
|
380 |
-
|
381 |
-
|
382 |
-
|
383 |
-
|
384 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
385 |
|
386 |
df = pd.DataFrame(frames)
|
387 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
388 |
# Create animated scatter plot
|
389 |
fig = px.scatter(
|
390 |
df,
|
@@ -396,38 +488,92 @@ def create_interactive_animation(story, concepts):
|
|
396 |
size_max=45,
|
397 |
color_discrete_sequence=[character["color"]],
|
398 |
range_x=[0, 10],
|
399 |
-
range_y=[0, 10]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
400 |
)
|
401 |
|
402 |
# Customize layout
|
403 |
fig.update_layout(
|
404 |
-
title=f'Animation
|
405 |
showlegend=False,
|
406 |
plot_bgcolor='rgba(240,248,255,1)',
|
407 |
paper_bgcolor='rgba(240,248,255,1)',
|
408 |
width=800,
|
409 |
-
height=600
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
410 |
)
|
411 |
|
412 |
fig.update_traces(
|
413 |
-
textfont_size=
|
414 |
-
textposition='middle center'
|
|
|
415 |
)
|
416 |
|
417 |
-
fig.layout.updatemenus[0].buttons[0].args[1]["frame"]["duration"] = 1000
|
418 |
-
fig.layout.updatemenus[0].buttons[0].args[1]["transition"]["duration"] = 500
|
419 |
-
|
420 |
return fig
|
421 |
|
422 |
except Exception as e:
|
423 |
st.error(f"Interactive animation error: {str(e)}")
|
424 |
return None
|
425 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
426 |
def generate_animation_code(story, concepts):
|
427 |
"""Generate Python animation code based on story"""
|
428 |
try:
|
429 |
-
#
|
430 |
-
character =
|
431 |
count = extract_count_from_story(story)
|
432 |
concept = concepts[0] if concepts else "loop"
|
433 |
|
@@ -435,19 +581,21 @@ def generate_animation_code(story, concepts):
|
|
435 |
template = ANIMATION_TEMPLATES.get(concept, ANIMATION_TEMPLATES["loop"])
|
436 |
|
437 |
# Fill in the template
|
438 |
-
|
439 |
code = template["code"].format(
|
440 |
emoji=character["emoji"],
|
441 |
color=character["color"],
|
442 |
story=story[:80] + ('...' if len(story) > 80 else ''),
|
443 |
count=count,
|
444 |
-
|
|
|
|
|
445 |
)
|
446 |
|
447 |
-
return code, template["description"]
|
448 |
|
449 |
except Exception as e:
|
450 |
-
return f"# Error generating code\nprint('Could not generate code: {str(e)}')", ""
|
451 |
|
452 |
def create_story_image(story):
|
453 |
"""Create a story image with Matplotlib"""
|
@@ -508,14 +656,18 @@ def main():
|
|
508 |
st.session_state.story = ""
|
509 |
if 'concepts' not in st.session_state:
|
510 |
st.session_state.concepts = []
|
511 |
-
if '
|
512 |
-
st.session_state.
|
513 |
if 'interactive_animation' not in st.session_state:
|
514 |
st.session_state.interactive_animation = None
|
515 |
if 'animation_code' not in st.session_state:
|
516 |
st.session_state.animation_code = ""
|
517 |
if 'code_description' not in st.session_state:
|
518 |
st.session_state.code_description = ""
|
|
|
|
|
|
|
|
|
519 |
if 'active_tab' not in st.session_state:
|
520 |
st.session_state.active_tab = "story"
|
521 |
|
@@ -538,10 +690,12 @@ def main():
|
|
538 |
if st.button("π Reset"):
|
539 |
st.session_state.story = ""
|
540 |
st.session_state.concepts = []
|
541 |
-
st.session_state.
|
542 |
st.session_state.interactive_animation = None
|
543 |
st.session_state.animation_code = ""
|
544 |
st.session_state.code_description = ""
|
|
|
|
|
545 |
st.session_state.active_tab = "story"
|
546 |
|
547 |
# Story creation tab
|
@@ -566,19 +720,25 @@ def main():
|
|
566 |
with st.spinner("π§ Analyzing your story for coding concepts..."):
|
567 |
st.session_state.concepts = analyze_story(story)
|
568 |
|
569 |
-
with st.spinner("
|
570 |
-
st.session_state.
|
571 |
story, st.session_state.concepts
|
572 |
)
|
573 |
|
|
|
574 |
st.session_state.interactive_animation = create_interactive_animation(
|
575 |
story, st.session_state.concepts
|
576 |
)
|
577 |
|
578 |
-
|
|
|
579 |
story, st.session_state.concepts
|
580 |
)
|
581 |
|
|
|
|
|
|
|
|
|
582 |
st.session_state.active_tab = "animation"
|
583 |
st.rerun()
|
584 |
|
@@ -588,12 +748,15 @@ def main():
|
|
588 |
with col1:
|
589 |
st.caption("Loop Example")
|
590 |
st.code('"A dragon breathes fire 5 times at the castle"', language="text")
|
|
|
591 |
with col2:
|
592 |
st.caption("Conditional Example")
|
593 |
st.code('"If it rains, the cat stays inside, else it goes out"', language="text")
|
|
|
594 |
with col3:
|
595 |
st.caption("Function Example")
|
596 |
st.code('"A wizard casts a spell to make flowers grow"', language="text")
|
|
|
597 |
|
598 |
# Animation tab
|
599 |
elif st.session_state.active_tab == "animation":
|
@@ -604,29 +767,34 @@ def main():
|
|
604 |
st.session_state.active_tab = "story"
|
605 |
st.rerun()
|
606 |
|
607 |
-
# Display
|
608 |
-
st.
|
609 |
-
|
610 |
-
|
611 |
-
</div>
|
612 |
-
""", unsafe_allow_html=True)
|
613 |
-
|
614 |
-
# Display static animation
|
615 |
-
if st.session_state.animation:
|
616 |
-
st.image(st.session_state.animation, use_container_width=True)
|
617 |
else:
|
618 |
-
st.warning("
|
619 |
story_img = create_story_image(st.session_state.story)
|
620 |
if story_img:
|
621 |
st.image(story_img, use_container_width=True)
|
622 |
|
623 |
# Display interactive animation
|
624 |
-
st.subheader("
|
625 |
if st.session_state.interactive_animation:
|
626 |
st.plotly_chart(st.session_state.interactive_animation, use_container_width=True)
|
627 |
else:
|
628 |
st.info("Interactive animation preview. The real animation would run on your computer!")
|
629 |
-
st.image("https://i.imgur.com/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
630 |
|
631 |
st.success("β¨ Animation created successfully!")
|
632 |
st.caption("This animation was generated with Python code based on your story!")
|
@@ -667,7 +835,8 @@ def main():
|
|
667 |
st.write("Here's the Python code that would create your animation:")
|
668 |
|
669 |
if st.session_state.animation_code:
|
670 |
-
st.
|
|
|
671 |
st.code(st.session_state.animation_code, language="python")
|
672 |
|
673 |
# Download button
|
|
|
1 |
+
# app.py - Final Version with Enhanced Animations
|
2 |
import streamlit as st
|
3 |
import os
|
4 |
import time
|
5 |
import random
|
6 |
import numpy as np
|
7 |
+
import pandas as pd
|
8 |
+
import plotly.express as px
|
9 |
+
import plotly.graph_objects as go
|
10 |
+
from gtts import gTTS
|
11 |
import base64
|
12 |
from PIL import Image
|
13 |
import io
|
14 |
+
import matplotlib.pyplot as plt
|
15 |
+
import requests
|
16 |
|
17 |
# Configure Streamlit page
|
18 |
st.set_page_config(
|
|
|
139 |
text-align: center;
|
140 |
margin: 10px 0;
|
141 |
}
|
142 |
+
|
143 |
+
.audio-player {
|
144 |
+
width: 100%;
|
145 |
+
margin: 20px 0;
|
146 |
+
border-radius: 20px;
|
147 |
+
background: #f0f8ff;
|
148 |
+
padding: 15px;
|
149 |
+
}
|
150 |
</style>
|
151 |
""", unsafe_allow_html=True)
|
152 |
|
|
|
189 |
}
|
190 |
}
|
191 |
|
192 |
+
# Character database with enhanced visuals
|
193 |
CHARACTERS = [
|
194 |
+
{"name": "rabbit", "emoji": "π°", "color": "#FFB6C1", "speed": 1.5},
|
195 |
+
{"name": "dragon", "emoji": "π", "color": "#FF6347", "speed": 0.8},
|
196 |
+
{"name": "cat", "emoji": "π±", "color": "#DDA0DD", "speed": 2.0},
|
197 |
+
{"name": "dog", "emoji": "πΆ", "color": "#FFD700", "speed": 2.5},
|
198 |
+
{"name": "knight", "emoji": "π€Ί", "color": "#87CEEB", "speed": 1.2},
|
199 |
+
{"name": "wizard", "emoji": "π§", "color": "#98FB98", "speed": 1.0},
|
200 |
+
{"name": "scientist", "emoji": "π¬", "color": "#20B2AA", "speed": 1.3},
|
201 |
+
{"name": "pirate", "emoji": "π΄ββ οΈ", "color": "#FFA500", "speed": 1.4}
|
202 |
+
]
|
203 |
+
|
204 |
+
# Object database for stories
|
205 |
+
STORY_OBJECTS = [
|
206 |
+
{"name": "carrot", "emoji": "π₯", "color": "#FF8C00"},
|
207 |
+
{"name": "treasure", "emoji": "π°", "color": "#FFD700"},
|
208 |
+
{"name": "castle", "emoji": "π°", "color": "#A9A9A9"},
|
209 |
+
{"name": "flower", "emoji": "πΈ", "color": "#FF69B4"},
|
210 |
+
{"name": "book", "emoji": "π", "color": "#8B4513"},
|
211 |
+
{"name": "star", "emoji": "β", "color": "#FFFF00"},
|
212 |
+
{"name": "key", "emoji": "π", "color": "#DAA520"},
|
213 |
+
{"name": "apple", "emoji": "π", "color": "#FF0000"}
|
214 |
]
|
215 |
|
216 |
+
# Animation templates with visual cues
|
217 |
ANIMATION_TEMPLATES = {
|
218 |
"loop": {
|
219 |
"description": "Character moving to a target multiple times",
|
220 |
+
"visual_cue": "π Repeating action multiple times",
|
221 |
"code": """
|
222 |
# Loop Animation
|
223 |
import matplotlib.pyplot as plt
|
224 |
import numpy as np
|
225 |
|
226 |
fig, ax = plt.subplots(figsize=(10, 6))
|
227 |
+
ax.set_title('Your Story: {story}')
|
228 |
ax.set_xlim(0, 10)
|
229 |
ax.set_ylim(0, 10)
|
|
|
230 |
|
231 |
+
character = ax.text(1, 5, '{emoji}', fontsize=48, color='{color}')
|
232 |
+
target = ax.text(9, 5, '{target_emoji}', fontsize=48)
|
233 |
+
info = ax.text(5, 8, 'Loop: Repeating {count} times',
|
234 |
+
fontsize=16, ha='center', color='{concept_color}')
|
235 |
|
236 |
for i in range({count}):
|
237 |
+
# Update position
|
238 |
for pos in np.linspace(1, 9, 20):
|
239 |
character.set_position((pos, 5))
|
240 |
plt.pause(0.05)
|
241 |
+
|
242 |
+
# Show loop counter
|
243 |
+
info.set_text(f'Loop: Completed {{i+1}}/{count}')
|
244 |
+
plt.pause(0.2)
|
245 |
+
|
246 |
+
# Return to start
|
247 |
+
for pos in np.linspace(9, 1, 20):
|
248 |
+
character.set_position((pos, 5))
|
249 |
+
plt.pause(0.05)
|
250 |
plt.show()
|
251 |
"""
|
252 |
},
|
253 |
"conditional": {
|
254 |
"description": "Character making a decision based on a condition",
|
255 |
+
"visual_cue": "β Making a decision based on a condition",
|
256 |
"code": """
|
257 |
# Conditional Animation
|
258 |
import matplotlib.pyplot as plt
|
259 |
import numpy as np
|
260 |
+
import random
|
261 |
|
262 |
fig, ax = plt.subplots(figsize=(10, 6))
|
263 |
+
ax.set_title('Your Story: {story}')
|
264 |
ax.set_xlim(0, 10)
|
265 |
ax.set_ylim(0, 10)
|
|
|
266 |
|
267 |
+
character = ax.text(5, 5, '{emoji}', fontsize=48, color='{color}')
|
268 |
+
condition = random.choice([True, False])
|
269 |
+
info = ax.text(5, 8, 'Condition: {condition_desc}',
|
270 |
+
fontsize=16, ha='center', color='{concept_color}')
|
271 |
|
272 |
+
if condition:
|
273 |
+
# Path for True condition
|
274 |
+
decision = ax.text(8, 7, 'β
True Path', fontsize=20, color='green')
|
275 |
path = np.linspace(5, 8, 20)
|
276 |
else:
|
277 |
+
# Path for False condition
|
278 |
+
decision = ax.text(2, 7, 'β False Path', fontsize=20, color='red')
|
279 |
path = np.linspace(5, 2, 20)
|
280 |
|
281 |
for pos in path:
|
|
|
286 |
},
|
287 |
"function": {
|
288 |
"description": "Character performing an action multiple times",
|
289 |
+
"visual_cue": "β¨ Performing action with a function",
|
290 |
"code": """
|
291 |
# Function Animation
|
292 |
import matplotlib.pyplot as plt
|
293 |
import numpy as np
|
294 |
|
295 |
fig, ax = plt.subplots(figsize=(10, 6))
|
296 |
+
ax.set_title('Your Story: {story}')
|
297 |
ax.set_xlim(0, 10)
|
298 |
ax.set_ylim(0, 10)
|
|
|
299 |
|
300 |
+
character = ax.text(5, 5, '{emoji}', fontsize=48, color='{color}')
|
301 |
+
info = ax.text(5, 8, 'Function: Performing action {count} times',
|
302 |
+
fontsize=16, ha='center', color='{concept_color}')
|
303 |
|
304 |
+
def perform_action(position):
|
305 |
+
# Action animation
|
306 |
+
character.set_fontsize(60)
|
307 |
+
plt.pause(0.1)
|
308 |
+
character.set_fontsize(48)
|
309 |
+
plt.pause(0.1)
|
310 |
+
# Move to new position
|
311 |
+
character.set_position((position, 5))
|
312 |
|
313 |
for i in range({count}):
|
314 |
+
perform_action(5 + i)
|
315 |
+
info.set_text(f'Function: Action {{i+1}}/{count}')
|
316 |
+
plt.pause(0.3)
|
317 |
plt.show()
|
318 |
"""
|
319 |
}
|
|
|
353 |
return min(int(word), 10)
|
354 |
return 3 # Default value
|
355 |
|
356 |
+
def extract_story_elements(story):
|
357 |
+
"""Extract characters and objects from the story"""
|
358 |
+
# Choose a random character
|
359 |
+
character = random.choice(CHARACTERS)
|
360 |
+
|
361 |
+
# Choose a random object
|
362 |
+
story_object = random.choice(STORY_OBJECTS)
|
363 |
+
|
364 |
+
# Try to find character in story
|
365 |
+
for char in CHARACTERS:
|
366 |
+
if char["name"] in story.lower():
|
367 |
+
character = char
|
368 |
+
break
|
369 |
+
|
370 |
+
# Try to find object in story
|
371 |
+
for obj in STORY_OBJECTS:
|
372 |
+
if obj["name"] in story.lower():
|
373 |
+
story_object = obj
|
374 |
+
break
|
375 |
+
|
376 |
+
return character, story_object
|
377 |
+
|
378 |
+
def create_visualization(story, concepts):
|
379 |
+
"""Create a visualization image based on the story and concepts"""
|
380 |
try:
|
381 |
+
# Get story elements
|
382 |
+
character, story_object = extract_story_elements(story)
|
383 |
count = extract_count_from_story(story)
|
384 |
+
concept = concepts[0] if concepts else "variable"
|
385 |
+
concept_color = CONCEPTS[concept]["color"]
|
386 |
|
387 |
# Create figure
|
388 |
fig, ax = plt.subplots(figsize=(10, 6))
|
|
|
392 |
ax.axis('off')
|
393 |
|
394 |
# Add title
|
395 |
+
ax.text(5, 9, 'β¨ Your Story Visualization β¨',
|
396 |
fontsize=20, ha='center', color='purple', fontweight='bold')
|
397 |
|
398 |
# Add story text
|
|
|
401 |
fontsize=14, ha='center', color='#333')
|
402 |
|
403 |
# Add character
|
404 |
+
ax.text(3, 5, character["emoji"],
|
405 |
fontsize=100, ha='center', color=character["color"])
|
406 |
|
407 |
+
# Add object
|
408 |
+
ax.text(7, 5, story_object["emoji"],
|
409 |
+
fontsize=100, ha='center', color=story_object["color"])
|
410 |
+
|
411 |
+
# Add connection arrow
|
412 |
+
ax.annotate('', xy=(7, 5), xytext=(3, 5),
|
413 |
+
arrowprops=dict(arrowstyle='->', lw=3, color=concept_color))
|
414 |
+
|
415 |
# Add concept visualization
|
416 |
+
concept_text = CONCEPTS[concept]["emoji"] + " " + CONCEPTS[concept]["name"]
|
417 |
+
ax.text(5, 3, concept_text,
|
418 |
+
fontsize=24, ha='center', color=concept_color, fontweight='bold')
|
419 |
+
|
420 |
+
# Add action text
|
421 |
+
action = f"Action: {character['name'].title()} moves toward {story_object['name']}"
|
422 |
+
ax.text(5, 2.5, action,
|
423 |
+
fontsize=16, ha='center', color='#333')
|
|
|
|
|
|
|
|
|
|
|
424 |
|
425 |
# Add footer
|
426 |
ax.text(5, 1, "Created with StoryCoder",
|
|
|
434 |
return buf
|
435 |
|
436 |
except Exception as e:
|
437 |
+
st.error(f"Visualization error: {str(e)}")
|
438 |
return None
|
439 |
|
440 |
def create_interactive_animation(story, concepts):
|
441 |
"""Create an interactive animation using Plotly"""
|
442 |
try:
|
443 |
+
# Get story elements
|
444 |
+
character, story_object = extract_story_elements(story)
|
445 |
count = extract_count_from_story(story)
|
446 |
+
concept = concepts[0] if concepts else "variable"
|
447 |
+
concept_color = CONCEPTS[concept]["color"]
|
448 |
|
449 |
# Create animation data
|
450 |
frames = []
|
451 |
for i in range(count):
|
452 |
+
# Create a path for the character
|
453 |
+
path_length = 20
|
454 |
+
for step in range(path_length):
|
455 |
+
progress = (i * path_length + step) / (count * path_length)
|
456 |
+
frames.append({
|
457 |
+
"frame": i * path_length + step,
|
458 |
+
"x": 3 + 4 * progress,
|
459 |
+
"y": 5 + np.sin(progress * np.pi * 2) * 2, # Wave motion
|
460 |
+
"size": 20 + np.sin(step * 0.3) * 10, # Pulsing effect
|
461 |
+
"character": character["emoji"],
|
462 |
+
"color": character["color"],
|
463 |
+
"info": f"{character['name'].title()} moving to {story_object['name']}",
|
464 |
+
"step": f"Action {i+1}/{count}"
|
465 |
+
})
|
466 |
|
467 |
df = pd.DataFrame(frames)
|
468 |
|
469 |
+
# Create the target object (static)
|
470 |
+
target_df = pd.DataFrame([{
|
471 |
+
"x": 7,
|
472 |
+
"y": 5,
|
473 |
+
"character": story_object["emoji"],
|
474 |
+
"color": story_object["color"],
|
475 |
+
"size": 30,
|
476 |
+
"info": f"Target: {story_object['name']}",
|
477 |
+
"step": "Target"
|
478 |
+
}])
|
479 |
+
|
480 |
# Create animated scatter plot
|
481 |
fig = px.scatter(
|
482 |
df,
|
|
|
488 |
size_max=45,
|
489 |
color_discrete_sequence=[character["color"]],
|
490 |
range_x=[0, 10],
|
491 |
+
range_y=[0, 10],
|
492 |
+
hover_name="info",
|
493 |
+
hover_data={"step": True}
|
494 |
+
)
|
495 |
+
|
496 |
+
# Add the target object
|
497 |
+
fig.add_trace(go.Scatter(
|
498 |
+
x=target_df["x"],
|
499 |
+
y=target_df["y"],
|
500 |
+
text=target_df["character"],
|
501 |
+
mode="text",
|
502 |
+
textfont=dict(size=40, color=story_object["color"]),
|
503 |
+
hoverinfo="text",
|
504 |
+
hovertext=target_df["info"],
|
505 |
+
showlegend=False
|
506 |
+
))
|
507 |
+
|
508 |
+
# Add concept indicator
|
509 |
+
fig.add_annotation(
|
510 |
+
x=0.5,
|
511 |
+
y=1.05,
|
512 |
+
xref="paper",
|
513 |
+
yref="paper",
|
514 |
+
text=f"{CONCEPTS[concept]['emoji']} {CONCEPTS[concept]['name']}",
|
515 |
+
showarrow=False,
|
516 |
+
font=dict(size=24, color=concept_color)
|
517 |
)
|
518 |
|
519 |
# Customize layout
|
520 |
fig.update_layout(
|
521 |
+
title=f'Animation: {story[:60]}{"..." if len(story) > 60 else ""}',
|
522 |
showlegend=False,
|
523 |
plot_bgcolor='rgba(240,248,255,1)',
|
524 |
paper_bgcolor='rgba(240,248,255,1)',
|
525 |
width=800,
|
526 |
+
height=600,
|
527 |
+
xaxis=dict(showgrid=False, zeroline=False, visible=False),
|
528 |
+
yaxis=dict(showgrid=False, zeroline=False, visible=False),
|
529 |
+
updatemenus=[dict(
|
530 |
+
type="buttons",
|
531 |
+
buttons=[dict(
|
532 |
+
label="Play",
|
533 |
+
method="animate",
|
534 |
+
args=[None, {"frame": {"duration": 100, "redraw": True}}]
|
535 |
+
)]
|
536 |
)
|
537 |
|
538 |
fig.update_traces(
|
539 |
+
textfont_size=40,
|
540 |
+
textposition='middle center',
|
541 |
+
hoverlabel=dict(bgcolor="white", font_size=16)
|
542 |
)
|
543 |
|
|
|
|
|
|
|
544 |
return fig
|
545 |
|
546 |
except Exception as e:
|
547 |
st.error(f"Interactive animation error: {str(e)}")
|
548 |
return None
|
549 |
|
550 |
+
def text_to_speech(text, filename="story_audio.mp3"):
|
551 |
+
"""Convert text to speech using gTTS"""
|
552 |
+
try:
|
553 |
+
tts = gTTS(text=text, lang='en')
|
554 |
+
tts.save(filename)
|
555 |
+
return filename
|
556 |
+
except Exception as e:
|
557 |
+
st.error(f"Audio creation error: {str(e)}")
|
558 |
+
return None
|
559 |
+
|
560 |
+
def autoplay_audio(file_path):
|
561 |
+
"""Autoplay audio in Streamlit"""
|
562 |
+
with open(file_path, "rb") as f:
|
563 |
+
data = f.read()
|
564 |
+
b64 = base64.b64encode(data).decode()
|
565 |
+
md = f"""
|
566 |
+
<audio autoplay>
|
567 |
+
<source src="data:audio/mp3;base64,{b64}" type="audio/mp3">
|
568 |
+
</audio>
|
569 |
+
"""
|
570 |
+
st.markdown(md, unsafe_allow_html=True)
|
571 |
+
|
572 |
def generate_animation_code(story, concepts):
|
573 |
"""Generate Python animation code based on story"""
|
574 |
try:
|
575 |
+
# Get story elements
|
576 |
+
character, story_object = extract_story_elements(story)
|
577 |
count = extract_count_from_story(story)
|
578 |
concept = concepts[0] if concepts else "loop"
|
579 |
|
|
|
581 |
template = ANIMATION_TEMPLATES.get(concept, ANIMATION_TEMPLATES["loop"])
|
582 |
|
583 |
# Fill in the template
|
584 |
+
condition_desc = random.choice(["sunny", "raining", "dark"])
|
585 |
code = template["code"].format(
|
586 |
emoji=character["emoji"],
|
587 |
color=character["color"],
|
588 |
story=story[:80] + ('...' if len(story) > 80 else ''),
|
589 |
count=count,
|
590 |
+
condition_desc=condition_desc,
|
591 |
+
target_emoji=story_object["emoji"],
|
592 |
+
concept_color=CONCEPTS[concept]["color"]
|
593 |
)
|
594 |
|
595 |
+
return code, template["description"], template["visual_cue"]
|
596 |
|
597 |
except Exception as e:
|
598 |
+
return f"# Error generating code\nprint('Could not generate code: {str(e)}')", "", ""
|
599 |
|
600 |
def create_story_image(story):
|
601 |
"""Create a story image with Matplotlib"""
|
|
|
656 |
st.session_state.story = ""
|
657 |
if 'concepts' not in st.session_state:
|
658 |
st.session_state.concepts = []
|
659 |
+
if 'visualization' not in st.session_state:
|
660 |
+
st.session_state.visualization = None
|
661 |
if 'interactive_animation' not in st.session_state:
|
662 |
st.session_state.interactive_animation = None
|
663 |
if 'animation_code' not in st.session_state:
|
664 |
st.session_state.animation_code = ""
|
665 |
if 'code_description' not in st.session_state:
|
666 |
st.session_state.code_description = ""
|
667 |
+
if 'visual_cue' not in st.session_state:
|
668 |
+
st.session_state.visual_cue = ""
|
669 |
+
if 'audio_file' not in st.session_state:
|
670 |
+
st.session_state.audio_file = None
|
671 |
if 'active_tab' not in st.session_state:
|
672 |
st.session_state.active_tab = "story"
|
673 |
|
|
|
690 |
if st.button("π Reset"):
|
691 |
st.session_state.story = ""
|
692 |
st.session_state.concepts = []
|
693 |
+
st.session_state.visualization = None
|
694 |
st.session_state.interactive_animation = None
|
695 |
st.session_state.animation_code = ""
|
696 |
st.session_state.code_description = ""
|
697 |
+
st.session_state.visual_cue = ""
|
698 |
+
st.session_state.audio_file = None
|
699 |
st.session_state.active_tab = "story"
|
700 |
|
701 |
# Story creation tab
|
|
|
720 |
with st.spinner("π§ Analyzing your story for coding concepts..."):
|
721 |
st.session_state.concepts = analyze_story(story)
|
722 |
|
723 |
+
with st.spinner("π¨ Creating your visualization..."):
|
724 |
+
st.session_state.visualization = create_visualization(
|
725 |
story, st.session_state.concepts
|
726 |
)
|
727 |
|
728 |
+
with st.spinner("π Creating interactive animation..."):
|
729 |
st.session_state.interactive_animation = create_interactive_animation(
|
730 |
story, st.session_state.concepts
|
731 |
)
|
732 |
|
733 |
+
with st.spinner("π» Generating animation code..."):
|
734 |
+
st.session_state.animation_code, st.session_state.code_description, st.session_state.visual_cue = generate_animation_code(
|
735 |
story, st.session_state.concepts
|
736 |
)
|
737 |
|
738 |
+
with st.spinner("π Creating audio narration..."):
|
739 |
+
audio_text = f"Your story: {story}. This demonstrates the programming concept: {st.session_state.code_description}"
|
740 |
+
st.session_state.audio_file = text_to_speech(audio_text)
|
741 |
+
|
742 |
st.session_state.active_tab = "animation"
|
743 |
st.rerun()
|
744 |
|
|
|
748 |
with col1:
|
749 |
st.caption("Loop Example")
|
750 |
st.code('"A dragon breathes fire 5 times at the castle"', language="text")
|
751 |
+
st.image("https://i.imgur.com/7zQY1eE.png", width=200)
|
752 |
with col2:
|
753 |
st.caption("Conditional Example")
|
754 |
st.code('"If it rains, the cat stays inside, else it goes out"', language="text")
|
755 |
+
st.image("https://i.imgur.com/5X8jYAy.png", width=200)
|
756 |
with col3:
|
757 |
st.caption("Function Example")
|
758 |
st.code('"A wizard casts a spell to make flowers grow"', language="text")
|
759 |
+
st.image("https://i.imgur.com/9zJkQ7P.png", width=200)
|
760 |
|
761 |
# Animation tab
|
762 |
elif st.session_state.active_tab == "animation":
|
|
|
767 |
st.session_state.active_tab = "story"
|
768 |
st.rerun()
|
769 |
|
770 |
+
# Display visualization
|
771 |
+
st.subheader("π¨ Story Visualization")
|
772 |
+
if st.session_state.visualization:
|
773 |
+
st.image(st.session_state.visualization, use_container_width=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
774 |
else:
|
775 |
+
st.warning("Visualization couldn't be generated. Showing story image instead.")
|
776 |
story_img = create_story_image(st.session_state.story)
|
777 |
if story_img:
|
778 |
st.image(story_img, use_container_width=True)
|
779 |
|
780 |
# Display interactive animation
|
781 |
+
st.subheader("π Interactive Animation")
|
782 |
if st.session_state.interactive_animation:
|
783 |
st.plotly_chart(st.session_state.interactive_animation, use_container_width=True)
|
784 |
else:
|
785 |
st.info("Interactive animation preview. The real animation would run on your computer!")
|
786 |
+
st.image("https://i.imgur.com/8LzQY7a.gif", use_container_width=True)
|
787 |
+
|
788 |
+
# Play audio narration
|
789 |
+
st.subheader("π Story Narration")
|
790 |
+
if st.session_state.audio_file:
|
791 |
+
audio_bytes = open(st.session_state.audio_file, 'rb').read()
|
792 |
+
st.audio(audio_bytes, format='audio/mp3')
|
793 |
+
|
794 |
+
if st.button("βΆοΈ Play Automatically"):
|
795 |
+
autoplay_audio(st.session_state.audio_file)
|
796 |
+
else:
|
797 |
+
st.info("Audio narration would play automatically here")
|
798 |
|
799 |
st.success("β¨ Animation created successfully!")
|
800 |
st.caption("This animation was generated with Python code based on your story!")
|
|
|
835 |
st.write("Here's the Python code that would create your animation:")
|
836 |
|
837 |
if st.session_state.animation_code:
|
838 |
+
st.subheader(st.session_state.code_description)
|
839 |
+
st.markdown(f"**Visual Cue:** {st.session_state.visual_cue}")
|
840 |
st.code(st.session_state.animation_code, language="python")
|
841 |
|
842 |
# Download button
|