saad-sust commited on
Commit
71a23fa
ยท
verified ยท
1 Parent(s): a6ab439

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +336 -347
app.py CHANGED
@@ -1,271 +1,235 @@
1
- # โ”€โ”€ Imports โ”€โ”€ (ORDER MATTERS: re must come after sympy wildcard)
 
 
 
 
 
2
  import streamlit as st
3
- import sympy as sp
4
  import os
5
  import requests
 
6
  from sympy import (
7
- symbols, diff, integrate, limit, solve, simplify, expand, factor,
8
- latex, sympify, oo, sin, cos, tan, exp, log, sqrt, pi, E,
9
- Matrix, Symbol, Function, dsolve, Eq,
10
- series, Sum, Product, Rational, Integer, Float, Abs
11
  )
12
  from sympy.parsing.sympy_parser import (
13
  parse_expr,
14
  standard_transformations,
15
  implicit_multiplication_application
16
  )
17
- import re # MUST be last โ€” sympy wildcard would overwrite it otherwise
18
 
19
- # โ”€โ”€โ”€ Page Config โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
20
  st.set_page_config(
21
  page_title="SAAD AI ACADEMY",
22
  page_icon="๐Ÿ“",
23
- layout="wide",
24
  initial_sidebar_state="expanded"
25
  )
26
 
27
- # โ”€โ”€โ”€ CSS Styling โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
28
  st.markdown("""
29
  <style>
30
- @import url('https://fonts.googleapis.com/css2?family=Rajdhani:wght@400;600;700&family=JetBrains+Mono:wght@400;500&family=Nunito:wght@300;400;600&display=swap');
31
-
32
- :root {
33
- --bg: #060b14;
34
- --surface: #0d1526;
35
- --card: #111d35;
36
- --border: #1e3058;
37
- --accent: #38bdf8;
38
- --accent2: #818cf8;
39
- --gold: #f59e0b;
40
- --green: #34d399;
41
- --text: #e2e8f0;
42
- --muted: #64748b;
43
- }
44
 
 
45
  html, body, [class*="css"] {
46
- background-color: var(--bg);
47
- color: var(--text);
48
- font-family: 'Nunito', sans-serif;
 
 
 
 
 
49
  }
50
-
51
  #MainMenu, footer, header { visibility: hidden; }
52
- .block-container { padding: 1.5rem 2rem 2rem 2rem; max-width: 1100px; }
53
-
54
- .hero {
55
- background: linear-gradient(135deg, #0d1526 0%, #0f1f40 50%, #091428 100%);
56
- border: 1px solid var(--border);
57
- border-radius: 16px;
58
- padding: 2rem 2.5rem;
59
- margin-bottom: 1.5rem;
60
- position: relative;
61
- overflow: hidden;
62
  }
63
- .hero::before {
64
- content: "";
65
- position: absolute;
66
- top: -60px; right: -60px;
67
- width: 220px; height: 220px;
68
- background: radial-gradient(circle, rgba(56,189,248,0.12) 0%, transparent 70%);
69
- border-radius: 50%;
70
  }
71
- .hero-title {
72
- font-family: 'Rajdhani', sans-serif;
73
- font-size: 2.4rem;
74
- font-weight: 700;
75
- background: linear-gradient(90deg, var(--accent), var(--accent2));
76
- -webkit-background-clip: text;
77
- -webkit-text-fill-color: transparent;
78
- margin: 0 0 0.3rem 0;
79
- letter-spacing: 2px;
80
  }
81
- .hero-sub {
82
- color: var(--muted);
83
- font-size: 0.95rem;
84
- letter-spacing: 1px;
85
- text-transform: uppercase;
 
86
  }
87
- .badge-row { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 1rem; }
88
  .badge {
89
- background: rgba(56,189,248,0.08);
90
- border: 1px solid rgba(56,189,248,0.25);
91
- color: var(--accent);
92
- padding: 3px 10px;
93
  border-radius: 20px;
94
- font-size: 0.72rem;
95
  font-family: 'JetBrains Mono', monospace;
96
- letter-spacing: 0.5px;
97
  }
98
 
99
- .chat-area {
100
- background: var(--surface);
101
- border: 1px solid var(--border);
102
- border-radius: 14px;
103
- padding: 1.2rem 1.5rem;
104
- min-height: 420px;
105
- max-height: 560px;
106
- overflow-y: auto;
107
- margin-bottom: 1rem;
108
  }
109
 
110
- .msg-user {
111
- display: flex;
112
- justify-content: flex-end;
113
- margin: 0.8rem 0;
114
  }
115
- .msg-user .bubble {
116
- background: linear-gradient(135deg, #1e3a5f, #1a2f50);
117
- border: 1px solid rgba(56,189,248,0.3);
118
- color: var(--text);
119
- padding: 0.75rem 1.1rem;
120
- border-radius: 14px 4px 14px 14px;
121
- max-width: 75%;
122
- font-size: 0.92rem;
123
- line-height: 1.6;
124
- }
125
- .msg-ai {
126
- display: flex;
127
- justify-content: flex-start;
128
- margin: 0.8rem 0;
129
- gap: 0.6rem;
130
- align-items: flex-start;
131
- }
132
- .ai-avatar {
133
- width: 34px; height: 34px;
134
- background: linear-gradient(135deg, var(--accent), var(--accent2));
135
- border-radius: 50%;
136
- display: flex; align-items: center; justify-content: center;
137
- font-size: 0.9rem;
138
- flex-shrink: 0;
139
- color: #fff;
140
  }
141
- .msg-ai .bubble {
142
- background: var(--card);
143
- border: 1px solid var(--border);
144
- color: var(--text);
145
- padding: 0.85rem 1.2rem;
146
- border-radius: 4px 14px 14px 14px;
147
- max-width: 82%;
148
- font-size: 0.92rem;
149
- line-height: 1.7;
 
 
150
  }
151
 
152
- .stTextInput > div > div > input {
153
- background: var(--card) !important;
154
- border: 1px solid var(--border) !important;
155
- border-radius: 10px !important;
156
- color: var(--text) !important;
157
- font-family: 'Nunito', sans-serif !important;
158
- font-size: 0.95rem !important;
159
- padding: 0.75rem 1rem !important;
160
  }
161
- .stTextInput > div > div > input:focus {
162
- border-color: var(--accent) !important;
163
- box-shadow: 0 0 0 2px rgba(56,189,248,0.15) !important;
 
 
 
 
164
  }
165
 
166
- .stButton > button {
167
- background: linear-gradient(135deg, #1e4080, #1a3060) !important;
168
- border: 1px solid var(--accent) !important;
169
- color: var(--accent) !important;
170
- border-radius: 10px !important;
171
- font-family: 'Rajdhani', sans-serif !important;
172
- font-weight: 600 !important;
173
- letter-spacing: 1px !important;
174
- padding: 0.5rem 1.5rem !important;
175
- transition: all 0.2s !important;
176
  }
177
- .stButton > button:hover {
178
- background: linear-gradient(135deg, #2a50a0, #2040a0) !important;
179
- transform: translateY(-1px) !important;
180
- box-shadow: 0 4px 15px rgba(56,189,248,0.2) !important;
181
  }
182
 
 
183
  [data-testid="stSidebar"] {
184
- background: var(--surface) !important;
185
- border-right: 1px solid var(--border) !important;
186
  }
187
- [data-testid="stSidebar"] .stMarkdown h3 {
188
- font-family: 'Rajdhani', sans-serif;
189
- color: var(--accent);
190
- letter-spacing: 1px;
191
  }
192
 
193
- .topic-pill {
194
- background: rgba(129,140,248,0.08);
195
- border: 1px solid rgba(129,140,248,0.2);
196
- color: var(--accent2);
197
- padding: 4px 12px;
198
- border-radius: 20px;
199
- font-size: 0.78rem;
200
- margin: 3px 2px;
201
- display: inline-block;
202
- font-family: 'JetBrains Mono', monospace;
 
 
 
 
 
 
203
  }
204
 
205
- ::-webkit-scrollbar { width: 6px; }
206
- ::-webkit-scrollbar-track { background: var(--surface); }
207
- ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
208
- ::-webkit-scrollbar-thumb:hover { background: var(--muted); }
209
 
210
- .stSelectbox > div > div {
211
- background: var(--card) !important;
212
- border-color: var(--border) !important;
213
- color: var(--text) !important;
214
- border-radius: 10px !important;
215
- }
216
 
217
- /* โ”€โ”€ st.chat_message styling โ”€โ”€ */
218
- [data-testid="stChatMessage"] {
219
- background: #111d35 !important;
220
- border: 1px solid #1e3058 !important;
221
- border-radius: 12px !important;
222
- padding: 0.8rem 1rem !important;
223
- margin: 0.5rem 0 !important;
224
- }
225
- [data-testid="stChatMessageContent"] p {
226
- color: #e2e8f0 !important;
227
- font-family: 'Nunito', sans-serif !important;
228
- font-size: 0.93rem !important;
229
- line-height: 1.8 !important;
230
- }
231
- /* Make math blocks stand out */
232
- .katex-display {
233
- background: rgba(56,189,248,0.05) !important;
234
- border-left: 3px solid #38bdf8 !important;
235
- padding: 0.5rem 1rem !important;
236
- border-radius: 0 8px 8px 0 !important;
237
- margin: 0.5rem 0 !important;
238
  }
239
-
240
  </style>
241
  """, unsafe_allow_html=True)
