sunbal7 commited on
Commit
5c5482b
·
verified ·
1 Parent(s): 0647136

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +219 -200
app.py CHANGED
@@ -1,10 +1,4 @@
1
- # app.py - Final Version with AI-Powered Animations and Voice Explanations
2
- # Fix for huggingface_hub compatibility
3
- import huggingface_hub
4
- if not hasattr(huggingface_hub, 'cached_download'):
5
- from huggingface_hub import hf_hub_download
6
- huggingface_hub.cached_download = hf_hub_download
7
-
8
  import streamlit as st
9
  import os
10
  import time
@@ -21,15 +15,6 @@ import matplotlib.pyplot as plt
21
  import requests
22
  from io import BytesIO
23
  import json
24
- import torch
25
-
26
- # Use compatible import for diffusers
27
- try:
28
- from diffusers import StableDiffusionPipeline
29
- except ImportError:
30
- from diffusers import DiffusionPipeline as StableDiffusionPipeline
31
-
32
- from transformers import pipeline
33
 
34
  # Configure Streamlit page
35
  st.set_page_config(
@@ -39,44 +24,45 @@ st.set_page_config(
39
  initial_sidebar_state="expanded"
40
  )
41
 
42
- # Custom CSS for colorful UI
43
  st.markdown("""
44
  <style>
45
- @import url('https://fonts.googleapis.com/css2?family=Comic+Neue:wght@700&display=swap');
46
 
47
  :root {
48
- --primary: #FF6B6B;
49
- --secondary: #4ECDC4;
50
- --accent: #FFD166;
51
- --dark: #1A535C;
52
- --light: #F7FFF7;
 
53
  }
54
 
55
  body {
56
- background: linear-gradient(135deg, var(--light) 0%, #E8F4F8 100%);
57
- font-family: 'Comic Neue', cursive;
58
  }
59
 
60
  .stApp {
61
- background: url('https://www.transparenttextures.com/patterns/cartographer.png');
62
  }
63
 
64
  .story-box {
65
  background-color: white;
66
  border-radius: 20px;
67
  padding: 25px;
68
- box-shadow: 0 8px 16px rgba(26, 83, 92, 0.15);
69
  border: 3px solid var(--accent);
70
  margin-bottom: 25px;
71
  }
72
 
73
  .header {
74
  color: var(--dark);
75
- text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
76
  }
77
 
78
  .concept-card {
79
- background: linear-gradient(145deg, #ffffff, #f0f0f0);
80
  border-radius: 15px;
81
  padding: 15px;
82
  margin: 10px 0;
@@ -88,22 +74,24 @@ st.markdown("""
88
  background: linear-gradient(45deg, var(--primary), var(--secondary));
89
  color: white;
90
  border-radius: 12px;
91
- padding: 10px 24px;
92
  font-weight: bold;
93
  font-size: 18px;
94
  border: none;
95
  transition: all 0.3s;
 
96
  }
97
 
98
  .stButton>button:hover {
99
- transform: scale(1.05);
100
- box-shadow: 0 6px 12px rgba(0,0,0,0.15);
101
  }
102
 
103
  .stTextInput>div>div>input {
104
  border-radius: 12px;
105
  padding: 12px;
106
  border: 2px solid var(--accent);
 
107
  }
108
 
109
  .tabs {
@@ -114,17 +102,23 @@ st.markdown("""
114
  }
115
 
116
  .tab {
117
- padding: 10px 20px;
118
- background-color: var(--accent);
119
- border-radius: 10px;
120
  cursor: pointer;
121
  font-weight: bold;
122
  white-space: nowrap;
 
 
 
 
 
 
123
  }
124
 
125
  .tab.active {
126
- background-color: var(--secondary);
127
- color: white;
128
  }
129
 
130
  @media (max-width: 768px) {
@@ -134,10 +128,10 @@ st.markdown("""
134
  }
135
 
136
  .animation-container {
137
- background-color: #1a1a2e;
138
  border-radius: 15px;
139
  padding: 20px;
140
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
141
  margin-bottom: 25px;
142
  position: relative;
143
  overflow: hidden;
@@ -161,14 +155,15 @@ st.markdown("""
161
  width: 100%;
162
  margin: 20px 0;
163
  border-radius: 20px;
164
- background: #f0f8ff;
165
  padding: 15px;
166
  }
167
 
168
  .animation-gif {
169
  max-width: 100%;
170
  border-radius: 10px;
171
- box-shadow: 0 8px 16px rgba(0,0,0,0.2);
 
172
  }
173
 
174
  .ai-animation {
@@ -180,7 +175,7 @@ st.markdown("""
180
  }
181
 
182
  .explanation-box {
183
- background: linear-gradient(135deg, #e3f2fd, #bbdefb);
184
  border-radius: 15px;
185
  padding: 20px;
186
  margin: 20px 0;
@@ -193,6 +188,24 @@ st.markdown("""
193
  display: flex;
194
  align-items: center;
195
  gap: 10px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  }
197
  </style>
198
  """, unsafe_allow_html=True)
@@ -204,7 +217,7 @@ CONCEPTS = {
204
  "emoji": "🔄",
205
  "description": "Loops repeat actions multiple times",
206
  "example": "for i in range(5):\n print('Hello!')",
207
- "color": "#FF9E6D",
208
  "explanation": "A loop is like a magic spell that makes something happen again and again. In programming, we use loops when we want to repeat an action multiple times without writing the same code over and over."
209
  },
210
  "conditional": {
@@ -212,7 +225,7 @@ CONCEPTS = {
212
  "emoji": "❓",
213
  "description": "Conditionals make decisions in code",
214
  "example": "if sunny:\n go_outside()\nelse:\n stay_inside()",
215
- "color": "#4ECDC4",
216
  "explanation": "Conditionals are like crossroads in a story where you choose which path to take. In programming, we use 'if' statements to make decisions based on certain conditions, just like choosing what to wear based on the weather."
217
  },
218
  "function": {
@@ -220,7 +233,7 @@ CONCEPTS = {
220
  "emoji": "✨",
221
  "description": "Functions are reusable blocks of code",
222
  "example": "def greet(name):\n print(f'Hello {name}!')",
223
- "color": "#FFD166",
224
  "explanation": "Functions are like magic spells you can create and reuse whenever you need them. They help us organize code into reusable blocks so we don't have to write the same thing multiple times."
225
  },
226
  "variable": {
@@ -228,7 +241,7 @@ CONCEPTS = {
228
  "emoji": "📦",
229
  "description": "Variables store information",
230
  "example": "score = 10\nplayer = 'Alex'",
231
- "color": "#FF6B6B",
232
  "explanation": "Variables are like labeled boxes where you can store information. They help us remember values and use them later in our code, just like remembering a character's name in a story."
233
  },
234
  "list": {
@@ -236,7 +249,7 @@ CONCEPTS = {
236
  "emoji": "📝",
237
  "description": "Lists store collections of items",
238
  "example": "fruits = ['apple', 'banana', 'orange']",
239
- "color": "#1A535C",
240
  "explanation": "Lists are like magical scrolls that can hold multiple items. In programming, we use lists to store collections of related things, like a wizard's inventory of potions."
241
  }
242
  }
@@ -245,35 +258,40 @@ CONCEPTS = {
245
  ANIMATION_EXAMPLES = {
246
  "loop": "https://media.giphy.com/media/3o7abKhOpu0NwenH3O/giphy.gif",
247
  "conditional": "https://media.giphy.com/media/l0HlG8vJXW0X5yX4s/giphy.gif",
248
- "function": "https://media.giphy.com/media/3o7TKsQ8UQ4l4LhGz6/giphy.gif"
 
 
249
  }
250
 
251
- # Initialize AI models
252
- @st.cache_resource
253
- def load_models():
254
- """Load AI models for animation generation"""
255
- models = {}
256
-
 
 
 
 
 
257
  try:
258
- # Use a fast, optimized model
259
- models['text_to_image'] = StableDiffusionPipeline.from_pretrained(
260
- "OFA-Sys/small-stable-diffusion-v0", # Optimized for speed
261
- torch_dtype=torch.float32,
262
- safety_checker=None
263
- )
264
- device = "cuda" if torch.cuda.is_available() else "cpu"
265
- models['text_to_image'] = models['text_to_image'].to(device)
266
- except Exception as e:
267
- st.error(f"Could not load text-to-image model: {str(e)}")
268
- models['text_to_image'] = None
269
-
270
  try:
271
- # Text-to-speech model
272
- models['text_to_speech'] = pipeline("text-to-speech", model="suno/bark-small")
 
 
 
 
 
273
  except:
274
- models['text_to_speech'] = None
275
-
276
- return models
277
 
278
  def analyze_story(story):
279
  """Analyze story and identify programming concepts"""
@@ -281,23 +299,23 @@ def analyze_story(story):
281
  detected_concepts = []
282
 
283
  # Check for loops
284
- if any(word in story_lower for word in ["times", "repeat", "again", "multiple"]):
285
  detected_concepts.append("loop")
286
 
287
  # Check for conditionals
288
- if any(word in story_lower for word in ["if", "when", "unless", "whether"]):
289
  detected_concepts.append("conditional")
290
 
291
  # Check for functions
292
- if any(word in story_lower for word in ["make", "create", "do", "perform", "cast"]):
293
  detected_concepts.append("function")
294
 
295
  # Check for variables
296
- if any(word in story_lower for word in ["is", "has", "set to", "value"]):
297
  detected_concepts.append("variable")
298
 
299
  # Check for lists
300
- if any(word in story_lower for word in ["and", "many", "several", "collection", "items"]):
301
  detected_concepts.append("list")
302
 
303
  return list(set(detected_concepts))
@@ -309,55 +327,9 @@ def extract_count_from_story(story):
309
  return min(int(word), 10)
310
  return 3 # Default value
311
 
312
- def create_story_scene(story, concept, models):
313
- """Create a story scene using AI text-to-image model"""
314
- try:
315
- # Create a prompt based on the story and concept
316
- style = "cartoon style, bright colors, children's book illustration"
317
- prompt = f"{story}. {CONCEPTS[concept]['name']} concept. {style}"
318
-
319
- # Generate image with optimized settings
320
- image = models['text_to_image'](
321
- prompt,
322
- num_inference_steps=15, # Faster generation
323
- guidance_scale=7.5,
324
- height=256, # Smaller image for faster generation
325
- width=256
326
- ).images[0]
327
-
328
- # Convert to bytes
329
- buf = io.BytesIO()
330
- image.save(buf, format='PNG')
331
- buf.seek(0)
332
-
333
- return buf
334
-
335
- except Exception as e:
336
- st.error(f"AI scene generation error: {str(e)}")
337
- return None
338
-
339
- def create_animation_preview(story, concept):
340
- """Create an animation preview for the concept"""
341
- try:
342
- # Return a sample GIF for the concept
343
- return ANIMATION_EXAMPLES.get(concept, "https://media.giphy.com/media/3o7abKhOpu0NwenH3O/giphy.gif")
344
- except:
345
- return "https://media.giphy.com/media/3o7abKhOpu0NwenH3O/giphy.gif"
346
-
347
- def text_to_speech_custom(text, models, filename="story_audio.wav"):
348
- """Convert text to speech using AI model"""
349
  try:
350
- if models['text_to_speech']:
351
- # Generate audio
352
- audio = models['text_to_speech'](text)
353
-
354
- # Save to file
355
- with open(filename, "wb") as f:
356
- f.write(audio["audio"])
357
-
358
- return filename
359
-
360
- # Fallback to gTTS
361
  tts = gTTS(text=text, lang='en')
362
  tts.save(filename)
363
  return filename
@@ -372,7 +344,7 @@ def autoplay_audio(file_path):
372
  b64 = base64.b64encode(data).decode()
373
  md = f"""
374
  <audio autoplay>
375
- <source src="data:audio/wav;base64,{b64}" type="audio/wav">
376
  </audio>
377
  """
378
  st.markdown(md, unsafe_allow_html=True)
@@ -380,20 +352,28 @@ def autoplay_audio(file_path):
380
  def generate_animation_code(story, concept):
381
  """Generate Python animation code based on story"""
382
  try:
 
 
 
383
  # Sample code based on concept
384
  if concept == "loop":
385
  code = f"""
386
- # Loop Animation for: {story[:30]}...
387
  import time
388
 
389
  def animate_story():
390
- actions = {extract_count_from_story(story)}
 
 
 
391
 
392
  for i in range(actions):
393
- print(f"Action {{i+1}}: {story[:20]}...")
394
  # Animation code would go here
395
  time.sleep(0.5)
396
 
 
 
397
  animate_story()
398
  """
399
  description = "Looping through actions multiple times"
@@ -401,35 +381,93 @@ animate_story()
401
 
402
  elif concept == "conditional":
403
  code = f"""
404
- # Conditional Animation for: {story[:30]}...
405
  import random
406
 
 
 
 
407
  condition = random.choice([True, False])
 
408
 
409
  if condition:
410
- print("Condition is True: {story[:20]}...")
411
  # True branch animation
412
  else:
413
- print("Condition is False: {story[:20]}...")
414
  # False branch animation
 
 
415
  """
416
  description = "Making decisions based on conditions"
417
  visual_cue = "Branching paths based on conditions"
418
 
419
- else: # function
420
  code = f"""
421
- # Function Animation for: {story[:30]}...
 
 
 
 
 
422
  def perform_action():
423
- print("Performing action: {story[:20]}...")
424
  # Action animation code
425
 
426
  # Call the function multiple times
427
- for _ in range({extract_count_from_story(story)}):
428
  perform_action()
 
 
 
429
  """
430
  description = "Reusing code with functions"
431
  visual_cue = "Calling a reusable function"
432
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
433
  return code, description, visual_cue
434
 
435
  except Exception as e:
@@ -464,7 +502,7 @@ def create_interactive_animation(story, concept):
464
  size="size",
465
  size_max=45,
466
  color="color",
467
- color_discrete_sequence=px.colors.qualitative.Alphabet,
468
  range_x=[0, 10],
469
  range_y=[0, 10],
470
  title=f"Interactive Animation: {story[:40]}{'...' if len(story) > 40 else ''}"
@@ -473,16 +511,17 @@ def create_interactive_animation(story, concept):
473
  # Customize layout
474
  fig.update_layout(
475
  showlegend=False,
476
- plot_bgcolor='rgba(240,248,255,1)',
477
- paper_bgcolor='rgba(240,248,255,1)',
478
  width=800,
479
  height=600,
480
  xaxis=dict(showgrid=False, zeroline=False, visible=False),
481
  yaxis=dict(showgrid=False, zeroline=False, visible=False),
 
482
  )
483
 
484
  fig.update_traces(
485
- textfont_size=20,
486
  textposition='middle center',
487
  marker=dict(sizemode='diameter')
488
  )
@@ -500,24 +539,29 @@ def generate_concept_explanation(story, concept, code_description, visual_cue):
500
  explanation = f"""
501
  Let me explain what's happening in your story and how it relates to programming!
502
 
503
- Your story: "{story[:100]}..."
504
 
505
- This story demonstrates the programming concept of: {concept_info.get('name', 'Programming')}
506
 
507
  {concept_info.get('explanation', 'This is a fundamental programming concept.')}
508
 
509
- In our animation:
510
  - {visual_cue}
511
  - This represents: {code_description}
512
 
513
- How it works in code:
514
  We use {concept_info.get('name', 'this concept')} like this:
 
515
  {concept_info.get('example', 'Example code will appear here')}
 
516
 
517
- In real life, you might use this concept when:
 
518
  - Creating games with repeating actions (loops)
519
  - Making decisions in apps (conditionals)
520
  - Building reusable components (functions)
 
 
521
 
522
  Try to spot this concept in other stories you create!
523
  """
@@ -534,8 +578,6 @@ def main():
534
  st.session_state.story = ""
535
  if 'concepts' not in st.session_state:
536
  st.session_state.concepts = []
537
- if 'story_scene' not in st.session_state:
538
- st.session_state.story_scene = None
539
  if 'interactive_animation' not in st.session_state:
540
  st.session_state.interactive_animation = None
541
  if 'animation_code' not in st.session_state:
@@ -557,22 +599,21 @@ def main():
557
  tabs = st.empty()
558
  tab_cols = st.columns(5)
559
  with tab_cols[0]:
560
- if st.button("📖 Create Story"):
561
  st.session_state.active_tab = "story"
562
  with tab_cols[1]:
563
- if st.button("🎬 Animation"):
564
  st.session_state.active_tab = "animation"
565
  with tab_cols[2]:
566
- if st.button("🔍 Concepts"):
567
  st.session_state.active_tab = "concepts"
568
  with tab_cols[3]:
569
- if st.button("💻 Code"):
570
  st.session_state.active_tab = "code"
571
  with tab_cols[4]:
572
- if st.button("🔄 Reset"):
573
  st.session_state.story = ""
574
  st.session_state.concepts = []
575
- st.session_state.story_scene = None
576
  st.session_state.interactive_animation = None
577
  st.session_state.animation_code = ""
578
  st.session_state.code_description = ""
@@ -596,7 +637,7 @@ def main():
596
  key="story_input"
597
  )
598
 
599
- if st.button("Create Animation!", use_container_width=True):
600
  if len(story) < 10:
601
  st.error("Your story needs to be at least 10 characters long!")
602
  else:
@@ -607,15 +648,6 @@ def main():
607
  # Get the main concept
608
  main_concept = st.session_state.concepts[0] if st.session_state.concepts else "variable"
609
 
610
- # Load models only when needed
611
- with st.spinner("🚀 Loading AI models..."):
612
- ai_models = load_models()
613
-
614
- with st.spinner("🎨 Generating AI story scene (this may take 5-10 seconds)..."):
615
- st.session_state.story_scene = create_story_scene(
616
- story, main_concept, ai_models
617
- )
618
-
619
  with st.spinner("🚀 Creating interactive animation..."):
620
  st.session_state.interactive_animation = create_interactive_animation(
621
  story, main_concept
@@ -629,7 +661,7 @@ def main():
629
  with st.spinner("🔊 Creating audio narration..."):
630
  audio_text = f"Your story: {story}. This demonstrates the programming concept: {st.session_state.code_description}"
631
  st.session_state.audio_file = text_to_speech_custom(
632
- audio_text, ai_models
633
  )
634
 
635
  with st.spinner("🧠 Generating concept explanation..."):
@@ -640,6 +672,13 @@ def main():
640
  st.session_state.visual_cue
641
  )
642
 
 
 
 
 
 
 
 
643
  st.session_state.active_tab = "animation"
644
  st.rerun()
645
 
@@ -649,19 +688,19 @@ def main():
649
  with col1:
650
  st.caption("Loop Example")
651
  st.code('"A dragon breathes fire 5 times at the castle"', language="text")
652
- st.image(create_animation_preview("", "loop"),
653
  use_container_width=True,
654
  caption="Loop Animation Preview")
655
  with col2:
656
  st.caption("Conditional Example")
657
  st.code('"If it rains, the cat stays inside, else it goes out"', language="text")
658
- st.image(create_animation_preview("", "conditional"),
659
  use_container_width=True,
660
  caption="Conditional Animation Preview")
661
  with col3:
662
  st.caption("Function Example")
663
  st.code('"A wizard casts a spell to make flowers grow"', language="text")
664
- st.image(create_animation_preview("", "function"),
665
  use_container_width=True,
666
  caption="Function Animation Preview")
667
 
@@ -674,16 +713,6 @@ def main():
674
  st.session_state.active_tab = "story"
675
  st.rerun()
676
 
677
- # Display AI-generated scene
678
- st.subheader("🖼️ AI-Generated Story Scene")
679
- if st.session_state.story_scene:
680
- st.image(st.session_state.story_scene, use_container_width=True)
681
- else:
682
- st.warning("Scene couldn't be generated. Showing example instead.")
683
- concept = st.session_state.concepts[0] if st.session_state.concepts else "loop"
684
- st.image(create_animation_preview("", concept),
685
- use_container_width=True)
686
-
687
  # Display interactive animation
688
  st.subheader("🎮 Interactive Animation")
689
  if st.session_state.interactive_animation:
@@ -691,14 +720,14 @@ def main():
691
  else:
692
  st.info("Interactive animation preview. The real animation would run on your computer!")
693
  concept = st.session_state.concepts[0] if st.session_state.concepts else "loop"
694
- st.image(create_animation_preview("", concept),
695
  use_container_width=True)
696
 
697
  # Animation concept preview
698
  st.subheader("📽️ Animation Concept Preview")
699
  if st.session_state.concepts:
700
  concept = st.session_state.concepts[0]
701
- st.image(create_animation_preview("", concept),
702
  caption=f"{CONCEPTS[concept]['name']} Animation Example",
703
  use_container_width=True)
704
  else:
@@ -708,7 +737,7 @@ def main():
708
  st.subheader("🔊 Story Narration")
709
  if st.session_state.audio_file:
710
  audio_bytes = open(st.session_state.audio_file, 'rb').read()
711
- st.audio(audio_bytes, format='audio/wav')
712
 
713
  if st.button("▶️ Play Story Automatically", use_container_width=True):
714
  autoplay_audio(st.session_state.audio_file)
@@ -727,23 +756,13 @@ def main():
727
  <span style="font-size:36px;">{details.get('emoji', '✨')}</span>
728
  <h3>Understanding {details.get('name', 'Programming')} Concept</h3>
729
  </div>
730
- <p>{st.session_state.concept_explanation}</p>
731
  </div>
732
  """, unsafe_allow_html=True)
733
 
734
- # Generate explanation audio if not already generated
735
- if not st.session_state.explanation_audio:
736
- with st.spinner("🔊 Creating concept explanation audio..."):
737
- ai_models = load_models()
738
- st.session_state.explanation_audio = text_to_speech_custom(
739
- st.session_state.concept_explanation,
740
- ai_models,
741
- "concept_explanation.wav"
742
- )
743
-
744
  if st.session_state.explanation_audio:
745
  explanation_bytes = open(st.session_state.explanation_audio, 'rb').read()
746
- st.audio(explanation_bytes, format='audio/wav')
747
 
748
  if st.button("▶️ Play Concept Explanation", use_container_width=True):
749
  autoplay_audio(st.session_state.explanation_audio)
@@ -773,21 +792,21 @@ def main():
773
  <h3 style="color:{details['color']};">{details['name']}</h3>
774
  </div>
775
  <p>{details['description']}</p>
776
- <pre style="background:#f0f0f0; padding:10px; border-radius:8px;">{details['example']}</pre>
777
  <p><strong>Explanation:</strong> {details.get('explanation', 'Learn how this concept works!')}</p>
778
  </div>
779
  """, unsafe_allow_html=True)
780
 
781
  # Show animation preview for the concept
782
- st.image(create_animation_preview("", concept),
783
  caption=f"{details['name']} Animation Example",
784
  use_container_width=True)
785
 
786
  # Play explanation if available
787
  if st.session_state.explanation_audio:
788
- st.subheader("🔊 Concept Explanation")
789
  explanation_bytes = open(st.session_state.explanation_audio, 'rb').read()
790
- st.audio(explanation_bytes, format='audio/wav')
791
 
792
  if st.button("▶️ Play Full Explanation", use_container_width=True):
793
  autoplay_audio(st.session_state.explanation_audio)
@@ -808,7 +827,7 @@ def main():
808
 
809
  # Download button
810
  st.download_button(
811
- label="Download Animation Code",
812
  data=st.session_state.animation_code,
813
  file_name="story_animation.py",
814
  mime="text/python",
@@ -828,7 +847,7 @@ def main():
828
  # Show what the animation would look like
829
  st.subheader("🎬 What Your Animation Would Look Like")
830
  concept = st.session_state.concepts[0] if st.session_state.concepts else "loop"
831
- st.image(create_animation_preview("", concept),
832
  caption="This is similar to what your animation would look like",
833
  use_container_width=True)
834
 
@@ -844,21 +863,21 @@ def main():
844
  <span style="font-size:36px;">{details.get('emoji', '✨')}</span>
845
  <h3>Understanding the Code</h3>
846
  </div>
847
- <p>{st.session_state.concept_explanation}</p>
848
  </div>
849
  """, unsafe_allow_html=True)
850
 
851
  # Play explanation audio
852
  if st.session_state.explanation_audio:
853
  explanation_bytes = open(st.session_state.explanation_audio, 'rb').read()
854
- st.audio(explanation_bytes, format='audio/wav')
855
 
856
  if st.button("▶️ Play Code Explanation", use_container_width=True):
857
  autoplay_audio(st.session_state.explanation_audio)
858
  else:
859
  st.warning("No code generated yet!")
860
 
861
- if st.button("Create Another Story!", use_container_width=True):
862
  st.session_state.active_tab = "story"
863
  st.session_state.story = ""
864
  st.rerun()
 
1
+ # app.py - Final Version with Enhanced Performance & Light Purple Theme
 
 
 
 
 
 
2
  import streamlit as st
3
  import os
4
  import time
 
15
  import requests
16
  from io import BytesIO
17
  import json
 
 
 
 
 
 
 
 
 
18
 
19
  # Configure Streamlit page
20
  st.set_page_config(
 
24
  initial_sidebar_state="expanded"
25
  )
26
 
27
+ # Custom CSS with Light Purple Theme
28
  st.markdown("""
29
  <style>
30
+ @import url('https://fonts.googleapis.com/css2?family=Comic+Neue:wght@700&family=Poppins:wght@400;600&display=swap');
31
 
32
  :root {
33
+ --primary: #8A2BE2; /* Violet */
34
+ --secondary: #9370DB; /* Medium Purple */
35
+ --accent: #FFD700; /* Gold */
36
+ --dark: #4B0082; /* Indigo */
37
+ --light: #E6E6FA; /* Lavender */
38
+ --light2: #F5F0FF; /* Light Lavender */
39
  }
40
 
41
  body {
42
+ background: linear-gradient(135deg, var(--light) 0%, var(--light2) 100%);
43
+ font-family: 'Poppins', sans-serif;
44
  }
45
 
46
  .stApp {
47
+ background: url('https://www.transparenttextures.com/patterns/soft-circle-scales.png');
48
  }
49
 
50
  .story-box {
51
  background-color: white;
52
  border-radius: 20px;
53
  padding: 25px;
54
+ box-shadow: 0 8px 16px rgba(74, 0, 128, 0.15);
55
  border: 3px solid var(--accent);
56
  margin-bottom: 25px;
57
  }
58
 
59
  .header {
60
  color: var(--dark);
61
+ text-shadow: 1px 1px 2px rgba(0,0,0,0.1);
62
  }
63
 
64
  .concept-card {
65
+ background: linear-gradient(145deg, #ffffff, #f8f4ff);
66
  border-radius: 15px;
67
  padding: 15px;
68
  margin: 10px 0;
 
74
  background: linear-gradient(45deg, var(--primary), var(--secondary));
75
  color: white;
76
  border-radius: 12px;
77
+ padding: 12px 28px;
78
  font-weight: bold;
79
  font-size: 18px;
80
  border: none;
81
  transition: all 0.3s;
82
+ font-family: 'Comic Neue', cursive;
83
  }
84
 
85
  .stButton>button:hover {
86
+ transform: translateY(-3px);
87
+ box-shadow: 0 6px 12px rgba(138, 43, 226, 0.3);
88
  }
89
 
90
  .stTextInput>div>div>input {
91
  border-radius: 12px;
92
  padding: 12px;
93
  border: 2px solid var(--accent);
94
+ font-family: 'Poppins', sans-serif;
95
  }
96
 
97
  .tabs {
 
102
  }
103
 
104
  .tab {
105
+ padding: 12px 24px;
106
+ background-color: var(--secondary);
107
+ border-radius: 12px;
108
  cursor: pointer;
109
  font-weight: bold;
110
  white-space: nowrap;
111
+ color: white;
112
+ transition: all 0.3s;
113
+ }
114
+
115
+ .tab:hover {
116
+ background-color: var(--primary);
117
  }
118
 
119
  .tab.active {
120
+ background-color: var(--primary);
121
+ box-shadow: 0 4px 8px rgba(0,0,0,0.1);
122
  }
123
 
124
  @media (max-width: 768px) {
 
128
  }
129
 
130
  .animation-container {
131
+ background: linear-gradient(135deg, #d8bfd8, #e6e6fa);
132
  border-radius: 15px;
133
  padding: 20px;
134
+ box-shadow: 0 8px 32px rgba(75, 0, 130, 0.2);
135
  margin-bottom: 25px;
136
  position: relative;
137
  overflow: hidden;
 
155
  width: 100%;
156
  margin: 20px 0;
157
  border-radius: 20px;
158
+ background: #f0e6ff;
159
  padding: 15px;
160
  }
161
 
162
  .animation-gif {
163
  max-width: 100%;
164
  border-radius: 10px;
165
+ box-shadow: 0 8px 16px rgba(0,0,0,0.15);
166
+ border: 2px solid white;
167
  }
168
 
169
  .ai-animation {
 
175
  }
176
 
177
  .explanation-box {
178
+ background: linear-gradient(135deg, #f0e6ff, #e6d8ff);
179
  border-radius: 15px;
180
  padding: 20px;
181
  margin: 20px 0;
 
188
  display: flex;
189
  align-items: center;
190
  gap: 10px;
191
+ margin-bottom: 15px;
192
+ }
193
+
194
+ .magic-pulse {
195
+ animation: pulse 2s infinite;
196
+ }
197
+
198
+ @keyframes pulse {
199
+ 0% { transform: scale(1); }
200
+ 50% { transform: scale(1.05); }
201
+ 100% { transform: scale(1); }
202
+ }
203
+
204
+ .concept-highlight {
205
+ background: linear-gradient(120deg, #e0d6ff, #d8c8ff);
206
+ padding: 3px 8px;
207
+ border-radius: 6px;
208
+ font-weight: bold;
209
  }
210
  </style>
211
  """, unsafe_allow_html=True)
 
217
  "emoji": "🔄",
218
  "description": "Loops repeat actions multiple times",
219
  "example": "for i in range(5):\n print('Hello!')",
220
+ "color": "#8A2BE2",
221
  "explanation": "A loop is like a magic spell that makes something happen again and again. In programming, we use loops when we want to repeat an action multiple times without writing the same code over and over."
222
  },
223
  "conditional": {
 
225
  "emoji": "❓",
226
  "description": "Conditionals make decisions in code",
227
  "example": "if sunny:\n go_outside()\nelse:\n stay_inside()",
228
+ "color": "#9370DB",
229
  "explanation": "Conditionals are like crossroads in a story where you choose which path to take. In programming, we use 'if' statements to make decisions based on certain conditions, just like choosing what to wear based on the weather."
230
  },
231
  "function": {
 
233
  "emoji": "✨",
234
  "description": "Functions are reusable blocks of code",
235
  "example": "def greet(name):\n print(f'Hello {name}!')",
236
+ "color": "#DA70D6",
237
  "explanation": "Functions are like magic spells you can create and reuse whenever you need them. They help us organize code into reusable blocks so we don't have to write the same thing multiple times."
238
  },
239
  "variable": {
 
241
  "emoji": "📦",
242
  "description": "Variables store information",
243
  "example": "score = 10\nplayer = 'Alex'",
244
+ "color": "#BA55D3",
245
  "explanation": "Variables are like labeled boxes where you can store information. They help us remember values and use them later in our code, just like remembering a character's name in a story."
246
  },
247
  "list": {
 
249
  "emoji": "📝",
250
  "description": "Lists store collections of items",
251
  "example": "fruits = ['apple', 'banana', 'orange']",
252
+ "color": "#9932CC",
253
  "explanation": "Lists are like magical scrolls that can hold multiple items. In programming, we use lists to store collections of related things, like a wizard's inventory of potions."
254
  }
255
  }
 
258
  ANIMATION_EXAMPLES = {
259
  "loop": "https://media.giphy.com/media/3o7abKhOpu0NwenH3O/giphy.gif",
260
  "conditional": "https://media.giphy.com/media/l0HlG8vJXW0X5yX4s/giphy.gif",
261
+ "function": "https://media.giphy.com/media/3o7TKsQ8UQ4l4LhGz6/giphy.gif",
262
+ "variable": "https://media.giphy.com/media/3o7TKsQ8UQ4l4LhGz6/giphy.gif",
263
+ "list": "https://media.giphy.com/media/3o7abKhOpu0NwenH3O/giphy.gif"
264
  }
265
 
266
+ # Fallback images if URLs fail
267
+ FALLBACK_IMAGES = {
268
+ "loop": "https://i.ibb.co/4sY7d0j/loop.gif",
269
+ "conditional": "https://i.ibb.co/8Xg7zY6/conditional.gif",
270
+ "function": "https://i.ibb.co/7yVnZ8C/function.gif",
271
+ "variable": "https://i.ibb.co/7yVnZ8C/function.gif",
272
+ "list": "https://i.ibb.co/4sY7d0j/loop.gif"
273
+ }
274
+
275
+ def check_url(url):
276
+ """Check if a URL is valid and accessible"""
277
  try:
278
+ response = requests.head(url, timeout=5)
279
+ return response.status_code == 200
280
+ except:
281
+ return False
282
+
283
+ def create_animation_preview(concept):
284
+ """Create an animation preview for the concept with fallback"""
 
 
 
 
 
285
  try:
286
+ # Check if the URL is valid
287
+ url = ANIMATION_EXAMPLES.get(concept, "")
288
+ if url and check_url(url):
289
+ return url
290
+
291
+ # Use fallback if primary URL fails
292
+ return FALLBACK_IMAGES.get(concept, FALLBACK_IMAGES["loop"])
293
  except:
294
+ return FALLBACK_IMAGES["loop"]
 
 
295
 
296
  def analyze_story(story):
297
  """Analyze story and identify programming concepts"""
 
299
  detected_concepts = []
300
 
301
  # Check for loops
302
+ if any(word in story_lower for word in ["times", "repeat", "again", "multiple", "each"]):
303
  detected_concepts.append("loop")
304
 
305
  # Check for conditionals
306
+ if any(word in story_lower for word in ["if", "when", "unless", "whether", "decide"]):
307
  detected_concepts.append("conditional")
308
 
309
  # Check for functions
310
+ if any(word in story_lower for word in ["make", "create", "do", "perform", "cast", "spell"]):
311
  detected_concepts.append("function")
312
 
313
  # Check for variables
314
+ if any(word in story_lower for word in ["is", "has", "set to", "value", "named", "called"]):
315
  detected_concepts.append("variable")
316
 
317
  # Check for lists
318
+ if any(word in story_lower for word in ["and", "many", "several", "collection", "items", "group"]):
319
  detected_concepts.append("list")
320
 
321
  return list(set(detected_concepts))
 
327
  return min(int(word), 10)
328
  return 3 # Default value
329
 
330
+ def text_to_speech_custom(text, filename="story_audio.mp3"):
331
+ """Convert text to speech using gTTS (faster and reliable)"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
  try:
 
 
 
 
 
 
 
 
 
 
 
333
  tts = gTTS(text=text, lang='en')
334
  tts.save(filename)
335
  return filename
 
344
  b64 = base64.b64encode(data).decode()
345
  md = f"""
346
  <audio autoplay>
347
+ <source src="data:audio/mp3;base64,{b64}" type="audio/mp3">
348
  </audio>
349
  """
350
  st.markdown(md, unsafe_allow_html=True)
 
352
  def generate_animation_code(story, concept):
353
  """Generate Python animation code based on story"""
354
  try:
355
+ count = extract_count_from_story(story)
356
+ short_story = story[:20] + '...' if len(story) > 20 else story
357
+
358
  # Sample code based on concept
359
  if concept == "loop":
360
  code = f"""
361
+ # Loop Animation: Repeating Actions
362
  import time
363
 
364
  def animate_story():
365
+ actions = {count}
366
+
367
+ print("Story: {short_story}")
368
+ print("Concept: Looping {count} times")
369
 
370
  for i in range(actions):
371
+ print(f"Action {{i+1}}: Performing story action")
372
  # Animation code would go here
373
  time.sleep(0.5)
374
 
375
+ print("Story animation complete!")
376
+
377
  animate_story()
378
  """
379
  description = "Looping through actions multiple times"
 
381
 
382
  elif concept == "conditional":
383
  code = f"""
384
+ # Conditional Animation: Making Decisions
385
  import random
386
 
387
+ print("Story: {short_story}")
388
+ print("Concept: Making decisions based on conditions")
389
+
390
  condition = random.choice([True, False])
391
+ print(f"Condition is {{condition}}")
392
 
393
  if condition:
394
+ print("Path 1: Following the true branch")
395
  # True branch animation
396
  else:
397
+ print("Path 2: Following the false branch")
398
  # False branch animation
399
+
400
+ print("Story animation complete!")
401
  """
402
  description = "Making decisions based on conditions"
403
  visual_cue = "Branching paths based on conditions"
404
 
405
+ elif concept == "function":
406
  code = f"""
407
+ # Function Animation: Reusable Actions
408
+ import time
409
+
410
+ print("Story: {short_story}")
411
+ print("Concept: Creating reusable functions")
412
+
413
  def perform_action():
414
+ print("Performing story action")
415
  # Action animation code
416
 
417
  # Call the function multiple times
418
+ for _ in range({count}):
419
  perform_action()
420
+ time.sleep(0.3)
421
+
422
+ print("Story animation complete!")
423
  """
424
  description = "Reusing code with functions"
425
  visual_cue = "Calling a reusable function"
426
 
427
+ elif concept == "variable":
428
+ code = f"""
429
+ # Variable Animation: Storing Information
430
+ import time
431
+
432
+ print("Story: {short_story}")
433
+ print("Concept: Using variables to store information")
434
+
435
+ character = "Hero"
436
+ score = 10
437
+ inventory = ["sword", "shield", "potion"]
438
+
439
+ print(f"Welcome {{character}}! You have {{score}} points.")
440
+ print(f"Your inventory: {{inventory}}")
441
+
442
+ for item in inventory:
443
+ print(f"Using {{item}}...")
444
+ time.sleep(0.5)
445
+
446
+ print("Story animation complete!")
447
+ """
448
+ description = "Storing information in variables"
449
+ visual_cue = "Using stored values"
450
+
451
+ else: # list
452
+ code = f"""
453
+ # List Animation: Managing Collections
454
+ import time
455
+
456
+ print("Story: {short_story}")
457
+ print("Concept: Working with lists of items")
458
+
459
+ items = ["item1", "item2", "item3", "item4", "item5"][:{count}]
460
+ print(f"Collection has {{len(items)}} items")
461
+
462
+ for i, item in enumerate(items):
463
+ print(f"Processing item {{i+1}}: {{item}}")
464
+ time.sleep(0.4)
465
+
466
+ print("Story animation complete!")
467
+ """
468
+ description = "Managing collections of items"
469
+ visual_cue = "Processing multiple items"
470
+
471
  return code, description, visual_cue
472
 
473
  except Exception as e:
 
502
  size="size",
503
  size_max=45,
504
  color="color",
505
+ color_discrete_sequence=px.colors.qualitative.Pastel,
506
  range_x=[0, 10],
507
  range_y=[0, 10],
508
  title=f"Interactive Animation: {story[:40]}{'...' if len(story) > 40 else ''}"
 
511
  # Customize layout
512
  fig.update_layout(
513
  showlegend=False,
514
+ plot_bgcolor='rgba(245, 240, 255, 0.8)',
515
+ paper_bgcolor='rgba(245, 240, 255, 0.8)',
516
  width=800,
517
  height=600,
518
  xaxis=dict(showgrid=False, zeroline=False, visible=False),
519
  yaxis=dict(showgrid=False, zeroline=False, visible=False),
520
+ font_family="Poppins"
521
  )
522
 
523
  fig.update_traces(
524
+ textfont_size=18,
525
  textposition='middle center',
526
  marker=dict(sizemode='diameter')
527
  )
 
539
  explanation = f"""
540
  Let me explain what's happening in your story and how it relates to programming!
541
 
542
+ <div class='concept-highlight'>Your story</div>: "{story[:100]}..."
543
 
544
+ <div class='concept-highlight'>Programming concept</div>: {concept_info.get('name', 'Programming')}
545
 
546
  {concept_info.get('explanation', 'This is a fundamental programming concept.')}
547
 
548
+ <div class='concept-highlight'>In our animation</div>:
549
  - {visual_cue}
550
  - This represents: {code_description}
551
 
552
+ <div class='concept-highlight'>How it works in code</div>:
553
  We use {concept_info.get('name', 'this concept')} like this:
554
+ ```
555
  {concept_info.get('example', 'Example code will appear here')}
556
+ ```
557
 
558
+ <div class='concept-highlight'>Real-world use</div>:
559
+ You might use this concept when:
560
  - Creating games with repeating actions (loops)
561
  - Making decisions in apps (conditionals)
562
  - Building reusable components (functions)
563
+ - Storing player information (variables)
564
+ - Managing collections of data (lists)
565
 
566
  Try to spot this concept in other stories you create!
567
  """
 
578
  st.session_state.story = ""
579
  if 'concepts' not in st.session_state:
580
  st.session_state.concepts = []
 
 
581
  if 'interactive_animation' not in st.session_state:
582
  st.session_state.interactive_animation = None
583
  if 'animation_code' not in st.session_state:
 
599
  tabs = st.empty()
600
  tab_cols = st.columns(5)
601
  with tab_cols[0]:
602
+ if st.button("📖 Create Story", key="tab_story"):
603
  st.session_state.active_tab = "story"
604
  with tab_cols[1]:
605
+ if st.button("🎬 Animation", key="tab_animation"):
606
  st.session_state.active_tab = "animation"
607
  with tab_cols[2]:
608
+ if st.button("🔍 Concepts", key="tab_concepts"):
609
  st.session_state.active_tab = "concepts"
610
  with tab_cols[3]:
611
+ if st.button("💻 Code", key="tab_code"):
612
  st.session_state.active_tab = "code"
613
  with tab_cols[4]:
614
+ if st.button("🔄 Reset", key="tab_reset"):
615
  st.session_state.story = ""
616
  st.session_state.concepts = []
 
617
  st.session_state.interactive_animation = None
618
  st.session_state.animation_code = ""
619
  st.session_state.code_description = ""
 
637
  key="story_input"
638
  )
639
 
640
+ if st.button("Create Animation!", use_container_width=True, key="create_animation"):
641
  if len(story) < 10:
642
  st.error("Your story needs to be at least 10 characters long!")
643
  else:
 
648
  # Get the main concept
649
  main_concept = st.session_state.concepts[0] if st.session_state.concepts else "variable"
650
 
 
 
 
 
 
 
 
 
 
651
  with st.spinner("🚀 Creating interactive animation..."):
652
  st.session_state.interactive_animation = create_interactive_animation(
653
  story, main_concept
 
661
  with st.spinner("🔊 Creating audio narration..."):
662
  audio_text = f"Your story: {story}. This demonstrates the programming concept: {st.session_state.code_description}"
663
  st.session_state.audio_file = text_to_speech_custom(
664
+ audio_text
665
  )
666
 
667
  with st.spinner("🧠 Generating concept explanation..."):
 
672
  st.session_state.visual_cue
673
  )
674
 
675
+ with st.spinner("🔊 Creating explanation audio..."):
676
+ explanation_text = st.session_state.concept_explanation.replace('<div class="concept-highlight">', '').replace('</div>', '')
677
+ st.session_state.explanation_audio = text_to_speech_custom(
678
+ explanation_text,
679
+ "concept_explanation.mp3"
680
+ )
681
+
682
  st.session_state.active_tab = "animation"
683
  st.rerun()
684
 
 
688
  with col1:
689
  st.caption("Loop Example")
690
  st.code('"A dragon breathes fire 5 times at the castle"', language="text")
691
+ st.image(create_animation_preview("loop"),
692
  use_container_width=True,
693
  caption="Loop Animation Preview")
694
  with col2:
695
  st.caption("Conditional Example")
696
  st.code('"If it rains, the cat stays inside, else it goes out"', language="text")
697
+ st.image(create_animation_preview("conditional"),
698
  use_container_width=True,
699
  caption="Conditional Animation Preview")
700
  with col3:
701
  st.caption("Function Example")
702
  st.code('"A wizard casts a spell to make flowers grow"', language="text")
703
+ st.image(create_animation_preview("function"),
704
  use_container_width=True,
705
  caption="Function Animation Preview")
706
 
 
713
  st.session_state.active_tab = "story"
714
  st.rerun()
715
 
 
 
 
 
 
 
 
 
 
 
716
  # Display interactive animation
717
  st.subheader("🎮 Interactive Animation")
718
  if st.session_state.interactive_animation:
 
720
  else:
721
  st.info("Interactive animation preview. The real animation would run on your computer!")
722
  concept = st.session_state.concepts[0] if st.session_state.concepts else "loop"
723
+ st.image(create_animation_preview(concept),
724
  use_container_width=True)
725
 
726
  # Animation concept preview
727
  st.subheader("📽️ Animation Concept Preview")
728
  if st.session_state.concepts:
729
  concept = st.session_state.concepts[0]
730
+ st.image(create_animation_preview(concept),
731
  caption=f"{CONCEPTS[concept]['name']} Animation Example",
732
  use_container_width=True)
733
  else:
 
737
  st.subheader("🔊 Story Narration")
738
  if st.session_state.audio_file:
739
  audio_bytes = open(st.session_state.audio_file, 'rb').read()
740
+ st.audio(audio_bytes, format='audio/mp3')
741
 
742
  if st.button("▶️ Play Story Automatically", use_container_width=True):
743
  autoplay_audio(st.session_state.audio_file)
 
756
  <span style="font-size:36px;">{details.get('emoji', '✨')}</span>
757
  <h3>Understanding {details.get('name', 'Programming')} Concept</h3>
758
  </div>
759
+ {st.session_state.concept_explanation}
760
  </div>
761
  """, unsafe_allow_html=True)
762
 
 
 
 
 
 
 
 
 
 
 
763
  if st.session_state.explanation_audio:
764
  explanation_bytes = open(st.session_state.explanation_audio, 'rb').read()
765
+ st.audio(explanation_bytes, format='audio/mp3')
766
 
767
  if st.button("▶️ Play Concept Explanation", use_container_width=True):
768
  autoplay_audio(st.session_state.explanation_audio)
 
792
  <h3 style="color:{details['color']};">{details['name']}</h3>
793
  </div>
794
  <p>{details['description']}</p>
795
+ <pre style="background:#f8f4ff; padding:10px; border-radius:8px;">{details['example']}</pre>
796
  <p><strong>Explanation:</strong> {details.get('explanation', 'Learn how this concept works!')}</p>
797
  </div>
798
  """, unsafe_allow_html=True)
799
 
800
  # Show animation preview for the concept
801
+ st.image(create_animation_preview(concept),
802
  caption=f"{details['name']} Animation Example",
803
  use_container_width=True)
804
 
805
  # Play explanation if available
806
  if st.session_state.explanation_audio:
807
+ st.subheader("🔊 Full Concept Explanation")
808
  explanation_bytes = open(st.session_state.explanation_audio, 'rb').read()
809
+ st.audio(explanation_bytes, format='audio/mp3')
810
 
811
  if st.button("▶️ Play Full Explanation", use_container_width=True):
812
  autoplay_audio(st.session_state.explanation_audio)
 
827
 
828
  # Download button
829
  st.download_button(
830
+ label="📥 Download Animation Code",
831
  data=st.session_state.animation_code,
832
  file_name="story_animation.py",
833
  mime="text/python",
 
847
  # Show what the animation would look like
848
  st.subheader("🎬 What Your Animation Would Look Like")
849
  concept = st.session_state.concepts[0] if st.session_state.concepts else "loop"
850
+ st.image(create_animation_preview(concept),
851
  caption="This is similar to what your animation would look like",
852
  use_container_width=True)
853
 
 
863
  <span style="font-size:36px;">{details.get('emoji', '✨')}</span>
864
  <h3>Understanding the Code</h3>
865
  </div>
866
+ {st.session_state.concept_explanation}
867
  </div>
868
  """, unsafe_allow_html=True)
869
 
870
  # Play explanation audio
871
  if st.session_state.explanation_audio:
872
  explanation_bytes = open(st.session_state.explanation_audio, 'rb').read()
873
+ st.audio(explanation_bytes, format='audio/mp3')
874
 
875
  if st.button("▶️ Play Code Explanation", use_container_width=True):
876
  autoplay_audio(st.session_state.explanation_audio)
877
  else:
878
  st.warning("No code generated yet!")
879
 
880
+ if st.button("Create Another Story!", use_container_width=True):
881
  st.session_state.active_tab = "story"
882
  st.session_state.story = ""
883
  st.rerun()