jujutechnology commited on
Commit
eec5e4f
ยท
verified ยท
1 Parent(s): c1e6e2a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +316 -434
app.py CHANGED
@@ -5,9 +5,9 @@ import json
5
  import base64
6
  from dotenv import load_dotenv
7
  from streamlit_local_storage import LocalStorage
8
- import plotly.graph_objects as go
9
- import numpy as np
10
  import re
 
 
11
 
12
  # --- PAGE CONFIGURATION ---
13
  st.set_page_config(
@@ -35,322 +35,182 @@ def convert_role_for_gemini(role):
35
  return role # "user" stays the same
36
 
37
  def should_generate_visual(user_prompt, ai_response):
38
- """Determine if a visual aid would be helpful for K-12 concepts"""
39
- elementary_keywords = [
40
- 'add', 'subtract', 'multiply', 'divide', 'times', 'plus', 'minus',
41
- 'count', 'counting', 'number', 'numbers', 'fraction', 'fractions',
42
- 'shape', 'shapes', 'circle', 'square', 'triangle', 'rectangle',
43
- 'money', 'coins', 'dollars', 'cents', 'time', 'clock',
44
- 'pattern', 'patterns', 'grouping', 'groups', 'tens', 'ones',
45
- 'place value', 'hundred', 'thousand', 'comparison', 'greater', 'less',
46
- 'equal', 'equals', 'measurement', 'length', 'height', 'weight'
 
 
 
 
47
  ]
48
 
49
  combined_text = (user_prompt + " " + ai_response).lower()
50
- return any(keyword in combined_text for keyword in elementary_keywords)
51
 
52
- def create_counting_visual(numbers):
53
- """Create visual counting aids using dots/circles"""
 
54
  try:
55
- if not isinstance(numbers, list):
56
- numbers = [numbers]
57
-
58
- fig = go.Figure()
59
- colors = ['red', 'blue', 'green', 'orange', 'purple', 'yellow']
60
-
61
- for i, num in enumerate(numbers[:6]): # Limit to 6 different numbers
62
- if num <= 20: # Only for reasonable counting numbers
63
- # Create dots arranged in rows
64
- dots_per_row = min(5, num)
65
- rows = (num - 1) // dots_per_row + 1
66
-
67
- x_positions = []
68
- y_positions = []
69
-
70
- for dot in range(num):
71
- row = dot // dots_per_row
72
- col = dot % dots_per_row
73
- x_positions.append(col + i * 7) # Separate groups
74
- y_positions.append(-row + rows - 1)
75
-
76
- fig.add_trace(go.Scatter(
77
- x=x_positions,
78
- y=y_positions,
79
- mode='markers',
80
- marker=dict(
81
- size=20,
82
- color=colors[i],
83
- symbol='circle',
84
- line=dict(width=2, color='black')
85
- ),
86
- name=f'{num} items',
87
- showlegend=True
88
- ))
89
-
90
- # Add number label
91
- fig.add_annotation(
92
- x=2 + i * 7,
93
- y=-rows - 0.5,
94
- text=str(num),
95
- font=dict(size=24, color=colors[i]),
96
- showlegend=False
97
- )
98
-
99
- fig.update_layout(
100
- title="Counting Visualization",
101
- showlegend=True,
102
- xaxis=dict(showgrid=False, showticklabels=False, zeroline=False),
103
- yaxis=dict(showgrid=False, showticklabels=False, zeroline=False),
104
- height=400,
105
- template="simple_white"
106
- )
107
 
108
- return fig
109
- except:
110
- return None
 
 
 
 
 
111
 
112
- def create_addition_visual(num1, num2):
113
- """Create visual addition using manipulatives"""
114
- try:
115
- if num1 > 10 or num2 > 10: # Keep it simple for young learners
116
- return None
117
-
118
- fig = go.Figure()
119
-
120
- # First number (red circles)
121
- x1 = list(range(num1))
122
- y1 = [1] * num1
123
- fig.add_trace(go.Scatter(
124
- x=x1, y=y1,
125
- mode='markers',
126
- marker=dict(size=25, color='red', symbol='circle', line=dict(width=2, color='black')),
127
- name=f'First group: {num1}',
128
- showlegend=True
129
- ))
130
-
131
- # Second number (blue circles)
132
- x2 = list(range(num1 + 1, num1 + num2 + 1))
133
- y2 = [1] * num2
134
- fig.add_trace(go.Scatter(
135
- x=x2, y=y2,
136
- mode='markers',
137
- marker=dict(size=25, color='blue', symbol='circle', line=dict(width=2, color='black')),
138
- name=f'Second group: {num2}',
139
- showlegend=True
140
- ))
141
-
142
- # Plus sign
143
- fig.add_annotation(
144
- x=num1 - 0.5,
145
- y=1.5,
146
- text="+",
147
- font=dict(size=30, color='black'),
148
- showlegend=False
149
- )
150
-
151
- # Equals sign and result
152
- fig.add_annotation(
153
- x=num1 + num2 + 0.5,
154
- y=1.5,
155
- text="=",
156
- font=dict(size=30, color='black'),
157
- showlegend=False
158
- )
159
-
160
- fig.add_annotation(
161
- x=num1 + num2 + 1.5,
162
- y=1.5,
163
- text=str(num1 + num2),
164
- font=dict(size=30, color='green'),
165
- showlegend=False
166
- )
167
-
168
- fig.update_layout(
169
- title=f"Addition: {num1} + {num2} = {num1 + num2}",
170
- showlegend=True,
171
- xaxis=dict(showgrid=False, showticklabels=False, zeroline=False, range=[-1, num1 + num2 + 3]),
172
- yaxis=dict(showgrid=False, showticklabels=False, zeroline=False, range=[0, 2.5]),
173
- height=300,
174
- template="simple_white"
175
- )
176
-
177
- return fig
178
- except:
179
- return None
180
 
181
- def create_fraction_visual(numerator, denominator):
182
- """Create visual fraction using pie charts or bars"""
183
- try:
184
- if denominator > 12 or numerator > denominator: # Keep it simple
185
- return None
186
-
187
- fig = go.Figure()
188
-
189
- # Create a circle divided into parts
190
- angles = np.linspace(0, 2*np.pi, denominator + 1)
191
-
192
- for i in range(denominator):
193
- # Create each slice
194
- theta = np.linspace(angles[i], angles[i+1], 50)
195
- r = np.ones_like(theta)
196
- x = r * np.cos(theta)
197
- y = r * np.sin(theta)
198
-
199
- # Add center point
200
- x = np.concatenate([[0], x, [0]])
201
- y = np.concatenate([[0], y, [0]])
202
-
203
- color = 'lightblue' if i < numerator else 'lightgray'
204
-
205
- fig.add_trace(go.Scatter(
206
- x=x, y=y,
207
- fill='toself',
208
- mode='lines',
209
- line=dict(color='black', width=2),
210
- fillcolor=color,
211
- name=f'Slice {i+1}' if i < numerator else '',
212
- showlegend=False
213
- ))
214
-
215
- fig.update_layout(
216
- title=f"Fraction: {numerator}/{denominator}",
217
- xaxis=dict(showgrid=False, showticklabels=False, zeroline=False, scaleanchor="y", scaleratio=1),
218
- yaxis=dict(showgrid=False, showticklabels=False, zeroline=False),
219
- height=400,
220
- template="simple_white"
221
- )
222
-
223
- return fig
224
- except:
225
- return None
226
 
227
- def create_place_value_visual(number):
228
- """Create place value visualization"""
229
- try:
230
- if number > 9999: # Keep it reasonable for elementary
231
- return None
232
-
233
- str_num = str(number).zfill(4) # Pad with zeros
234
- ones = int(str_num[-1])
235
- tens = int(str_num[-2])
236
- hundreds = int(str_num[-3])
237
- thousands = int(str_num[-4])
238
-
239
- fig = go.Figure()
240
-
241
- # Create visual blocks for each place value
242
- positions = [0, 2, 4, 6] # x positions for thousands, hundreds, tens, ones
243
- values = [thousands, hundreds, tens, ones]
244
- labels = ['Thousands', 'Hundreds', 'Tens', 'Ones']
245
- colors = ['red', 'blue', 'green', 'orange']
246
-
247
- for i, (pos, val, label, color) in enumerate(zip(positions, values, labels, colors)):
248
- if val > 0:
249
- # Create blocks to represent the value
250
- blocks_per_row = min(5, val)
251
- rows = (val - 1) // blocks_per_row + 1
252
-
253
- x_coords = []
254
- y_coords = []
255
-
256
- for block in range(val):
257
- row = block // blocks_per_row
258
- col = block % blocks_per_row
259
- x_coords.append(pos + col * 0.3)
260
- y_coords.append(row * 0.3)
261
-
262
- fig.add_trace(go.Scatter(
263
- x=x_coords,
264
- y=y_coords,
265
- mode='markers',
266
- marker=dict(
267
- size=15,
268
- color=color,
269
- symbol='square',
270
- line=dict(width=1, color='black')
271
- ),
272
- name=f'{label}: {val}',
273
- showlegend=True
274
- ))
275
-
276
- # Add place value label
277
- fig.add_annotation(
278
- x=pos + 0.6,
279
- y=-0.5,
280
- text=label,
281
- font=dict(size=12),
282
- showlegend=False
283
- )
284
-
285
- # Add digit
286
- fig.add_annotation(
287
- x=pos + 0.6,
288
- y=-0.8,
289
- text=str(val),
290
- font=dict(size=16, color=colors[i]),
291
- showlegend=False
292
- )
293
-
294
- fig.update_layout(
295
- title=f"Place Value: {number}",
296
- showlegend=True,
297
- xaxis=dict(showgrid=False, showticklabels=False, zeroline=False, range=[-0.5, 7]),
298
- yaxis=dict(showgrid=False, showticklabels=False, zeroline=False),
299
- height=400,
300
- template="simple_white"
301
- )
302
-
303
- return fig
304
- except:
305
- return None
306
 
307
- def generate_k12_visual(user_prompt, ai_response):
308
- """Generate age-appropriate visualizations for K-12 students"""
309
- try:
310
- user_lower = user_prompt.lower()
311
-
312
- # COUNTING NUMBERS
313
- count_match = re.search(r'count.*?(\d+)', user_lower)
314
- if count_match or 'counting' in user_lower:
315
- number = int(count_match.group(1)) if count_match else 5
316
- return create_counting_visual(number)
317
-
318
- # SIMPLE ADDITION
319
- add_match = re.search(r'(\d+)\s*\+\s*(\d+)', user_prompt)
320
- if add_match and 'add' in user_lower or '+' in user_prompt:
321
- num1, num2 = int(add_match.group(1)), int(add_match.group(2))
322
- if num1 <= 10 and num2 <= 10: # Keep it simple
323
- return create_addition_visual(num1, num2)
324
-
325
- # FRACTIONS
326
- fraction_match = re.search(r'(\d+)/(\d+)', user_prompt)
327
- if fraction_match or 'fraction' in user_lower:
328
- if fraction_match:
329
- num, den = int(fraction_match.group(1)), int(fraction_match.group(2))
330
- else:
331
- num, den = 1, 2 # Default to 1/2
332
- return create_fraction_visual(num, den)
333
-
334
- # PLACE VALUE
335
- if 'place value' in user_lower or 'place' in user_lower:
336
- place_match = re.search(r'\b(\d{1,4})\b', user_prompt)
337
- if place_match:
338
- number = int(place_match.group(1))
339
- return create_place_value_visual(number)
340
-
341
- # NUMBERS (general counting)
342
- number_match = re.search(r'\b(\d+)\b', user_prompt)
343
- if number_match and any(word in user_lower for word in ['show', 'count', 'number']):
344
- number = int(number_match.group(1))
345
- if 1 <= number <= 20:
346
- return create_counting_visual(number)
347
-
348
- return None
349
 
350
  except Exception as e:
351
- st.error(f"Could not generate K-12 visual: {e}")
352
  return None
353
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354
  # --- API KEY & MODEL CONFIGURATION ---
355
  load_dotenv()
356
  api_key = None
@@ -367,43 +227,49 @@ if api_key:
367
  model = genai.GenerativeModel(
368
  model_name="gemini-2.5-flash-lite",
369
  system_instruction="""
370
- You are "Math Jegna", an AI math tutor specializing in K-12 mathematics using the Professor B methodology.
371
-
372
- FOCUS ON ELEMENTARY CONCEPTS:
373
- - Basic counting (1-100)
374
- - Simple addition and subtraction (single digits to start)
375
- - Beginning multiplication (times tables)
376
- - Basic fractions (halves, thirds, quarters)
377
- - Place value (ones, tens, hundreds)
378
- - Shape recognition
379
- - Simple word problems
380
- - Money and time concepts
381
-
382
- PROFESSOR B METHODOLOGY - ESSENTIAL PRINCIPLES:
383
- 1. Present math as a STORY that connects ideas
384
- 2. Use MENTAL GYMNASTICS - fun games and finger counting
385
- 3. Build from concrete to abstract naturally
386
- 4. NO ROTE MEMORIZATION - focus on understanding patterns and connections
387
- 5. Eliminate math anxiety through simple, truthful explanations
388
- 6. Use manipulatives and visual aids
389
- 7. Allow accelerated learning when student shows mastery
390
-
391
- TEACHING STYLE:
392
- - Start with what the child already knows
393
- - Build new concepts as natural extensions of previous learning
394
- - Use simple, clear language appropriate for the age
395
- - Make math enjoyable and reduce tension
396
- - Connect everything to real-world experiences
397
- - Celebrate understanding, not just correct answers
398
-
399
- VISUAL AIDS: Mention when visual aids will help, using phrases like:
400
- - "Let me show you this with counting dots..."
401
- - "I'll create a picture to help you see this..."
402
- - "A visual will make this clearer..."
403
-
404
- Remember: You're helping young minds discover the beauty and logic of mathematics through stories and connections, not through drilling and memorization.
 
 
 
 
 
 
405
 
406
- You are strictly forbidden from answering non-mathematical questions. If asked non-math questions, respond only with: "I can only answer mathematical questions. Please ask me a question about counting, adding, shapes, or another math topic."
407
  """
408
  )
409
  else:
@@ -430,7 +296,7 @@ if "chats" not in st.session_state:
430
  else:
431
  st.session_state.chats = {
432
  "New Chat": [
433
- {"role": "assistant", "content": "Hello! I'm Math Jegna, your friendly math helper! ๐Ÿง โœจ\n\nI love helping kids learn math through fun stories and pictures. Try asking me about:\n- Counting numbers\n- Adding or subtracting\n- Fractions like 1/2\n- Shapes and patterns\n- Or any math question!\n\nWhat would you like to learn about today?"}
434
  ]
435
  }
436
  st.session_state.active_chat_key = "New Chat"
@@ -456,110 +322,126 @@ def delete_chat(chat_key):
456
  st.warning(f"Are you sure you want to delete '{chat_key}'? This cannot be undone.")
457
  if st.button("Yes, Delete", type="primary", key=f"confirm_delete_{chat_key}"):
458
  st.session_state.chats.pop(chat_key)
 
459
  if st.session_state.active_chat_key == chat_key:
460
- st.session_state.active_chat_key = next(iter(st.session_state.chats))
 
 
 
 
 
 
 
 
461
  st.rerun()
462
 
463
- # --- SIDEBAR CHAT MANAGEMENT ---
464
- st.sidebar.title("๐Ÿ“š My Math Chats")
465
- st.sidebar.divider()
466
-
467
- if st.sidebar.button("โž• New Chat", use_container_width=True):
468
- i = 1
469
- while f"New Chat {i}" in st.session_state.chats:
470
- i += 1
471
- new_chat_key = f"New Chat {i}"
472
- st.session_state.chats[new_chat_key] = [
473
- {"role": "assistant", "content": "Hi there! Ready for some fun math learning? Ask me about counting, adding, shapes, or anything else! ๐Ÿš€๐Ÿ”ข"}
474
- ]
475
- st.session_state.active_chat_key = new_chat_key
476
- st.rerun()
 
 
 
 
 
477
 
478
- st.sidebar.divider()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
479
 
480
- for chat_key in list(st.session_state.chats.keys()):
481
- is_active = (chat_key == st.session_state.active_chat_key)
482
- expander_label = f"**{chat_key} (Active)**" if is_active else chat_key
 
 
 
 
 
 
 
483
 
484
- with st.sidebar.expander(expander_label):
485
- if st.button("Select Chat", key=f"select_{chat_key}", use_container_width=True, disabled=is_active):
486
- st.session_state.active_chat_key = chat_key
487
- st.rerun()
488
-
489
- if st.button("Rename", key=f"rename_{chat_key}", use_container_width=True):
490
- rename_chat(chat_key)
491
-
492
- with st.popover("Share", use_container_width=True):
493
- st.markdown("**Download Conversation**")
494
- st.download_button(
495
- label="Download as Markdown",
496
- data=format_chat_for_download(st.session_state.chats[chat_key]),
497
- file_name=f"{chat_key.replace(' ', '_')}.md",
498
- mime="text/markdown"
499
- )
500
- st.markdown("**Share via Link**")
501
- st.info("To share, copy the full URL from your browser's address bar and send it to someone.")
502
-
503
- if st.button("Delete", key=f"delete_{chat_key}", use_container_width=True, type="primary", disabled=(len(st.session_state.chats) <= 1)):
504
- delete_chat(chat_key)
505
-
506
- # --- MAIN CHAT INTERFACE ---
507
- active_chat = st.session_state.chats[st.session_state.active_chat_key]
508
-
509
- st.title(f"Math Helper: {st.session_state.active_chat_key} ๐Ÿง ")
510
- st.write("๐ŸŽฏ Perfect for young learners! Ask about counting, adding, shapes, fractions, and more!")
511
-
512
- # Add some example prompts for young learners
513
- with st.expander("๐Ÿ’ก Try asking me about..."):
514
- st.write("""
515
- - **Counting**: "Show me how to count to 10"
516
- - **Addition**: "What is 3 + 4?"
517
- - **Fractions**: "What is 1/2?"
518
- - **Place Value**: "What is the place value of 325?"
519
- - **Shapes**: "Tell me about triangles"
520
- - **Time**: "How do I read a clock?"
521
- """)
522
-
523
- for message in active_chat:
524
- with st.chat_message(name=message["role"], avatar="๐Ÿง‘โ€๐Ÿ’ป" if message["role"] == "user" else "๐Ÿง "):
525
  st.markdown(message["content"])
526
 
527
- if user_prompt := st.chat_input("Ask me a math question!"):
528
- active_chat.append({"role": "user", "content": user_prompt})
529
- with st.chat_message("user", avatar="๐Ÿง‘โ€๐Ÿ’ป"):
530
- st.markdown(user_prompt)
 
 
531
 
532
- with st.chat_message("assistant", avatar="๐Ÿง "):
533
- with st.spinner("Math Jegna is thinking... ๐Ÿค”"):
 
 
 
 
 
 
 
534
  try:
535
- # Generate text response first
536
- chat_session = model.start_chat(history=[
537
- {'role': convert_role_for_gemini(msg['role']), 'parts': [msg['content']]}
538
- for msg in active_chat[:-1] if 'content' in msg
539
- ])
540
- response = chat_session.send_message(user_prompt)
541
- ai_response_text = response.text
542
- st.markdown(ai_response_text)
543
 
544
- # Store the text response
545
- active_chat.append({"role": "assistant", "content": ai_response_text})
 
 
 
 
546
 
547
- # Check if we should generate a visual aid
548
- if should_generate_visual(user_prompt, ai_response_text):
549
- with st.spinner("Creating a helpful picture... ๐ŸŽจ"):
550
- k12_fig = generate_k12_visual(user_prompt, ai_response_text)
551
- if k12_fig:
552
- st.plotly_chart(k12_fig, use_container_width=True)
553
- st.success("โœจ Here's a picture to help you understand!")
 
 
554
 
 
 
 
555
  except Exception as e:
556
- error_message = f"Oops! Something went wrong. Let me try again! ๐Ÿค–\n\n**Error:** {e}"
557
- st.error(error_message)
558
- active_chat.append({"role": "assistant", "content": error_message})
559
-
560
- # --- SAVE DATA TO LOCAL STORAGE ---
561
- data_to_save = {
562
- "chats": st.session_state.chats,
563
- "active_chat_key": st.session_state.active_chat_key
564
- }
565
- localS.setItem("math_mentor_chats", json.dumps(data_to_save))
 
5
  import base64
6
  from dotenv import load_dotenv
7
  from streamlit_local_storage import LocalStorage
 
 
8
  import re
9
+ import streamlit.components.v1 as components
10
+ import math # Needed for trigonometry in dynamic visuals
11
 
12
  # --- PAGE CONFIGURATION ---
13
  st.set_page_config(
 
35
  return role # "user" stays the same
36
 
37
  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',
45
+ 'time', 'clock', 'hours', 'minutes', 'o\'clock',
46
+ 'measurement', 'length', 'height', 'weight',
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
+ return any(keyword in combined_text for keyword in k12_visual_keywords)
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: Time / Clock (e.g., "7:30", "4 o'clock")
63
+ time_match = re.search(r'(\d{1,2}):(\d{2})', user_lower) or re.search(r'(\d{1,2})\s*o\'clock', user_lower)
64
+ if time_match:
65
+ groups = time_match.groups()
66
+ hour = int(groups[0])
67
+ minute = int(groups[1]) if len(groups) > 1 and groups[1] else 0
68
+ if 1 <= hour <= 12 and 0 <= minute <= 59:
69
+ return create_clock_visual(hour, minute)
70
 
71
+ # Priority 2: Fractions (e.g., "2/5", "fraction 3/8")
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: # Keep it visually clean
76
+ return create_dynamic_fraction_circle(num, den)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
+ # Priority 3: Multiplication Arrays (e.g., "3 times 5", "4 x 6")
79
+ mult_match = re.search(r'(\d+)\s*(?:x|times)\s*(\d+)', user_lower)
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])
90
+ operation = 'add' if any(w in user_lower for w in ['add', 'plus', '+']) else 'subtract'
91
+ if num1 <= 20 and num2 <= 20:
92
+ return create_counting_blocks(num1, num2, operation)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
+ # Priority 5: Number Lines
95
+ if 'number line' in user_lower:
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 6: Place Value
103
+ if 'place value' in user_lower:
104
+ numbers = re.findall(r'\d+', user_prompt)
105
+ if numbers:
106
+ num = int(numbers[0])
107
+ if num <= 999:
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) # Show a default example
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) # Show a default example
115
+
116
+ return None # No relevant visual found
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
  except Exception as e:
