saad-sust commited on
Commit
edd2a88
ยท
verified ยท
1 Parent(s): 4ca0d27

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +50 -14
app.py CHANGED
@@ -2179,6 +2179,22 @@ def ask_gemini_vision(image_b64: str, mime_type: str, user_note: str) -> str:
2179
  if not any(k for k in gemini_keys if k.strip()):
2180
  return "โš ๏ธ **No Gemini API key found.** Add keys in HF Space โ†’ Settings โ†’ Secrets."
2181
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2182
  last_error = ""
2183
  for i, key in enumerate(gemini_keys):
2184
  if not key.strip():
@@ -2233,22 +2249,27 @@ def handle_uploaded_file(uploaded_file, user_note: str) -> str:
2233
  """
2234
  import base64
2235
 
2236
- # โ”€โ”€ Validate file โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
2237
  MAX_SIZE = 5 * 1024 * 1024 # 5MB
2238
  file_bytes = uploaded_file.read()
2239
-
 
2240
  if len(file_bytes) > MAX_SIZE:
2241
- return "โš ๏ธ File too large. Please upload a file under 5MB."
2242
 
 
 
2243
  mime_map = {
2244
  "jpg": "image/jpeg", "jpeg": "image/jpeg",
2245
- "png": "image/png", "webp": "image/webp",
2246
  "pdf": "application/pdf"
2247
  }
2248
- ext = uploaded_file.name.split(".")[-1].lower()
2249
- mime_type = mime_map.get(ext)
2250
-
2251
  if not mime_type:
 
 
 
2252
  return "โš ๏ธ Unsupported format. Please upload JPG, PNG, WEBP or PDF."
2253
 
2254
  # โ”€โ”€ Convert to base64 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -2261,24 +2282,39 @@ def handle_uploaded_file(uploaded_file, user_note: str) -> str:
2261
  if gemini_response.startswith("โš ๏ธ"):
2262
  return gemini_response
2263
 
2264
- # โ”€โ”€ Try SymPy verification on extracted text โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
2265
- # Gemini response contains the problem โ€” try to run SymPy on it
2266
- sympy_result = run_sympy(gemini_response)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2267
 
2268
  if (sympy_result.get("result") and
2269
  sympy_result["result"] not in (None, "matrix_detected", "mod_detected")):
2270
- # SymPy verified โ€” strip AI answer, append verified one
2271
  latex = sympy_result.get("latex", "")
2272
  cleaned = re.sub(
2273
  r'(โœ…\s*\*{0,2}Final\s*Answer\*{0,2}.*|โœ…[^\n]*$)',
2274
  "", gemini_response,
2275
  flags=re.DOTALL | re.IGNORECASE
2276
  ).rstrip()
2277
- verified_note = "\n\n๐Ÿ”’ **SymPy Verified**"
2278
- return cleaned + verified_note + f"\n\nโœ… **Final Answer:** $$\\boxed{{{latex}}}$$"
2279
  else:
2280
- # No SymPy verification โ€” return Gemini answer with warning
2281
  return gemini_response + "\n\nโš ๏ธ *AI-generated answer โ€” not SymPy verified.*"
 
 
2282
  def ask_ai_streaming(problem: str, sympy_info: dict, history: list) -> str:
2283
  """Get AI response and stream it word by word using st.write_stream."""
2284
  import time
 
2179
  if not any(k for k in gemini_keys if k.strip()):
2180
  return "โš ๏ธ **No Gemini API key found.** Add keys in HF Space โ†’ Settings โ†’ Secrets."
2181
 
2182
+ prompt = (
2183
+ "You are Saad.AI, a BSc Mathematics assistant built by Saad.\n"
2184
+ "The student has uploaded an image containing a math problem.\n\n"
2185
+ "YOUR TASKS:\n"
2186
+ "1. Read and extract the math problem from the image exactly.\n"
2187
+ "2. State what the problem is clearly.\n"
2188
+ "3. Solve it step by step using the full math structure:\n"
2189
+ " ๐Ÿ” **Given:** ...\n"
2190
+ " ๐Ÿ“Œ **Method:** ...\n"
2191
+ " ๐Ÿงฎ **Step 1:** ...\n"
2192
+ " โœ… **Final Answer:** $$\\boxed{answer}$$\n"
2193
+ "4. ALL math must be in LaTeX โ€” never plain text math.\n"
2194
+ "5. If the image is unclear or not math-related, say so politely.\n\n"
2195
+ f"Additional note from student: {user_note if user_note else 'None'}"
2196
+ )
2197
+
2198
  last_error = ""
2199
  for i, key in enumerate(gemini_keys):
2200
  if not key.strip():
 
2249
  """
