Upload app.py
Browse files
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
|
| 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
|
| 2242 |
|
|
|
|
|
|
|
| 2243 |
mime_map = {
|
| 2244 |
"jpg": "image/jpeg", "jpeg": "image/jpeg",
|
| 2245 |
-
"png": "image/png",
|
| 2246 |
"pdf": "application/pdf"
|
| 2247 |
}
|
| 2248 |
-
|
| 2249 |
-
mime_type =
|
| 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 |
-
# โโ
|
| 2265 |
-
#
|
| 2266 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 2278 |
-
return cleaned + verified_note + f"\n\nโ
**Final Answer:** $$\\boxed{{{latex}}}$$"
|
| 2279 |
else:
|
| 2280 |
-
#
|
| 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
|