119
+ st.error(f"Could not create visual: {e}")
120
  return None
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"""
127
+ <div style="padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; margin: 10px 0;">
128
+ <h3 style="color: white; text-align: center; margin-bottom: 20px;">๐Ÿงฎ Counting Blocks: {num1} {'+' if operation == 'add' else 'โˆ’'} {num2}</h3>
129
+ <div style="display: flex; justify-content: center; align-items: center; gap: 20px; flex-wrap: wrap;">
130
+ <!-- Blocks for Num1 -->
131
+ <div style="display: flex; flex-wrap: wrap; gap: 5px; border: 2px dashed #FFE066; padding: 5px; border-radius: 5px; align-items: center; justify-content: center; min-width: 100px;"><div style="width: 100%; text-align:center; color: white; font-weight: bold;">{num1}</div>{''.join([f'<div style="width: 25px; height: 25px; background: #FF6B6B; border-radius: 5px;"></div>' for _ in range(num1)])}</div>
132
+ <div style="font-size: 40px; color: #FFE066;">{'+' if operation == 'add' else 'โˆ’'}</div>
133
+ <!-- Blocks for Num2 -->
134
+ <div style="display: flex; flex-wrap: wrap; gap: 5px; border: 2px dashed #FFE066; padding: 5px; border-radius: 5px; align-items: center; justify-content: center; min-width: 100px;"><div style="width: 100%; text-align:center; color: white; font-weight: bold;">{num2}</div>{''.join([f'<div style="width: 25px; height: 25px; background: #4ECDC4; border-radius: 5px;"></div>' for _ in range(num2)])}</div>
135
+ <div style="font-size: 40px; color: #FFE066;">=</div>
136
+ <!-- Blocks for Answer -->
137
+ <div style="display: flex; flex-wrap: wrap; gap: 5px; border: 2px solid white; background: rgba(255,255,255,0.2); padding: 5px; border-radius: 5px; align-items: center; justify-content: center; min-width: 100px;"><div style="width: 100%; text-align:center; color: white; font-weight: bold;">{num1 + num2 if operation == 'add' else max(0, num1 - num2)}</div>{''.join([f'<div style="width: 25px; height: 25px; background: #95E1D3; border-radius: 5px;"></div>' for _ in range(num1 + num2 if operation == 'add' else max(0, num1 - num2))])}</div>
138
+ </div>
139
+ </div>"""
140
+ return html
141
+
142
+ def create_dynamic_fraction_circle(numerator, denominator):
143
+ """(Dynamic) Generates an SVG of a pizza/pie to represent a fraction."""
144
+ if not (0 < numerator <= denominator): return "<p>I can only show proper fractions!</p>"
145
+ width, height, radius = 150, 150, 60
146
+ cx, cy = width / 2, height / 2
147
+ slices_html = ''
148
+ angle_step = 360 / denominator
149
+ for i in range(denominator):
150
+ start_angle, end_angle = i * angle_step, (i + 1) * angle_step
151
+ fill_color = "#FF6B6B" if i < numerator else "#DDDDDD"
152
+ start_rad, end_rad = math.radians(start_angle - 90), math.radians(end_angle - 90)
153
+ x1, y1 = cx + radius * math.cos(start_rad), cy + radius * math.sin(start_rad)
154
+ x2, y2 = cx + radius * math.cos(end_rad), cy + radius * math.sin(end_rad)
155
+ large_arc_flag = 1 if angle_step > 180 else 0
156
+ path_d = f"M {cx},{cy} L {x1},{y1} A {radius},{radius} 0 {large_arc_flag},1 {x2},{y2} Z"
157
+ slices_html += f'<path d="{path_d}" fill="{fill_color}" stroke="#333" stroke-width="2"/>'
158
+ html = f"""<div style="padding: 20px; background: linear-gradient(135deg, #A8EDEA 0%, #FED6E3 100%); border-radius: 15px; margin: 10px 0;"><h3 style="color: #333; text-align: center;">Fraction Pizza: {numerator}/{denominator}</h3><div style="display: flex; justify-content: center;"><svg width="{width}" height="{height}">{slices_html}</svg></div><p style="color: #333; text-align: center; margin-top: 15px; font-size: 18px;">The pizza is cut into <b>{denominator}</b> equal slices, and we are showing <b>{numerator}</b> of them! ๐Ÿ•</p></div>"""
159
+ return html
160
+
161
+ def create_clock_visual(hours, minutes):
162
+ """(Dynamic) Create a clock showing a specific time."""
163
+ min_angle = minutes * 6
164
+ hour_angle = (hours % 12 + minutes / 60) * 30
165
+ html = f"""<div style="padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; margin: 10px 0;"><h3 style="color: white; text-align: center; margin-bottom: 20px;">๐Ÿ• Learning Time!</h3><div style="display: flex; justify-content: center;"><svg width="250" height="250" viewBox="0 0 250 250" style="background: white; border-radius: 50%; border: 8px solid #FFE066;"><circle cx="125" cy="125" r="110" fill="white" stroke="#333" stroke-width="2"/><text x="125" y="45" text-anchor="middle" font-size="20" font-weight="bold" fill="#333">12</text><text x="205" y="130" text-anchor="middle" font-size="20" font-weight="bold" fill="#333">3</text><text x="125" y="215" text-anchor="middle" font-size="20" font-weight="bold" fill="#333">6</text><text x="45" y="130" text-anchor="middle" font-size="20" font-weight="bold" fill="#333">9</text><line x1="125" y1="125" x2="125" y2="40" stroke="#FF6B6B" stroke-width="6" stroke-linecap="round" transform="rotate({hour_angle}, 125, 125)"/><line x1="125" y1="125" x2="125" y2="25" stroke="#4ECDC4" stroke-width="4" stroke-linecap="round" transform="rotate({min_angle}, 125, 125)"/><circle cx="125" cy="125" r="8" fill="#333"/></svg></div><div style="text-align: center; margin-top: 20px;"><p style="color: #FFE066; font-size: 24px; font-weight: bold;">This clock shows {hours:02d}:{minutes:02d}</p><p style="color: white; font-size: 16px;">The short <span style="color:#FF6B6B">red</span> hand points to the hour. The long <span style="color:#4ECDC4">blue</span> hand points to the minutes.</p></div></div>"""
166
+ return html
167
+
168
+ def create_multiplication_array(rows, cols):
169
+ """(NEW & Dynamic) Generates an SVG grid of dots to show multiplication."""
170
+ cell_size, gap = 25, 5
171
+ svg_width = cols * (cell_size + gap)
172
+ svg_height = rows * (cell_size + gap)
173
+ dots_html = "".join([f'<circle cx="{c * (cell_size + gap) + cell_size/2}" cy="{r * (cell_size + gap) + cell_size/2}" r="{cell_size/2 - 2}" fill="#FF6B6B"/>' for r in range(rows) for c in range(cols)])
174
+ html = f"""<div style="padding: 20px; background: linear-gradient(135deg, #FF9A9E 0%, #FECFEF 100%); border-radius: 15px; margin: 10px 0;"><h3 style="color:#333; text-align: center;">Multiplication Array: {rows} ร— {cols} = {rows * cols}</h3><div style="display: flex; justify-content: center; padding: 10px;"><svg width="{svg_width}" height="{svg_height}">{dots_html}</svg></div><p style="color: #333; text-align: center; font-size: 18px;">See? There are <b>{rows}</b> rows of <b>{cols}</b> dots. That's <b>{rows*cols}</b> dots in total!</p></div>"""
175
+ return html
176
+
177
+ def create_number_line(start, end, points, title="Number Line"):
178
+ """(NEW & Dynamic) Creates a simple number line SVG."""
179
+ width = 600
180
+ padding = 30
181
+ scale = (width - 2 * padding) / (end - start)
182
+ def to_x(n): return padding + (n - start) * scale
183
+ 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)])
184
+ points_html = "".join([f'<g transform="translate({to_x(p)}, 50)"><circle r="8" fill="#FF6B6B" stroke="white" stroke-width="2"/><text y="-15" text-anchor="middle" font-weight="bold" fill="#D63031">{p}</text></g>' for p in points])
185
+ html = f"""<div style="padding: 20px; background: #f7f1e3; border-radius: 15px; margin: 10px 0;"><h3 style="text-align: center; color: #333;">{title}</h3><svg width="{width}" height="100"><line x1="{padding}" y1="50" x2="{width-padding}" y2="50" stroke="#333" stroke-width="2"/>{ticks_html}{points_html}</svg></div>"""
186
+ return html
187
+
188
+ def create_place_value_blocks(number):
189
+ """(Dynamic) Create place value blocks for understanding numbers."""
190
+ hundreds, tens, ones = number // 100, (number % 100) // 10, number % 10
191
+ h_html = f'<div style="text-align: center;"><h4>Hundreds: {hundreds}</h4><div style="display: flex; gap: 5px;">{"".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;">{"".join(["<div style=\'background:#F5A6A6\'></div>"]*100)}</div>\' for _ in range(hundreds)])}</div></div>' if hundreds > 0 else ''
192
+ t_html = f'<div style="text-align: center;"><h4>Tens: {tens}</h4><div style="display: flex; gap: 5px;">{"".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;">{"".join(["<div style=\'background:#A2E8E4\'></div>"]*10)}</div>\' for _ in range(tens)])}</div></div>' if tens > 0 else ''
193
+ o_html = f'<div style="text-align: center;"><h4>Ones: {ones}</h4><div style="display: flex; gap: 5px;">{"".join([f\'<div style="width: 10px; height: 10px; background: #FFE066; border: 2px solid #FDCB6E;"></div>\' for _ in range(ones)])}</div></div>' if ones > 0 else ''
194
+ 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;">{h_html}{t_html}{o_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 if hundreds else 0} Hundreds + {tens if tens else 0} Tens + {ones} Ones = {number}</h4></div></div>"""
195
+ return html
196
+
197
+ def create_shape_explorer():
198
+ """(Static) Create colorful shape recognition tool."""
199
+ html = """<div style="padding: 20px; background: linear-gradient(135deg, #A8EDEA 0%, #FED6E3 100%); border-radius: 15px; margin: 10px 0;"><h3 style="color: #333; text-align: center; margin-bottom: 20px;">๐Ÿ”ท Shape Explorer!</h3><div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 20px; max-width: 600px; margin: 0 auto;"><div style="text-align: center; padding: 15px; background: white; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"><h4 style="color: #333; margin-bottom: 10px;">Circle</h4><svg width="80" height="80"><circle cx="40" cy="40" r="35" fill="#FF6B6B" stroke="#333" stroke-width="3"/></svg><p style="color: #666; font-size: 12px; margin-top: 10px;">Round and smooth!</p></div><div style="text-align: center; padding: 15px; background: white; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"><h4 style="color: #333; margin-bottom: 10px;">Square</h4><svg width="80" height="80"><rect x="12.5" y="12.5" width="55" height="55" fill="#4ECDC4" stroke="#333" stroke-width="3"/></svg><p style="color: #666; font-size: 12px; margin-top: 10px;">4 equal sides!</p></div><div style="text-align: center; padding: 15px; background: white; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"><h4 style="color: #333; margin-bottom: 10px;">Triangle</h4><svg width="80" height="80"><polygon points="40,15 15,65 65,65" fill="#FFD93D" stroke="#333" stroke-width="3"/></svg><p style="color: #666; font-size: 12px; margin-top: 10px;">3 sides and corners!</p></div><div style="text-align: center; padding: 15px; background: white; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"><h4 style="color: #333; margin-bottom: 10px;">Rectangle</h4><svg width="80" height="80"><rect x="10" y="25" width="60" height="30" fill="#95E1D3" stroke="#333" stroke-width="3"/></svg><p style="color: #666; font-size: 12px; margin-top: 10px;">4 sides, opposite sides equal!</p></div></div><p style="color: #333; text-align: center; margin-top: 20px; font-size: 18px;">Can you find these shapes around you? ๐Ÿ”โœจ</p></div>"""
200
+ return html
201
+
202
+ def create_money_counter():
203
+ """(Static) Create coin counting visual."""
204
+ html = """<div style="padding: 20px; background: linear-gradient(135deg, #FFE259 0%, #FFA751 100%); border-radius: 15px; margin: 10px 0;"><h3 style="color: #333; text-align: center; margin-bottom: 20px;">๐Ÿ’ฐ Money Counter!</h3><div style="display: flex; justify-content: center; gap: 30px; flex-wrap: wrap;"><div style="text-align: center; padding: 15px; background: white; border-radius: 10px;"><h4 style="color: #333;">Penny</h4><div style="width: 50px; height: 50px; background: #CD7F32; border-radius: 50%; margin: 10px auto; display: flex; align-items: center; justify-content: center; border: 3px solid #8B4513;"><span style="color: white; font-weight: bold;">1ยข</span></div><p style="color: #666; font-size: 12px;">1 cent</p></div><div style="text-align: center; padding: 15px; background: white; border-radius: 10px;"><h4 style="color: #333;">Nickel</h4><div style="width: 55px; height: 55px; background: #C0C0C0; border-radius: 50%; margin: 10px auto; display: flex; align-items: center; justify-content: center; border: 3px solid #808080;"><span style="color: #333; font-weight: bold;">5ยข</span></div><p style="color: #666; font-size: 12px;">5 cents</p></div><div style="text-align: center; padding: 15px; background: white; border-radius: 10px;"><h4 style="color: #333;">Dime</h4><div style="width: 45px; height: 45px; background: #C0C0C0; border-radius: 50%; margin: 10px auto; display: flex; align-items: center; justify-content: center; border: 3px solid #808080;"><span style="color: #333; font-weight: bold;">10ยข</span></div><p style="color: #666; font-size: 12px;">10 cents</p></div><div style="text-align: center; padding: 15px; background: white; border-radius: 10px;"><h4 style="color: #333;">Quarter</h4><div style="width: 60px; height: 60px; background: #C0C0C0; border-radius: 50%; margin: 10px auto; display: flex; align-items: center; justify-content: center; border: 3px solid #808080;"><span style="color: #333; font-weight: bold;">25ยข</span></div><p style="color: #666; font-size: 12px;">25 cents</p></div></div><p style="color: #333; text-align: center; margin-top: 20px; font-size: 18px;">Practice counting coins to make different amounts! ๐Ÿช™โœจ</p></div>"""
205
+ return html
206
+
207
+ # --- [The rest of your application code remains the same] ---
208
+ # --- API KEY & MODEL CONFIGURATION, SESSION STATE, DIALOGS, etc. ---
209
+ # ... (Paste the rest of your original app.py code from the "API KEY" section onwards here) ...
210
+
211
+ # NOTE: For brevity, I am not repeating the entire second half of your app.
212
+ # The code below is identical to your original file.
213
+
214
  # --- API KEY & MODEL CONFIGURATION ---