2250
  import base64
2251
 
2252
+ # โ”€โ”€ Validate size โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
2253
  MAX_SIZE = 5 * 1024 * 1024 # 5MB
2254
  file_bytes = uploaded_file.read()
2255
+ if len(file_bytes) == 0:
2256
+ return "โš ๏ธ The uploaded file is empty. Please try again."
2257
  if len(file_bytes) > MAX_SIZE:
2258
+ return f"โš ๏ธ File too large ({len(file_bytes)//1024}KB). Please upload under 5MB."
2259
 
2260
+ # โ”€โ”€ Detect MIME type from Streamlit's type field, not filename โ”€โ”€โ”€โ”€
2261
+ # This works even if filename has spaces, brackets, or no extension
2262
  mime_map = {
2263
  "jpg": "image/jpeg", "jpeg": "image/jpeg",
2264
+ "png": "image/png", "webp": "image/webp",
2265
  "pdf": "application/pdf"
2266
  }
2267
+ # Try Streamlit's type first (most reliable), fall back to extension
2268
+ mime_type = uploaded_file.type if uploaded_file.type else None
 
2269
  if not mime_type:
2270
+ ext = uploaded_file.name.rsplit(".", 1)[-1].lower() if "." in uploaded_file.name else ""
2271
+ mime_type = mime_map.get(ext)
2272
+ if mime_type not in mime_map.values():
2273
  return "โš ๏ธ Unsupported format. Please upload JPG, PNG, WEBP or PDF."
2274
 
2275
  # โ”€โ”€ Convert to base64 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 
2282
  if gemini_response.startswith("โš ๏ธ"):
2283
  return gemini_response
2284
 
2285
+ # โ”€โ”€ SymPy verification โ€” extract problem line first โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
2286
+ # Run SymPy on the first user-question line, not Gemini's full markdown
2287
+ # This avoids SymPy choking on LaTeX formatting in the solution
2288
+ extracted_problem = ""
2289
+ for line in gemini_response.splitlines():
2290
+ stripped = line.strip()
2291
+ # Skip empty lines, headers, and Gemini's own solution steps
2292
+ if (stripped and
2293
+ not stripped.startswith("#") and
2294
+ not stripped.startswith("๐Ÿ”") and
2295
+ not stripped.startswith("๐Ÿ“Œ") and
2296
+ not stripped.startswith("๐Ÿงฎ") and
2297
+ not stripped.startswith("โœ…") and
2298
+ not stripped.startswith("**") and
2299
+ len(stripped) > 5):
2300
+ extracted_problem = stripped
2301
+ break
2302
+ sympy_result = run_sympy(extracted_problem) if extracted_problem else {"type": "general", "result": None, "latex": ""}
2303
 
2304
  if (sympy_result.get("result") and
2305
  sympy_result["result"] not in (None, "matrix_detected", "mod_detected")):
 
2306
  latex = sympy_result.get("latex", "")
2307
  cleaned = re.sub(
2308
  r'(โœ…\s*\*{0,2}Final\s*Answer\*{0,2}.*|โœ…[^\n]*$)',
2309
  "", gemini_response,
2310
  flags=re.DOTALL | re.IGNORECASE
2311
  ).rstrip()
2312
+ return cleaned + "\n\n๐Ÿ”’ **SymPy Verified**" + f"\n\nโœ… **Final Answer:** $$\\boxed{{{latex}}}$$"
 
2313
  else:
2314
+ # Gemini answered, SymPy couldn't verify โ€” single clean note
2315
  return gemini_response + "\n\nโš ๏ธ *AI-generated answer โ€” not SymPy verified.*"
2316
+
2317
+
2318
  def ask_ai_streaming(problem: str, sympy_info: dict, history: list) -> str:
2319
  """Get AI response and stream it word by word using st.write_stream."""
2320
  import time