saad-sust commited on
Commit
70d1fc9
·
verified ·
1 Parent(s): e459963

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +149 -80
app.py CHANGED
@@ -393,8 +393,6 @@ if "supa_loaded" not in st.session_state:
393
  st.session_state.supa_loaded = False
394
  if "current_chat_id" not in st.session_state:
395
  st.session_state.current_chat_id = None
396
- if "show_uploader" not in st.session_state:
397
- st.session_state.show_uploader = False
398
  if "pending_file_bytes" not in st.session_state:
399
  st.session_state.pending_file_bytes = None
400
  if "pending_file_name" not in st.session_state:
@@ -2289,23 +2287,36 @@ def ask_gemini_vision(image_b64: str, mime_type: str, user_note: str) -> str:
2289
  pass # Conversion failed — fall through to Gemini
2290
 
2291
  prompt = (
2292
- "You are Saad.AI, a BSc Mathematics assistant built by Saad.\n"
2293
- "The student has uploaded a file that may contain MULTIPLE math problems.\n\n"
2294
- "CRITICAL RULES:\n"
2295
- "- Read the ENTIRE document carefully from top to bottom.\n"
2296
- "- Extract EVERY question/problem you see do NOT skip any.\n"
2297
- "- Do NOT invent or make up questions. ONLY solve what is written in the file.\n"
2298
- "- If you cannot read part of the text, say so explicitly.\n\n"
2299
- "FOR EACH PROBLEM FOUND, use this structure:\n"
2300
- "---\n"
2301
- "### Question [N]: [restate the exact question from the file]\n"
2302
- "🔍 **Given:** ...\n"
2303
- "📌 **Method:** ...\n"
2304
- "🧮 **Step 1:** ...\n"
2305
- " **Final Answer:** $$\\boxed{answer}$$\n"
2306
- "---\n\n"
2307
- "ALL math must be in LaTeX — never plain text math.\n\n"
2308
- f"Student's specific instruction: {user_note if user_note else 'Read the full file and solve ALL problems you find.'}"
 
 
 
 
 
 
 
 
 
 
 
 
 
2309
  )
2310
 
2311
  errors = []
@@ -2587,35 +2598,6 @@ with st.sidebar:
2587
 
2588
  st.divider()
2589
 
2590
- # ── File Attach — always visible in sidebar ───────────────────
2591
- st.markdown("**📎 Attach File**")
2592
- st.caption("Image or PDF with math problems")
2593
- sidebar_file = st.file_uploader(
2594
- "sidebar_upload",
2595
- type=["jpg", "jpeg", "png", "webp", "pdf"],
2596
- label_visibility="collapsed",
2597
- help="JPG · PNG · WEBP · PDF — max 5 MB",
2598
- key="sidebar_uploader"
2599
- )
2600
- if sidebar_file:
2601
- file_key = f"{sidebar_file.size}_{sidebar_file.type}"
2602
- if file_key != st.session_state.get("last_uploaded_file", ""):
2603
- st.session_state["last_uploaded_file"] = file_key
2604
- st.session_state.pending_file_bytes = sidebar_file.read()
2605
- st.session_state.pending_file_name = sidebar_file.name
2606
- st.session_state.pending_file_mime = sidebar_file.type or "application/octet-stream"
2607
- st.rerun()
2608
- if st.session_state.pending_file_bytes is not None:
2609
- st.success(f"✅ **{st.session_state.pending_file_name}**\nType your question in chat ↓")
2610
- if st.button("✕ Remove file", use_container_width=True, key="sidebar_remove"):
2611
- st.session_state.pending_file_bytes = None
2612
- st.session_state.pending_file_name = None
2613
- st.session_state.pending_file_mime = None
2614
- st.session_state["last_uploaded_file"] = "" # allow re-uploading the same file
2615
- st.rerun()
2616
-
2617
- st.divider()
2618
-
2619
  # ── Chat History ─────────────────────────────────────────────
2620
  if st.session_state.chats:
2621
  st.markdown("**💬 Chat History**")
@@ -2876,30 +2858,93 @@ for i, msg in enumerate(st.session_state.messages):
2876
  st.code(msg["content"], language=None)
2877
 
2878
  # ════════════════════════════════════════════════════════════════════
2879
- # FILE ATTACH STATUS fixed bar just above the chat input
2880
- # (uploader lives in the sidebar always visible, never scrolls away)
2881
  # ════════════════════════════════════════════════════════════════════
2882
 