242
 
243
- # โ”€โ”€โ”€ Session State โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
244
  if "messages" not in st.session_state:
245
  st.session_state.messages = []
246
  if "last_submitted" not in st.session_state:
247
- st.session_state.last_submitted = "" # prevents infinite rerun loop
248
 
249
- # โ”€โ”€โ”€ SymPy Engine โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 
 
 
250
  def run_sympy(problem: str) -> dict:
251
  """
252
- Safely attempt symbolic computation.
253
- Uses sp.* explicitly โ€” no wildcard collisions possible here.
 
254
  """
255
  p = problem.lower().strip()
256
  x = sp.Symbol('x')
257
- default = {"type": "general", "result": None, "latex": ""}
258
-
259
- try:
260
- tfms = standard_transformations + (implicit_multiplication_application,)
 
 
 
 
 
 
 
 
261
 
262
- def clean(s):
263
- """Normalise expression string for sympy parsing."""
264
- s = re.sub(r"\s+", "", s)
265
- s = re.sub(r"\^", "**", s)
266
- return s
267
 
268
- # โ”€โ”€ Derivative โ”€โ”€
 
269
  if any(k in p for k in ["derivative", "differentiate", "d/dx", "diff"]):
270
  raw = p
271
  for kw in ["derivative of", "differentiate", "diff of", "d/dx of", "d/dx"]:
@@ -273,11 +237,11 @@ def run_sympy(problem: str) -> dict:
273
  raw = p.split(kw, 1)[-1].strip()
274
  break
275
  raw = re.sub(r"\s*(dx|with\s*respect\s*to\s*x).*$", "", raw).strip()
276
- expr = parse_expr(clean(raw), transformations=tfms, local_dict={"x": x, "e": sp.E, "E": sp.E, "pi": sp.pi, "ln": sp.log, "inf": sp.oo})
277
  result = sp.diff(expr, x)
278
  return {"type": "Derivative", "result": str(result), "latex": sp.latex(result)}
279
 
280
- # โ”€โ”€ Integral โ”€โ”€
281
  elif any(k in p for k in ["integral", "integrate", "antiderivative"]):
282
  raw = p
283
  for kw in ["integral of", "integrate", "antiderivative of"]:
@@ -285,253 +249,278 @@ def run_sympy(problem: str) -> dict:
285
  raw = p.split(kw, 1)[-1].strip()
286
  break
287
  raw = re.sub(r"\s*dx.*$", "", raw).strip()
288
- expr = parse_expr(clean(raw), transformations=tfms, local_dict={"x": x, "e": sp.E, "E": sp.E, "pi": sp.pi, "ln": sp.log, "inf": sp.oo})
289
  result = sp.integrate(expr, x)
290
  return {"type": "Integral", "result": str(result), "latex": sp.latex(result)}
291
 
292
- # โ”€โ”€ Limit โ”€โ”€
293
  elif "limit" in p:
