Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -2,13 +2,18 @@
|
|
2 |
import streamlit as st
|
3 |
import time
|
4 |
import random
|
5 |
-
|
6 |
-
from PIL import Image, ImageDraw, ImageFont
|
7 |
-
import numpy as np
|
8 |
import textwrap
|
9 |
import os
|
10 |
import base64
|
11 |
from io import BytesIO
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
|
13 |
# Set up the page
|
14 |
st.set_page_config(
|
@@ -18,22 +23,26 @@ st.set_page_config(
|
|
18 |
initial_sidebar_state="expanded"
|
19 |
)
|
20 |
|
|
|
|
|
|
|
21 |
# Custom CSS
|
22 |
st.markdown("""
|
23 |
<style>
|
24 |
-
@import url('https://fonts.googleapis.com/css2?family=Comic+Neue:wght@700&display=swap');
|
25 |
|
26 |
.stApp {
|
27 |
background: linear-gradient(135deg, #6e8efb, #a777e3);
|
28 |
}
|
29 |
|
30 |
.header {
|
31 |
-
font-family: '
|
32 |
color: white;
|
33 |
text-align: center;
|
34 |
-
font-size:
|
35 |
text-shadow: 3px 3px 0 #000;
|
36 |
padding: 1rem;
|
|
|
37 |
}
|
38 |
|
39 |
.subheader {
|
@@ -50,6 +59,7 @@ st.markdown("""
|
|
50 |
padding: 2rem;
|
51 |
box-shadow: 0 8px 16px rgba(0,0,0,0.2);
|
52 |
margin-bottom: 2rem;
|
|
|
53 |
}
|
54 |
|
55 |
.robot-speech {
|
@@ -60,6 +70,7 @@ st.markdown("""
|
|
60 |
font-size: 1.2rem;
|
61 |
position: relative;
|
62 |
margin-top: 2rem;
|
|
|
63 |
}
|
64 |
|
65 |
.robot-speech:after {
|
@@ -72,7 +83,7 @@ st.markdown("""
|
|
72 |
}
|
73 |
|
74 |
.generate-btn {
|
75 |
-
background: #ff5722 !important;
|
76 |
color: white !important;
|
77 |
font-weight: bold !important;
|
78 |
font-size: 1.2rem !important;
|
@@ -82,11 +93,14 @@ st.markdown("""
|
|
82 |
box-shadow: 0 4px 8px rgba(0,0,0,0.3) !important;
|
83 |
transition: all 0.3s !important;
|
84 |
margin-top: 1rem;
|
|
|
|
|
85 |
}
|
86 |
|
87 |
.generate-btn:hover {
|
88 |
transform: scale(1.05) !important;
|
89 |
box-shadow: 0 6px 12px rgba(0,0,0,0.4) !important;
|
|
|
90 |
}
|
91 |
|
92 |
.code-block {
|
@@ -94,10 +108,11 @@ st.markdown("""
|
|
94 |
color: #f8f8f2;
|
95 |
padding: 1rem;
|
96 |
border-radius: 10px;
|
97 |
-
font-family: monospace;
|
98 |
font-size: 1.1rem;
|
99 |
margin: 1rem 0;
|
100 |
overflow-x: auto;
|
|
|
101 |
}
|
102 |
|
103 |
.animation-frame {
|
@@ -105,6 +120,60 @@ st.markdown("""
|
|
105 |
border-radius: 10px;
|
106 |
margin: 5px;
|
107 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
108 |
</style>
|
109 |
""", unsafe_allow_html=True)
|
110 |
|
@@ -122,10 +191,13 @@ def load_models():
|
|
122 |
# Named entity recognition for identifying objects
|
123 |
ner_model = pipeline("ner", model="dbmdz/bert-large-cased-finetuned-conll03-english")
|
124 |
|
125 |
-
|
|
|
|
|
|
|
126 |
except Exception as e:
|
127 |
st.error(f"Error loading models: {e}")
|
128 |
-
return None, None, None
|
129 |
|
130 |
# Image generation functions
|
131 |
def create_storyboard_image(text, width=400, height=300):
|
@@ -157,24 +229,38 @@ def create_storyboard_image(text, width=400, height=300):
|
|
157 |
|
158 |
return img
|
159 |
|
160 |
-
def generate_sprite_animation(story, num_frames=4):
|
161 |
"""Generate a sprite-based animation from story"""
|
162 |
frames = []
|
163 |
width, height = 300, 200
|
164 |
|
165 |
for i in range(num_frames):
|
166 |
-
# Create base image
|
167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
168 |
draw = ImageDraw.Draw(img)
|
169 |
|
170 |
-
# Draw stars
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
|
|
175 |
|
176 |
# Draw moving elements based on frame
|
177 |
-
if "spaceship"
|
178 |
ship_x = 50 + i * 60
|
179 |
ship_y = 80
|
180 |
draw.polygon([(ship_x, ship_y), (ship_x+30, ship_y),
|
@@ -186,44 +272,75 @@ def generate_sprite_animation(story, num_frames=4):
|
|
186 |
laser_y = ship_y - 20 + j*5
|
187 |
draw.line([(laser_x, laser_y), (width, laser_y)], fill=(255, 0, 0), width=2)
|
188 |
|
189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
190 |
alien_x = 200
|
191 |
alien_y = 100 - i*10
|
192 |
draw.ellipse([alien_x, alien_y, alien_x+20, alien_y+20], fill=(50, 205, 50))
|
193 |
draw.ellipse([alien_x+5, alien_y+5, alien_x+7, alien_y+7], fill=(0, 0, 0))
|
194 |
draw.ellipse([alien_x+13, alien_y+5, alien_x+15, alien_y+7], fill=(0, 0, 0))
|
195 |
|
196 |
-
|
197 |
-
|
198 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
199 |
draw.ellipse([dragon_x, dragon_y, dragon_x+40, dragon_y+20], fill=(178, 34, 34))
|
200 |
draw.line([(dragon_x+40, dragon_y+10), (dragon_x+60, dragon_y)], fill=(178, 34, 34), width=3)
|
201 |
-
|
202 |
-
if "fire" in story.lower() and i > 0:
|
203 |
-
for j in range(5):
|
204 |
-
flame_x = dragon_x + 60
|
205 |
-
flame_y = dragon_y - j*5
|
206 |
-
flame_size = random.randint(5, 15)
|
207 |
-
draw.ellipse([flame_x, flame_y, flame_x+flame_size, flame_y+flame_size],
|
208 |
-
fill=(255, random.randint(100, 200), 0))
|
209 |
|
210 |
frames.append(img)
|
211 |
|
212 |
return frames
|
213 |
|
214 |
-
def generate_code_explanation(story,
|
215 |
"""Generate code explanation using open-source model"""
|
216 |
try:
|
217 |
# Create a prompt for the model
|
218 |
-
prompt = f"Explain how this story would
|
219 |
|
220 |
# Generate text
|
221 |
-
result =
|
222 |
prompt,
|
223 |
-
max_length=
|
224 |
num_return_sequences=1,
|
225 |
-
temperature=0.7,
|
226 |
-
top_k=50
|
227 |
)
|
228 |
|
229 |
return result[0]['generated_text']
|
@@ -232,6 +349,34 @@ def generate_code_explanation(story, story_to_code):
|
|
232 |
return f"""See how your story became real code? For example, when you wrote "{story.split()[0]}",
|
233 |
we used code like: character.move(). That's how we turn words into actions!"""
|
234 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
235 |
# Header section
|
236 |
st.markdown('<div class="header">CodeTales โจ</div>', unsafe_allow_html=True)
|
237 |
st.markdown('<div class="subheader">Storytime + Coding Magic</div>', unsafe_allow_html=True)
|
@@ -251,10 +396,13 @@ with st.expander("โจ How It Works (Like Baking a Cake) ๐"):
|
|
251 |
|
252 |
**4. Robot Teacher Explains ๐ค**
|
253 |
Tavus shows: *"See? 'spaceship.move_right()' makes it fly! That's coding!"*
|
|
|
|
|
|
|
254 |
""")
|
255 |
|
256 |
# Load models
|
257 |
-
story_to_code, sentiment_analyzer, ner_model = load_models()
|
258 |
|
259 |
# Initialize session state
|
260 |
if 'animation_generated' not in st.session_state:
|
@@ -265,19 +413,103 @@ if 'animation_frames' not in st.session_state:
|
|
265 |
st.session_state.animation_frames = []
|
266 |
if 'code_explanation' not in st.session_state:
|
267 |
st.session_state.code_explanation = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
268 |
|
269 |
# Main content
|
|
|
|
|
|
|
270 |
col1, col2 = st.columns([1, 1])
|
271 |
|
272 |
with col1:
|
273 |
st.markdown('<div class="story-box">', unsafe_allow_html=True)
|
274 |
st.markdown("### ๐ Write Your Story Here:")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
275 |
story_text = st.text_area(
|
276 |
"Tell your adventure story...",
|
277 |
height=200,
|
278 |
placeholder="Once upon a time, a brave spaceship zoomed through space, shooting lasers at alien spaceships...",
|
279 |
label_visibility="collapsed",
|
280 |
-
value=st.session_state.story_text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
281 |
)
|
282 |
|
283 |
# Generate button with animation
|
@@ -288,28 +520,41 @@ with col1:
|
|
288 |
|
289 |
with st.spinner("๐งโโ๏ธ Cooking your story in the magic oven..."):
|
290 |
# Generate animation frames
|
291 |
-
st.session_state.animation_frames = generate_sprite_animation(
|
|
|
|
|
|
|
|
|
292 |
|
293 |
# Generate code explanation
|
294 |
-
st.session_state.code_explanation = generate_code_explanation(story_text,
|
295 |
|
296 |
-
#
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
302 |
|
303 |
-
#
|
304 |
-
if
|
305 |
-
|
306 |
-
characters = list(set([ent['word'] for ent in entities if ent['entity'] in ['B-PER', 'I-PER']]))
|
307 |
-
if characters:
|
308 |
-
st.session_state.characters = f"Main characters: {', '.join(characters)}"
|
309 |
-
else:
|
310 |
-
st.session_state.characters = "Exciting characters are ready for action!"
|
311 |
else:
|
312 |
-
st.session_state.
|
313 |
else:
|
314 |
st.warning("Please enter a story first!")
|
315 |
st.markdown('</div>', unsafe_allow_html=True)
|
@@ -345,10 +590,21 @@ with col2:
|
|
345 |
use_container_width=True
|
346 |
)
|
347 |
|
348 |
-
# Display
|
349 |
-
st.
|
350 |
-
|
351 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
352 |
|
353 |
elif st.session_state.animation_generated:
|
354 |
st.warning("Couldn't generate animation. Try a different story!")
|
@@ -451,6 +707,64 @@ if st.session_state.animation_generated and st.session_state.story_text:
|
|
451 |
|
452 |
st.markdown("</div>", unsafe_allow_html=True)
|
453 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
454 |
# Benefits section
|
455 |
st.markdown("""
|
456 |
## โค Why Everyone Will Love CodeTales
|
@@ -462,6 +776,8 @@ st.markdown("""
|
|
462 |
| ๐ฎ Creates personal video games | โ Reinforces math fundamentals |
|
463 |
| ๐ Makes learning fun and exciting | ๐งฉ Encourages problem-solving abilities |
|
464 |
| ๐ 100% private with no API keys | ๐ฐ Completely free and open-source |
|
|
|
|
|
465 |
""")
|
466 |
|
467 |
# Footer
|
|
|
2 |
import streamlit as st
|
3 |
import time
|
4 |
import random
|
5 |
+
import json
|
|
|
|
|
6 |
import textwrap
|
7 |
import os
|
8 |
import base64
|
9 |
from io import BytesIO
|
10 |
+
from PIL import Image, ImageDraw, ImageFont
|
11 |
+
import numpy as np
|
12 |
+
from gtts import gTTS
|
13 |
+
from pygame import mixer
|
14 |
+
import tempfile
|
15 |
+
import requests
|
16 |
+
from transformers import pipeline, set_seed
|
17 |
|
18 |
# Set up the page
|
19 |
st.set_page_config(
|
|
|
23 |
initial_sidebar_state="expanded"
|
24 |
)
|
25 |
|
26 |
+
# Initialize pygame mixer
|
27 |
+
mixer.init()
|
28 |
+
|
29 |
# Custom CSS
|
30 |
st.markdown("""
|
31 |
<style>
|
32 |
+
@import url('https://fonts.googleapis.com/css2?family=Comic+Neue:wght@700&family=Press+Start+2P&display=swap');
|
33 |
|
34 |
.stApp {
|
35 |
background: linear-gradient(135deg, #6e8efb, #a777e3);
|
36 |
}
|
37 |
|
38 |
.header {
|
39 |
+
font-family: 'Press Start 2P', cursive;
|
40 |
color: white;
|
41 |
text-align: center;
|
42 |
+
font-size: 2.5rem;
|
43 |
text-shadow: 3px 3px 0 #000;
|
44 |
padding: 1rem;
|
45 |
+
letter-spacing: 2px;
|
46 |
}
|
47 |
|
48 |
.subheader {
|
|
|
59 |
padding: 2rem;
|
60 |
box-shadow: 0 8px 16px rgba(0,0,0,0.2);
|
61 |
margin-bottom: 2rem;
|
62 |
+
border: 4px solid #ff6b6b;
|
63 |
}
|
64 |
|
65 |
.robot-speech {
|
|
|
70 |
font-size: 1.2rem;
|
71 |
position: relative;
|
72 |
margin-top: 2rem;
|
73 |
+
border: 3px solid #2e7d32;
|
74 |
}
|
75 |
|
76 |
.robot-speech:after {
|
|
|
83 |
}
|
84 |
|
85 |
.generate-btn {
|
86 |
+
background: linear-gradient(135deg, #ff5722, #ff9800) !important;
|
87 |
color: white !important;
|
88 |
font-weight: bold !important;
|
89 |
font-size: 1.2rem !important;
|
|
|
93 |
box-shadow: 0 4px 8px rgba(0,0,0,0.3) !important;
|
94 |
transition: all 0.3s !important;
|
95 |
margin-top: 1rem;
|
96 |
+
font-family: 'Press Start 2P', cursive !important;
|
97 |
+
letter-spacing: 1px;
|
98 |
}
|
99 |
|
100 |
.generate-btn:hover {
|
101 |
transform: scale(1.05) !important;
|
102 |
box-shadow: 0 6px 12px rgba(0,0,0,0.4) !important;
|
103 |
+
background: linear-gradient(135deg, #ff7043, #ffa726) !important;
|
104 |
}
|
105 |
|
106 |
.code-block {
|
|
|
108 |
color: #f8f8f2;
|
109 |
padding: 1rem;
|
110 |
border-radius: 10px;
|
111 |
+
font-family: 'Courier New', monospace;
|
112 |
font-size: 1.1rem;
|
113 |
margin: 1rem 0;
|
114 |
overflow-x: auto;
|
115 |
+
border-left: 4px solid #ff9800;
|
116 |
}
|
117 |
|
118 |
.animation-frame {
|
|
|
120 |
border-radius: 10px;
|
121 |
margin: 5px;
|
122 |
}
|
123 |
+
|
124 |
+
.achievement-badge {
|
125 |
+
background: #ffd54f;
|
126 |
+
color: #333;
|
127 |
+
border-radius: 50%;
|
128 |
+
width: 60px;
|
129 |
+
height: 60px;
|
130 |
+
display: flex;
|
131 |
+
align-items: center;
|
132 |
+
justify-content: center;
|
133 |
+
font-size: 1.5rem;
|
134 |
+
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
135 |
+
margin: 0 auto;
|
136 |
+
}
|
137 |
+
|
138 |
+
.character-option {
|
139 |
+
border: 3px solid transparent;
|
140 |
+
border-radius: 10px;
|
141 |
+
padding: 5px;
|
142 |
+
margin: 5px;
|
143 |
+
cursor: pointer;
|
144 |
+
transition: all 0.3s;
|
145 |
+
}
|
146 |
+
|
147 |
+
.character-option:hover {
|
148 |
+
transform: scale(1.05);
|
149 |
+
}
|
150 |
+
|
151 |
+
.character-option.selected {
|
152 |
+
border-color: #ff5722;
|
153 |
+
}
|
154 |
+
|
155 |
+
.tab-content {
|
156 |
+
padding: 1rem;
|
157 |
+
border: 2px solid #4caf50;
|
158 |
+
border-radius: 10px;
|
159 |
+
background: rgba(255, 255, 255, 0.8);
|
160 |
+
margin-top: 1rem;
|
161 |
+
}
|
162 |
+
|
163 |
+
.level-indicator {
|
164 |
+
background: #4caf50;
|
165 |
+
color: white;
|
166 |
+
border-radius: 20px;
|
167 |
+
padding: 0.5rem 1rem;
|
168 |
+
display: inline-block;
|
169 |
+
font-weight: bold;
|
170 |
+
margin-bottom: 1rem;
|
171 |
+
}
|
172 |
+
|
173 |
+
/* Progress bar styling */
|
174 |
+
.stProgress > div > div > div {
|
175 |
+
background-color: #ff5722 !important;
|
176 |
+
}
|
177 |
</style>
|
178 |
""", unsafe_allow_html=True)
|
179 |
|
|
|
191 |
# Named entity recognition for identifying objects
|
192 |
ner_model = pipeline("ner", model="dbmdz/bert-large-cased-finetuned-conll03-english")
|
193 |
|
194 |
+
# Text-to-text generation for better explanations
|
195 |
+
explanation_generator = pipeline("text2text-generation", model="google/flan-t5-base")
|
196 |
+
|
197 |
+
return story_to_code, sentiment_analyzer, ner_model, explanation_generator
|
198 |
except Exception as e:
|
199 |
st.error(f"Error loading models: {e}")
|
200 |
+
return None, None, None, None
|
201 |
|
202 |
# Image generation functions
|
203 |
def create_storyboard_image(text, width=400, height=300):
|
|
|
229 |
|
230 |
return img
|
231 |
|
232 |
+
def generate_sprite_animation(story, character="spaceship", theme="space", num_frames=4):
|
233 |
"""Generate a sprite-based animation from story"""
|
234 |
frames = []
|
235 |
width, height = 300, 200
|
236 |
|
237 |
for i in range(num_frames):
|
238 |
+
# Create base image with theme
|
239 |
+
if theme == "space":
|
240 |
+
bg_color = (0, 0, 30)
|
241 |
+
star_color = (255, 255, 255)
|
242 |
+
elif theme == "jungle":
|
243 |
+
bg_color = (0, 100, 0)
|
244 |
+
star_color = None # No stars in jungle
|
245 |
+
elif theme == "medieval":
|
246 |
+
bg_color = (139, 69, 19)
|
247 |
+
star_color = None
|
248 |
+
else:
|
249 |
+
bg_color = (0, 0, 30)
|
250 |
+
star_color = (255, 255, 255)
|
251 |
+
|
252 |
+
img = Image.new('RGB', (width, height), color=bg_color)
|
253 |
draw = ImageDraw.Draw(img)
|
254 |
|
255 |
+
# Draw stars for space theme
|
256 |
+
if star_color:
|
257 |
+
for _ in range(30):
|
258 |
+
x = random.randint(0, width)
|
259 |
+
y = random.randint(0, height)
|
260 |
+
draw.ellipse([x, y, x+2, y+2], fill=star_color)
|
261 |
|
262 |
# Draw moving elements based on frame
|
263 |
+
if character == "spaceship":
|
264 |
ship_x = 50 + i * 60
|
265 |
ship_y = 80
|
266 |
draw.polygon([(ship_x, ship_y), (ship_x+30, ship_y),
|
|
|
272 |
laser_y = ship_y - 20 + j*5
|
273 |
draw.line([(laser_x, laser_y), (width, laser_y)], fill=(255, 0, 0), width=2)
|
274 |
|
275 |
+
elif character == "dragon":
|
276 |
+
dragon_x = 50 + i * 40
|
277 |
+
dragon_y = 100
|
278 |
+
# Draw dragon body
|
279 |
+
draw.ellipse([dragon_x, dragon_y, dragon_x+40, dragon_y+20], fill=(178, 34, 34))
|
280 |
+
# Draw dragon head
|
281 |
+
draw.ellipse([dragon_x+30, dragon_y-5, dragon_x+50, dragon_y+15], fill=(178, 34, 34))
|
282 |
+
# Draw wings
|
283 |
+
draw.ellipse([dragon_x+10, dragon_y-15, dragon_x+30, dragon_y], fill=(138, 43, 226))
|
284 |
+
|
285 |
+
if "fire" in story.lower() and i > 0:
|
286 |
+
for j in range(5):
|
287 |
+
flame_x = dragon_x + 50
|
288 |
+
flame_y = dragon_y + 5 - j*5
|
289 |
+
flame_size = random.randint(5, 15)
|
290 |
+
draw.ellipse([flame_x, flame_y, flame_x+flame_size, flame_y+flame_size],
|
291 |
+
fill=(255, random.randint(100, 200), 0))
|
292 |
+
|
293 |
+
elif character == "knight":
|
294 |
+
knight_x = 50 + i * 40
|
295 |
+
knight_y = 120
|
296 |
+
# Draw knight body
|
297 |
+
draw.rectangle([knight_x, knight_y, knight_x+20, knight_y+40], fill=(70, 70, 70))
|
298 |
+
# Draw knight head
|
299 |
+
draw.ellipse([knight_x+5, knight_y-15, knight_x+15, knight_y-5], fill=(210, 180, 140))
|
300 |
+
# Draw sword
|
301 |
+
draw.rectangle([knight_x+15, knight_y+10, knight_x+25, knight_y+15], fill=(192, 192, 192))
|
302 |
+
draw.polygon([(knight_x+25, knight_y+12), (knight_x+35, knight_y+10), (knight_x+35, knight_y+15)], fill=(192, 192, 192))
|
303 |
+
|
304 |
+
if "attack" in story.lower() and i % 2 == 1:
|
305 |
+
# Draw sword swing
|
306 |
+
draw.line([(knight_x+25, knight_y+12), (knight_x+45, knight_y-10)], fill=(255, 255, 0), width=2)
|
307 |
+
|
308 |
+
# Draw enemies based on theme
|
309 |
+
if theme == "space" and "alien" in story.lower():
|
310 |
alien_x = 200
|
311 |
alien_y = 100 - i*10
|
312 |
draw.ellipse([alien_x, alien_y, alien_x+20, alien_y+20], fill=(50, 205, 50))
|
313 |
draw.ellipse([alien_x+5, alien_y+5, alien_x+7, alien_y+7], fill=(0, 0, 0))
|
314 |
draw.ellipse([alien_x+13, alien_y+5, alien_x+15, alien_y+7], fill=(0, 0, 0))
|
315 |
|
316 |
+
elif theme == "jungle" and "snake" in story.lower():
|
317 |
+
snake_x = 200
|
318 |
+
snake_y = 150 - i*5
|
319 |
+
for segment in range(5):
|
320 |
+
offset = segment * 5
|
321 |
+
draw.ellipse([snake_x+offset, snake_y+offset, snake_x+offset+15, snake_y+offset+15], fill=(0, 128, 0))
|
322 |
+
|
323 |
+
elif theme == "medieval" and "dragon" in story.lower() and character != "dragon":
|
324 |
+
dragon_x = 220
|
325 |
+
dragon_y = 80
|
326 |
draw.ellipse([dragon_x, dragon_y, dragon_x+40, dragon_y+20], fill=(178, 34, 34))
|
327 |
draw.line([(dragon_x+40, dragon_y+10), (dragon_x+60, dragon_y)], fill=(178, 34, 34), width=3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
328 |
|
329 |
frames.append(img)
|
330 |
|
331 |
return frames
|
332 |
|
333 |
+
def generate_code_explanation(story, explanation_generator):
|
334 |
"""Generate code explanation using open-source model"""
|
335 |
try:
|
336 |
# Create a prompt for the model
|
337 |
+
prompt = f"Explain to a child how this story would become code: '{story}'"
|
338 |
|
339 |
# Generate text
|
340 |
+
result = explanation_generator(
|
341 |
prompt,
|
342 |
+
max_length=200,
|
343 |
num_return_sequences=1,
|
|
|
|
|
344 |
)
|
345 |
|
346 |
return result[0]['generated_text']
|
|
|
349 |
return f"""See how your story became real code? For example, when you wrote "{story.split()[0]}",
|
350 |
we used code like: character.move(). That's how we turn words into actions!"""
|
351 |
|
352 |
+
def text_to_speech(text, lang='en'):
|
353 |
+
"""Convert text to speech using gTTS"""
|
354 |
+
try:
|
355 |
+
tts = gTTS(text=text, lang=lang, slow=False)
|
356 |
+
with tempfile.NamedTemporaryFile(delete=True, suffix='.mp3') as fp:
|
357 |
+
tts.save(fp.name)
|
358 |
+
return fp.name
|
359 |
+
except Exception as e:
|
360 |
+
st.error(f"Text-to-speech error: {e}")
|
361 |
+
return None
|
362 |
+
|
363 |
+
# Achievement system
|
364 |
+
ACHIEVEMENTS = {
|
365 |
+
"first_story": {"name": "Storyteller", "icon": "๐", "description": "Created your first story!"},
|
366 |
+
"code_master": {"name": "Code Master", "icon": "๐ป", "description": "Used 5 different coding concepts"},
|
367 |
+
"animator": {"name": "Animator", "icon": "๐ฌ", "description": "Created 3 animations"},
|
368 |
+
"voice_artist": {"name": "Voice Artist", "icon": "๐ค", "description": "Used text-to-speech feature"},
|
369 |
+
"character_designer": {"name": "Character Designer", "icon": "๐พ", "description": "Created a custom character"}
|
370 |
+
}
|
371 |
+
|
372 |
+
def unlock_achievement(achievement_id):
|
373 |
+
"""Unlock an achievement for the user"""
|
374 |
+
if achievement_id not in st.session_state.achievements_unlocked:
|
375 |
+
st.session_state.achievements_unlocked.append(achievement_id)
|
376 |
+
st.session_state.new_achievement = achievement_id
|
377 |
+
return True
|
378 |
+
return False
|
379 |
+
|
380 |
# Header section
|
381 |
st.markdown('<div class="header">CodeTales โจ</div>', unsafe_allow_html=True)
|
382 |
st.markdown('<div class="subheader">Storytime + Coding Magic</div>', unsafe_allow_html=True)
|
|
|
396 |
|
397 |
**4. Robot Teacher Explains ๐ค**
|
398 |
Tavus shows: *"See? 'spaceship.move_right()' makes it fly! That's coding!"*
|
399 |
+
|
400 |
+
**5. Level Up! ๐**
|
401 |
+
Earn achievements as you learn and create!
|
402 |
""")
|
403 |
|
404 |
# Load models
|
405 |
+
story_to_code, sentiment_analyzer, ner_model, explanation_generator = load_models()
|
406 |
|
407 |
# Initialize session state
|
408 |
if 'animation_generated' not in st.session_state:
|
|
|
413 |
st.session_state.animation_frames = []
|
414 |
if 'code_explanation' not in st.session_state:
|
415 |
st.session_state.code_explanation = ""
|
416 |
+
if 'selected_character' not in st.session_state:
|
417 |
+
st.session_state.selected_character = "spaceship"
|
418 |
+
if 'selected_theme' not in st.session_state:
|
419 |
+
st.session_state.selected_theme = "space"
|
420 |
+
if 'sound_enabled' not in st.session_state:
|
421 |
+
st.session_state.sound_enabled = True
|
422 |
+
if 'achievements_unlocked' not in st.session_state:
|
423 |
+
st.session_state.achievements_unlocked = []
|
424 |
+
if 'new_achievement' not in st.session_state:
|
425 |
+
st.session_state.new_achievement = None
|
426 |
+
if 'story_count' not in st.session_state:
|
427 |
+
st.session_state.story_count = 0
|
428 |
+
if 'level' not in st.session_state:
|
429 |
+
st.session_state.level = 1
|
430 |
+
if 'xp' not in st.session_state:
|
431 |
+
st.session_state.xp = 0
|
432 |
+
if 'voice_enabled' not in st.session_state:
|
433 |
+
st.session_state.voice_enabled = False
|
434 |
+
if 'custom_character' not in st.session_state:
|
435 |
+
st.session_state.custom_character = None
|
436 |
+
|
437 |
+
# Story templates
|
438 |
+
story_templates = {
|
439 |
+
"None": "",
|
440 |
+
"Space Adventure": "A brave spaceship zooms through space, shooting lasers at alien spaceships!",
|
441 |
+
"Dragon Quest": "A knight fights a fire-breathing dragon to save the princess in the castle.",
|
442 |
+
"Jungle Explorer": "An explorer runs through the jungle, jumping over snakes and swinging on vines."
|
443 |
+
}
|
444 |
|
445 |
# Main content
|
446 |
+
st.markdown(f'<div class="level-indicator">Level {st.session_state.level} โจ XP: {st.session_state.xp}/100</div>', unsafe_allow_html=True)
|
447 |
+
progress = st.progress(st.session_state.xp / 100)
|
448 |
+
|
449 |
col1, col2 = st.columns([1, 1])
|
450 |
|
451 |
with col1:
|
452 |
st.markdown('<div class="story-box">', unsafe_allow_html=True)
|
453 |
st.markdown("### ๐ Write Your Story Here:")
|
454 |
+
|
455 |
+
# Story template selector
|
456 |
+
selected_template = st.selectbox(
|
457 |
+
"Or choose a story template to start with:",
|
458 |
+
list(story_templates.keys()),
|
459 |
+
index=0
|
460 |
+
)
|
461 |
+
|
462 |
story_text = st.text_area(
|
463 |
"Tell your adventure story...",
|
464 |
height=200,
|
465 |
placeholder="Once upon a time, a brave spaceship zoomed through space, shooting lasers at alien spaceships...",
|
466 |
label_visibility="collapsed",
|
467 |
+
value=story_templates[selected_template] if selected_template != "None" else st.session_state.story_text
|
468 |
+
)
|
469 |
+
|
470 |
+
# Character selection
|
471 |
+
st.markdown("### ๐ง Choose Your Hero")
|
472 |
+
char_cols = st.columns(3)
|
473 |
+
characters = {
|
474 |
+
"spaceship": "๐ Spaceship",
|
475 |
+
"dragon": "๐ Dragon",
|
476 |
+
"knight": "๐ก๏ธ Knight"
|
477 |
+
}
|
478 |
+
|
479 |
+
for i, (char_key, char_label) in enumerate(characters.items()):
|
480 |
+
with char_cols[i]:
|
481 |
+
if st.button(
|
482 |
+
char_label,
|
483 |
+
key=f"char_{char_key}",
|
484 |
+
use_container_width=True,
|
485 |
+
type="primary" if st.session_state.selected_character == char_key else "secondary"
|
486 |
+
):
|
487 |
+
st.session_state.selected_character = char_key
|
488 |
+
|
489 |
+
# Theme selection
|
490 |
+
st.markdown("### ๐จ Choose Your World")
|
491 |
+
theme_cols = st.columns(3)
|
492 |
+
themes = {
|
493 |
+
"space": "๐ Space",
|
494 |
+
"jungle": "๐ฟ Jungle",
|
495 |
+
"medieval": "๐ฐ Medieval"
|
496 |
+
}
|
497 |
+
|
498 |
+
for i, (theme_key, theme_label) in enumerate(themes.items()):
|
499 |
+
with theme_cols[i]:
|
500 |
+
if st.button(
|
501 |
+
theme_label,
|
502 |
+
key=f"theme_{theme_key}",
|
503 |
+
use_container_width=True,
|
504 |
+
type="primary" if st.session_state.selected_theme == theme_key else "secondary"
|
505 |
+
):
|
506 |
+
st.session_state.selected_theme = theme_key
|
507 |
+
|
508 |
+
# Voice options
|
509 |
+
st.session_state.voice_enabled = st.toggle(
|
510 |
+
"๐ฃ๏ธ Enable Tavus Voice",
|
511 |
+
value=st.session_state.voice_enabled,
|
512 |
+
help="Hear Tavus explain coding concepts with text-to-speech"
|
513 |
)
|
514 |
|
515 |
# Generate button with animation
|
|
|
520 |
|
521 |
with st.spinner("๐งโโ๏ธ Cooking your story in the magic oven..."):
|
522 |
# Generate animation frames
|
523 |
+
st.session_state.animation_frames = generate_sprite_animation(
|
524 |
+
story_text,
|
525 |
+
character=st.session_state.selected_character,
|
526 |
+
theme=st.session_state.selected_theme
|
527 |
+
)
|
528 |
|
529 |
# Generate code explanation
|
530 |
+
st.session_state.code_explanation = generate_code_explanation(story_text, explanation_generator)
|
531 |
|
532 |
+
# Update user progress
|
533 |
+
st.session_state.story_count += 1
|
534 |
+
st.session_state.xp += 15
|
535 |
+
|
536 |
+
# Check for level up
|
537 |
+
if st.session_state.xp >= 100:
|
538 |
+
st.session_state.level += 1
|
539 |
+
st.session_state.xp = st.session_state.xp - 100
|
540 |
+
st.session_state.new_level = True
|
541 |
+
|
542 |
+
# Unlock achievements
|
543 |
+
if st.session_state.story_count == 1:
|
544 |
+
unlock_achievement("first_story")
|
545 |
+
|
546 |
+
# Generate speech if enabled
|
547 |
+
if st.session_state.voice_enabled:
|
548 |
+
speech_file = text_to_speech(st.session_state.code_explanation)
|
549 |
+
if speech_file:
|
550 |
+
st.session_state.speech_file = speech_file
|
551 |
+
unlock_achievement("voice_artist")
|
552 |
|
553 |
+
# Simulate sound effect
|
554 |
+
if st.session_state.sound_enabled:
|
555 |
+
st.session_state.sound_message = "๐ Sound effects added: Laser blasts, explosions, and more!"
|
|
|
|
|
|
|
|
|
|
|
556 |
else:
|
557 |
+
st.session_state.sound_message = ""
|
558 |
else:
|
559 |
st.warning("Please enter a story first!")
|
560 |
st.markdown('</div>', unsafe_allow_html=True)
|
|
|
590 |
use_container_width=True
|
591 |
)
|
592 |
|
593 |
+
# Display sound message
|
594 |
+
if st.session_state.sound_enabled and 'sound_message' in st.session_state:
|
595 |
+
st.info(st.session_state.sound_message)
|
596 |
+
|
597 |
+
# Display character and theme info
|
598 |
+
st.success(f"โจ Your {characters[st.session_state.selected_character]} in the {themes[st.session_state.selected_theme]} world!")
|
599 |
+
|
600 |
+
# Play voice explanation
|
601 |
+
if st.session_state.voice_enabled and 'speech_file' in st.session_state:
|
602 |
+
if st.button("๐ Play Tavus Explanation", use_container_width=True):
|
603 |
+
try:
|
604 |
+
mixer.music.load(st.session_state.speech_file)
|
605 |
+
mixer.music.play()
|
606 |
+
except:
|
607 |
+
st.warning("Couldn't play audio. Try enabling sound in your browser.")
|
608 |
|
609 |
elif st.session_state.animation_generated:
|
610 |
st.warning("Couldn't generate animation. Try a different story!")
|
|
|
707 |
|
708 |
st.markdown("</div>", unsafe_allow_html=True)
|
709 |
|
710 |
+
# Achievements section
|
711 |
+
if st.session_state.achievements_unlocked:
|
712 |
+
st.markdown("### ๐ Your Achievements")
|
713 |
+
achievement_cols = st.columns(5)
|
714 |
+
for i, ach_id in enumerate(st.session_state.achievements_unlocked):
|
715 |
+
with achievement_cols[i % 5]:
|
716 |
+
ach = ACHIEVEMENTS[ach_id]
|
717 |
+
st.markdown(f'<div class="achievement-badge">{ach["icon"]}</div>', unsafe_allow_html=True)
|
718 |
+
st.markdown(f"**{ach['name']}**")
|
719 |
+
st.caption(ach['description'])
|
720 |
+
|
721 |
+
# New achievement notification
|
722 |
+
if 'new_achievement' in st.session_state and st.session_state.new_achievement:
|
723 |
+
ach = ACHIEVEMENTS[st.session_state.new_achievement]
|
724 |
+
st.success(f"๐ Achievement Unlocked: {ach['name']} - {ach['description']}")
|
725 |
+
st.session_state.new_achievement = None
|
726 |
+
|
727 |
+
# New level notification
|
728 |
+
if 'new_level' in st.session_state and st.session_state.new_level:
|
729 |
+
st.balloons()
|
730 |
+
st.success(f"๐ Level Up! You're now Level {st.session_state.level}!")
|
731 |
+
st.session_state.new_level = False
|
732 |
+
|
733 |
+
# Coding challenges section
|
734 |
+
st.markdown("### ๐ป Coding Challenges")
|
735 |
+
with st.expander("Complete challenges to earn XP!"):
|
736 |
+
challenge_cols = st.columns(3)
|
737 |
+
|
738 |
+
with challenge_cols[0]:
|
739 |
+
st.markdown("#### Challenge 1: The Mover")
|
740 |
+
st.code("""
|
741 |
+
# Make the character move to the right
|
742 |
+
character.move_right(10)
|
743 |
+
""")
|
744 |
+
if st.button("Run Challenge 1", key="challenge1"):
|
745 |
+
st.session_state.xp += 5
|
746 |
+
st.success("+5 XP! Character moved right!")
|
747 |
+
|
748 |
+
with challenge_cols[1]:
|
749 |
+
st.markdown("#### Challenge 2: The Jumper")
|
750 |
+
st.code("""
|
751 |
+
# Make the character jump
|
752 |
+
character.jump(20)
|
753 |
+
""")
|
754 |
+
if st.button("Run Challenge 2", key="challenge2"):
|
755 |
+
st.session_state.xp += 5
|
756 |
+
st.success("+5 XP! Character jumped!")
|
757 |
+
|
758 |
+
with challenge_cols[2]:
|
759 |
+
st.markdown("#### Challenge 3: The Shooter")
|
760 |
+
st.code("""
|
761 |
+
# Make the character shoot a laser
|
762 |
+
laser = character.shoot()
|
763 |
+
""")
|
764 |
+
if st.button("Run Challenge 3", key="challenge3"):
|
765 |
+
st.session_state.xp += 5
|
766 |
+
st.success("+5 XP! Laser fired!")
|
767 |
+
|
768 |
# Benefits section
|
769 |
st.markdown("""
|
770 |
## โค Why Everyone Will Love CodeTales
|
|
|
776 |
| ๐ฎ Creates personal video games | โ Reinforces math fundamentals |
|
777 |
| ๐ Makes learning fun and exciting | ๐งฉ Encourages problem-solving abilities |
|
778 |
| ๐ 100% private with no API keys | ๐ฐ Completely free and open-source |
|
779 |
+
| ๐ Achievement system motivates learning | ๐ Progress tracking shows growth |
|
780 |
+
| ๐ฃ๏ธ Voice explanations for accessibility | ๐จ Creative expression through stories |
|
781 |
""")
|
782 |
|
783 |
# Footer
|