# app.py import streamlit as st import time import random import textwrap from io import BytesIO from PIL import Image, ImageDraw, ImageFont import numpy as np from transformers import pipeline import base64 import re # Set up the page st.set_page_config( page_title="CodeTales ✨", page_icon="🚀", layout="wide", initial_sidebar_state="expanded" ) # Custom CSS st.markdown(""" """, unsafe_allow_html=True) # Initialize AI models @st.cache_resource def load_models(): """Load open-source AI models""" try: # Named entity recognition for identifying objects ner_model = pipeline("ner", model="dbmdz/bert-large-cased-finetuned-conll03-english") # Text classification for theme detection classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli") # Text generation for code explanations explanation_generator = pipeline("text2text-generation", model="google/flan-t5-large") return ner_model, classifier, explanation_generator except Exception as e: st.error(f"Error loading models: {e}") return None, None, None # Image generation functions def create_storyboard_image(text, width=400, height=300): """Create a storyboard image from text""" # Create blank image img = Image.new('RGB', (width, height), color=(25, 25, 112)) # Dark blue background # Load a comic-style font (fallback to default if not available) try: font = ImageFont.truetype("comic.ttf", 16) except: font = ImageFont.load_default() draw = ImageDraw.Draw(img) # Draw title draw.text((10, 10), "Your Story Comes to Life!", fill=(255, 215, 0), font=font) # Draw text box draw.rectangle([10, 40, width-10, height-40], fill=(240, 248, 255), outline=(255, 215, 0), width=2) # Wrap text wrapped_text = textwrap.fill(text, width=40) draw.text((20, 50), wrapped_text, fill=(25, 25, 112), font=font) # Draw decorations draw.rectangle([width-50, height-30, width-30, height-10], fill=(220, 20, 60), outline=(255, 215, 0), width=1) draw.ellipse([20, height-50, 70, height], fill=(30, 144, 255), outline=(255, 215, 0), width=1) return img def generate_sprite_animation(story, character="spaceship", theme="space", num_frames=4): """Generate a sprite-based animation from story""" frames = [] width, height = 300, 200 for i in range(num_frames): # Create base image with theme if theme == "space": bg_color = (0, 0, 30) star_color = (255, 255, 255) elif theme == "jungle": bg_color = (0, 100, 0) star_color = None # No stars in jungle elif theme == "medieval": bg_color = (139, 69, 19) star_color = None elif theme == "underwater": bg_color = (0, 105, 148) star_color = None else: bg_color = (0, 0, 30) star_color = (255, 255, 255) img = Image.new('RGB', (width, height), color=bg_color) draw = ImageDraw.Draw(img) # Draw stars for space theme if star_color: for _ in range(30): x = random.randint(0, width) y = random.randint(0, height) draw.ellipse([x, y, x+2, y+2], fill=star_color) # Draw moving elements based on frame if character == "spaceship": ship_x = 50 + i * 60 ship_y = 80 draw.polygon([(ship_x, ship_y), (ship_x+30, ship_y), (ship_x+15, ship_y-20)], fill=(169, 169, 169)) if "shoot" in story.lower() and i > 1: for j in range(3): laser_x = ship_x + 15 laser_y = ship_y - 20 + j*5 draw.line([(laser_x, laser_y), (width, laser_y)], fill=(255, 0, 0), width=2) elif character == "dragon": dragon_x = 50 + i * 40 dragon_y = 100 # Draw dragon body draw.ellipse([dragon_x, dragon_y, dragon_x+40, dragon_y+20], fill=(178, 34, 34)) # Draw dragon head draw.ellipse([dragon_x+30, dragon_y-5, dragon_x+50, dragon_y+15], fill=(178, 34, 34)) # Draw wings draw.ellipse([dragon_x+10, dragon_y-15, dragon_x+30, dragon_y], fill=(138, 43, 226)) if "fire" in story.lower() and i > 0: for j in range(5): flame_x = dragon_x + 50 flame_y = dragon_y + 5 - j*5 flame_size = random.randint(5, 15) draw.ellipse([flame_x, flame_y, flame_x+flame_size, flame_y+flame_size], fill=(255, random.randint(100, 200), 0)) elif character == "knight": knight_x = 50 + i * 40 knight_y = 120 # Draw knight body draw.rectangle([knight_x, knight_y, knight_x+20, knight_y+40], fill=(70, 70, 70)) # Draw knight head draw.ellipse([knight_x+5, knight_y-15, knight_x+15, knight_y-5], fill=(210, 180, 140)) # Draw sword draw.rectangle([knight_x+15, knight_y+10, knight_x+25, knight_y+15], fill=(192, 192, 192)) draw.polygon([(knight_x+25, knight_y+12), (knight_x+35, knight_y+10), (knight_x+35, knight_y+15)], fill=(192, 192, 192)) if "attack" in story.lower() and i % 2 == 1: # Draw sword swing draw.line([(knight_x+25, knight_y+12), (knight_x+45, knight_y-10)], fill=(255, 255, 0), width=2) elif character == "mermaid": mermaid_x = 50 + i * 40 mermaid_y = 120 # Draw mermaid tail draw.ellipse([mermaid_x, mermaid_y, mermaid_x+30, mermaid_y+20], fill=(255, 105, 180)) # Draw mermaid body draw.ellipse([mermaid_x+5, mermaid_y-20, mermaid_x+25, mermaid_y], fill=(255, 218, 185)) # Draw hair draw.ellipse([mermaid_x-5, mermaid_y-25, mermaid_x+30, mermaid_y-15], fill=(255, 215, 0)) if "swim" in story.lower() and i > 0: # Draw bubbles for j in range(3): bubble_x = mermaid_x + random.randint(0, 30) bubble_y = mermaid_y - random.randint(10, 30) draw.ellipse([bubble_x, bubble_y, bubble_x+5, bubble_y+5], fill=(173, 216, 230)) # Draw enemies based on theme if theme == "space" and "alien" in story.lower(): alien_x = 200 alien_y = 100 - i*10 draw.ellipse([alien_x, alien_y, alien_x+20, alien_y+20], fill=(50, 205, 50)) draw.ellipse([alien_x+5, alien_y+5, alien_x+7, alien_y+7], fill=(0, 0, 0)) draw.ellipse([alien_x+13, alien_y+5, alien_x+15, alien_y+7], fill=(0, 0, 0)) elif theme == "jungle" and "snake" in story.lower(): snake_x = 200 snake_y = 150 - i*5 for segment in range(5): offset = segment * 5 draw.ellipse([snake_x+offset, snake_y+offset, snake_x+offset+15, snake_y+offset+15], fill=(0, 128, 0)) elif theme == "medieval" and "dragon" in story.lower() and character != "dragon": dragon_x = 220 dragon_y = 80 draw.ellipse([dragon_x, dragon_y, dragon_x+40, dragon_y+20], fill=(178, 34, 34)) draw.line([(dragon_x+40, dragon_y+10), (dragon_x+60, dragon_y)], fill=(178, 34, 34), width=3) elif theme == "underwater" and "shark" in story.lower(): shark_x = 220 shark_y = 80 + i*10 # Draw shark body draw.ellipse([shark_x, shark_y, shark_x+60, shark_y+30], fill=(169, 169, 169)) # Draw shark fin draw.polygon([(shark_x+40, shark_y), (shark_x+50, shark_y-20), (shark_x+60, shark_y)], fill=(169, 169, 169)) frames.append(img) return frames def generate_code_explanation(story, explanation_generator): """Generate code explanation using open-source model""" try: # Create a prompt for the model prompt = f"Explain to a child how this story would become code: '{story}'. Use simple analogies and relate to real-world objects." # Generate text result = explanation_generator( prompt, max_length=250, num_return_sequences=1, ) return result[0]['generated_text'] except: # Fallback explanation if model fails return f"""See how your story became real code? For example, when you wrote "{story.split()[0]}", we used code like: character.move(). That's how we turn words into actions!""" def extract_story_elements(story, ner_model, classifier): """Extract hero and world from the story using AI models""" try: # Default values hero = "spaceship" world = "space" # Find hero based on keywords and entities hero_keywords = { "spaceship": ["spaceship", "rocket", "ship", "alien", "planet", "star"], "dragon": ["dragon", "monster", "creature", "beast", "wyvern"], "knight": ["knight", "warrior", "prince", "princess", "king", "queen", "sword"], "mermaid": ["mermaid", "merman", "sea", "ocean", "underwater", "fish"] } # Find world based on keywords and classification world_labels = ["space", "jungle", "medieval", "underwater"] # Find hero by keywords story_lower = story.lower() for candidate, keywords in hero_keywords.items(): if any(keyword in story_lower for keyword in keywords): hero = candidate break # Use NER to find entities entities = ner_model(story) person_entities = [e['word'] for e in entities if e['entity'] in ['B-PER', 'I-PER']] # If we found specific character names, adjust hero if person_entities: if "dragon" in story_lower: hero = "dragon" elif "knight" in story_lower or "king" in story_lower or "queen" in story_lower: hero = "knight" elif "mermaid" in story_lower or "sea" in story_lower: hero = "mermaid" # Classify world result = classifier(story, world_labels) world = result['labels'][0] # Override based on specific keywords if "underwater" in story_lower or "ocean" in story_lower or "sea" in story_lower: world = "underwater" if "forest" in story_lower or "jungle" in story_lower: world = "jungle" if "castle" in story_lower or "kingdom" in story_lower or "dragon" in story_lower: world = "medieval" if "space" in story_lower or "alien" in story_lower or "planet" in story_lower: world = "space" return hero, world except Exception as e: st.error(f"Error analyzing story: {str(e)}") return "spaceship", "space" # Header section st.markdown('
CodeTales ✨
', unsafe_allow_html=True) st.markdown('
Storytime + Coding Magic
', unsafe_allow_html=True) st.markdown('
Turn wild stories into playable games with AI magic!
', unsafe_allow_html=True) # How it works section st.markdown("### ✨ How It Works") step_container = st.container() with step_container: cols = st.columns(4) with cols[0]: st.markdown('
1
Write Your Story
', unsafe_allow_html=True) with cols[1]: st.markdown('
2
AI Chooses Hero & World
', unsafe_allow_html=True) with cols[2]: st.markdown('
3
Watch Animation
', unsafe_allow_html=True) with cols[3]: st.markdown('
4
Learn Coding
', unsafe_allow_html=True) # Load models ner_model, classifier, explanation_generator = load_models() # Initialize session state if 'animation_generated' not in st.session_state: st.session_state.animation_generated = False if 'story_text' not in st.session_state: st.session_state.story_text = "" if 'animation_frames' not in st.session_state: st.session_state.animation_frames = [] if 'code_explanation' not in st.session_state: st.session_state.code_explanation = "" if 'selected_character' not in st.session_state: st.session_state.selected_character = "spaceship" if 'selected_theme' not in st.session_state: st.session_state.selected_theme = "space" if 'story_count' not in st.session_state: st.session_state.story_count = 0 if 'level' not in st.session_state: st.session_state.level = 1 if 'xp' not in st.session_state: st.session_state.xp = 0 if 'analyzed' not in st.session_state: st.session_state.analyzed = False # Character and theme mapping characters = { "spaceship": "🚀 Spaceship", "dragon": "🐉 Dragon", "knight": "🛡️ Knight", "mermaid": "🧜‍♀️ Mermaid" } themes = { "space": "🌌 Space", "jungle": "🌿 Jungle", "medieval": "🏰 Medieval", "underwater": "🌊 Underwater" } # Main content st.markdown(f'
Level {st.session_state.level} ✨ XP: {st.session_state.xp}/100
', unsafe_allow_html=True) progress = st.progress(st.session_state.xp / 100) col1, col2 = st.columns([1, 1]) with col1: st.markdown('
', unsafe_allow_html=True) st.markdown("### 📖 Step 1: Write Your Story") story_text = st.text_area( "Tell your adventure story...", height=200, placeholder="Once upon a time, a brave spaceship zoomed through space, shooting lasers at alien spaceships...", label_visibility="collapsed", value=st.session_state.story_text ) if st.button("✨ Analyze My Story!", use_container_width=True, key="analyze", type="primary"): if story_text.strip(): st.session_state.story_text = story_text with st.spinner("🔍 AI is reading your story to find the perfect hero and world..."): # Extract story elements using AI hero, world = extract_story_elements(story_text, ner_model, classifier) st.session_state.selected_character = hero st.session_state.selected_theme = world st.session_state.analyzed = True # Show suggestions st.success(f"🎯 AI found a {characters[hero]} hero in a {themes[world]} world!") else: st.warning("Please enter a story first!") if st.session_state.analyzed: st.markdown("### 🧙 Step 2: Your Hero & World") st.markdown(f'
Your Hero: {characters[st.session_state.selected_character]}
', unsafe_allow_html=True) st.markdown(f'
Your World: {themes[st.session_state.selected_theme]}
', unsafe_allow_html=True) st.info("💡 The AI chose these based on your story! Click Generate Animation when ready.") # Generate animation button if st.button("🎬 Generate Animation!", use_container_width=True, key="generate", type="primary"): st.session_state.animation_generated = True with st.spinner("🧙‍♂️ Creating your animation..."): # Generate animation frames st.session_state.animation_frames = generate_sprite_animation( story_text, character=st.session_state.selected_character, theme=st.session_state.selected_theme ) # Generate code explanation st.session_state.code_explanation = generate_code_explanation(story_text, explanation_generator) # Update user progress st.session_state.story_count += 1 st.session_state.xp += 20 # Check for level up if st.session_state.xp >= 100: st.session_state.level += 1 st.session_state.xp = st.session_state.xp - 100 st.session_state.new_level = True st.markdown('
', unsafe_allow_html=True) with col2: st.markdown('
', unsafe_allow_html=True) if st.session_state.animation_generated and st.session_state.animation_frames: st.markdown("### 🎮 Step 3: Your Animation") # Display animation frames st.markdown("**Your Story Comes to Life!**") cols = st.columns(len(st.session_state.animation_frames)) for i, frame in enumerate(st.session_state.animation_frames): with cols[i]: st.image(frame, caption=f"Frame {i+1}", use_container_width=True) # Create an animated GIF gif_buffer = BytesIO() st.session_state.animation_frames[0].save( gif_buffer, format='GIF', save_all=True, append_images=st.session_state.animation_frames[1:], duration=500, loop=0 ) gif_data = gif_buffer.getvalue() st.download_button( label="⬇️ Download Animation", data=gif_data, file_name="your_story.gif", mime="image/gif", use_container_width=True ) # Display character and theme info st.success(f"✨ Your {characters[st.session_state.selected_character]} in the {themes[st.session_state.selected_theme]} world!") elif st.session_state.analyzed: st.markdown("### 🎮 Step 3: Your Animation") st.image("https://img.freepik.com/free-vector/hand-drawn-colorful-space-background_23-2148821798.jpg", use_container_width=True) st.info("👆 Click 'Generate Animation' to bring your story to life!") elif story_text: st.markdown("### 🎮 Your Animation Will Appear Here") preview_img = create_storyboard_image(story_text) st.image(preview_img, caption="Your Story Preview", use_container_width=True) st.info("👆 Click 'Analyze My Story' to begin the magic!") else: st.image("https://img.freepik.com/free-vector/hand-drawn-colorful-space-background_23-2148821798.jpg", use_container_width=True) st.info("👆 Write your story and click 'Analyze My Story' to begin!") st.markdown('
', unsafe_allow_html=True) # Tavus explanation section if st.session_state.animation_generated and st.session_state.story_text: st.markdown('
', unsafe_allow_html=True) st.markdown("### 🤖 Step 4: Tavus the Robot Teacher Explains Coding") # Extract action words from story action_words = ["zoom", "shoot", "fly", "move", "jump", "run", "attack", "laser", "alien", "spaceship", "dragon", "fire", "hero", "sword", "castle", "escape", "fight", "win", "swim", "dive"] found_actions = [word for word in action_words if re.search(r'\b' + word + r'\b', st.session_state.story_text.lower())] if found_actions: # Create explanation based on found words st.markdown("#### 🧩 Story Actions to Code Concepts") cols = st.columns(3) action_colors = ["#ff6b6b", "#4ecdc4", "#ffd166", "#06d6a0", "#118ab2"] for i, action in enumerate(found_actions[:6]): with cols[i % 3]: color = action_colors[i % len(action_colors)] st.markdown(f'
', unsafe_allow_html=True) st.markdown(f"**{action.capitalize()}**") # Add code snippets based on action if action == "zoom": st.code("hero.move_fast(10)", language="python") elif action == "shoot": st.code("laser = hero.create_weapon()", language="python") elif action == "fly": st.code("hero.fly(height=100)", language="python") elif action == "move": st.code("hero.move(direction='right')", language="python") elif action == "jump": st.code("hero.jump(power=20)", language="python") elif action == "run": st.code("hero.speed = hero.speed * 2", language="python") elif action == "attack": st.code("hero.attack(enemy)", language="python") elif action == "swim": st.code("hero.swim(speed=5)", language="python") elif action == "dive": st.code("hero.dive(depth=30)", language="python") else: st.code(f"hero.{action}()", language="python") st.markdown("
", unsafe_allow_html=True) st.markdown("---") # Show AI-generated explanation st.markdown("### 🧠 AI-Powered Explanation:") st.write(st.session_state.code_explanation) st.markdown("#### 💡 Remember:") st.markdown(""" - Every action in your story becomes code - Code is just instructions for computers - You're already thinking like a coder! """) st.markdown("
", unsafe_allow_html=True) # New level notification if 'new_level' in st.session_state and st.session_state.new_level: st.balloons() st.success(f"🌟 Level Up! You're now Level {st.session_state.level}!") st.session_state.new_level = False # Benefits section st.markdown(""" ## ❤ Why Kids & Parents Love CodeTales | For Kids 👧👦 | For Parents & Teachers 👪👩‍🏫 | |--------------|----------------------------| | ✨ Create your own animated stories | 🧠 Teaches computational thinking | | 🚀 See imagination come to life | 🔍 Develops problem-solving skills | | 🎮 Interactive game creation | ➗ Reinforces STEM fundamentals | | 😄 Fun characters and worlds | 🧩 Encourages logical reasoning | | 🌟 Unlock new levels | 📈 Tracks learning progress | """) # Footer st.markdown("---") st.markdown("""

✨ Made with magic by CodeTales Team ✨
Transforming stories into games since 2023

""", unsafe_allow_html=True)