294
  match = re.search(
295
- r"limit\s+of\s+([\w\s\(\)\+\-\*/\^\.\,]+?)\s+as\s+x\s*(?:->|โ†’|approaches)\s*([\w\.\+\-]+)",
 
296
  p
297
  )
298
  if match:
299
  raw_expr = clean(match.group(1))
300
- point_str = match.group(2).strip()
301
- point = sp.oo if point_str in ("inf", "infinity", "oo") else sp.sympify(point_str)
302
- expr = parse_expr(raw_expr, transformations=tfms, local_dict={"x": x, "e": sp.E, "E": sp.E, "pi": sp.pi, "ln": sp.log, "inf": sp.oo})
303
  result = sp.limit(expr, x, point)
304
  return {"type": "Limit", "result": str(result), "latex": sp.latex(result)}
305
 
306
- # โ”€โ”€ Solve equation โ”€โ”€
307
  elif any(k in p for k in ["solve", "roots", "find x"]):
308
- raw = re.sub(r"(solve|find x|roots of|roots|the equation)", "", p).strip().strip(": ")
 
309
  if "=" in raw:
310
  lhs_s, rhs_s = raw.split("=", 1)
311
- lhs_e = parse_expr(clean(lhs_s), transformations=tfms, local_dict={"x": x, "e": sp.E, "E": sp.E, "pi": sp.pi, "ln": sp.log, "inf": sp.oo})
312
- rhs_e = parse_expr(clean(rhs_s), transformations=tfms, local_dict={"x": x, "e": sp.E, "E": sp.E, "pi": sp.pi, "ln": sp.log, "inf": sp.oo})
313
  expr = lhs_e - rhs_e
314
  else:
315
- expr = parse_expr(clean(raw), transformations=tfms, local_dict={"x": x, "e": sp.E, "E": sp.E, "pi": sp.pi, "ln": sp.log, "inf": sp.oo})
 
316
  if x in expr.free_symbols:
317
  sol = sp.solve(expr, x)
318
  sol_latex = ", ".join([sp.latex(s) for s in sol])
319
- return {"type": "Equation", "result": str(sol), "latex": f"x \\in \\{{{sol_latex}\\}}"}
320
-
321
- # โ”€โ”€ Matrix / eigenvalues โ€” delegate to Claude โ”€โ”€
322
- elif any(k in p for k in ["matrix", "determinant", "eigenvalue", "eigenvector", "det("]):
 
 
 
 
 
323
  return {"type": "Matrix", "result": "matrix_detected", "latex": ""}
324
 
325
- # โ”€โ”€ Modular arithmetic โ€” delegate to Claude โ”€โ”€
326
  elif "mod" in p or "congruence" in p:
327
  return {"type": "NumberTheory", "result": "mod_detected", "latex": ""}
328
 
329
  except Exception:
330
- pass # fall back gracefully; Claude handles it without sympy hint
331
 
332
- return default
333
 
334
 
335
- # โ”€โ”€โ”€ Groq API (100% free, fast, reliable) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
336
- def ask_claude(problem: str, sympy_info: dict, history: list) -> str:
337
- import requests
338
-
339
  api_key = os.environ.get("GROQ_API_KEY", "")
340
  if not api_key:
341
  return (
342
  "โš ๏ธ **GROQ_API_KEY not set.**\n\n"
343
- "1. Go to https://console.groq.com\n"
344
- "2. Sign up free โ†’ API Keys โ†’ Create API Key\n"
345
- "3. HF Space โ†’ Settings โ†’ Secrets โ†’ add GROQ_API_KEY"
 
346
  )
347
 
 
348
  sympy_context = ""
349
  if (sympy_info.get("result")
350
  and sympy_info["result"] not in (None, "matrix_detected", "mod_detected")):
351
  sympy_context = (
352
- f"SYMPY VERIFIED RESULT (your final answer MUST match this):\n"
353
- f"- Operation: {sympy_info.get('type', 'N/A')}\n"
354
- f"- Result: {sympy_info.get('result', 'N/A')}\n"
355
- f"- LaTeX: {sympy_info.get('latex', 'N/A')}\n"
356
  )
357
 
358
  system_prompt = (
359
- "You are SAAD AI, an elite BSc Mathematics tutor. "
360
- "Solve every problem with perfect step-by-step working. "
361
- "Label every step: Step 1:, Step 2:, etc. "
362
- "Use $...$ for inline math and $$...$$ for display equations. "
363
- "State the method or theorem used at the start. "
364
- "Show ALL intermediate algebra, never skip steps. "
365
- "End every solution with: **Final Answer:** $$...$$ "
366
- "NEVER make arithmetic errors. "
 
 
 
 
 
 
 
 
 
367
  "Topics: Calculus, Linear Algebra, Number Theory, ODEs, "
368
  "Numerical Methods, Differential Geometry, Hydro Mechanics, "
369
- "Theory of Numbers, Real Analysis II, General Math.\n\n"
370
  + sympy_context
371
  )
372
 
 
373
  messages = [{"role": "system", "content": system_prompt}]
374
- for msg in history[-8:]:
375
  messages.append({"role": msg["role"], "content": msg["content"]})
376
  messages.append({"role": "user", "content": problem})
377
 
378
- headers = {
379
- "Authorization": f"Bearer {api_key}",
380
- "Content-Type": "application/json"
381
- }
382
- payload = {
383
- "model": "llama-3.3-70b-versatile",
384
- "messages": messages,
385
- "max_tokens": 2048,
386
- "temperature": 0.2
387
- }
388
-
389
  try:
390
  resp = requests.post(
391
  "https://api.groq.com/openai/v1/chat/completions",
392
- headers=headers,
393
- json=payload,
 
 
 
 
 
 
 
 
 
394
  timeout=60
395
  )
396
  resp.raise_for_status()
397
  return resp.json()["choices"][0]["message"]["content"]
 
398
  except requests.exceptions.Timeout:
399
- return "โณ Request timed out. Please try again."
 
 
 
 
 
 
 
400
  except Exception as e:
401
- return f"โš ๏ธ Error: {str(e)}"
402
 
403
 
404
- # โ”€โ”€โ”€ Sidebar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 
 
