lanna_lalala;- commited on
Commit
a9fb560
ยท
2 Parent(s): 28e1b26702933a

Merge branch 'main' of https://github.com/Alalalallalalalalalalalal/Frontend

Browse files
app.py CHANGED
@@ -16,21 +16,9 @@ from phase import welcome
16
 
17
  import os, requests
18
 
19
- from utils.api import BACKEND
20
- st.sidebar.caption(f"Backend URL: {BACKEND}")
21
-
22
 
23
  DISABLE_DB = os.getenv("DISABLE_DB", "1") == "1"
24
 
25
- try:
26
- ok = api.health().get("ok")
27
- if ok:
28
- st.sidebar.success("Backend: UP")
29
- else:
30
- st.sidebar.warning("Backend: ?")
31
- except Exception as e:
32
- st.sidebar.error(f"Backend DOWN: {e}")
33
-
34
 
35
  # --- SESSION STATE INITIALIZATION ---
36
  for key, default in [("user", None), ("current_page", "Welcome"),
 
16
 
17
  import os, requests
18
 
 
 
 
19
 
20
  DISABLE_DB = os.getenv("DISABLE_DB", "1") == "1"
21
 
 
 
 
 
 
 
 
 
 
22
 
23
  # --- SESSION STATE INITIALIZATION ---
24
  for key, default in [("user", None), ("current_page", "Welcome"),
phase/Student_view/chatbot.py CHANGED
@@ -1,5 +1,6 @@
1
  # phase/Student_view/chatbot.py
2
  import os
 
3
  import datetime
4
  import traceback
5
  import streamlit as st
@@ -17,6 +18,15 @@ except ModuleNotFoundError:
17
 
18
  TUTOR_WELCOME = "Hi! I'm your AI Financial Tutor. What would you like to learn today?"
19
 
 
 
 
 
 
 
 
 
 
20
  # -------------------------------
21
  # History helpers
22
  # -------------------------------
@@ -60,32 +70,36 @@ def _normalize_messages():
60
  st.session_state.messages = normed
61
 
62
  def _history_for_backend():
63
- """Convert our local history into [{role, content}] for the backend."""
64
  hist = []
65
  for m in st.session_state.get("messages", []):
66
  text = (m.get("text") or "").strip()
67
- if not text:
68
  continue
69
  role = "assistant" if (m.get("sender") == "assistant") else "user"
70
  hist.append({"role": role, "content": text})
71
- return hist
72
 
73
  # -------------------------------
74
  # Reply via backend (/chat)
75
  # -------------------------------
76
  def _reply_via_backend(user_text: str) -> str:
77
- # Defaults: use selected lesson/level if present
78
  lesson_id = st.session_state.get("current_lesson_id") or 0
79
  level_slug = (st.session_state.get("user", {}).get("level") or "beginner").strip().lower()
80
 
 
 
 
 
 
 
81
  try:
82
  answer = backend.chat_ai(
83
  query=user_text,
84
  lesson_id=lesson_id,
85
  level_slug=level_slug,
86
- history=_history_for_backend(),
87
  )
88
- return (answer or "").strip()
89
  except Exception as e:
90
  err_text = "".join(traceback.format_exception_only(type(e), e)).strip()
91
  return f"โš ๏ธ Chat failed: {err_text}"
@@ -97,6 +111,7 @@ def show_page():
97
  st.title("๐Ÿค– AI Financial Tutor")
98
  st.caption("Get personalized help with your financial questions")
99
 
 
100
  if "messages" not in st.session_state:
101
  st.session_state.messages = [{
102
  "id": "1",
@@ -106,9 +121,12 @@ def show_page():
106
  }]
107
  if "is_typing" not in st.session_state:
108
  st.session_state.is_typing = False
 
 
109
 
110
  _normalize_messages()
111
 
 
112
  chat_container = st.container()
113
  with chat_container:
114
  for msg in st.session_state.messages:
@@ -130,7 +148,16 @@ def show_page():
130
  if st.session_state.is_typing:
131
  st.markdown("๐Ÿค– _FinanceBot is typing..._")
132
 
133
- # Quick suggestions when only the welcome is present
 
 
 
 
 
 
 
 
 
134
  if len(st.session_state.messages) == 1:
135
  st.markdown("Try asking about:")
136
  cols = st.columns(2)
@@ -146,19 +173,22 @@ def show_page():
146
  st.session_state.is_typing = True
147
  st.rerun()
148
 
 
149
  user_input = st.chat_input("Ask me anything about personal finance...")
150
  if user_input:
151
  add_message(user_input, "user")
152
  st.session_state.is_typing = True
153
  st.rerun()
154
 
 
155
  if st.session_state.is_typing:
156
  with st.spinner("FinanceBot is thinking..."):
157
- bot_reply = _reply_via_backend(st.session_state.messages[-1]["text"])
 
158
  add_message(bot_reply, "assistant")
159
  st.session_state.is_typing = False
160
  st.rerun()
161
 
162
  if st.button("Back to Dashboard", key="ai_tutor_back_btn"):
163
  st.session_state.current_page = "Student Dashboard"
164
- st.rerun()
 
1
  # phase/Student_view/chatbot.py
2
  import os
3
+ import re
4
  import datetime
5
  import traceback
6
  import streamlit as st
 
18
 
19
  TUTOR_WELCOME = "Hi! I'm your AI Financial Tutor. What would you like to learn today?"
20
 
21
+ def _clean_bot_text(t: str) -> str:
22
+ # strip xml-ish tags like <user>...</user>, <assistant>...</assistant>
23
+ t = re.sub(r"</?(user|assistant|system)\b[^>]*>", "", t, flags=re.I)
24
+ # strip leading speaker labels (User:, Assistant:, System:)
25
+ t = re.sub(r"(?im)^(user|assistant|system)\s*:\s*", "", t)
26
+ # collapse extra newlines
27
+ t = re.sub(r"\n{3,}", "\n\n", t)
28
+ return t.strip()
29
+
30
  # -------------------------------
31
  # History helpers
32
  # -------------------------------
 
70
  st.session_state.messages = normed
71
 
72
  def _history_for_backend():
 
73
  hist = []
74
  for m in st.session_state.get("messages", []):
75
  text = (m.get("text") or "").strip()
76
+ if not text or text == TUTOR_WELCOME:
77
  continue
78
  role = "assistant" if (m.get("sender") == "assistant") else "user"
79
  hist.append({"role": role, "content": text})
80
+ return hist[-4:] # <= keep it tiny
81
 
82
  # -------------------------------
83
  # Reply via backend (/chat)
84
  # -------------------------------
85
  def _reply_via_backend(user_text: str) -> str:
 
86
  lesson_id = st.session_state.get("current_lesson_id") or 0
87
  level_slug = (st.session_state.get("user", {}).get("level") or "beginner").strip().lower()
88
 
89
+ # Build history and remove duplicate of the message we are sending as `query`
90
+ hist = _history_for_backend()
91
+ if hist and hist[-1].get("role") == "user" and hist[-1].get("content", "").strip() == (user_text or "").strip():
92
+ hist = hist[:-1]
93
+ hist = hist[-4:]
94
+
95
  try:
96
  answer = backend.chat_ai(
97
  query=user_text,
98
  lesson_id=lesson_id,
99
  level_slug=level_slug,
100
+ history=hist,
101
  )
102
+ return _clean_bot_text((answer or "").strip())
103
  except Exception as e:
104
  err_text = "".join(traceback.format_exception_only(type(e), e)).strip()
105
  return f"โš ๏ธ Chat failed: {err_text}"
 
111
  st.title("๐Ÿค– AI Financial Tutor")
112
  st.caption("Get personalized help with your financial questions")
113
 
114
+ # --- session state init ---
115
  if "messages" not in st.session_state:
116
  st.session_state.messages = [{
117
  "id": "1",
 
121
  }]
122
  if "is_typing" not in st.session_state:
123
  st.session_state.is_typing = False
124
+ if "chatbot_prefill_sent" not in st.session_state:
125
+ st.session_state.chatbot_prefill_sent = False
126
 
127
  _normalize_messages()
128
 
129
+ # --- render chat bubbles ---
130
  chat_container = st.container()
131
  with chat_container:
132
  for msg in st.session_state.messages:
 
148
  if st.session_state.is_typing:
149
  st.markdown("๐Ÿค– _FinanceBot is typing..._")
150
 
151
+ # --- quiz handoff auto-prompt (only once) ---
152
+ prefill = st.session_state.get("chatbot_prefill")
153
+ if prefill and not st.session_state.chatbot_prefill_sent:
154
+ add_message(prefill, "user")
155
+ st.session_state.is_typing = True
156
+ st.session_state.chatbot_prefill_sent = True
157
+ st.session_state.chatbot_prefill = None
158
+ st.rerun()
159
+
160
+ # --- quick suggestions when fresh ---
161
  if len(st.session_state.messages) == 1:
162
  st.markdown("Try asking about:")
163
  cols = st.columns(2)
 
173
  st.session_state.is_typing = True
174
  st.rerun()
175
 
176
+ # --- user input ---
177
  user_input = st.chat_input("Ask me anything about personal finance...")
178
  if user_input:
179
  add_message(user_input, "user")
180
  st.session_state.is_typing = True
181
  st.rerun()
182
 
183
+ # --- handle pending bot reply ---
184
  if st.session_state.is_typing:
185
  with st.spinner("FinanceBot is thinking..."):
186
+ last_user_msg = next((m["text"] for m in reversed(st.session_state.messages) if m["sender"] == "user"), "")
187
+ bot_reply = _reply_via_backend(last_user_msg)
188
  add_message(bot_reply, "assistant")
189
  st.session_state.is_typing = False
190
  st.rerun()
191
 
192
  if st.button("Back to Dashboard", key="ai_tutor_back_btn"):
193
  st.session_state.current_page = "Student Dashboard"
194
+ st.rerun()
phase/Student_view/quiz.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import os
2
  import streamlit as st
3
  from utils.quizdata import quizzes_data
@@ -350,13 +351,26 @@ def show_results(quiz_id):
350
  st.session_state.answers = {}
351
  if "messages" not in st.session_state:
352
  st.session_state.messages = []
353
- wrong_q_text = "\n".join(
354
- [f"Q: {q}\nYour answer: {ua}\nCorrect answer: {ca}\nExplanation: {ex}"
355
- for q, ua, ca, ex in wrong_answers])
356
- tutor_prompt = f"I just completed a financial quiz and got some questions wrong. Here are the details:\n{wrong_q_text}\nCan you help me understand these concepts better?"
 
 
 
 
 
 
 
 
 
 
 
 
 
357
  st.session_state.messages.append({
358
  "id": str(datetime.datetime.now().timestamp()),
359
- "text": tutor_prompt,
360
  "sender": "user",
361
  "timestamp": datetime.datetime.now()
362
  })
 
1
+ #quiz.py
2
  import os
3
  import streamlit as st
4
  from utils.quizdata import quizzes_data
 
351
  st.session_state.answers = {}
352
  if "messages" not in st.session_state:
353
  st.session_state.messages = []
354
+
355
+ # keep only first 3 wrong items, and cap text length
356
+ short = wrong_answers[:3]
357
+ rows = []
358
+ for q, ua, ca, ex in short:
359
+ q = (q or "")[:160]
360
+ ua = (", ".join(ua) if isinstance(ua, list) else str(ua or ""))[:120]
361
+ ca = (", ".join(ca) if isinstance(ca, list) else str(ca or ""))[:120]
362
+ ex = (ex or "")[:200]
363
+ rows.append(f"Q: {q}\nYour answer: {ua}\nCorrect answer: {ca}\nExplanation: {ex}")
364
+
365
+ handoff = (
366
+ "I just completed a financial quiz and missed a few. "
367
+ "Explain each briefly and give one easy tip to remember:\n\n" +
368
+ "\n\n".join(rows)
369
+ )
370
+
371
  st.session_state.messages.append({
372
  "id": str(datetime.datetime.now().timestamp()),
373
+ "text": handoff,
374
  "sender": "user",
375
  "timestamp": datetime.datetime.now()
376
  })
phase/welcome.py CHANGED
@@ -128,8 +128,8 @@ def welcomeui():
128
  col1, col2, col3, col4 = st.columns(4)
129
 
130
  features = [
131
- ("๐Ÿ“–", "Interactive Lessons", "Engaging content that makes financial literacy fun and accessible for all ages!"),
132
- ("๐ŸŽฎ", "Educational Games", "Learn through play with our collection of money management games and challenges!"),
133
  ("๐Ÿ†", "Progress Tracking", "Monitor learning progress with quizzes, achievements, and detailed analytics!"),
134
  ("๐Ÿค–", "AI Assistant", "Get instant help and personalized guidance from our friendly chatbot!")
135
  ]
@@ -165,7 +165,7 @@ def welcomeui():
165
  <div class="card" style="background: linear-gradient(to right, #30E8BF, #FF8235); color: #fff;">
166
  <div class="card-icon">๐Ÿ‘ฉโ€๐Ÿซ</div>
167
  <h3>For Teachers</h3>
168
- <p>Manage your classroom, track progress, and engage your students with financial education!</p>
169
  </div>
170
  """, unsafe_allow_html=True)
171
  if st.button("๐Ÿ“Š Teacher Dashboard"):
 
128
  col1, col2, col3, col4 = st.columns(4)
129
 
130
  features = [
131
+ ("๐Ÿ“–", "Lessons", "Engaging content that makes financial literacy fun and accessible for all ages!"),
132
+ ("๐ŸŽฎ", "Games", "Learn through play with our collection of money management games and challenges!"),
133
  ("๐Ÿ†", "Progress Tracking", "Monitor learning progress with quizzes, achievements, and detailed analytics!"),
134
  ("๐Ÿค–", "AI Assistant", "Get instant help and personalized guidance from our friendly chatbot!")
135
  ]
 
165
  <div class="card" style="background: linear-gradient(to right, #30E8BF, #FF8235); color: #fff;">
166
  <div class="card-icon">๐Ÿ‘ฉโ€๐Ÿซ</div>
167
  <h3>For Teachers</h3>
168
+ <p>Manage your classroom, track progress, and engage your students!</p>
169
  </div>
170
  """, unsafe_allow_html=True)
171
  if st.button("๐Ÿ“Š Teacher Dashboard"):
utils/api.py CHANGED
@@ -12,7 +12,7 @@ if not BACKEND:
12
  raise RuntimeError("BACKEND_URL is not set in Space secrets.")
13
 
14
  TOKEN = (os.getenv("BACKEND_TOKEN") or os.getenv("HF_TOKEN") or "").strip()
15
- DEFAULT_TIMEOUT = int(os.getenv("BACKEND_TIMEOUT", "60"))
16
 
17
  _session = requests.Session()
18
  retry = Retry(
@@ -65,18 +65,24 @@ def _req(method: str, path: str, **kw):
65
  # try next prefix on 404
66
  if status == 404:
67
  continue
 
 
 
 
 
 
 
 
 
 
 
68
  body = ""
69
  try:
70
  body = e.response.text[:500]
71
  except Exception:
72
  pass
73
- if status in (401, 403):
74
- raise RuntimeError(
75
- f"{method} {url} failed [{status}] โ€“ auth rejected. "
76
- f"Check BACKEND_TOKEN/HF_TOKEN and backend visibility."
77
- ) from e
78
  raise RuntimeError(f"{method} {url} failed [{status}]: {body}") from e
79
- except requests.RequestException as e:
80
  # try next prefix
81
  continue
82
  raise RuntimeError(f"No matching endpoint for {method} {path} with prefixes {list(_prefixes())}")
@@ -337,12 +343,12 @@ def chat_ai(query: str, lesson_id: int, level_slug: str, history=None) -> str:
337
  "history": history or [],
338
  }
339
  try:
340
- # Force the full path without relying on _prefixes
341
- url = f"{BACKEND}/chat" # make sure BACKEND has no trailing slash
342
- r = _session.post(url, json=payload, timeout=120)
 
343
  r.raise_for_status()
344
- d = r.json()
345
- return d.get("answer", "")
346
  except Exception as e:
347
  return f"(chat failed: {e})"
348
 
 
12
  raise RuntimeError("BACKEND_URL is not set in Space secrets.")
13
 
14
  TOKEN = (os.getenv("BACKEND_TOKEN") or os.getenv("HF_TOKEN") or "").strip()
15
+ DEFAULT_TIMEOUT = int(os.getenv("BACKEND_TIMEOUT", "120"))
16
 
17
  _session = requests.Session()
18
  retry = Retry(
 
65
  # try next prefix on 404
66
  if status == 404:
67
  continue
68
+
69
+ # ---------- friendlier auth errors ----------
70
+ if status in (401, 403):
71
+ # Normal bad credentials on the login endpoint
72
+ if path.endswith("/auth/login"):
73
+ raise RuntimeError("Incorrect email or password.") from e
74
+ # Generic auth issue elsewhere (expired session, etc.)
75
+ raise RuntimeError("Authentication failed. Please sign in again.") from e
76
+ # -------------------------------------------
77
+
78
+ # keep a tiny body for other errors
79
  body = ""
80
  try:
81
  body = e.response.text[:500]
82
  except Exception:
83
  pass
 
 
 
 
 
84
  raise RuntimeError(f"{method} {url} failed [{status}]: {body}") from e
85
+ except requests.RequestException:
86
  # try next prefix
87
  continue
88
  raise RuntimeError(f"No matching endpoint for {method} {path} with prefixes {list(_prefixes())}")
 
343
  "history": history or [],
344
  }
345
  try:
346
+ url = f"{BACKEND}/chat"
347
+ with requests.Session() as s:
348
+ s.headers.update(_session.headers)
349
+ r = s.post(url, json=payload, timeout=DEFAULT_TIMEOUT) # no retry adapter
350
  r.raise_for_status()
351
+ return r.json().get("answer", "")
 
352
  except Exception as e:
353
  return f"(chat failed: {e})"
354