215
  load_dotenv()
216
  api_key = None
 
227
  model = genai.GenerativeModel(
228
  model_name="gemini-2.5-flash-lite",
229
  system_instruction="""
230
+ You are "Math Jegna", an AI specializing exclusively in K-12 mathematics.
231
+ Your one and only function is to solve and explain math problems for children.
232
+ You are an AI math tutor that uses the Professor B methodology developed by Everard Barrett. This methodology is designed to activate children's natural learning capacities and present mathematics as a contextual, developmental story that makes sense.
233
+
234
+ IMPORTANT: When explaining mathematical concepts to young learners, mention that colorful visual aids will be provided to help illustrate the concept. Use phrases like:
235
+ - "Let me show you this with some colorful blocks..."
236
+ - "A fun visual will help you see how this works..."
237
+ - "I'll create a picture to help you understand this fraction..."
238
+
239
+ Focus on concepts appropriate for K-12 students:
240
+ - Basic counting and number recognition
241
+ - Simple addition and subtraction (using manipulatives)
242
+ - Fractions as parts of wholes (pizza slices, etc.)
243
+ - Multiplication as arrays or groups
244
+ - Basic shapes and geometry
245
+ - Place value with hundreds, tens, ones
246
+ - Money counting and coin recognition
247
+ - Time telling with analog clocks
248
+ - Simple patterns and sequences
249
+ - Basic measurement concepts
250
+
251
+ Always use age-appropriate language and relate math to real-world examples children understand.
252
+
253
+ Core Philosophy and Principles
254
+ 1. Contextual Learning Approach
255
+ Present math as a story: Every mathematical concept should be taught as part of a continuing narrative that builds connections between ideas
256
+ Use concrete manipulatives: Always relate abstract concepts to physical, visual representations
257
+ Truth-telling: Present arithmetic computations simply and truthfully without confusing steps
258
+
259
+ 2. Natural Learning Activation
260
+ Leverage natural capacities: Recognize that each child has mental capabilities designed to learn naturally
261
+ Story-based retention: Use stories and visual representations that children can easily remember
262
+ Reduced anxiety: Make math fun and engaging, not scary or confusing
263
+
264
+ 3. Hands-on Learning
265
+ Mental gymnastics: Use finger counting, visual blocks, and interactive elements
266
+ No rote memorization: Focus on understanding through play and exploration
267
+ Build confidence: Celebrate small victories and progress
268
+
269
+ You are strictly forbidden from answering any question that is not mathematical in nature.
270
+ If you receive a non-mathematical question, you MUST decline with: "I can only answer math questions for students. Please ask me about numbers, shapes, counting, or other math topics!"
271
 
272
+ Keep explanations simple, encouraging, and fun for young learners.
273
  """
274
  )