405
  with st.sidebar:
406
- st.markdown("### ๐Ÿ“š SAAD AI ACADEMY")
407
- st.markdown("---")
 
408
 
409
- st.markdown("**๐ŸŽฏ Topics Covered**")
410
  topics = [
411
- "Calculus", "Linear Algebra", "Number Theory",
412
- "Differential Equations", "Numerical Methods",
413
- "Differential Geometry", "Hydro Mechanics",
414
- "Theory of Numbers", "Real Analysis II", "General Math"
415
  ]
416
- pills_html = "".join([f'<span class="topic-pill">{t}</span>' for t in topics])
417
- st.markdown(pills_html, unsafe_allow_html=True)
418
 
419
- st.markdown("---")
420
- st.markdown("**โšก Quick Examples**")
421
 
422
  examples = {
423
- "-- Choose --": "",
424
- "๐Ÿ”ข Derivative": "Find the derivative of x^3 + 5x^2 - 3x + 7",
425
  "โˆซ Integral": "Integrate sin(x) * e^x dx",
426
- "๐Ÿ“Š Eigenvalue": "Find eigenvalues of matrix [[3,1],[1,3]]",
427
- "๐Ÿ” Congruence": "Solve 14x โ‰ก 30 (mod 44)",
428
- "๐ŸŒŠ ODE": "Solve dy/dx + 2y = e^x",
429
  "๐Ÿ“ Limit": "Find limit of sin(x)/x as x -> 0",
430
- "๐Ÿ” Series": "Test convergence of sum 1/n^2 from n=1 to infinity",
431
- "๐Ÿ“ Newton-Raphson": "Apply Newton-Raphson to find root of x^3 - 2x - 5 = 0",
 
 
 
 
 
432
  }
433
 
434
- selected_example = st.selectbox(
435
- "Load an example:",
436
  list(examples.keys()),
437
- key="example_select"
 
438
  )
439
 
440
- st.markdown("---")
441
  st.markdown("**๐Ÿ”ง Engine**")
442
- st.markdown("""
443
- <div style="font-size:0.8rem; color:#64748b; line-height:1.8;">
444
- ๐Ÿค– <b style="color:#38bdf8">Claude AI</b> โ€” reasoning & steps<br>
445
- ๐Ÿงฎ <b style="color:#34d399">SymPy</b> โ€” verified computation<br>
446
- โœ… Zero arithmetic errors
447
- </div>
448
- """, unsafe_allow_html=True)
449
 
450
- st.markdown("---")
451
- if st.button("๐Ÿ—‘๏ธ Clear Chat"):
452
  st.session_state.messages = []
453
  st.session_state.last_submitted = ""
454
  st.rerun()
455
 
456
 
457
- # โ”€โ”€โ”€ Hero Banner โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 
 
 
 
458
  st.markdown("""
459
- <div class="hero">
460
- <div class="hero-title">๐Ÿ“ SAAD AI ACADEMY</div>
461
- <div class="hero-sub">BSc Mathematics โ€” Computational Engine</div>
462
  <div class="badge-row">
463
- <span class="badge">Claude AI</span>
464
  <span class="badge">SymPy Verified</span>
465
  <span class="badge">Step-by-Step</span>
466
- <span class="badge">Zero Errors</span>
467
- <span class="badge">LaTeX Output</span>
468
  </div>
469
  </div>
470
  """, unsafe_allow_html=True)
471
 
472
-
473
- # โ”€โ”€โ”€ Chat Display (uses st.chat_message for proper LaTeX rendering) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
474
  if not st.session_state.messages:
475
  st.markdown("""
476
- <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;
477
- height:260px;text-align:center;
478
- background:#0d1526;border:1px solid #1e3058;border-radius:14px;
479
- margin-bottom:1rem;">
480
- <div style="font-size:3rem;margin-bottom:0.8rem;">๐Ÿ“</div>
481
- <div style="font-family:'Rajdhani',sans-serif;font-size:1.2rem;
482
- color:#475569;letter-spacing:1px;">
483
- Ask any BSc Mathematics problem
484
- </div>
485
- <div style="font-size:0.82rem;color:#334155;margin-top:0.5rem;">
486
  Calculus ยท Linear Algebra ยท Number Theory ยท ODEs ยท Real Analysis ยท and more
 
487
  </div>
488
  </div>
489
  """, unsafe_allow_html=True)
490
- else:
491
- for msg in st.session_state.messages:
492
- if msg["role"] == "user":
493
- with st.chat_message("user", avatar="๐Ÿง‘โ€๐ŸŽ“"):
494
- st.markdown(msg["content"])
495
- else:
496
- with st.chat_message("assistant", avatar="๐Ÿ“"):
497
- st.markdown(msg["content"])
498
-
499
- # โ”€โ”€โ”€ Input Row โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
500
- col1, col2 = st.columns([5, 1])
501
-
502
- with col1:
503
- prefill = examples.get(selected_example, "") if selected_example != "-- Choose --" else ""
504
- user_input = st.text_input(
505
- label="problem_input",
506
- value=prefill,
507
- placeholder="Type a BSc math problem... e.g. 'Solve dยฒy/dxยฒ + 4y = cos(2x)'",
508
- label_visibility="collapsed",
509
- key="user_input_field"
510
- )
511
 
512
- with col2:
513
- send_btn = st.button("SOLVE โ†’", use_container_width=True)
 
 
 
 
 
 
 
 
 
 
514
 
 
 
 
 
515
 
516
- # โ”€โ”€โ”€ Process Input โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
517
- # Only fires when SOLVE is clicked, input is non-empty, AND it's a new problem
518
- # This prevents the infinite rerun loop caused by value="" pre-fill on every cycle
519
- problem = user_input.strip() if user_input else ""
 
 
 
520
 
521
- if send_btn and problem and problem != st.session_state.last_submitted:
 
 
 
522
  st.session_state.last_submitted = problem
523
 
