Kerikim commited on
Commit
5f63087
·
1 Parent(s): 1f8a76a

elkay: api.py, chatbot

Browse files
Files changed (2) hide show
  1. phase/Student_view/chatbot.py +85 -96
  2. utils/api.py +1 -1
phase/Student_view/chatbot.py CHANGED
@@ -1,40 +1,52 @@
1
  # phase/Student_view/chatbot.py
2
- import streamlit as st
3
  import datetime
4
  import traceback
5
- import sys
6
- from pathlib import Path
7
-
8
- # --- Make sure we can import phase/api.py no matter where Streamlit runs from ---
9
- ROOT = Path(__file__).resolve().parents[1] # .../phase
10
- if str(ROOT) not in sys.path:
11
- sys.path.insert(0, str(ROOT))
12
-
13
- import api as backend # uses BACKEND_URL + optional BACKEND_TOKEN
14
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  TUTOR_WELCOME = "Hi! I'm your AI Financial Tutor. What would you like to learn today?"
17
 
18
-
19
  # -------------------------------
20
- # Session helpers
21
  # -------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
22
  def _coerce_ts(ts):
23
  if isinstance(ts, datetime.datetime):
24
  return ts
25
- try:
26
- # try epoch
27
- if isinstance(ts, (int, float)):
28
  return datetime.datetime.fromtimestamp(ts)
29
- if isinstance(ts, str):
 
 
 
30
  try:
31
- return datetime.datetime.fromisoformat(ts)
32
  except Exception:
33
- return datetime.datetime.fromtimestamp(float(ts))
34
- except Exception:
35
- pass
36
- return datetime.datetime.now()
37
-
38
 
39
  def _normalize_messages():
40
  msgs = st.session_state.get("messages", [])
@@ -44,131 +56,108 @@ def _normalize_messages():
44
  text = (m.get("text") or "").strip()
45
  sender = m.get("sender") or "user"
46
  ts = _coerce_ts(m.get("timestamp")) or now
47
- if text:
48
- normed.append({**m, "text": text, "sender": sender, "timestamp": ts})
49
  st.session_state.messages = normed
50
 
51
-
52
- def add_message(text: str, sender: str):
53
- if "messages" not in st.session_state:
54
- st.session_state.messages = []
55
- st.session_state.messages.append(
56
- {
57
- "id": str(datetime.datetime.now().timestamp()),
58
- "text": text,
59
- "sender": sender,
60
- "timestamp": datetime.datetime.now(),
61
- }
62
- )
63
-
64
-
65
  def _history_for_backend():
66
- """
67
- Convert local message format -> backend ChatRequest.history
68
- Keeps only the last ~10 turns to keep prompts lean.
69
- """
70
  hist = []
71
- for m in st.session_state.get("messages", [])[-20:]:
72
- role = "assistant" if m.get("sender") == "assistant" else "user"
73
- hist.append({"role": role, "content": m.get("text", "")})
 
 
 
74
  return hist
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
  # -------------------------------
78
  # Streamlit page
79
  # -------------------------------
80
  def show_page():
81
  st.title("🤖 AI Financial Tutor")
82
- st.caption("Backed by your local TinyLlama on the backend")
83
-
84
- # Health ping (optional, but helpful)
85
- col_a, col_b = st.columns(2)
86
- with col_a:
87
- try:
88
- h = backend.health()
89
- ok = h.get("status") == "ok"
90
- st.success(f"Backend: {'OK' if ok else 'Not ready'}")
91
- except Exception as e:
92
- st.error(f"Backend health failed: {e}")
93
 
94
- # First-time session state
95
  if "messages" not in st.session_state:
96
  st.session_state.messages = [{
97
- "id": "welcome",
98
  "text": TUTOR_WELCOME,
99
  "sender": "assistant",
100
- "timestamp": datetime.datetime.now(),
101
  }]
102
  if "is_typing" not in st.session_state:
103
  st.session_state.is_typing = False
104
 
105
  _normalize_messages()
106
 
107
- # Chat history bubbles
108
  chat_container = st.container()
109
  with chat_container:
110
  for msg in st.session_state.messages:
111
- time_str = msg["timestamp"].strftime("%H:%M")
112
  if msg.get("sender") == "assistant":
113
  bubble = (
114
- f"<div style='background:#e0e0e0;color:#000;padding:10px;border-radius:12px;"
115
- f"max-width:70%;margin-bottom:6px;'>{msg.get('text','')}<br>"
116
- f"<sub>{time_str}</sub></div>"
117
  )
118
  else:
119
  bubble = (
120
- f"<div style='background:#4CAF50;color:#fff;padding:10px;border-radius:12px;"
121
- f"max-width:70%;margin-left:auto;margin-bottom:6px;'>{msg.get('text','')}<br>"
122
- f"<sub>{time_str}</sub></div>"
123
  )
124
  st.markdown(bubble, unsafe_allow_html=True)
125
 
126
  if st.session_state.is_typing:
127
- st.markdown("🤖 _Tutor is typing..._")
128
 
129
- # Quick starters
130
  if len(st.session_state.messages) == 1:
131
  st.markdown("Try asking about:")
132
  cols = st.columns(2)
133
- for i, q in enumerate([
134
  "How does compound interest work?",
135
  "How much should I save for emergencies?",
136
- "What's a simple budgeting rule?",
137
- "How do I start investing safely?",
138
- ]):
139
- if cols[i % 2].button(q):
 