275
  else:
 
296
  else:
297
  st.session_state.chats = {
298
  "New Chat": [
299
+ {"role": "assistant", "content": "Hello! I'm Math Jegna, your friendly math helper! ๐Ÿง โœจ I love helping students learn math with colorful pictures and fun activities. What would you like to learn about today? Maybe counting, shapes, or solving a math problem? ๐ŸŒŸ"}
300
  ]
301
  }
302
  st.session_state.active_chat_key = "New Chat"
 
322
  st.warning(f"Are you sure you want to delete '{chat_key}'? This cannot be undone.")
323
  if st.button("Yes, Delete", type="primary", key=f"confirm_delete_{chat_key}"):
324
  st.session_state.chats.pop(chat_key)
325
+ # Add the logic to switch to a new or different chat after deletion
326
  if st.session_state.active_chat_key == chat_key:
327
+ # Simple fallback to the first available chat or a new one
328
+ if st.session_state.chats:
329
+ st.session_state.active_chat_key = next(iter(st.session_state.chats))
330
+ else:
331
+ # Create a new chat if none are left
332
+ st.session_state.chats["New Chat"] = [
333
+ {"role": "assistant", "content": "Hello! Let's start a new math adventure! ๐Ÿš€"}
334
+ ]
335
+ st.session_state.active_chat_key = "New Chat"
336
  st.rerun()