524
- with st.spinner("๐Ÿงฎ Running SymPy verification + Claude reasoning..."):
525
- sympy_result = run_sympy(problem)
526
- try:
527
- answer = ask_claude(problem, sympy_result, st.session_state.messages)
528
- except Exception as e:
529
- answer = (
530
- f"โš ๏ธ **API Error:** {str(e)}\n\n"
531
- "Please ensure **ANTHROPIC_API_KEY** is added in your Hugging Face Space โ†’ "
532
- "Settings โ†’ Variables and Secrets."
533
- )
534
 
 
535
  st.session_state.messages.append({"role": "user", "content": problem})
536
  st.session_state.messages.append({"role": "assistant", "content": answer})
537
- st.rerun()
 
1
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
2
+ # SAAD AI ACADEMY โ€” BSc Mathematics Solver
3
+ # Engine: Groq (free) + SymPy (verified computation)
4
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
5
+
6
+ # โ”€โ”€ Imports (order matters: stdlib/third-party before sympy, re LAST) โ”€โ”€
7
  import streamlit as st
 
8
  import os
9
  import requests
10
+ import sympy as sp
11
  from sympy import (
12
+ symbols, diff, integrate, limit, solve,
13
+ sympify, oo, sin, cos, tan, exp, log, sqrt, pi, E,
14
+ Matrix, Symbol, Function, dsolve, Eq
 
15
  )
16
  from sympy.parsing.sympy_parser import (
17
  parse_expr,
18
  standard_transformations,
19
  implicit_multiplication_application
20
  )
21
+ import re # MUST be last โ€” 'from sympy import *' would overwrite re otherwise
22
 
23
+ # โ”€โ”€ Page config โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
24
  st.set_page_config(
25
  page_title="SAAD AI ACADEMY",
26
  page_icon="๐Ÿ“",
27
+ layout="centered",
28
  initial_sidebar_state="expanded"
29
  )
30
 
31
+ # โ”€โ”€ CSS โ€” ChatGPT-style, compact font, clean dark theme โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
32
  st.markdown("""
33
  <style>
34
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
+ /* โ”€โ”€ Base โ”€โ”€ */
37
  html, body, [class*="css"] {
38
+ font-family: 'Inter', sans-serif !important;
39
+ font-size: 14px !important;
40
+ background-color: #0f0f0f !important;
41
+ color: #ececec !important;
42
+ }
43
+ .block-container {
44
+ max-width: 780px !important;
45
+ padding: 1rem 1.5rem 2rem 1.5rem !important;
46
  }
 
47
  #MainMenu, footer, header { visibility: hidden; }
48
+
49
+ /* โ”€โ”€ Header โ”€โ”€ */
50
+ .saad-header {
51
+ text-align: center;
52
+ padding: 1.2rem 0 0.5rem 0;
53
+ margin-bottom: 0.5rem;
 
 
 
 
54
  }
55
+ .saad-header h1 {
56
+ font-size: 1.5rem !important;
57
+ font-weight: 600;
58
+ color: #ffffff;
59
+ margin: 0;
60
+ letter-spacing: 0.5px;
 
61
  }
62
+ .saad-header p {
63
+ font-size: 0.78rem;
64
+ color: #666;
65
+ margin: 0.2rem 0 0 0;
 
 
 
 
 
66
  }
67
+ .badge-row {
68
+ display: flex;
69
+ gap: 0.4rem;
70
+ justify-content: center;
71
+ flex-wrap: wrap;
72
+ margin-top: 0.6rem;
73
  }
 
74
  .badge {
75
+ background: #1a1a1a;
76
+ border: 1px solid #2a2a2a;
77
+ color: #888;
78
+ padding: 2px 8px;
79
  border-radius: 20px;
80
+ font-size: 0.68rem;
81
  font-family: 'JetBrains Mono', monospace;
 
82
  }
83
 
84
+ /* โ”€โ”€ Chat messages โ€” override Streamlit defaults โ”€โ”€ */
85
+ [data-testid="stChatMessage"] {
86
+ background: transparent !important;
87
+ border: none !important;
88
+ padding: 0.3rem 0 !important;
89
+ margin: 0 !important;
90
+ gap: 0.6rem !important;
 
 
91
  }
92
 
93
+ /* User message */
94
+ [data-testid="stChatMessage"]:has([data-testid="chatAvatarIcon-user"]) {
95
+ flex-direction: row-reverse !important;
 
96
  }
97
+ [data-testid="stChatMessage"]:has([data-testid="chatAvatarIcon-user"])
98
+ [data-testid="stChatMessageContent"] {
99
+ background: #1e3a5f !important;
100
+ border: 1px solid #2a4a7f !important;
101
+ border-radius: 18px 4px 18px 18px !important;
102
+ padding: 0.6rem 0.9rem !important;
103
+ max-width: 80% !important;
104
+ font-size: 0.88rem !important;
105
+ color: #e2e8f0 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  }
107
+
108
+ /* Assistant message */
109
+ [data-testid="stChatMessage"]:has([data-testid="chatAvatarIcon-assistant"])
110
+ [data-testid="stChatMessageContent"] {
111
+ background: #1a1a1a !important;
112
+ border: 1px solid #2a2a2a !important;
113
+ border-radius: 4px 18px 18px 18px !important;
114
+ padding: 0.7rem 1rem !important;
115
+ font-size: 0.88rem !important;
116
+ line-height: 1.75 !important;
117
+ color: #ececec !important;
118
  }
119
 
120
+ /* Math display blocks */
121
+ .katex-display {
122
+ background: #161616 !important;
123
+ border-left: 3px solid #3b82f6 !important;
124
+ border-radius: 0 6px 6px 0 !important;
125
+ padding: 0.5rem 1rem !important;
126
+ margin: 0.4rem 0 !important;
127
+ overflow-x: auto !important;
128
  }
129
+ .katex { font-size: 1em !important; }
130
+
131
+ /* Step labels bold + colored */
132
+ [data-testid="stChatMessageContent"] p {
133
+ margin: 0.2rem 0 !important;
134
+ font-size: 0.88rem !important;
135
+ line-height: 1.75 !important;
136
  }
137
 
138
+ /* โ”€โ”€ Input box โ”€โ”€ */
139
+ [data-testid="stChatInput"] {
140
+ background: #1a1a1a !important;
141
+ border: 1px solid #2a2a2a !important;
142
+ border-radius: 12px !important;
 
 
 
 
 
143
  }
144
+ [data-testid="stChatInput"] textarea {
145
+ font-size: 0.88rem !important;
146
+ color: #ececec !important;
147
+ background: transparent !important;
148
  }
149
 
150
+ /* โ”€โ”€ Sidebar โ”€โ”€ */
151
  [data-testid="stSidebar"] {
152
+ background: #0a0a0a !important;
153
+ border-right: 1px solid #1a1a1a !important;
154
  }
155
+ [data-testid="stSidebar"] * {
156
+ font-size: 0.82rem !important;
 
 
157
  }
158
 
159
+ /* โ”€โ”€ Buttons โ”€โ”€ */
160
+ .stButton > button {
161
+ background: #1a1a1a !important;
162
+ border: 1px solid #2a2a2a !important;
163
+ color: #888 !important;
164
+ border-radius: 8px !important;
165
+ font-size: 0.78rem !important;
166
+ padding: 0.3rem 0.8rem !important;
167
+ width: 100% !important;
168
+ text-align: left !important;
169
+ transition: all 0.15s !important;
170
+ }
171
+ .stButton > button:hover {
172
+ border-color: #3b82f6 !important;
173
+ color: #ececec !important;
174
+ background: #161b2e !important;
175
  }
176
 
177
+ /* โ”€โ”€ Spinner โ”€โ”€ */
178
+ .stSpinner > div { border-top-color: #3b82f6 !important; }
 
 
179
 
180
+ /* โ”€โ”€ Divider โ”€โ”€ */
181
+ hr { border-color: #1a1a1a !important; }
 
 
 
 
182
 
183
+ /* โ”€โ”€ Selectbox โ”€โ”€ */
184
+ .stSelectbox > div > div {
185
+ background: #1a1a1a !important;
186
+ border-color: #2a2a2a !important;
187
+ color: #ececec !important;
188
+ border-radius: 8px !important;
189
+ font-size: 0.82rem !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  }
 
191
  </style>
192
  """, unsafe_allow_html=True)
