Update app.py
Browse files
app.py
CHANGED
|
@@ -396,6 +396,12 @@ if "current_chat_id" not in st.session_state:
|
|
| 396 |
st.session_state.current_chat_id = None
|
| 397 |
if "show_uploader" not in st.session_state:
|
| 398 |
st.session_state.show_uploader = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
|
| 400 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 401 |
# SUPABASE β Persistent chat history
|
|
@@ -2312,9 +2318,11 @@ def ask_gemini_vision(image_b64: str, mime_type: str, user_note: str) -> str:
|
|
| 2312 |
|
| 2313 |
if "429" in last_error or "quota" in last_error:
|
| 2314 |
return (
|
| 2315 |
-
"β οΈ **All Gemini keys are rate-limited.**\n\n"
|
| 2316 |
-
"
|
| 2317 |
-
"
|
|
|
|
|
|
|
| 2318 |
)
|
| 2319 |
if "API_KEY_INVALID" in last_error or "403" in last_error:
|
| 2320 |
return (
|
|
@@ -2708,7 +2716,7 @@ for i, msg in enumerate(st.session_state.messages):
|
|
| 2708 |
st.code(msg["content"], language=None)
|
| 2709 |
|
| 2710 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 2711 |
-
# FILE UPLOAD β ChatGPT-style:
|
| 2712 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 2713 |
|
| 2714 |
# Toolbar row: paperclip button (left-aligned, compact)
|
|
@@ -2720,7 +2728,6 @@ with col_attach:
|
|
| 2720 |
st.rerun()
|
| 2721 |
|
| 2722 |
# Compact uploader β only visible when toggled ON
|
| 2723 |
-
uploaded_file = None
|
| 2724 |
if st.session_state.show_uploader:
|
| 2725 |
uploaded_file = st.file_uploader(
|
| 2726 |
"Upload image or PDF of your math problem",
|
|
@@ -2728,33 +2735,28 @@ if st.session_state.show_uploader:
|
|
| 2728 |
label_visibility="collapsed",
|
| 2729 |
help="JPG Β· PNG Β· WEBP Β· PDF β max 5 MB"
|
| 2730 |
)
|
| 2731 |
-
|
| 2732 |
-
|
| 2733 |
-
|
| 2734 |
-
|
| 2735 |
-
|
| 2736 |
-
|
| 2737 |
-
|
| 2738 |
-
|
| 2739 |
-
|
| 2740 |
-
|
| 2741 |
-
|
| 2742 |
-
|
| 2743 |
-
|
| 2744 |
-
|
| 2745 |
-
|
| 2746 |
-
|
| 2747 |
-
|
| 2748 |
-
|
| 2749 |
-
|
| 2750 |
-
|
| 2751 |
-
|
| 2752 |
-
|
| 2753 |
-
"role": "user",
|
| 2754 |
-
"content": f"π Uploaded: `{uploaded_file.name}`"
|
| 2755 |
-
})
|
| 2756 |
-
st.session_state.messages.append({"role": "assistant", "content": answer})
|
| 2757 |
-
save_current_chat()
|
| 2758 |
|
| 2759 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 2760 |
# INPUT β ChatGPT-style input bar
|
|
@@ -2781,6 +2783,40 @@ else:
|
|
| 2781 |
if problem and problem != st.session_state.last_submitted:
|
| 2782 |
st.session_state.last_submitted = problem
|
| 2783 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2784 |
# ββ Detect casual / non-math messages βββββββββββββββββββββββββββ
|
| 2785 |
p_lower = problem.lower().strip()
|
| 2786 |
casual_keywords = [
|
|
|
|
| 396 |
st.session_state.current_chat_id = None
|
| 397 |
if "show_uploader" not in st.session_state:
|
| 398 |
st.session_state.show_uploader = False
|
| 399 |
+
if "pending_file_bytes" not in st.session_state:
|
| 400 |
+
st.session_state.pending_file_bytes = None
|
| 401 |
+
if "pending_file_name" not in st.session_state:
|
| 402 |
+
st.session_state.pending_file_name = None
|
| 403 |
+
if "pending_file_mime" not in st.session_state:
|
| 404 |
+
st.session_state.pending_file_mime = None
|
| 405 |
|
| 406 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 407 |
# SUPABASE β Persistent chat history
|
|
|
|
| 2318 |
|
| 2319 |
if "429" in last_error or "quota" in last_error:
|
| 2320 |
return (
|
| 2321 |
+
f"β οΈ **All Gemini keys are rate-limited.**\n\n"
|
| 2322 |
+
f"Exact error: `{last_error}`\n\n"
|
| 2323 |
+
"**Per-minute limit** (15 req/min): wait 60 seconds.\n"
|
| 2324 |
+
"**Daily limit** (1500 req/day): resets at midnight Pacific β must wait until then.\n\n"
|
| 2325 |
+
"Make sure each key is from a **different Google account** β keys from the same account share the same quota pool."
|
| 2326 |
)
|
| 2327 |
if "API_KEY_INVALID" in last_error or "403" in last_error:
|
| 2328 |
return (
|
|
|
|
| 2716 |
st.code(msg["content"], language=None)
|
| 2717 |
|
| 2718 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 2719 |
+
# FILE UPLOAD β True ChatGPT-style: attach file THEN type question
|
| 2720 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 2721 |
|
| 2722 |
# Toolbar row: paperclip button (left-aligned, compact)
|
|
|
|
| 2728 |
st.rerun()
|
| 2729 |
|
| 2730 |
# Compact uploader β only visible when toggled ON
|
|
|
|
| 2731 |
if st.session_state.show_uploader:
|
| 2732 |
uploaded_file = st.file_uploader(
|
| 2733 |
"Upload image or PDF of your math problem",
|
|
|
|
| 2735 |
label_visibility="collapsed",
|
| 2736 |
help="JPG Β· PNG Β· WEBP Β· PDF β max 5 MB"
|
| 2737 |
)
|
| 2738 |
+
if uploaded_file:
|
| 2739 |
+
file_key = f"{uploaded_file.size}_{uploaded_file.type}"
|
| 2740 |
+
if file_key != st.session_state.get("last_uploaded_file", ""):
|
| 2741 |
+
st.session_state["last_uploaded_file"] = file_key
|
| 2742 |
+
# Store file bytes in session state β wait for user's typed question
|
| 2743 |
+
st.session_state.pending_file_bytes = uploaded_file.read()
|
| 2744 |
+
st.session_state.pending_file_name = uploaded_file.name
|
| 2745 |
+
st.session_state.pending_file_mime = uploaded_file.type or "application/octet-stream"
|
| 2746 |
+
st.session_state.show_uploader = False
|
| 2747 |
+
st.rerun()
|
| 2748 |
+
|
| 2749 |
+
# Show attached file pill above input bar so user knows it's ready
|
| 2750 |
+
if st.session_state.pending_file_bytes is not None:
|
| 2751 |
+
col_pill, col_x = st.columns([9, 1])
|
| 2752 |
+
with col_pill:
|
| 2753 |
+
st.info(f"π **{st.session_state.pending_file_name}** attached β type your question below and press Enter")
|
| 2754 |
+
with col_x:
|
| 2755 |
+
if st.button("β", key="remove_pending", help="Remove attached file"):
|
| 2756 |
+
st.session_state.pending_file_bytes = None
|
| 2757 |
+
st.session_state.pending_file_name = None
|
| 2758 |
+
st.session_state.pending_file_mime = None
|
| 2759 |
+
st.rerun()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2760 |
|
| 2761 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 2762 |
# INPUT β ChatGPT-style input bar
|
|
|
|
| 2783 |
if problem and problem != st.session_state.last_submitted:
|
| 2784 |
st.session_state.last_submitted = problem
|
| 2785 |
|
| 2786 |
+
# ββ If a file is attached, send file + question to Gemini Vision β
|
| 2787 |
+
if st.session_state.pending_file_bytes is not None:
|
| 2788 |
+
import base64
|
| 2789 |
+
|
| 2790 |
+
file_bytes = st.session_state.pending_file_bytes
|
| 2791 |
+
file_name = st.session_state.pending_file_name
|
| 2792 |
+
file_mime = st.session_state.pending_file_mime
|
| 2793 |
+
|
| 2794 |
+
# Clear pending file immediately so it's not re-sent on rerun
|
| 2795 |
+
st.session_state.pending_file_bytes = None
|
| 2796 |
+
st.session_state.pending_file_name = None
|
| 2797 |
+
st.session_state.pending_file_mime = None
|
| 2798 |
+
|
| 2799 |
+
with st.chat_message("user", avatar="π§βπ"):
|
| 2800 |
+
st.markdown(f"π **{file_name}** β {problem}")
|
| 2801 |
+
|
| 2802 |
+
with st.chat_message("assistant", avatar="π"):
|
| 2803 |
+
with st.spinner("π Reading your file with Gemini..."):
|
| 2804 |
+
image_b64 = base64.b64encode(file_bytes).decode("utf-8")
|
| 2805 |
+
answer = ask_gemini_vision(image_b64, file_mime, problem)
|
| 2806 |
+
st.markdown(answer)
|
| 2807 |
+
with st.expander("π Copy"):
|
| 2808 |
+
st.code(answer, language=None)
|
| 2809 |
+
|
| 2810 |
+
if not st.session_state.current_chat_id:
|
| 2811 |
+
new_chat()
|
| 2812 |
+
st.session_state.messages.append({
|
| 2813 |
+
"role": "user",
|
| 2814 |
+
"content": f"π {file_name} β {problem}"
|
| 2815 |
+
})
|
| 2816 |
+
st.session_state.messages.append({"role": "assistant", "content": answer})
|
| 2817 |
+
save_current_chat()
|
| 2818 |
+
st.stop()
|
| 2819 |
+
|
| 2820 |
# ββ Detect casual / non-math messages βββββββββββββββββββββββββββ
|
| 2821 |
p_lower = problem.lower().strip()
|
| 2822 |
casual_keywords = [
|