337
 
338
+ # --- MAIN APP LAYOUT ---
339
+ with st.sidebar:
340
+ st.title("๐Ÿงฎ Math Jegna")
341
+ st.write("Your K-8 AI Math Tutor")
342
+ st.divider()
343
+
344
+ # Chat history list
345
+ for chat_key in list(st.session_state.chats.keys()):
346
+ col1, col2, col3 = st.columns([0.6, 0.2, 0.2])
347
+ with col1:
348
+ if st.button(chat_key, key=f"switch_{chat_key}", use_container_width=True, type="primary" if st.session_state.active_chat_key == chat_key else "secondary"):
349
+ st.session_state.active_chat_key = chat_key
350
+ st.rerun()
351
+ with col2:
352
+ if st.button("โœ๏ธ", key=f"rename_{chat_key}", help="Rename Chat"):
353
+ rename_chat(chat_key)
354
+ with col3:
355
+ if st.button("๐Ÿ—‘๏ธ", key=f"delete_{chat_key}", help="Delete Chat"):
356
+ delete_chat(chat_key)
357
 
358
+ if st.button("โž• New Chat", use_container_width=True):
359
+ new_chat_name = f"Chat {len(st.session_state.chats) + 1}"
360
+ # Ensure the name is unique
361
+ while new_chat_name in st.session_state.chats:
362
+ new_chat_name += "*"
363
+ st.session_state.chats[new_chat_name] = [
364
+ {"role": "assistant", "content": "Ready for a new math problem! What's on your mind? ๐Ÿ˜ƒ"}
365
+ ]
366
+ st.session_state.active_chat_key = new_chat_name
367
+ st.rerun()
368
+
369
+ st.divider()
370
+
371
+ # Save chats to local storage
372
+ if st.button("๐Ÿ’พ Save Chats", use_container_width=True):
373
+ data_to_save = {
374
+ "chats": st.session_state.chats,
375
+ "active_chat_key": st.session_state.active_chat_key
376
+ }
377
+ localS.setItem("math_mentor_chats", json.dumps(data_to_save))
378
+ st.toast("Chats saved to your browser!", icon="โœ…")
379
 