193
 
194
+ # โ”€โ”€ Session state โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
195
  if "messages" not in st.session_state:
196
  st.session_state.messages = []
197
  if "last_submitted" not in st.session_state:
198
+ st.session_state.last_submitted = ""
199
 
200
+
201
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
202
+ # SYMPY ENGINE โ€” exact symbolic computation, no AI needed for this
203
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
204
  def run_sympy(problem: str) -> dict:
205
  """
206
+ Attempt symbolic computation using SymPy.
207
+ Returns verified result dict. Falls back silently on any error.
208
+ All sympy calls use sp.* explicitly to avoid wildcard name collisions.
209
  """
210
  p = problem.lower().strip()
211
  x = sp.Symbol('x')
212
+ tfms = standard_transformations + (implicit_multiplication_application,)
213
+
214
+ # local_dict maps common names to correct sympy objects
215
+ # Critical: 'e' must map to E (Euler's number), not unknown symbol
216
+ ld = {
217
+ "x": x,
218
+ "e": sp.E, "E": sp.E,
219
+ "pi": sp.pi, "PI": sp.pi,
220
+ "sin": sp.sin, "cos": sp.cos, "tan": sp.tan,
221
+ "exp": sp.exp, "log": sp.log, "ln": sp.log,
222
+ "sqrt": sp.sqrt, "inf": sp.oo, "oo": sp.oo
223
+ }
224
 
225
+ def clean(s):
226
+ """Normalize expression: strip spaces, convert ^ to **"""
227
+ s = re.sub(r"\s+", "", s)
228
+ s = re.sub(r"\^", "**", s)
229
+ return s
230
 
231
+ try:
232
+ # โ”€โ”€ Derivative โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
233
  if any(k in p for k in ["derivative", "differentiate", "d/dx", "diff"]):
234
  raw = p
235
  for kw in ["derivative of", "differentiate", "diff of", "d/dx of", "d/dx"]:
 
237
  raw = p.split(kw, 1)[-1].strip()
238
  break
239
  raw = re.sub(r"\s*(dx|with\s*respect\s*to\s*x).*$", "", raw).strip()
240
+ expr = parse_expr(clean(raw), transformations=tfms, local_dict=ld)
241
  result = sp.diff(expr, x)
242
  return {"type": "Derivative", "result": str(result), "latex": sp.latex(result)}
243
 
244
+ # โ”€โ”€ Integral โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
245
  elif any(k in p for k in ["integral", "integrate", "antiderivative"]):
246
  raw = p
247
  for kw in ["integral of", "integrate", "antiderivative of"]:
 
249
  raw = p.split(kw, 1)[-1].strip()
250
  break
251
  raw = re.sub(r"\s*dx.*$", "", raw).strip()
252
+ expr = parse_expr(clean(raw), transformations=tfms, local_dict=ld)
253
  result = sp.integrate(expr, x)
254
  return {"type": "Integral", "result": str(result), "latex": sp.latex(result)}
255
 
256
+ # โ”€โ”€ Limit โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
257
  elif "limit" in p:
258
  match = re.search(
259
+ r"limit\s+of\s+([\w\s\(\)\+\-\*/\^\.\,]+?)"
260
+ r"\s+as\s+x\s*(?:->|โ†’|approaches)\s*([\w\.\+\-]+)",
261
  p
262
  )
263
  if match:
264
  raw_expr = clean(match.group(1))
265
+ pt = match.group(2).strip()
266
+ point = sp.oo if pt in ("inf", "infinity", "oo") else sp.sympify(pt)
267
+ expr = parse_expr(raw_expr, transformations=tfms, local_dict=ld)
268
  result = sp.limit(expr, x, point)
269
  return {"type": "Limit", "result": str(result), "latex": sp.latex(result)}
270
 
271
+ # โ”€โ”€ Solve equation โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
272
  elif any(k in p for k in ["solve", "roots", "find x"]):
273
+ raw = re.sub(r"(solve|find x|roots of|roots|the equation)", "", p)
274
+ raw = raw.strip().strip(":").strip()
275
  if "=" in raw:
276
  lhs_s, rhs_s = raw.split("=", 1)