2883
- # ── Always-visible fixed attach bar above the chat input ─────────
2884
- # Shows three states: no file / file pending / file in memory
2885
- _pending_name = st.session_state.pending_file_bytes is not None
2886
- _memory_name = st.session_state.attached_file_name
2887
-
2888
- if _pending_name:
2889
- _bar_color = "#1a2e1a"
2890
- _bar_border = "#22c55e"
2891
- _bar_text = f"✅ <strong>{st.session_state.pending_file_name}</strong> ready — type your question below and press Enter"
2892
- elif _memory_name:
2893
- _bar_color = "#1a1f2e"
2894
- _bar_border = "#3b82f6"
2895
- _bar_text = f"📎 <strong>{_memory_name}</strong> in memory — ask a follow-up or attach a new file via sidebar"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2896
  else:
2897
- _bar_color = "#111"
2898
- _bar_border = "#374151"
2899
- _bar_text = "📎 Attach a file: open the <strong>sidebar ←</strong> to upload an image or PDF"
 
2900
 
 
2901
  st.markdown(
2902
  f"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2903
  <div style="
2904
  position: fixed;
2905
  bottom: 68px;
@@ -2907,33 +2952,57 @@ st.markdown(
2907
  transform: translateX(-50%);
2908
  width: 720px;
2909
  max-width: 88vw;
2910
- background: {_bar_color};
2911
- border: 1px solid {_bar_border};
2912
- border-radius: 10px;
2913
- padding: 7px 16px;
2914
  z-index: 9999;
2915
- font-size: 0.80rem;
2916
- color: #d1d5db;
2917
  ">
2918
- {_bar_text}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2919
  </div>
2920
  """,
2921
  unsafe_allow_html=True
2922
  )
2923
 
2924
- # Remove-file buttons (in-page, shown only when file exists)
2925
- if _pending_name:
2926
  col_sp, col_rm = st.columns([11, 1])
2927
  with col_rm:
2928
- if st.button("✕", key="remove_pending", help="Remove pending file"):
2929
  st.session_state.pending_file_bytes = None
2930
  st.session_state.pending_file_name = None
2931
  st.session_state.pending_file_mime = None
 
2932
  st.rerun()
2933
- elif _memory_name:
2934
  col_sp, col_rm = st.columns([11, 1])
2935
  with col_rm:
2936
- if st.button("✕", key="clear_memory_file", help="Remove file from memory"):
2937
  st.session_state.attached_file_bytes = None
2938
  st.session_state.attached_file_name = None
2939
  st.session_state.attached_file_mime = None
@@ -3004,7 +3073,7 @@ if problem and problem != st.session_state.last_submitted:
3004
  st.markdown(f"📎 **{file_name}** — {problem}")
3005
  # Show file preview so user can see what was attached
3006
  if file_mime and file_mime.startswith("image/"):
3007
- st.image(file_bytes, caption=file_name, use_container_width=True)
3008
  else:
3009
  # PDF — try to show first page
3010
  try:
@@ -3012,7 +3081,7 @@ if problem and problem != st.session_state.last_submitted:
3012
  doc = fitz.open(stream=file_bytes, filetype="pdf")
3013
  pix = doc[0].get_pixmap(matrix=fitz.Matrix(1.5, 1.5))
3014
  doc.close()
3015
- st.image(pix.tobytes("png"), caption=f"📄 {file_name} (page 1 preview)", use_container_width=True)
3016
  except Exception:
3017
  st.caption(f"📄 {file_name}")
3018
 
 
393
  st.session_state.supa_loaded = False
394
  if "current_chat_id" not in st.session_state:
395
  st.session_state.current_chat_id = None
 
 
396
  if "pending_file_bytes" not in st.session_state:
397
  st.session_state.pending_file_bytes = None
398
  if "pending_file_name" not in st.session_state:
 
2287
  pass # Conversion failed — fall through to Gemini
2288
 
2289
  prompt = (
2290
+ "You are Saad.AI, a helpful AI assistant built by Saad.\n"
2291
+ "You can read and understand ALL types of images and documents.\n\n"
2292
+
2293
+ "STEP 1 Look at the image carefully from top to bottom.\n"
2294
+ "STEP 2 Identify what type of content is in the image:\n\n"
2295
+
2296
+ "━━━ CASE A: IMAGE CONTAINS MATH PROBLEMS ━━━\n"
2297
+ "(Equations, exam paper, homework sheet, math diagrams, numbered questions)\n"
2298
+ "→ Read the ENTIRE document. Extract EVERY question — do NOT skip any.\n"
2299
+ " Do NOT invent questions. ONLY solve what is actually written.\n"
2300
+ " For EACH problem use this structure:\n"
2301
+ " ---\n"
2302
+ " ### Question [N]: [restate exact question from file]\n"
2303
+ " 🔍 **Given:** ...\n"
2304
+ " 📌 **Method:** ...\n"
2305
+ " 🧮 **Step 1:** ...\n"
2306
+ " **Final Answer:** $$\\boxed{answer}$$\n"
2307
+ " ---\n"
2308
+ "→ ALL math must be in LaTeX — never plain text math.\n\n"
2309
+
2310
+ "━━━ CASE B: IMAGE IS NOT A MATH PROBLEM ━━━\n"
2311
+ "(Photo, screenshot, diagram, chart, meme, nature, objects, people, text, etc.)\n"
2312
+ "→ Describe the image in detail — what you see, what it shows.\n"
2313
+ "→ Be conversational and helpful like ChatGPT or Claude.\n"
2314
+ "→ Answer the student's specific question about the image.\n"
2315
+ "→ No forced math structure. Just natural, helpful conversation.\n"
2316
+ "→ Point out interesting details, context, or meaning.\n\n"
2317
+
2318
+ f"Student's instruction: {user_note if user_note else 'Look at this image and describe or analyze it.'}\n\n"
2319
+ "Always be helpful, friendly, and clear."
2320
  )
2321
 
2322
  errors = []
 
2598
 
2599
  st.divider()
2600
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2601
  # ── Chat History ─────────────────────────────────────────────
2602
  if st.session_state.chats:
2603
  st.markdown("**💬 Chat History**")
 
2858
  st.code(msg["content"], language=None)
2859
 
2860
  # ════════════════════════════════════════════════════════════════════
2861
+ # FILE ATTACH — ChatGPT-style: 📎 button right next to the send bar
2862
+ # The file uploader is hidden in the DOM; JS click triggers it.
2863
  # ════════════════════════════════════════════════════════════════════
2864
 
2865
+ # ── Hidden uploader rendered but invisible, triggered by JS ────
2866
+ main_uploader = st.file_uploader(
2867
+ "main_attach",
2868
+ type=["jpg", "jpeg", "png", "webp", "pdf"],
2869
+ label_visibility="collapsed",
2870
+ key="main_uploader"
2871
+ )
2872
+ if main_uploader:
2873
+ _fkey = f"{main_uploader.size}_{main_uploader.type}"
2874
+ if _fkey != st.session_state.get("last_uploaded_file", ""):
2875
+ st.session_state["last_uploaded_file"] = _fkey
2876
+ st.session_state.pending_file_bytes = main_uploader.read()
2877
+ st.session_state.pending_file_name = main_uploader.name
2878
+ st.session_state.pending_file_mime = main_uploader.type or "application/octet-stream"
2879
+ st.rerun()
2880
+
2881
+ # ── Status values ─────────────────────────────────────────────────
2882
+ _pending = st.session_state.pending_file_bytes is not None
2883
+ _memfile = st.session_state.attached_file_name
2884
+
2885
+ if _pending:
2886
+ _fname = st.session_state.pending_file_name
2887
+ _status = f"✅ <b>{_fname}</b> — ready · type your question and press Enter"
2888
+ _bcolor = "#0d2318"
2889
+ _bborder = "#22c55e"
2890
+ _tcolor = "#86efac"
2891
+ elif _memfile:
2892
+ _status = f"📎 <b>{_memfile}</b> in memory — ask a follow-up or click 📎 to attach new"
2893
+ _bcolor = "#0d1a2e"
2894
+ _bborder = "#3b82f6"
2895
+ _tcolor = "#93c5fd"
2896
  else:
2897
+ _status = "Click <b>📎</b> below to attach an image or PDF"
2898
+ _bcolor = "#111827"
2899
+ _bborder = "#2d3748"
2900
+ _tcolor = "#6b7280"
2901
 
2902
+ # ── Fixed bar: [📎 button] [status text] [✕ if file exists] ──────
2903
  st.markdown(
2904
  f"""
2905
+ <style>
2906
+ /* Hide the Streamlit file-uploader widget — triggered via JS */
2907
+ [data-testid="stFileUploader"] {{
2908
+ visibility: hidden !important;
2909
+ height: 0 !important;
2910
+ min-height: 0 !important;
2911
+ overflow: hidden !important;
2912
+ margin: 0 !important;
2913
+ padding: 0 !important;
2914
+ position: absolute !important;
2915
+ pointer-events: none !important;
2916
+ }}
2917
+ /* Keep the actual <input type=file> reachable by JS */
2918
+ [data-testid="stFileUploaderDropzoneInput"] {{
2919
+ visibility: visible !important;
2920
+ pointer-events: auto !important;
2921
+ }}
2922
+ /* Hide the Streamlit ✕ clear-buttons below — they're fired by JS */
2923
+ div[data-testid="stHorizontalBlock"]:has(button[kind="secondary"]) {{
2924
+ visibility: hidden !important;
2925
+ height: 0 !important;
2926
+ overflow: hidden !important;
2927
+ margin: 0 !important;
2928
+ padding: 0 !important;
2929
+ position: absolute !important;
2930
+ }}
2931
+ </style>
2932
+
2933
+ <script>
2934
+ function saadAttach() {{
2935
+ var inp = document.querySelector('[data-testid="stFileUploaderDropzoneInput"]');
2936
+ if (inp) {{ inp.click(); }}
2937
+ }}
2938
+ function saadClearFile() {{
2939
+ /* Find and click the Streamlit ✕ button by its text content */
2940
+ var btns = document.querySelectorAll('button');
2941
+ for (var b of btns) {{
2942
+ if (b.innerText.trim() === '✕') {{ b.click(); break; }}
2943
+ }}
2944
+ }}
2945
+ </script>
2946
+
2947
+ <!-- Fixed attach bar just above the chat input -->
2948
  <div style="
2949
  position: fixed;
2950
  bottom: 68px;
 
2952
  transform: translateX(-50%);
2953
  width: 720px;
2954
  max-width: 88vw;
2955
+ display: flex;
2956
+ align-items: center;
2957
+ gap: 8px;
 
2958
  z-index: 9999;
 
 
2959
  ">
2960
+ <!-- 📎 Paperclip button — triggers hidden file input -->
2961
+ <button onclick="saadAttach()" title="Attach image or PDF"
2962
+ style="flex-shrink:0; width:40px; height:36px;
2963
+ background:#1e293b; border:1.5px solid #334155;
2964
+ border-radius:9px; cursor:pointer; font-size:17px;
2965
+ color:#9ca3af; line-height:1; transition:all .15s;"
2966
+ onmouseover="this.style.background='#334155';this.style.borderColor='#60a5fa';this.style.color='#93c5fd';"
2967
+ onmouseout="this.style.background='#1e293b';this.style.borderColor='#334155';this.style.color='#9ca3af';">
2968
+ 📎
2969
+ </button>
2970
+ <!-- Status pill -->
2971
+ <div style="flex:1; background:{_bcolor}; border:1px solid {_bborder};
2972
+ border-radius:9px; padding:6px 14px; font-size:0.79rem;
2973
+ color:{_tcolor}; white-space:nowrap; overflow:hidden;
2974
+ text-overflow:ellipsis;">
2975
+ {_status}
2976
+ </div>
2977
+ {"""<!-- ✕ clear button -->
2978
+ <button onclick="saadClearFile()" title="Remove file"
2979
+ style="flex-shrink:0; width:30px; height:30px;
2980
+ background:transparent; border:1px solid #4b5563;
2981
+ border-radius:7px; cursor:pointer; font-size:13px;
2982
+ color:#9ca3af; line-height:1; transition:all .15s;"
2983
+ onmouseover="this.style.borderColor='#f87171';this.style.color='#f87171';"
2984
+ onmouseout="this.style.borderColor='#4b5563';this.style.color='#9ca3af';">
2985
+
2986
+ </button>""" if (_pending or _memfile) else ""}
2987
  </div>
2988
  """,
2989
  unsafe_allow_html=True
2990
  )
2991
 
2992
+ # ── Real Streamlit clear-buttons (hidden by CSS, fired by JS above)
2993
+ if _pending:
2994
  col_sp, col_rm = st.columns([11, 1])
2995
  with col_rm:
2996
+ if st.button("✕", key="remove_pending", help="Remove file"):
2997
  st.session_state.pending_file_bytes = None
2998
  st.session_state.pending_file_name = None
2999
  st.session_state.pending_file_mime = None
3000
+ st.session_state["last_uploaded_file"] = ""
3001
  st.rerun()
3002
+ elif _memfile:
3003
  col_sp, col_rm = st.columns([11, 1])
3004
  with col_rm:
3005
+ if st.button("✕", key="clear_memory_file", help="Clear file memory"):
3006
  st.session_state.attached_file_bytes = None
3007
  st.session_state.attached_file_name = None
3008
  st.session_state.attached_file_mime = None
 
3073
  st.markdown(f"📎 **{file_name}** — {problem}")
3074
  # Show file preview so user can see what was attached
3075
  if file_mime and file_mime.startswith("image/"):
3076
+ st.image(file_bytes, caption=file_name, use_column_width=True)
3077
  else:
3078
  # PDF — try to show first page
3079
  try:
 
3081
  doc = fitz.open(stream=file_bytes, filetype="pdf")
3082
  pix = doc[0].get_pixmap(matrix=fitz.Matrix(1.5, 1.5))
3083
  doc.close()
3084
+ st.image(pix.tobytes("png"), caption=f"📄 {file_name} (page 1 preview)", use_column_width=True)
3085
  except Exception:
3086
  st.caption(f"📄 {file_name}")
3087