380
+ # Download chat button
381
+ active_chat_history = st.session_state.chats[st.session_state.active_chat_key]
382
+ download_str = format_chat_for_download(active_chat_history)
383
+ st.download_button(
384
+ label="๐Ÿ“ฅ Download Chat",
385
+ data=download_str,
386
+ file_name=f"{st.session_state.active_chat_key.replace(' ', '_')}_history.md",
387
+ mime="text/markdown",
388
+ use_container_width=True
389
+ )
390
 
391
+ # Share chat button
392
+ if st.button("๐Ÿ”— Share Chat", use_container_width=True):
393
+ chat_json = json.dumps(st.session_state.chats[st.session_state.active_chat_key])
394
+ chat_b64 = base64.urlsafe_b64encode(chat_json.encode()).decode()
395
+ share_url = f"{st.get_option('server.baseUrlPath')}?shared_chat={chat_b64}"
396
+ st.code(share_url)
397
+ st.info("Copy the URL above to share this specific chat!")
398
+
399
+ st.header(f"Chatting with Math Jegna: _{st.session_state.active_chat_key}_")
400
+
401
+ # Display chat messages
402
+ for message in st.session_state.chats[st.session_state.active_chat_key]:
403
+ with st.chat_message(message["role"]):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
404
  st.markdown(message["content"])