277
+ lhs_e = parse_expr(clean(lhs_s), transformations=tfms, local_dict=ld)
278
+ rhs_e = parse_expr(clean(rhs_s), transformations=tfms, local_dict=ld)
279
  expr = lhs_e - rhs_e
280
  else:
281
+ expr = parse_expr(clean(raw), transformations=tfms, local_dict=ld)
282
+ # Guard: only solve if x is actually in the expression
283
  if x in expr.free_symbols:
284
  sol = sp.solve(expr, x)
285
  sol_latex = ", ".join([sp.latex(s) for s in sol])
286
+ return {
287
+ "type": "Equation",
288
+ "result": str(sol),
289
+ "latex": r"x \in \{" + sol_latex + r"\}"
290
+ }
291
+
292
+ # โ”€โ”€ Matrix / Eigenvalues โ€” too varied, delegate to AI โ”€โ”€โ”€โ”€โ”€โ”€โ”€
293
+ elif any(k in p for k in ["matrix", "determinant", "eigenvalue",
294
+ "eigenvector", "det("]):
295
  return {"type": "Matrix", "result": "matrix_detected", "latex": ""}
296
 
297
+ # โ”€โ”€ Modular arithmetic โ€” delegate to AI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
298
  elif "mod" in p or "congruence" in p:
299
  return {"type": "NumberTheory", "result": "mod_detected", "latex": ""}
300
 
301
  except Exception:
302
+ pass # Silently fall back โ€” AI handles it without sympy hint
303
 
304
+ return {"type": "general", "result": None, "latex": ""}
305
 
306
 
307
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
308
+ # GROQ API โ€” free, fast, Llama 3.3 70B
309
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
310
+ def ask_ai(problem: str, sympy_info: dict, history: list) -> str:
311
  api_key = os.environ.get("GROQ_API_KEY", "")
312
  if not api_key:
313
  return (
314
  "โš ๏ธ **GROQ_API_KEY not set.**\n\n"
315
+ "**Steps to fix:**\n"
316
+ "1. Go to https://console.groq.com โ†’ sign up free\n"
317
+ "2. API Keys โ†’ Create API Key โ†’ copy it\n"
318
+ "3. HF Space โ†’ Settings โ†’ Secrets โ†’ add `GROQ_API_KEY`"
319
  )
320
 
321
+ # Build sympy context if we have a verified result
322
  sympy_context = ""
323
  if (sympy_info.get("result")
324
  and sympy_info["result"] not in (None, "matrix_detected", "mod_detected")):
325
  sympy_context = (
326
+ "\n\nSYMPY VERIFIED RESULT โ€” your final answer MUST match this exactly:\n"
327
+ f" Operation : {sympy_info.get('type', '')}\n"
328
+ f" Result : {sympy_info.get('result', '')}\n"
329
+ f" LaTeX : {sympy_info.get('latex', '')}\n"
330
  )
331
 
332
  system_prompt = (
333
+ "You are SAAD AI, a precise BSc Mathematics tutor.\n\n"
334
+ "STRICT FORMATTING RULES โ€” follow every rule exactly:\n"
335
+ "1. Start with the method name: e.g. **Method: Integration by Parts**\n"
336
+ "2. Label steps: **Step 1:**, **Step 2:**, etc.\n"
337
+ "3. ALL math must be in LaTeX โ€” NEVER write math in plain text.\n"
338
+ " - Inline math: $expression$\n"
339
+ " - Display math (new line, centered): $$expression$$\n"
340
+ "4. NEVER write the same expression twice (once in LaTeX, once plain).\n"
341
+ " BAD: 'x = 2x = 2' GOOD: '$x = 2$'\n"
342
+ "5. Use emojis to mark sections:\n"
343
+ " ๐Ÿ” for identifying/given info\n"
344
+ " ๐Ÿงฎ for computation steps\n"
345
+ " โœ… for the final answer\n"
346
+ "6. End EVERY solution with:\n"
347
+ " โœ… **Final Answer:** $$your_answer$$\n"
348
+ "7. NEVER make arithmetic errors.\n"
349
+ "8. Keep explanations concise โ€” one sentence per step.\n\n"
350
  "Topics: Calculus, Linear Algebra, Number Theory, ODEs, "
351
  "Numerical Methods, Differential Geometry, Hydro Mechanics, "
352
+ "Theory of Numbers, Real Analysis II, General Math."
353
  + sympy_context
354
  )
355
 
356
+ # Build messages โ€” last 6 exchanges for context
357
  messages = [{"role": "system", "content": system_prompt}]
358
+ for msg in history[-12:]:
359
  messages.append({"role": msg["role"], "content": msg["content"]})
360
  messages.append({"role": "user", "content": problem})
361
 
 
 
 
 
 
 
 
 
 
 
 
362
  try:
363
  resp = requests.post(
364
  "https://api.groq.com/openai/v1/chat/completions",
365
+ headers={
366
+ "Authorization": f"Bearer {api_key}",
367
+ "Content-Type": "application/json"
368
+ },
369
+ json={
370
+ "model": "llama-3.3-70b-versatile",
371
+ "messages": messages,
372
+ "max_tokens": 2048,
373
+ "temperature": 0.15, # low = precise, no hallucinations
374
+ "top_p": 0.9
375
+ },
376
  timeout=60
377
  )
378
  resp.raise_for_status()
379
  return resp.json()["choices"][0]["message"]["content"]
380
+
381
  except requests.exceptions.Timeout:
382
+ return "โณ Request timed out. Please click **SOLVE** again."
383
+ except requests.exceptions.HTTPError as e:
384
+ code = e.response.status_code if e.response else "?"
385
+ if code == 429:
386
+ return "โณ Rate limit hit. Please wait 10 seconds and try again."
387
+ if code == 401:
388
+ return "โš ๏ธ Invalid GROQ_API_KEY. Please check your key in HF Secrets."
389
+ return f"โš ๏ธ HTTP Error {code}: {str(e)}"
390
  except Exception as e:
391
+ return f"โš ๏ธ Unexpected error: {str(e)}"
392
 
393
 
394
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
395
+ # SIDEBAR
396
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
397
  with st.sidebar:
398
+ st.markdown("### ๐Ÿ“ SAAD AI ACADEMY")
399
+ st.caption("BSc Mathematics Engine")
400
+ st.divider()
401
 
