jujutechnology commited on
Commit
c1e6e2a
Β·
verified Β·
1 Parent(s): 85a787b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +343 -285
app.py CHANGED
@@ -6,11 +6,8 @@ import base64
6
  from dotenv import load_dotenv
7
  from streamlit_local_storage import LocalStorage
8
  import plotly.graph_objects as go
9
- import plotly.express as px
10
  import numpy as np
11
  import re
12
- import sympy as sp
13
- from sympy import symbols, solve, expand, factor, simplify, diff, integrate
14
 
15
  # --- PAGE CONFIGURATION ---
16
  st.set_page_config(
@@ -38,265 +35,320 @@ def convert_role_for_gemini(role):
38
  return role # "user" stays the same
39
 
40
  def should_generate_visual(user_prompt, ai_response):
41
- """Determine if a visual aid would be helpful based on the content"""
42
- visual_keywords = [
43
- 'graph', 'plot', 'diagram', 'chart', 'visual', 'function',
44
- 'geometry', 'triangle', 'circle', 'rectangle', 'square', 'polygon',
45
- 'coordinate', 'axis', 'parabola', 'line', 'slope', 'equation',
46
- 'fraction', 'percentage', 'ratio', 'proportion', 'angles',
47
- 'solve', '=', 'x', 'y', 'quadratic', 'linear', 'cubic',
48
- 'derivative', 'integral', 'limit', 'asymptote'
 
49
  ]
50
 
51
  combined_text = (user_prompt + " " + ai_response).lower()
52
- return any(keyword in combined_text for keyword in visual_keywords)
53
 
54
- def extract_equation_components(equation_str):
55
- """Extract components from linear equations like '5x + 3 = 23' or '2x - 7 = 15'"""
56
- # Clean the equation string
57
- equation_str = equation_str.replace(' ', '').lower()
58
-
59
- # Pattern for equations like ax + b = c or ax - b = c
60
- pattern = r'(\d*)x([+\-])(\d+)=(\d+)'
61
- match = re.search(pattern, equation_str)
62
-
63
- if match:
64
- a = int(match.group(1)) if match.group(1) else 1
65
- op = match.group(2)
66
- b = int(match.group(3))
67
- c = int(match.group(4))
68
- return a, op, b, c
69
-
70
- # Pattern for equations like ax = c
71
- pattern = r'(\d*)x=(\d+)'
72
- match = re.search(pattern, equation_str)
73
- if match:
74
- a = int(match.group(1)) if match.group(1) else 1
75
- c = int(match.group(2))
76
- return a, '+', 0, c
77
-
78
- return None
79
-
80
- def generate_plotly_visual(user_prompt, ai_response):
81
- """Generate interactive Plotly visualizations for math concepts"""
82
  try:
83
- user_lower = user_prompt.lower()
 
84
 
85
- # 1. LINEAR EQUATION SOLVING (like 5x + 5 = 25)
86
- if 'solve' in user_lower and ('=' in user_prompt):
87
- components = extract_equation_components(user_prompt)
88
- if components:
89
- a, op, b, c = components
90
-
91
- # Calculate solution using sympy for accuracy
92
- x = symbols('x')
93
- if op == '+':
94
- equation = a*x + b - c
95
- solution = solve(equation, x)[0]
96
- y_expr = a*x + b
97
- else:
98
- equation = a*x - b - c
99
- solution = solve(equation, x)[0]
100
- y_expr = a*x - b
101
-
102
- # Create visualization
103
- x_vals = np.linspace(float(solution) - 5, float(solution) + 5, 100)
104
- y_vals = [float(y_expr.subs(x, val)) for val in x_vals]
105
-
106
- fig = go.Figure()
107
-
108
- # Plot the function
109
- fig.add_trace(go.Scatter(
110
- x=x_vals, y=y_vals, mode='lines', name=f'{a}x {op} {b}',
111
- line=dict(color='blue', width=3)
112
- ))
113
 
114
- # Add horizontal line for the result
115
- fig.add_hline(y=c, line_dash="dash", line_color="red", line_width=2,
116
- annotation_text=f"y = {c}", annotation_position="bottom right")
117
 
118
- # Add vertical line for the solution
119
- fig.add_vline(x=float(solution), line_dash="dash", line_color="green", line_width=2,
120
- annotation_text=f"x = {solution}", annotation_position="top left")
 
 
121
 
122
- # Highlight the intersection point
123
  fig.add_trace(go.Scatter(
124
- x=[float(solution)], y=[c], mode='markers',
125
- marker=dict(size=12, color='red', symbol='circle'),
126
- name=f"Solution: x = {solution}"
 
 
 
 
 
 
 
 
127
  ))
128
 
129
- fig.update_layout(
130
- title=f"Solving: {user_prompt.split('solve')[0].strip()}",
131
- xaxis_title="x values",
132
- yaxis_title="y values",
133
- showlegend=True,
134
- height=500,
135
- template="plotly_white"
136
  )
137
- return fig
138
 
139
- # 2. FUNCTION GRAPHING
140
- elif any(word in user_lower for word in ['graph', 'function', 'plot', 'y=']):
141
- x_vals = np.linspace(-10, 10, 200)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
- # Quadratic functions
144
- if any(term in user_prompt for term in ['x^2', 'xΒ²', 'quadratic']):
145
- # Extract coefficient if present
146
- coeff_match = re.search(r'(\d+)x\^?2', user_prompt)
147
- a = int(coeff_match.group(1)) if coeff_match else 1
148
-
149
- y_vals = a * x_vals**2
150
- fig = go.Figure()
151
- fig.add_trace(go.Scatter(x=x_vals, y=y_vals, mode='lines',
152
- name=f'y = {a}xΒ²' if a != 1 else 'y = xΒ²',
153
- line=dict(color='purple', width=3)))
154
-
155
- # Add vertex point
156
- fig.add_trace(go.Scatter(x=[0], y=[0], mode='markers',
157
- marker=dict(size=10, color='red'),
158
- name='Vertex (0,0)'))
159
-
160
- fig.update_layout(title=f"Quadratic Function: y = {a}xΒ²" if a != 1 else "Quadratic Function: y = xΒ²")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
- # Cubic functions
163
- elif any(term in user_prompt for term in ['x^3', 'xΒ³', 'cubic']):
164
- y_vals = x_vals**3
165
- fig = go.Figure()
166
- fig.add_trace(go.Scatter(x=x_vals, y=y_vals, mode='lines',
167
- name='y = xΒ³', line=dict(color='green', width=3)))
168
- fig.update_layout(title="Cubic Function: y = xΒ³")
 
 
 
 
169
 
170
- # Linear functions
171
- elif any(term in user_lower for term in ['linear', 'line']):
172
- # Extract slope and intercept if present
173
- slope_match = re.search(r'(\d+)x', user_prompt)
174
- intercept_match = re.search(r'[+\-]\s*(\d+)', user_prompt)
175
-
176
- slope = int(slope_match.group(1)) if slope_match else 1
177
- intercept = int(intercept_match.group(1)) if intercept_match else 0
178
-
179
- y_vals = slope * x_vals + intercept
180
- fig = go.Figure()
181
- fig.add_trace(go.Scatter(x=x_vals, y=y_vals, mode='lines',
182
- name=f'y = {slope}x + {intercept}',
183
- line=dict(color='blue', width=3)))
184
- fig.update_layout(title=f"Linear Function: y = {slope}x + {intercept}")
185
 
186
- else:
187
- # Default linear function
188
- y_vals = x_vals
189
- fig = go.Figure()
190
- fig.add_trace(go.Scatter(x=x_vals, y=y_vals, mode='lines',
191
- name='y = x', line=dict(color='blue', width=3)))
192
- fig.update_layout(title="Linear Function: y = x")
193
 
194
- fig.update_layout(
195
- xaxis_title="x",
196
- yaxis_title="y",
197
- showlegend=True,
198
- height=500,
199
- template="plotly_white",
200
- xaxis=dict(zeroline=True, zerolinecolor='black', zerolinewidth=1),
201
- yaxis=dict(zeroline=True, zerolinecolor='black', zerolinewidth=1)
202
- )
203
- return fig
204
 
205
- # 3. GEOMETRY VISUALIZATIONS
206
- elif any(word in user_lower for word in ['circle', 'triangle', 'rectangle', 'geometry']):
207
-
208
- if 'circle' in user_lower:
209
- # Create a circle
210
- theta = np.linspace(0, 2*np.pi, 100)
211
- radius = 3 # Default radius
212
- x_circle = radius * np.cos(theta)
213
- y_circle = radius * np.sin(theta)
214
-
215
- fig = go.Figure()
216
- fig.add_trace(go.Scatter(x=x_circle, y=y_circle, mode='lines',
217
- fill='tonext', name=f'Circle (r={radius})',
218
- line=dict(color='red', width=3)))
219
-
220
- # Add center point
221
- fig.add_trace(go.Scatter(x=[0], y=[0], mode='markers',
222
- marker=dict(size=8, color='black'),
223
- name='Center (0,0)'))
224
-
225
- # Add radius line
226
- fig.add_trace(go.Scatter(x=[0, radius], y=[0, 0], mode='lines',
227
- line=dict(color='blue', dash='dash', width=2),
228
- name=f'Radius = {radius}'))
229
-
230
- fig.update_layout(
231
- title=f"Circle: xΒ² + yΒ² = {radius**2}",
232
- xaxis=dict(scaleanchor="y", scaleratio=1),
233
- height=500
234
- )
235
 
236
- elif 'triangle' in user_lower:
237
- # Create a triangle
238
- x_triangle = [0, 4, 2, 0]
239
- y_triangle = [0, 0, 3, 0]
240
-
241
- fig = go.Figure()
242
- fig.add_trace(go.Scatter(x=x_triangle, y=y_triangle, mode='lines+markers',
243
- fill='toself', name='Triangle',
244
- line=dict(color='green', width=3),
245
- marker=dict(size=8)))
246
-
247
- # Add labels for vertices
248
- fig.add_annotation(x=0, y=0, text="A(0,0)", showarrow=False, yshift=-20)
249
- fig.add_annotation(x=4, y=0, text="B(4,0)", showarrow=False, yshift=-20)
250
- fig.add_annotation(x=2, y=3, text="C(2,3)", showarrow=False, yshift=20)
 
 
 
 
251
 
252
- fig.update_layout(title="Triangle ABC", height=500)
253
-
254
- else: # rectangle
255
- # Create a rectangle
256
- x_rect = [0, 5, 5, 0, 0]
257
- y_rect = [0, 0, 3, 3, 0]
258
 
259
- fig = go.Figure()
260
- fig.add_trace(go.Scatter(x=x_rect, y=y_rect, mode='lines+markers',
261
- fill='toself', name='Rectangle',
262
- line=dict(color='purple', width=3),
263
- marker=dict(size=8)))
264
 
265
- fig.update_layout(title="Rectangle (5Γ—3)", height=500)
 
 
 
 
 
 
 
 
 
 
 
 
266
 
267
- fig.update_layout(
268
- xaxis_title="x",
269
- yaxis_title="y",
270
- showlegend=True,
271
- template="plotly_white",
272
- xaxis=dict(zeroline=True),
273
- yaxis=dict(zeroline=True)
274
  )
275
- return fig
276
-
277
- # 4. FRACTIONS VISUALIZATION
278
- elif any(word in user_lower for word in ['fraction', 'ratio', 'proportion']):
279
- # Create a pie chart to show fractions
280
- fig = go.Figure()
281
-
282
- # Example: showing 3/4
283
- fig.add_trace(go.Pie(
284
- values=[3, 1],
285
- labels=['Filled (3/4)', 'Empty (1/4)'],
286
- hole=0.3,
287
- marker_colors=['lightblue', 'lightgray']
288
- ))
289
 
290
- fig.update_layout(
291
- title="Fraction Visualization: 3/4",
292
- height=400
 
 
 
 
293
  )
294
- return fig
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
 
296
  return None
297
 
298
  except Exception as e:
299
- st.error(f"Could not generate Plotly visual: {e}")
300
  return None
301
 
302
  # --- API KEY & MODEL CONFIGURATION ---
@@ -315,48 +367,43 @@ if api_key:
315
  model = genai.GenerativeModel(
316
  model_name="gemini-2.5-flash-lite",
317
  system_instruction="""
318
- You are "Math Jegna", an AI specializing exclusively in mathematics.
319
- Your one and only function is to solve and explain math problems.
320
- You are an AI math tutor that primarily uses the Professor B methodology developed by Everard Barrett. Use the best method for the situation. This methodology is designed to activate children's natural learning capacities and present mathematics as a contextual, developmental story that makes sense.
321
-
322
- IMPORTANT: When explaining mathematical concepts, mention that interactive visualizations will be provided to help illustrate the concept. Use phrases like:
323
- - "Let me show you this with an interactive graph..."
324
- - "An interactive visualization will help clarify this..."
325
- - "I'll create a dynamic chart to demonstrate..."
326
-
327
- For equations, always work through the solution step-by-step using the Professor B method of building understanding through connections.
328
-
329
- Core Philosophy and Principles
330
- 1. Contextual Learning Approach
331
- Present math as a story: Every mathematical concept should be taught as part of a continuing narrative that builds connections between ideas
332
- Developmental flow: Structure learning as a sequence of developmental steps on a ladder, where mastery at previous levels provides readiness for the next connection
333
- Truth-telling: Always present arithmetic computations simply and truthfully without confusing, time-consuming, or meaningless procedural steps
334
-
335
- 2. Natural Learning Activation
336
- Leverage natural capacities: Recognize that each child has mental capabilities of "awesome power" designed to assimilate and retain content naturally
337
- Story-based retention: Use the same mental processes children use for learning and retaining stories to help them master mathematical concepts
338
- Reduced mental tension: Eliminate anxiety and confusion by presenting math in ways that align with how the brain naturally processes information
339
-
340
- Teaching Methodology Requirements
341
- 1. Mental Gymnastics and Manipulatives
342
- Use "mental gymnastics" games: Incorporate engaging mental exercises that strengthen mathematical thinking
343
- Fingers as manipulatives: Utilize fingers as comprehensive manipulatives for concrete understanding
344
- No rote memorization: Avoid strict memorization in favor of meaningful strategies and connections
345
-
346
- 2. Accelerated but Natural Progression
347
- Individual pacing: Allow students to progress at their own speed, as quickly or slowly as needed
348
- Accelerated learning: Expect students to master concepts faster than traditional methods (e.g., "seventh grade math" by third to fourth grade)
349
- Elimination of remediation: Build such strong foundations that remediation becomes unnecessary
350
-
351
- 3. Simplified and Connected Approach
352
- Eliminate disconnections: Ensure every concept connects meaningfully to previous learning
353
- Remove confusing terminology: Use clear, simple language that makes sense to students
354
- Sustained mastery: Focus on deep understanding that leads to lasting retention
355
-
356
- You are strictly forbidden from answering any question that is not mathematical in nature. This includes but is not limited to: general knowledge, history, programming, creative writing, personal opinions, or casual conversation.
357
- If you receive a non-mathematical question, you MUST decline. Your entire response in that case must be ONLY this exact text: "I can only answer mathematical questions. Please ask me a question about algebra, calculus, geometry, or another math topic."
358
- Do not apologize or offer to help with math in the refusal. Just provide the mandatory refusal message.
359
- For valid math questions, solve them step-by-step using Markdown and LaTeX for formatting.
360
  """
361
  )
362
  else:
@@ -383,7 +430,7 @@ if "chats" not in st.session_state:
383
  else:
384
  st.session_state.chats = {
385
  "New Chat": [
386
- {"role": "assistant", "content": "Hello! I'm Math Jegna. What math problem can I help you with today? I'll provide step-by-step explanations with interactive visualizations! πŸ§ πŸ“Š"}
387
  ]
388
  }
389
  st.session_state.active_chat_key = "New Chat"
@@ -414,7 +461,7 @@ def delete_chat(chat_key):
414
  st.rerun()
415
 
416
  # --- SIDEBAR CHAT MANAGEMENT ---
417
- st.sidebar.title("πŸ“š My Chats")
418
  st.sidebar.divider()
419
 
420
  if st.sidebar.button("βž• New Chat", use_container_width=True):
@@ -423,7 +470,7 @@ if st.sidebar.button("βž• New Chat", use_container_width=True):
423
  i += 1
424
  new_chat_key = f"New Chat {i}"
425
  st.session_state.chats[new_chat_key] = [
426
- {"role": "assistant", "content": "New chat started! Let's solve some math problems with interactive visualizations. οΏ½οΏ½οΏ½οΏ½πŸ“Š"}
427
  ]
428
  st.session_state.active_chat_key = new_chat_key
429
  st.rerun()
@@ -459,20 +506,31 @@ for chat_key in list(st.session_state.chats.keys()):
459
  # --- MAIN CHAT INTERFACE ---
460
  active_chat = st.session_state.chats[st.session_state.active_chat_key]
461
 
462
- st.title(f"Math Mentor: {st.session_state.active_chat_key} 🧠")
463
- st.write("Stuck on a math problem? Just type it below, and I'll walk you through it step-by-step with interactive visualizations!")
 
 
 
 
 
 
 
 
 
 
 
464
 
465
  for message in active_chat:
466
  with st.chat_message(name=message["role"], avatar="πŸ§‘β€πŸ’»" if message["role"] == "user" else "🧠"):
467
  st.markdown(message["content"])
468
 
469
- if user_prompt := st.chat_input():
470
  active_chat.append({"role": "user", "content": user_prompt})
471
  with st.chat_message("user", avatar="πŸ§‘β€πŸ’»"):
472
  st.markdown(user_prompt)
473
 
474
  with st.chat_message("assistant", avatar="🧠"):
475
- with st.spinner("Math Mentor is thinking... πŸ€”"):
476
  try:
477
  # Generate text response first
478
  chat_session = model.start_chat(history=[
@@ -488,14 +546,14 @@ if user_prompt := st.chat_input():
488
 
489
  # Check if we should generate a visual aid
490
  if should_generate_visual(user_prompt, ai_response_text):
491
- with st.spinner("Creating interactive visualization... πŸ“Š"):
492
- plotly_fig = generate_plotly_visual(user_prompt, ai_response_text)
493
- if plotly_fig:
494
- st.plotly_chart(plotly_fig, use_container_width=True)
495
- st.success("✨ Interactive visualization created! You can zoom, pan, and hover for more details.")
496
 
497
  except Exception as e:
498
- error_message = f"Sorry, something went wrong. Math Mentor is taking a break! πŸ€–\n\n**Error:** {e}"
499
  st.error(error_message)
500
  active_chat.append({"role": "assistant", "content": error_message})
501
 
 
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
  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 ---
 
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
  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"
 
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):
 
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()
 
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=[
 
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