405
 
406
+ # User input
407
+ if prompt := st.chat_input("Ask a K-8 math question..."):
408
+ # Add user message to chat history
409
+ st.session_state.chats[st.session_state.active_chat_key].append({"role": "user", "content": prompt})
410
+ with st.chat_message("user"):
411
+ st.markdown(prompt)
412
 
413
+ # Prepare chat for Gemini API
414
+ gemini_chat_history = [
415
+ {"role": convert_role_for_gemini(m["role"]), "parts": [m["content"]]}
416
+ for m in st.session_state.chats[st.session_state.active_chat_key]
417
+ ]
418
+
419
+ # Generate response
420
+ with st.chat_message("assistant"):
421
+ with st.spinner("Math Jegna is thinking..."):
422
  try:
423
+ chat_session = model.start_chat(history=gemini_chat_history)
424
+ response = chat_session.send_message(prompt, stream=True)
 
 
 
 
 
 
425
 
426
+ full_response = ""
427
+ response_container = st.empty()
428
+ for chunk in response:
429
+ full_response += chunk.text
430
+ response_container.markdown(full_response + " โ–Œ")
431
+ response_container.markdown(full_response)
432
 
433
+ # After generating text, decide if a visual is needed
434
+ if should_generate_visual(prompt, full_response):
435
+ visual_html = create_visual_manipulative(prompt, full_response)
436
+ if visual_html:
437
+ # Display the generated HTML/SVG visual
438
+ components.html(visual_html, height=400, scrolling=True)
439
+
440
+ # Add AI response to session state
441
+ st.session_state.chats[st.session_state.active_chat_key].append({"role": "assistant", "content": full_response})
442
 
443
+ except genai.types.generation_types.BlockedPromptException as e:
444
+ st.error("I can only answer math questions for students. Please ask me about numbers, shapes, or other math topics!")
445
+ st.session_state.chats[st.session_state.active_chat_key].append({"role": "assistant", "content": "I can only answer math questions for students. Please ask me about numbers, shapes, or other math topics!"})
446
  except Exception as e:
447
+ st.error(f"An error occurred: {e}")