402
+ st.markdown("**๐ŸŽฏ Topics**")
403
  topics = [
404
+ "๐Ÿ“ˆ Calculus", "๐Ÿ”ข Linear Algebra", "๐Ÿ” Number Theory",
405
+ "๐Ÿ“‰ Differential Equations", "๐Ÿงฎ Numerical Methods",
406
+ "๐Ÿ“ Differential Geometry", "๐ŸŒŠ Hydro Mechanics",
407
+ "๐Ÿ” Theory of Numbers", "๐Ÿ“Š Real Analysis II", "โž• General Math"
408
  ]
409
+ for t in topics:
410
+ st.caption(t)
411
 
412
+ st.divider()
413
+ st.markdown("**โšก Example Problems**")
414
 
415
  examples = {
416
+ "-- Select --": "",
417
+ "๐Ÿ“ˆ Derivative": "Find the derivative of x^3 + 5x^2 - 3x + 7",
418
  "โˆซ Integral": "Integrate sin(x) * e^x dx",
 
 
 
419
  "๐Ÿ“ Limit": "Find limit of sin(x)/x as x -> 0",
420
+ "๐Ÿ”ข Eigenvalues": "Find eigenvalues of matrix [[4,1],[2,3]]",
421
+ "๐Ÿ” Congruence": "Solve 14x โ‰ก 30 (mod 44) using Euclidean algorithm",
422
+ "๐Ÿ“‰ ODE": "Solve dy/dx + 2y = e^(-x) with y(0) = 1",
423
+ "๐Ÿงฎ Newton-Raphson": "Apply Newton-Raphson to x^3 - 2x - 5 = 0, x0=2, 3 iterations",
424
+ "๐Ÿ“Š Series": "Test convergence of sum 1/n^2 from n=1 to infinity",
425
+ "๐ŸŒŠ Bernoulli": "Explain Bernoulli equation in fluid mechanics with example",
426
+ "๐Ÿ” Fermat": "State and prove Fermat's Little Theorem with example",
427
  }
428
 
429
+ selected = st.selectbox(
430
+ "Load example:",
431
  list(examples.keys()),
432
+ key="example_select",
433
+ label_visibility="collapsed"
434
  )
435
 
436
+ st.divider()
437
  st.markdown("**๐Ÿ”ง Engine**")
438
+ st.caption("๐Ÿค– Groq Llama 3.3 70B โ€” reasoning")
439
+ st.caption("๐Ÿงฎ SymPy โ€” verified computation")
440
+ st.caption("โœ… Zero arithmetic errors")
 
 
 
 
441
 
442
+ st.divider()
443
+ if st.button("๐Ÿ—‘๏ธ Clear Chat", use_container_width=True):
444
  st.session_state.messages = []
445
  st.session_state.last_submitted = ""
446
  st.rerun()
447
 
448
 
449
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
450
+ # MAIN AREA
451
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
452
+
453
+ # Header
454
  st.markdown("""
455
+ <div class="saad-header">
456
+ <h1>๐Ÿ“ SAAD AI ACADEMY</h1>
457
+ <p>BSc Mathematics โ€” Step-by-Step Solver</p>
458
  <div class="badge-row">
459
+ <span class="badge">Groq Free</span>
460
  <span class="badge">SymPy Verified</span>
461
  <span class="badge">Step-by-Step</span>
462
+ <span class="badge">LaTeX Rendered</span>
 
463
  </div>
464
  </div>
465
  """, unsafe_allow_html=True)
466
 
467
+ # Welcome message if no chat
 
468
  if not st.session_state.messages:
469
  st.markdown("""
470
+ <div style="text-align:center; padding:2rem 1rem; color:#444;">
471
+ <div style="font-size:2.5rem; margin-bottom:0.5rem;">๐Ÿงฎ</div>
472
+ <div style="font-size:0.9rem; color:#555;">
473
+ Ask any BSc Mathematics problem below<br>
474
+ <span style="font-size:0.78rem; color:#333;">
 
 
 
 
 
475
  Calculus ยท Linear Algebra ยท Number Theory ยท ODEs ยท Real Analysis ยท and more
476
+ </span>
477
  </div>
478
  </div>
479
  """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
480
 
481
+ # Render chat history
482
+ for msg in st.session_state.messages:
483
+ avatar = "๐Ÿง‘โ€๐ŸŽ“" if msg["role"] == "user" else "๐Ÿ“"
484
+ with st.chat_message(msg["role"], avatar=avatar):
485
+ st.markdown(msg["content"])
486
+
487
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
488
+ # INPUT โ€” use st.chat_input (cleaner than text_input + button)
489
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
490
+
491
+ # Pre-fill from example selector
492
+ prefill = examples.get(selected, "") if selected != "-- Select --" else ""
493
 
494
+ # st.chat_input is the clean ChatGPT-style input bar
495
+ user_input = st.chat_input(
496
+ placeholder="Type a BSc math problem... e.g. 'Solve dยฒy/dxยฒ + 4y = cos(2x)'",
497
+ )
498
 
499
+ # Also allow clicking an example to submit it directly
500
+ if prefill and prefill != st.session_state.last_submitted:
501
+ problem = prefill
502
+ elif user_input and user_input.strip():
503
+ problem = user_input.strip()
504
+ else:
505
+ problem = ""
506
 
507
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
508
+ # PROCESS โ€” only when there's a new problem
509
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
510
+ if problem and problem != st.session_state.last_submitted:
511
  st.session_state.last_submitted = problem
512
 
513
+ # Show user message immediately
514
+ with st.chat_message("user", avatar="๐Ÿง‘โ€๐ŸŽ“"):
515
+ st.markdown(problem)
516
+
517
+ # Show AI response
518
+ with st.chat_message("assistant", avatar="๐Ÿ“"):
519
+ with st.spinner("๐Ÿงฎ Computing..."):
520
+ sympy_result = run_sympy(problem)
521
+ answer = ask_ai(problem, sympy_result, st.session_state.messages)
522
+ st.markdown(answer)
523
 
524
+ # Save to history
525
  st.session_state.messages.append({"role": "user", "content": problem})
526
  st.session_state.messages.append({"role": "assistant", "content": answer})