Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -38,7 +38,7 @@ def should_generate_visual(user_prompt, ai_response):
|
|
| 38 |
"""Determine if a visual aid would be helpful based on the content"""
|
| 39 |
# Expanded keywords to trigger new dynamic visuals
|
| 40 |
k12_visual_keywords = [
|
| 41 |
-
'add', 'subtract', 'multiply', 'times', 'divide', 'counting', 'numbers',
|
| 42 |
'fraction', 'half', 'quarter', 'third', 'parts', 'whole',
|
| 43 |
'shape', 'triangle', 'circle', 'square', 'rectangle',
|
| 44 |
'money', 'coins', 'dollars', 'cents', 'change',
|
|
@@ -47,20 +47,40 @@ def should_generate_visual(user_prompt, ai_response):
|
|
| 47 |
'place value', 'tens', 'ones', 'hundreds',
|
| 48 |
'pattern', 'sequence', 'skip counting',
|
| 49 |
'greater than', 'less than', 'equal', 'compare',
|
| 50 |
-
'number line', 'array', 'grid'
|
| 51 |
]
|
| 52 |
|
| 53 |
combined_text = (user_prompt + " " + ai_response).lower()
|
| 54 |
-
|
|
|
|
|
|
|
| 55 |
|
| 56 |
def create_visual_manipulative(user_prompt, ai_response):
|
| 57 |
"""-- SMART VISUAL ROUTER --
|
| 58 |
Parses the user prompt and calls the appropriate dynamic visual function."""
|
| 59 |
try:
|
| 60 |
-
user_lower = user_prompt.lower()
|
| 61 |
|
| 62 |
-
# Priority 1:
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
if time_match:
|
| 65 |
groups = time_match.groups()
|
| 66 |
hour = int(groups[0])
|
|
@@ -68,22 +88,15 @@ def create_visual_manipulative(user_prompt, ai_response):
|
|
| 68 |
if 1 <= hour <= 12 and 0 <= minute <= 59:
|
| 69 |
return create_clock_visual(hour, minute)
|
| 70 |
|
| 71 |
-
# Priority
|
| 72 |
fraction_match = re.search(r'(\d+)/(\d+)', user_lower)
|
| 73 |
if fraction_match:
|
| 74 |
num, den = int(fraction_match.group(1)), int(fraction_match.group(2))
|
| 75 |
-
if 0 < num <= den and den <= 16:
|
| 76 |
return create_dynamic_fraction_circle(num, den)
|
| 77 |
|
| 78 |
-
# Priority
|
| 79 |
-
|
| 80 |
-
if mult_match:
|
| 81 |
-
rows, cols = int(mult_match.group(1)), int(mult_match.group(2))
|
| 82 |
-
if rows <= 10 and cols <= 10: # Keep arrays reasonable
|
| 83 |
-
return create_multiplication_array(rows, cols)
|
| 84 |
-
|
| 85 |
-
# Priority 4: Addition/Subtraction Blocks
|
| 86 |
-
if any(word in user_lower for word in ['add', 'plus', '+', 'subtract', 'minus', 'take away', '-']):
|
| 87 |
numbers = re.findall(r'\d+', user_prompt)
|
| 88 |
if len(numbers) >= 2:
|
| 89 |
num1, num2 = int(numbers[0]), int(numbers[1])
|
|
@@ -91,16 +104,16 @@ def create_visual_manipulative(user_prompt, ai_response):
|
|
| 91 |
if num1 <= 20 and num2 <= 20:
|
| 92 |
return create_counting_blocks(num1, num2, operation)
|
| 93 |
|
| 94 |
-
# Priority
|
| 95 |
-
if '
|
| 96 |
numbers = [int(n) for n in re.findall(r'\d+', user_prompt)]
|
| 97 |
if numbers:
|
| 98 |
start = min(numbers) - 2
|
| 99 |
end = max(numbers) + 2
|
| 100 |
return create_number_line(start, end, numbers, "Your Numbers on the Line")
|
| 101 |
|
| 102 |
-
# Priority
|
| 103 |
-
if '
|
| 104 |
numbers = re.findall(r'\d+', user_prompt)
|
| 105 |
if numbers:
|
| 106 |
num = int(numbers[0])
|
|
@@ -108,10 +121,10 @@ def create_visual_manipulative(user_prompt, ai_response):
|
|
| 108 |
return create_place_value_blocks(num)
|
| 109 |
|
| 110 |
# Fallback to static, general visuals
|
| 111 |
-
if any(word in user_lower for word in ['fraction', 'part']): return create_dynamic_fraction_circle(1, 2)
|
| 112 |
if any(word in user_lower for word in ['shape']): return create_shape_explorer()
|
| 113 |
if any(word in user_lower for word in ['money', 'coin']): return create_money_counter()
|
| 114 |
-
if any(word in user_lower for word in ['time', 'clock']): return create_clock_visual(10, 10)
|
| 115 |
|
| 116 |
return None # No relevant visual found
|
| 117 |
|
|
@@ -121,6 +134,74 @@ def create_visual_manipulative(user_prompt, ai_response):
|
|
| 121 |
|
| 122 |
# --- VISUAL TOOLBOX FUNCTIONS ---
|
| 123 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
def create_counting_blocks(num1, num2, operation):
|
| 125 |
"""(Dynamic) Create colorful counting blocks for addition/subtraction."""
|
| 126 |
html = f"""
|
|
@@ -166,7 +247,7 @@ def create_clock_visual(hours, minutes):
|
|
| 166 |
return html
|
| 167 |
|
| 168 |
def create_multiplication_array(rows, cols):
|
| 169 |
-
"""(
|
| 170 |
cell_size, gap = 25, 5
|
| 171 |
svg_width = cols * (cell_size + gap)
|
| 172 |
svg_height = rows * (cell_size + gap)
|
|
@@ -175,12 +256,10 @@ def create_multiplication_array(rows, cols):
|
|
| 175 |
return html
|
| 176 |
|
| 177 |
def create_number_line(start, end, points, title="Number Line"):
|
| 178 |
-
"""(
|
| 179 |
width = 600
|
| 180 |
padding = 30
|
| 181 |
-
|
| 182 |
-
if start >= end:
|
| 183 |
-
end = start + 1
|
| 184 |
scale = (width - 2 * padding) / (end - start)
|
| 185 |
def to_x(n): return padding + (n - start) * scale
|
| 186 |
ticks_html = "".join([f'<g transform="translate({to_x(i)}, 50)"><line y2="10" stroke="#aaa"/><text y="30" text-anchor="middle" fill="#555">{i}</text></g>' for i in range(start, end + 1)])
|
|
@@ -191,66 +270,19 @@ def create_number_line(start, end, points, title="Number Line"):
|
|
| 191 |
def create_place_value_blocks(number):
|
| 192 |
"""(FIXED & Dynamic) Create place value blocks for understanding numbers."""
|
| 193 |
hundreds, tens, ones = number // 100, (number % 100) // 10, number % 10
|
| 194 |
-
|
| 195 |
-
# --- Hundreds Block HTML ---
|
| 196 |
-
h_block_html = ""
|
| 197 |
if hundreds > 0:
|
| 198 |
hundreds_grid = "".join(["<div style='background:#F5A6A6'></div>"] * 100)
|
| 199 |
-
hundreds_squares = "".join([f""
|
| 200 |
-
|
| 201 |
-
{hundreds_grid}
|
| 202 |
-
</div>
|
| 203 |
-
""" for _ in range(hundreds)])
|
| 204 |
-
h_block_html = f"""
|
| 205 |
-
<div style="text-align: center;">
|
| 206 |
-
<h4>Hundreds: {hundreds}</h4>
|
| 207 |
-
<div style="display: flex; gap: 5px;">{hundreds_squares}</div>
|
| 208 |
-
</div>
|
| 209 |
-
"""
|
| 210 |
-
|
| 211 |
-
# --- Tens Block HTML ---
|
| 212 |
-
t_block_html = ""
|
| 213 |
if tens > 0:
|
| 214 |
tens_grid = "".join(["<div style='background:#A2E8E4'></div>"] * 10)
|
| 215 |
-
tens_sticks = "".join([f""
|
| 216 |
-
|
| 217 |
-
{tens_grid}
|
| 218 |
-
</div>
|
| 219 |
-
""" for _ in range(tens)])
|
| 220 |
-
t_block_html = f"""
|
| 221 |
-
<div style="text-align: center;">
|
| 222 |
-
<h4>Tens: {tens}</h4>
|
| 223 |
-
<div style="display: flex; gap: 5px; align-items: flex-end;">{tens_sticks}</div>
|
| 224 |
-
</div>
|
| 225 |
-
"""
|
| 226 |
-
|
| 227 |
-
# --- Ones Block HTML ---
|
| 228 |
-
o_block_html = ""
|
| 229 |
if ones > 0:
|
| 230 |
ones_cubes = "".join(['<div style="width: 10px; height: 10px; background: #FFE066; border: 2px solid #FDCB6E;"></div>' for _ in range(ones)])
|
| 231 |
-
o_block_html = f"""
|
| 232 |
-
|
| 233 |
-
<h4>Ones: {ones}</h4>
|
| 234 |
-
<div style="display: flex; gap: 5px; align-items: flex-end; flex-wrap: wrap; width: 50px; justify-content: center;">{ones_cubes}</div>
|
| 235 |
-
</div>
|
| 236 |
-
"""
|
| 237 |
-
|
| 238 |
-
# --- Final Assembly ---
|
| 239 |
-
html = f"""
|
| 240 |
-
<div style="padding: 20px; background: linear-gradient(135deg, #dfe6e9 0%, #b2bec3 100%); border-radius: 15px; margin: 10px 0;">
|
| 241 |
-
<h3 style="color: #333; text-align: center;">Place Value Blocks for {number}</h3>
|
| 242 |
-
<div style="display: flex; justify-content: center; align-items: flex-end; gap: 20px; flex-wrap: wrap; padding: 20px 0; min-height: 150px;">
|
| 243 |
-
{h_block_html}
|
| 244 |
-
{t_block_html}
|
| 245 |
-
{o_block_html}
|
| 246 |
-
</div>
|
| 247 |
-
<div style="text-align: center; margin-top: 15px; padding: 10px; background: rgba(0,0,0,0.1); border-radius: 10px;">
|
| 248 |
-
<h4 style="color: #333; margin:0;">
|
| 249 |
-
{hundreds} Hundreds + {tens} Tens + {ones} Ones = {number}
|
| 250 |
-
</h4>
|
| 251 |
-
</div>
|
| 252 |
-
</div>
|
| 253 |
-
"""
|
| 254 |
return html
|
| 255 |
|
| 256 |
def create_shape_explorer():
|
|
@@ -289,18 +321,18 @@ if api_key:
|
|
| 289 |
IMPORTANT: When explaining mathematical concepts to young learners, mention that colorful visual aids will be provided to help illustrate the concept. Use phrases like:
|
| 290 |
- "Let me show you this with some colorful blocks..."
|
| 291 |
- "A fun visual will help you see how this works..."
|
| 292 |
-
- "
|
|
|
|
| 293 |
|
| 294 |
Focus on concepts appropriate for K-12 students:
|
| 295 |
- Basic counting and number recognition
|
| 296 |
- Simple addition and subtraction (using manipulatives)
|
| 297 |
- Multiplication as arrays or groups
|
|
|
|
| 298 |
- Basic shapes and geometry
|
| 299 |
- Place value with hundreds, tens, ones
|
| 300 |
- Money counting and coin recognition
|
| 301 |
- Time telling with analog clocks
|
| 302 |
-
- Simple patterns and sequences
|
| 303 |
-
- Basic measurement concepts
|
| 304 |
|
| 305 |
Always use age-appropriate language and relate math to real-world examples children understand.
|
| 306 |
|
|
@@ -446,7 +478,6 @@ with st.sidebar:
|
|
| 446 |
if st.button("🔗 Share Chat", use_container_width=True):
|
| 447 |
chat_json = json.dumps(st.session_state.chats[st.session_state.active_chat_key])
|
| 448 |
chat_b64 = base64.urlsafe_b64encode(chat_json.encode()).decode()
|
| 449 |
-
# This part might need adjustment depending on how Streamlit Community Cloud handles base URLs
|
| 450 |
share_url = f"https://huggingface.co/spaces/YOUR_SPACE_HERE?shared_chat={chat_b64}" # Placeholder
|
| 451 |
st.code(share_url)
|
| 452 |
st.info("Copy the URL above to share this specific chat! (You might need to update the base URL)")
|
|
@@ -460,7 +491,7 @@ for message in st.session_state.chats[st.session_state.active_chat_key]:
|
|
| 460 |
st.markdown(message["content"])
|
| 461 |
# If a visual was generated and saved with the message, display it
|
| 462 |
if "visual_html" in message and message["visual_html"]:
|
| 463 |
-
components.html(message["visual_html"], height=
|
| 464 |
|
| 465 |
# User input
|
| 466 |
if prompt := st.chat_input("Ask a K-8 math question..."):
|
|
@@ -494,7 +525,7 @@ if prompt := st.chat_input("Ask a K-8 math question..."):
|
|
| 494 |
if should_generate_visual(prompt, full_response):
|
| 495 |
visual_html_content = create_visual_manipulative(prompt, full_response)
|
| 496 |
if visual_html_content:
|
| 497 |
-
components.html(visual_html_content, height=
|
| 498 |
|
| 499 |
# Add AI response and visual to session state
|
| 500 |
st.session_state.chats[st.session_state.active_chat_key].append({
|
|
|
|
| 38 |
"""Determine if a visual aid would be helpful based on the content"""
|
| 39 |
# Expanded keywords to trigger new dynamic visuals
|
| 40 |
k12_visual_keywords = [
|
| 41 |
+
'add', 'subtract', 'multiply', 'times', 'divide', 'divided by', 'counting', 'numbers',
|
| 42 |
'fraction', 'half', 'quarter', 'third', 'parts', 'whole',
|
| 43 |
'shape', 'triangle', 'circle', 'square', 'rectangle',
|
| 44 |
'money', 'coins', 'dollars', 'cents', 'change',
|
|
|
|
| 47 |
'place value', 'tens', 'ones', 'hundreds',
|
| 48 |
'pattern', 'sequence', 'skip counting',
|
| 49 |
'greater than', 'less than', 'equal', 'compare',
|
| 50 |
+
'number line', 'array', 'grid', 'area model'
|
| 51 |
]
|
| 52 |
|
| 53 |
combined_text = (user_prompt + " " + ai_response).lower()
|
| 54 |
+
# Also check for symbols
|
| 55 |
+
return any(keyword in combined_text for keyword in k12_visual_keywords) or any(op in user_prompt for op in ['*', '/'])
|
| 56 |
+
|
| 57 |
|
| 58 |
def create_visual_manipulative(user_prompt, ai_response):
|
| 59 |
"""-- SMART VISUAL ROUTER --
|
| 60 |
Parses the user prompt and calls the appropriate dynamic visual function."""
|
| 61 |
try:
|
| 62 |
+
user_lower = user_prompt.lower().replace(' ', '') # Remove spaces for easier parsing
|
| 63 |
|
| 64 |
+
# Priority 1: Division (e.g., "42 divided by 6", "21 / 3")
|
| 65 |
+
div_match = re.search(r'(\d+)dividedby(\d+)', user_lower) or re.search(r'(\d+)/(\d+)', user_lower)
|
| 66 |
+
if div_match and "fraction" not in user_lower: # Avoid confusion with fractions
|
| 67 |
+
dividend, divisor = int(div_match.group(1)), int(div_match.group(2))
|
| 68 |
+
if dividend <= 50 and divisor > 0: # Keep visuals manageable
|
| 69 |
+
return create_division_groups_visual(dividend, divisor)
|
| 70 |
+
|
| 71 |
+
# Priority 2: Multiplication (e.g., "3 times 5", "15 * 19")
|
| 72 |
+
mult_match = re.search(r'(\d+)(?:x|times|\*)(\d+)', user_lower)
|
| 73 |
+
if mult_match:
|
| 74 |
+
num1, num2 = int(mult_match.group(1)), int(mult_match.group(2))
|
| 75 |
+
# Use dot array for small numbers
|
| 76 |
+
if num1 <= 10 and num2 <= 10:
|
| 77 |
+
return create_multiplication_array(num1, num2)
|
| 78 |
+
# Use area model for larger numbers (up to 99x99)
|
| 79 |
+
elif 10 < num1 < 100 and 10 < num2 < 100:
|
| 80 |
+
return create_multiplication_area_model(num1, num2)
|
| 81 |
+
|
| 82 |
+
# Priority 3: Time / Clock (e.g., "7:30", "4 o'clock")
|
| 83 |
+
time_match = re.search(r'(\d{1,2}):(\d{2})', user_lower) or re.search(r'(\d{1,2})o\'clock', user_lower)
|
| 84 |
if time_match:
|
| 85 |
groups = time_match.groups()
|
| 86 |
hour = int(groups[0])
|
|
|
|
| 88 |
if 1 <= hour <= 12 and 0 <= minute <= 59:
|
| 89 |
return create_clock_visual(hour, minute)
|
| 90 |
|
| 91 |
+
# Priority 4: Fractions (e.g., "2/5", "fraction 3/8")
|
| 92 |
fraction_match = re.search(r'(\d+)/(\d+)', user_lower)
|
| 93 |
if fraction_match:
|
| 94 |
num, den = int(fraction_match.group(1)), int(fraction_match.group(2))
|
| 95 |
+
if 0 < num <= den and den <= 16:
|
| 96 |
return create_dynamic_fraction_circle(num, den)
|
| 97 |
|
| 98 |
+
# Priority 5: Addition/Subtraction Blocks
|
| 99 |
+
if any(word in user_lower for word in ['add', 'plus', '+', 'subtract', 'minus', 'takeaway', '-']):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
numbers = re.findall(r'\d+', user_prompt)
|
| 101 |
if len(numbers) >= 2:
|
| 102 |
num1, num2 = int(numbers[0]), int(numbers[1])
|
|
|
|
| 104 |
if num1 <= 20 and num2 <= 20:
|
| 105 |
return create_counting_blocks(num1, num2, operation)
|
| 106 |
|
| 107 |
+
# Priority 6: Number Lines
|
| 108 |
+
if 'numberline' in user_lower:
|
| 109 |
numbers = [int(n) for n in re.findall(r'\d+', user_prompt)]
|
| 110 |
if numbers:
|
| 111 |
start = min(numbers) - 2
|
| 112 |
end = max(numbers) + 2
|
| 113 |
return create_number_line(start, end, numbers, "Your Numbers on the Line")
|
| 114 |
|
| 115 |
+
# Priority 7: Place Value
|
| 116 |
+
if 'placevalue' in user_lower:
|
| 117 |
numbers = re.findall(r'\d+', user_prompt)
|
| 118 |
if numbers:
|
| 119 |
num = int(numbers[0])
|
|
|
|
| 121 |
return create_place_value_blocks(num)
|
| 122 |
|
| 123 |
# Fallback to static, general visuals
|
| 124 |
+
if any(word in user_lower for word in ['fraction', 'part']): return create_dynamic_fraction_circle(1, 2)
|
| 125 |
if any(word in user_lower for word in ['shape']): return create_shape_explorer()
|
| 126 |
if any(word in user_lower for word in ['money', 'coin']): return create_money_counter()
|
| 127 |
+
if any(word in user_lower for word in ['time', 'clock']): return create_clock_visual(10, 10)
|
| 128 |
|
| 129 |
return None # No relevant visual found
|
| 130 |
|
|
|
|
| 134 |
|
| 135 |
# --- VISUAL TOOLBOX FUNCTIONS ---
|
| 136 |
|
| 137 |
+
def create_multiplication_area_model(num1, num2):
|
| 138 |
+
"""(NEW & Dynamic) Creates an area model for 2-digit by 2-digit multiplication."""
|
| 139 |
+
n1_tens, n1_ones = num1 // 10, num1 % 10
|
| 140 |
+
n2_tens, n2_ones = num2 // 10, num2 % 10
|
| 141 |
+
|
| 142 |
+
p1 = n1_tens * n2_tens * 100 # E.g., 10 * 10
|
| 143 |
+
p2 = n1_tens * n2_ones * 10 # E.g., 10 * 9
|
| 144 |
+
p3 = n1_ones * n2_tens * 10 # E.g., 5 * 10
|
| 145 |
+
p4 = n1_ones * n2_ones # E.g., 5 * 9
|
| 146 |
+
|
| 147 |
+
total = p1 + p2 + p3 + p4
|
| 148 |
+
|
| 149 |
+
html = f"""
|
| 150 |
+
<div style="font-family: sans-serif; padding: 20px; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); border-radius: 15px; margin: 10px 0;">
|
| 151 |
+
<h3 style="text-align: center; color: #333;">Area Model for {num1} × {num2}</h3>
|
| 152 |
+
<div style="display: grid; grid-template-columns: auto 1fr 1fr; grid-template-rows: auto 1fr 1fr; gap: 5px; max-width: 400px; margin: 20px auto; color: #333;">
|
| 153 |
+
<!-- Corner --> <div/>
|
| 154 |
+
<div style="text-align: center; font-weight: bold; padding: 5px;">{n2_tens*10}</div>
|
| 155 |
+
<div style="text-align: center; font-weight: bold; padding: 5px;">{n2_ones}</div>
|
| 156 |
+
|
| 157 |
+
<div style="text-align: center; font-weight: bold; padding: 5px;">{n1_tens*10}</div>
|
| 158 |
+
<div style="background: #FFADAD; padding: 20px; text-align: center; border-radius: 8px;"><b>{n1_tens*10}×{n2_tens*10}</b><br/>{p1}</div>
|
| 159 |
+
<div style="background: #FFD6A5; padding: 20px; text-align: center; border-radius: 8px;"><b>{n1_tens*10}×{n2_ones}</b><br/>{p2}</div>
|
| 160 |
+
|
| 161 |
+
<div style="text-align: center; font-weight: bold; padding: 5px;">{n1_ones}</div>
|
| 162 |
+
<div style="background: #FDFFB6; padding: 20px; text-align: center; border-radius: 8px;"><b>{n1_ones}×{n2_tens*10}</b><br/>{p3}</div>
|
| 163 |
+
<div style="background: #CAFFBF; padding: 20px; text-align: center; border-radius: 8px;"><b>{n1_ones}×{n2_ones}</b><br/>{p4}</div>
|
| 164 |
+
</div>
|
| 165 |
+
<div style="text-align: center; margin-top: 15px; font-size: 1.1em;">
|
| 166 |
+
<b>Add the parts together:</b> {p1} + {p2} + {p3} + {p4} = <b>{total}</b>
|
| 167 |
+
</div>
|
| 168 |
+
</div>
|
| 169 |
+
"""
|
| 170 |
+
return html
|
| 171 |
+
|
| 172 |
+
def create_division_groups_visual(dividend, divisor):
|
| 173 |
+
"""(NEW & Dynamic) Creates a visual for division by grouping."""
|
| 174 |
+
if divisor == 0: return ""
|
| 175 |
+
quotient = dividend // divisor
|
| 176 |
+
|
| 177 |
+
groups_html = ""
|
| 178 |
+
dot_colors = ["#FF6B6B", "#4ECDC4", "#FFD93D", "#95E1D3", "#A0C4FF", "#FDBF6F"]
|
| 179 |
+
|
| 180 |
+
for i in range(divisor):
|
| 181 |
+
dots_in_group = "".join([f'<div style="width: 15px; height: 15px; background: {dot_colors[i % len(dot_colors)]}; border-radius: 50%;"></div>' for _ in range(quotient)])
|
| 182 |
+
groups_html += f"""
|
| 183 |
+
<div style="border: 2px dashed {dot_colors[i % len(dot_colors)]}; border-radius: 10px; padding: 10px; text-align: center;">
|
| 184 |
+
<b style="color: #333;">Group {i+1}</b>
|
| 185 |
+
<div style="display: flex; flex-wrap: wrap; gap: 5px; margin-top: 10px; justify-content: center;">
|
| 186 |
+
{dots_in_group}
|
| 187 |
+
</div>
|
| 188 |
+
</div>
|
| 189 |
+
"""
|
| 190 |
+
|
| 191 |
+
html = f"""
|
| 192 |
+
<div style="padding: 20px; background: #f0f2f6; border-radius: 15px; margin: 10px 0;">
|
| 193 |
+
<h3 style="text-align: center; color: #333;">Dividing {dividend} into {divisor} Groups</h3>
|
| 194 |
+
<p style="text-align: center; color: #555;">We are sharing {dividend} items equally among {divisor} groups.</p>
|
| 195 |
+
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 15px; margin-top: 20px;">
|
| 196 |
+
{groups_html}
|
| 197 |
+
</div>
|
| 198 |
+
<h4 style="text-align: center; margin-top: 25px; color: #333;">
|
| 199 |
+
Each group gets <b>{quotient}</b> items. So, {dividend} ÷ {divisor} = {quotient}.
|
| 200 |
+
</h4>
|
| 201 |
+
</div>
|
| 202 |
+
"""
|
| 203 |
+
return html
|
| 204 |
+
|
| 205 |
def create_counting_blocks(num1, num2, operation):
|
| 206 |
"""(Dynamic) Create colorful counting blocks for addition/subtraction."""
|
| 207 |
html = f"""
|
|
|
|
| 247 |
return html
|
| 248 |
|
| 249 |
def create_multiplication_array(rows, cols):
|
| 250 |
+
"""(Dynamic) Generates an SVG grid of dots to show small multiplication."""
|
| 251 |
cell_size, gap = 25, 5
|
| 252 |
svg_width = cols * (cell_size + gap)
|
| 253 |
svg_height = rows * (cell_size + gap)
|
|
|
|
| 256 |
return html
|
| 257 |
|
| 258 |
def create_number_line(start, end, points, title="Number Line"):
|
| 259 |
+
"""(Dynamic) Creates a simple number line SVG."""
|
| 260 |
width = 600
|
| 261 |
padding = 30
|
| 262 |
+
if start >= end: end = start + 1
|
|
|
|
|
|
|
| 263 |
scale = (width - 2 * padding) / (end - start)
|
| 264 |
def to_x(n): return padding + (n - start) * scale
|
| 265 |
ticks_html = "".join([f'<g transform="translate({to_x(i)}, 50)"><line y2="10" stroke="#aaa"/><text y="30" text-anchor="middle" fill="#555">{i}</text></g>' for i in range(start, end + 1)])
|
|
|
|
| 270 |
def create_place_value_blocks(number):
|
| 271 |
"""(FIXED & Dynamic) Create place value blocks for understanding numbers."""
|
| 272 |
hundreds, tens, ones = number // 100, (number % 100) // 10, number % 10
|
| 273 |
+
h_block_html, t_block_html, o_block_html = "", "", ""
|
|
|
|
|
|
|
| 274 |
if hundreds > 0:
|
| 275 |
hundreds_grid = "".join(["<div style='background:#F5A6A6'></div>"] * 100)
|
| 276 |
+
hundreds_squares = "".join([f'<div style="width: 100px; height: 100px; background: #FF6B6B; border: 2px solid #D63031; display: grid; grid-template-columns: repeat(10, 1fr); gap: 2px; padding: 2px;">{hundreds_grid}</div>' for _ in range(hundreds)])
|
| 277 |
+
h_block_html = f'<div style="text-align: center;"><h4>Hundreds: {hundreds}</h4><div style="display: flex; gap: 5px;">{hundreds_squares}</div></div>'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
if tens > 0:
|
| 279 |
tens_grid = "".join(["<div style='background:#A2E8E4'></div>"] * 10)
|
| 280 |
+
tens_sticks = "".join([f'<div style="width: 10px; height: 100px; background: #4ECDC4; border: 2px solid #00B894; display: grid; grid-template-rows: repeat(10, 1fr); gap: 2px; padding: 2px;">{tens_grid}</div>' for _ in range(tens)])
|
| 281 |
+
t_block_html = f'<div style="text-align: center;"><h4>Tens: {tens}</h4><div style="display: flex; gap: 5px; align-items: flex-end;">{tens_sticks}</div></div>'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
if ones > 0:
|
| 283 |
ones_cubes = "".join(['<div style="width: 10px; height: 10px; background: #FFE066; border: 2px solid #FDCB6E;"></div>' for _ in range(ones)])
|
| 284 |
+
o_block_html = f'<div style="text-align: center;"><h4>Ones: {ones}</h4><div style="display: flex; gap: 5px; align-items: flex-end; flex-wrap: wrap; width: 50px; justify-content: center;">{ones_cubes}</div></div>'
|
| 285 |
+
html = f"""<div style="padding: 20px; background: linear-gradient(135deg, #dfe6e9 0%, #b2bec3 100%); border-radius: 15px; margin: 10px 0;"><h3 style="color: #333; text-align: center;">Place Value Blocks for {number}</h3><div style="display: flex; justify-content: center; align-items: flex-end; gap: 20px; flex-wrap: wrap; padding: 20px 0; min-height: 150px;">{h_block_html}{t_block_html}{o_block_html}</div><div style="text-align: center; margin-top: 15px; padding: 10px; background: rgba(0,0,0,0.1); border-radius: 10px;"><h4 style="color: #333; margin:0;">{hundreds} Hundreds + {tens} Tens + {ones} Ones = {number}</h4></div></div>"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
return html
|
| 287 |
|
| 288 |
def create_shape_explorer():
|
|
|
|
| 321 |
IMPORTANT: When explaining mathematical concepts to young learners, mention that colorful visual aids will be provided to help illustrate the concept. Use phrases like:
|
| 322 |
- "Let me show you this with some colorful blocks..."
|
| 323 |
- "A fun visual will help you see how this works..."
|
| 324 |
+
- "Let's use an area model to understand this multiplication problem..."
|
| 325 |
+
- "I'll create a picture showing how we can divide these into groups..."
|
| 326 |
|
| 327 |
Focus on concepts appropriate for K-12 students:
|
| 328 |
- Basic counting and number recognition
|
| 329 |
- Simple addition and subtraction (using manipulatives)
|
| 330 |
- Multiplication as arrays or groups
|
| 331 |
+
- Division as sharing into equal groups
|
| 332 |
- Basic shapes and geometry
|
| 333 |
- Place value with hundreds, tens, ones
|
| 334 |
- Money counting and coin recognition
|
| 335 |
- Time telling with analog clocks
|
|
|
|
|
|
|
| 336 |
|
| 337 |
Always use age-appropriate language and relate math to real-world examples children understand.
|
| 338 |
|
|
|
|
| 478 |
if st.button("🔗 Share Chat", use_container_width=True):
|
| 479 |
chat_json = json.dumps(st.session_state.chats[st.session_state.active_chat_key])
|
| 480 |
chat_b64 = base64.urlsafe_b64encode(chat_json.encode()).decode()
|
|
|
|
| 481 |
share_url = f"https://huggingface.co/spaces/YOUR_SPACE_HERE?shared_chat={chat_b64}" # Placeholder
|
| 482 |
st.code(share_url)
|
| 483 |
st.info("Copy the URL above to share this specific chat! (You might need to update the base URL)")
|
|
|
|
| 491 |
st.markdown(message["content"])
|
| 492 |
# If a visual was generated and saved with the message, display it
|
| 493 |
if "visual_html" in message and message["visual_html"]:
|
| 494 |
+
components.html(message["visual_html"], height=450, scrolling=True)
|
| 495 |
|
| 496 |
# User input
|
| 497 |
if prompt := st.chat_input("Ask a K-8 math question..."):
|
|
|
|
| 525 |
if should_generate_visual(prompt, full_response):
|
| 526 |
visual_html_content = create_visual_manipulative(prompt, full_response)
|
| 527 |
if visual_html_content:
|
| 528 |
+
components.html(visual_html_content, height=450, scrolling=True)
|
| 529 |
|
| 530 |
# Add AI response and visual to session state
|
| 531 |
st.session_state.chats[st.session_state.active_chat_key].append({
|