140
  add_message(q, "user")
141
  st.session_state.is_typing = True
142
  st.rerun()
143
 
144
- # Input
145
- user_input = st.chat_input("Ask me anything about personal finance…")
146
  if user_input:
147
  add_message(user_input, "user")
148
  st.session_state.is_typing = True
149
  st.rerun()
150
 
151
- # Produce reply (via backend /chat)
152
  if st.session_state.is_typing:
153
- try:
154
- with st.spinner("Thinking…"):
155
- history = _history_for_backend()
156
- # If your app tracks lesson_id/level in session, pass them here; else use defaults.
157
- lesson_id = st.session_state.get("current_lesson_id", 0)
158
- level = st.session_state.get("current_level_slug", "beginner")
159
-
160
- bot_reply = backend.chat_ai(
161
- query=st.session_state.messages[-1]["text"],
162
- lesson_id=lesson_id,
163
- level_slug=level,
164
- history=history,
165
- ) or "I couldn't generate a response just now."
166
- add_message(bot_reply, "assistant")
167
- except Exception as e:
168
- add_message(f"⚠️ Error: {''.join(traceback.format_exception_only(type(e), e)).strip()}", "assistant")
169
- finally:
170
- st.session_state.is_typing = False
171
- st.rerun()
172
 
173
  if st.button("Back to Dashboard", key="ai_tutor_back_btn"):
174
  st.session_state.current_page = "Student Dashboard"
 
1
  # phase/Student_view/chatbot.py
2
+ import os
3
  import datetime
4
  import traceback
5
+ import streamlit as st
 
 
 
 
 
 
 
 
6
 
7
+ # --- use our backend client (utils/api.py) ---
8
+ try:
9
+ from utils import api as backend
10
+ except ModuleNotFoundError:
11
+ # fallback if running from a different CWD
12
+ import sys, pathlib
13
+ ROOT = pathlib.Path(__file__).resolve().parents[2]
14
+ if str(ROOT) not in sys.path:
15
+ sys.path.insert(0, str(ROOT))
16
+ from utils import api as backend
17
 
18
  TUTOR_WELCOME = "Hi! I'm your AI Financial Tutor. What would you like to learn today?"
19
 
 
20
  # -------------------------------
21
+ # History helpers
22
  # -------------------------------
23
+ def add_message(text: str, sender: str):
24
+ if "messages" not in st.session_state:
25
+ st.session_state.messages = []
26
+ st.session_state.messages.append(
27
+ {
28
+ "id": str(datetime.datetime.now().timestamp()),
29
+ "text": (text or "").strip(),
30
+ "sender": sender,
31
+ "timestamp": datetime.datetime.now(),
32
+ }
33
+ )
34
+
35
  def _coerce_ts(ts):
36
  if isinstance(ts, datetime.datetime):
37
  return ts
38
+ if isinstance(ts, (int, float)):
39
+ try:
 
40
  return datetime.datetime.fromtimestamp(ts)
41
+ except Exception:
42
+ return None
43
+ if isinstance(ts, str):
44
+ for parser in (datetime.datetime.fromisoformat, lambda s: datetime.datetime.fromtimestamp(float(s))):
45
  try:
46
+ return parser(ts)
47
  except Exception:
48
+ pass
49
+ return None
 
 
 
50
 
51
  def _normalize_messages():
52
  msgs = st.session_state.get("messages", [])
 
56
  text = (m.get("text") or "").strip()
57
  sender = m.get("sender") or "user"
58
  ts = _coerce_ts(m.get("timestamp")) or now
59
+ normed.append({**m, "text": text, "sender": sender, "timestamp": ts})
 
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}"
92
 
93
  # -------------------------------
94
  # Streamlit page
95
  # -------------------------------
96
  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",
103
  "text": TUTOR_WELCOME,
104
  "sender": "assistant",
105
+ "timestamp": datetime.datetime.now()
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:
115
+ t = msg["timestamp"].strftime("%H:%M")
116
  if msg.get("sender") == "assistant":
117
  bubble = (
118
+ "<div style='background:#e0e0e0;color:#000;padding:10px;border-radius:12px;"
119
+ "max-width:70%;margin-bottom:6px;'>"
120
+ f"{msg.get('text','')}<br><sub>{t}</sub></div>"
121
  )
122
  else:
123
  bubble = (
124
+ "<div style='background:#4CAF50;color:#fff;padding:10px;border-radius:12px;"
125
+ "max-width:70%;margin-left:auto;margin-bottom:6px;'>"
126
+ f"{msg.get('text','')}<br><sub>{t}</sub></div>"
127
  )
128
  st.markdown(bubble, unsafe_allow_html=True)
129
 
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)
137
+ quick = [
138
  "How does compound interest work?",
139
  "How much should I save for emergencies?",
140
+ "What's a good budgeting strategy?",
141
+ "How do I start investing?",
142
+ ]
143
+ for i, q in enumerate(quick):
144
+ if cols[i % 2].button(q, key=f"suggest_{i}"):
145
  add_message(q, "user")
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"
utils/api.py CHANGED
@@ -1,4 +1,4 @@
1
- # phase/api.py
2
  import os
3
  import json
4
  import logging
 
1
+ # utils/api.py
2
  import os
3
  import json
4
  import logging