saad-sust commited on
Commit
be78f4a
Β·
verified Β·
1 Parent(s): 916457f

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +160 -6
app.py CHANGED
@@ -7,6 +7,7 @@
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,
@@ -192,10 +193,111 @@ hr { border-color: #1a1a1a !important; }
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
  # ════════════════════════════════════════════════════════════════════
@@ -1635,12 +1737,16 @@ def ask_ai_streaming(problem: str, sympy_info: dict, history: list) -> str:
1635
  # Get full response first
1636
  full_response = ask_ai(problem, sympy_info, history)
1637
 
1638
- # Stream it word by word
1639
  def word_generator():
1640
  words = full_response.split(" ")
1641
- for i, word in enumerate(words):
1642
- yield word + (" " if i < len(words)-1 else "")
1643
- time.sleep(0.015) # 15ms per word β€” smooth streaming
 
 
 
 
1644
 
1645
  # Use st.write_stream for streaming display
1646
  streamed = st.write_stream(word_generator())
@@ -1651,10 +1757,46 @@ def ask_ai_streaming(problem: str, sympy_info: dict, history: list) -> str:
1651
  # SIDEBAR
1652
  # ════════════════════════════════════════════════════════════════════
1653
  with st.sidebar:
1654
- st.markdown("### πŸ“ Saad.AI")
1655
  st.caption("BSc Mathematics Engine")
1656
  st.divider()
1657
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1658
  st.markdown("**🎯 Topics**")
1659
  topics = [
1660
  "πŸ“ˆ Calculus", "πŸ”’ Linear Algebra", "πŸ” Number Theory",
@@ -1735,10 +1877,18 @@ if not st.session_state.messages:
1735
  """, unsafe_allow_html=True)
1736
 
1737
  # Render chat history
1738
- for msg in st.session_state.messages:
1739
  avatar = "πŸ§‘β€πŸŽ“" if msg["role"] == "user" else "πŸ“"
1740
  with st.chat_message(msg["role"], avatar=avatar):
1741
  st.markdown(msg["content"])
 
 
 
 
 
 
 
 
1742
 
1743
  # ════════════════════════════════════════════════════════════════════
1744
  # INPUT β€” use st.chat_input (cleaner than text_input + button)
@@ -1782,3 +1932,7 @@ if problem and problem != st.session_state.last_submitted:
1782
  # Save to history
1783
  st.session_state.messages.append({"role": "user", "content": problem})
1784
  st.session_state.messages.append({"role": "assistant", "content": answer})
 
 
 
 
 
7
  import streamlit as st
8
  import os
9
  import requests
10
+ import json as _json
11
  import sympy as sp
12
  from sympy import (
13
  symbols, diff, integrate, limit, solve,
 
193
  """, unsafe_allow_html=True)
194
 
195
  # ── Session state ────────────────────────────────────────────────────
196
+ import datetime as _dt
197
+
198
+ # ── Session state ────────────────────────────────────────────────
199
  if "messages" not in st.session_state:
200
  st.session_state.messages = []
201
  if "last_submitted" not in st.session_state:
202
  st.session_state.last_submitted = ""
203
+ if "chats" not in st.session_state:
204
+ # Load from Supabase on first load
205
+ st.session_state.chats = supa_load_all_chats()
206
+ if "current_chat_id" not in st.session_state:
207
+ st.session_state.current_chat_id = None
208
+
209
+ # ════════════════════════════════════════════════════════════════════
210
+ # SUPABASE β€” Persistent chat history
211
+ # ════════════════════════════════════════════════════════════════════
212
+ _SUPA_URL = os.environ.get("SUPABASE_URL", "")
213
+ _SUPA_KEY = os.environ.get("SUPABASE_KEY", "")
214
+
215
+ def _supa_headers():
216
+ return {
217
+ "apikey": _SUPA_KEY,
218
+ "Authorization": f"Bearer {_SUPA_KEY}",
219
+ "Content-Type": "application/json",
220
+ "Prefer": "return=minimal"
221
+ }
222
+
223
+ def supa_load_all_chats():
224
+ """Load all chats from Supabase."""
225
+ if not _SUPA_URL or not _SUPA_KEY:
226
+ return {}
227
+ try:
228
+ resp = requests.get(
229
+ f"{_SUPA_URL}/rest/v1/chats?select=*&order=created_at.desc",
230
+ headers=_supa_headers(), timeout=5
231
+ )
232
+ if resp.status_code == 200:
233
+ rows = resp.json()
234
+ return {r["id"]: {
235
+ "title": r["title"],
236
+ "messages": r["messages"],
237
+ "created": r["created_at"][:16].replace("T"," ")
238
+ } for r in rows}
239
+ except Exception:
240
+ pass
241
+ return {}
242
+
243
+ def supa_save_chat(chat_id, title, messages):
244
+ """Save or update a chat in Supabase."""
245
+ if not _SUPA_URL or not _SUPA_KEY or not messages:
246
+ return
247
+ try:
248
+ requests.post(
249
+ f"{_SUPA_URL}/rest/v1/chats",
250
+ headers={**_supa_headers(), "Prefer": "resolution=merge-duplicates"},
251
+ data=_json.dumps({
252
+ "id": chat_id,
253
+ "title": title,
254
+ "messages": messages
255
+ }), timeout=5
256
+ )
257
+ except Exception:
258
+ pass
259
+
260
+ def supa_delete_chat(chat_id):
261
+ """Delete a chat from Supabase."""
262
+ if not _SUPA_URL or not _SUPA_KEY:
263
+ return
264
+ try:
265
+ requests.delete(
266
+ f"{_SUPA_URL}/rest/v1/chats?id=eq.{chat_id}",
267
+ headers=_supa_headers(), timeout=5
268
+ )
269
+ except Exception:
270
+ pass
271
+
272
+ def new_chat():
273
+ """Start a fresh chat session."""
274
+ chat_id = f"chat_{_dt.datetime.now().strftime('%Y%m%d_%H%M%S')}"
275
+ st.session_state.current_chat_id = chat_id
276
+ st.session_state.messages = []
277
+ st.session_state.last_submitted = ""
278
+
279
+ def save_current_chat():
280
+ """Save current messages to session state and Supabase."""
281
+ cid = st.session_state.current_chat_id
282
+ if not cid or not st.session_state.messages:
283
+ return
284
+ # Auto-title from first user message
285
+ first_user = next((m["content"] for m in st.session_state.messages if m["role"]=="user"), "New Chat")
286
+ title = first_user[:35] + "..." if len(first_user) > 35 else first_user
287
+ st.session_state.chats[cid] = {
288
+ "title": title,
289
+ "messages": list(st.session_state.messages),
290
+ "created": _dt.datetime.now().strftime("%d %b %H:%M")
291
+ }
292
+ # Save to Supabase (persistent)
293
+ supa_save_chat(cid, title, list(st.session_state.messages))
294
+
295
+ def load_chat(chat_id):
296
+ """Load a previous chat."""
297
+ if chat_id in st.session_state.chats:
298
+ st.session_state.current_chat_id = chat_id
299
+ st.session_state.messages = list(st.session_state.chats[chat_id]["messages"])
300
+ st.session_state.last_submitted = ""
301
 
302
 
303
  # ════════════════════════════════════════════════════════════════════
 
1737
  # Get full response first
1738
  full_response = ask_ai(problem, sympy_info, history)
1739
 
1740
+ # Stream chunk by chunk (3-4 words at a time) β€” natural reading pace
1741
  def word_generator():
1742
  words = full_response.split(" ")
1743
+ chunk_size = 3 # 3 words at a time
1744
+ for i in range(0, len(words), chunk_size):
1745
+ chunk = " ".join(words[i:i+chunk_size])
1746
+ if i + chunk_size < len(words):
1747
+ chunk += " "
1748
+ yield chunk
1749
+ time.sleep(0.08) # 80ms per chunk β€” comfortable reading pace
1750
 
1751
  # Use st.write_stream for streaming display
1752
  streamed = st.write_stream(word_generator())
 
1757
  # SIDEBAR
1758
  # ════════════════════════════════════════════════════════════════════
1759
  with st.sidebar:
1760
+ st.markdown("### 🧠 Saad.AI")
1761
  st.caption("BSc Mathematics Engine")
1762
  st.divider()
1763
 
1764
+ # ── New Chat Button ──────────────────────────────────────────
1765
+ if st.button("βž• New Chat", use_container_width=True):
1766
+ save_current_chat()
1767
+ new_chat()
1768
+ st.rerun()
1769
+
1770
+ st.divider()
1771
+
1772
+ # ── Chat History ─────────────────────────────────────────────
1773
+ if st.session_state.chats:
1774
+ st.markdown("**πŸ’¬ Chat History**")
1775
+ # Show most recent first
1776
+ sorted_chats = sorted(
1777
+ st.session_state.chats.items(),
1778
+ key=lambda x: x[1]["created"],
1779
+ reverse=True
1780
+ )
1781
+ for chat_id, chat_data in sorted_chats:
1782
+ col1, col2 = st.columns([4,1])
1783
+ with col1:
1784
+ # Highlight current chat
1785
+ is_current = chat_id == st.session_state.current_chat_id
1786
+ label = ("β–Ά " if is_current else "") + chat_data["title"]
1787
+ if st.button(label, key=f"load_{chat_id}", use_container_width=True):
1788
+ save_current_chat()
1789
+ load_chat(chat_id)
1790
+ st.rerun()
1791
+ with col2:
1792
+ if st.button("πŸ—‘", key=f"del_{chat_id}"):
1793
+ del st.session_state.chats[chat_id]
1794
+ supa_delete_chat(chat_id) # delete from Supabase too
1795
+ if chat_id == st.session_state.current_chat_id:
1796
+ new_chat()
1797
+ st.rerun()
1798
+ st.divider()
1799
+
1800
  st.markdown("**🎯 Topics**")
1801
  topics = [
1802
  "πŸ“ˆ Calculus", "πŸ”’ Linear Algebra", "πŸ” Number Theory",
 
1877
  """, unsafe_allow_html=True)
1878
 
1879
  # Render chat history
1880
+ for i, msg in enumerate(st.session_state.messages):
1881
  avatar = "πŸ§‘β€πŸŽ“" if msg["role"] == "user" else "πŸ“"
1882
  with st.chat_message(msg["role"], avatar=avatar):
1883
  st.markdown(msg["content"])
1884
+ # Copy button on AI responses
1885
+ if msg["role"] == "assistant":
1886
+ if st.button("πŸ“‹ Copy", key=f"copy_{i}", help="Copy response"):
1887
+ st.write(
1888
+ f'<script>navigator.clipboard.writeText({repr(msg["content"])})</script>',
1889
+ unsafe_allow_html=True
1890
+ )
1891
+ st.toast("βœ… Copied!", icon="πŸ“‹")
1892
 
1893
  # ════════════════════════════════════════════════════════════════════
1894
  # INPUT β€” use st.chat_input (cleaner than text_input + button)
 
1932
  # Save to history
1933
  st.session_state.messages.append({"role": "user", "content": problem})
1934
  st.session_state.messages.append({"role": "assistant", "content": answer})
1935
+ # Auto-save to chat history
1936
+ if not st.session_state.current_chat_id:
1937
+ new_chat()
1938
+ save_current_chat()