bhushanrocks commited on
Commit
c32c6c4
·
verified ·
1 Parent(s): f1ca6e6

trying v3 fix app.py

Browse files
Files changed (1) hide show
  1. app.py +147 -261
app.py CHANGED
@@ -8,14 +8,17 @@ import uuid
8
  import json
9
  import re
10
  import random
11
- from typing import List, Tuple, Dict, Any
12
  import hashlib
 
13
 
14
- # Set device globally
 
 
15
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
 
16
 
17
  # ------------------------------
18
- # 🔐 MongoDB Setup & Auth Management
19
  # ------------------------------
20
  MONGO_URI = os.getenv("MONGO_URI")
21
  use_mongo = False
@@ -33,395 +36,278 @@ if MONGO_URI:
33
  use_mongo = True
34
  print("✅ Connected to MongoDB for persistence and authentication.")
35
  except Exception as e:
36
- # NOTE: In a Hugging Face Space, network connections can sometimes fail.
37
- # Falling back to local storage is the correct behavior.
38
  print(f"⚠️ MongoDB unavailable: {e}. Using local JSON storage.")
39
  else:
40
  print("⚠️ MONGO_URI not set. Using local JSON storage.")
41
 
42
- # ------------------------------
43
- # 💾 Local JSON Fallback (User Auth and Chat History)
44
- # ------------------------------
45
  LOCAL_USERS_FILE = "local_users.json"
46
  LOCAL_CHATS_FILE = "local_chats.json"
47
 
 
48
  def load_local_data(filepath):
49
  if os.path.exists(filepath):
50
  with open(filepath, "r") as f:
51
  return json.load(f)
52
  return {}
53
 
 
54
  def save_local_data(data, filepath):
55
  with open(filepath, "w") as f:
56
  json.dump(data, f, indent=4)
57
 
 
58
  local_users = load_local_data(LOCAL_USERS_FILE)
59
  local_chats = load_local_data(LOCAL_CHATS_FILE)
60
 
61
- # Utility to hash passwords for storage (even locally)
62
  def hash_password(password):
63
  return hashlib.sha256(password.encode()).hexdigest()
64
 
65
  # ------------------------------
66
- # 🔑 Authentication Functions
67
  # ------------------------------
68
-
69
  def authenticate_user(username, password):
70
- hashed_password = hash_password(password)
71
-
72
  if use_mongo:
73
  user_data = users_collection.find_one({"username": username})
74
  else:
75
  user_data = local_users.get(username)
76
 
77
- if user_data and user_data['password'] == hashed_password:
78
- return user_data['user_id'], f"Welcome back, {username}!"
79
  return None, "Invalid username or password."
80
 
 
81
  def register_user(username, password):
82
- hashed_password = hash_password(password)
83
  new_user_id = str(uuid.uuid4())
84
-
85
  if use_mongo:
86
  if users_collection.find_one({"username": username}):
87
  return None, "Username already taken."
88
- users_collection.insert_one({"username": username, "password": hashed_password, "user_id": new_user_id})
89
  else:
90
  if username in local_users:
91
  return None, "Username already taken."
92
- local_users[username] = {"username": username, "password": hashed_password, "user_id": new_user_id}
93
  save_local_data(local_users, LOCAL_USERS_FILE)
94
-
95
  return new_user_id, f"Account created! Welcome, {username}."
96
 
97
  # ------------------------------
98
- # 📜 Conversation Management & Gradio Format Conversion
99
  # ------------------------------
100
-
101
- # Helper to convert stored tuple history to Gradio 'messages' format
102
- def history_to_gradio_format(history: List[Tuple[str, str]]) -> List[Dict[str, str]]:
103
- """Converts [(user_msg, bot_msg)] tuples to Gradio message dictionaries."""
104
- gradio_history = []
105
- for user_msg, bot_msg in history:
106
- if user_msg:
107
- gradio_history.append({"role": "user", "content": user_msg})
108
- if bot_msg:
109
- gradio_history.append({"role": "assistant", "content": bot_msg})
110
- return gradio_history
111
-
112
  def get_history(user_id):
113
- """Fetch user conversation history (stored as tuples)."""
114
  if not user_id:
115
  return []
116
-
117
  if use_mongo:
118
  convo = chats_collection.find_one({"user_id": user_id})
119
- # History is stored as list of tuples: [(user_msg, bot_msg), ...]
120
  return convo.get("history", [])
121
- else:
122
- return local_chats.get(user_id, [])
123
 
124
- def save_history(user_id, history: List[Tuple[str, str]]):
125
- """Save conversation history (stored as tuples)."""
126
  if not user_id:
127
  return
128
-
129
  if use_mongo:
130
- chats_collection.update_one(
131
- {"user_id": user_id},
132
- {"$set": {"history": history}},
133
- upsert=True
134
- )
135
  else:
136
  local_chats[user_id] = history
137
  save_local_data(local_chats, LOCAL_CHATS_FILE)
138
 
 
 
 
 
 
 
 
 
 
 
139
  # ------------------------------
140
- # 🤗 Load Model (PEFT/LoRA Integration)
141
  # ------------------------------
142
- # Assuming the user's latest checkpoint is pushed here.
143
- MODEL_NAME = "bhushanrocks/supportpal-dialoGPT-v3"
144
  tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
145
 
146
  try:
147
- # 1. Load the PEFT config from the Hub to get the base model name
148
  config = PeftConfig.from_pretrained(MODEL_NAME)
149
- base_model_name = config.base_model_name_or_path
150
-
151
- # 2. Load the base model (e.g., microsoft/DialoGPT-medium)
152
  base_model = AutoModelForCausalLM.from_pretrained(
153
- base_model_name,
154
- torch_dtype=torch.float16 if DEVICE == 'cuda' else None,
155
  ).to(DEVICE)
156
-
157
- # 3. Load the LoRA adapters and attach them to the base model
158
  model = PeftModel.from_pretrained(base_model, MODEL_NAME)
159
- print(f"✅ Successfully loaded PEFT model from {MODEL_NAME}")
160
-
161
  except Exception as e:
162
- # Fallback if the PEFT load fails (e.g., if it was already merged or structure changed)
163
- print(f"⚠️ PEFT model loading failed. Falling back to AutoModelForCausalLM. Error: {e}")
164
  model = AutoModelForCausalLM.from_pretrained(MODEL_NAME).to(DEVICE)
165
 
166
  model.eval()
167
 
168
  # ------------------------------
169
- # 💬 Chat Logic (Refined Context Handling)
170
  # ------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
- def generate_response(user_input, chat_history, model, tokenizer, system_prompt, max_new_tokens=150):
173
- """
174
- Generates a model response using the trained DialoGPT with robust handling
175
- for missing AI markers, truncation, and empty outputs.
176
- """
177
 
178
- # ------------------------------------------------------------------
179
- # 1️⃣ Prepare dialogue context (robust handling)
180
- # ------------------------------------------------------------------
181
  prompt_turns = []
182
-
183
- # Handle both dict-based and tuple/string-based history
184
- for turn in chat_history:
185
- if isinstance(turn, dict) and "user" in turn and "bot" in turn:
186
- # Expected structured history
187
- prompt_turns.append(f"Human: {turn['user']}")
188
- prompt_turns.append(f"AI: {turn['bot']}")
189
  elif isinstance(turn, (list, tuple)) and len(turn) == 2:
190
- # Gradio ChatInterface sometimes stores (user, bot) tuples
191
- prompt_turns.append(f"Human: {turn[0]}")
192
- prompt_turns.append(f"AI: {turn[1]}")
193
- elif isinstance(turn, str):
194
- # Fallback: unexpected raw string history
195
- prompt_turns.append(f"Human: {turn}")
196
 
197
  dialogue = system_prompt.strip() + "\n\n" + "\n".join(prompt_turns)
198
- dialogue += f"\nHuman: {user_input.strip()}\nAI:"
199
-
200
- # ------------------------------------------------------------------
201
- # 2️⃣ Tokenize safely (preserve recent turns)
202
- # ------------------------------------------------------------------
203
- tokenizer.truncation_side = "left" # keep last turns
204
- inputs = tokenizer(
205
- dialogue + tokenizer.eos_token,
206
- return_tensors="pt",
207
- truncation=True,
208
- max_length=512,
209
- ).to(model.device)
210
-
211
- # ------------------------------------------------------------------
212
- # 3️⃣ Generate
213
- # ------------------------------------------------------------------
214
  with torch.no_grad():
215
  outputs = model.generate(
216
  **inputs,
217
- max_new_tokens=max_new_tokens,
218
- temperature=0.8,
 
219
  top_p=0.9,
220
- repetition_penalty=1.1,
221
  do_sample=True,
222
  pad_token_id=tokenizer.eos_token_id,
223
  )
224
 
225
  decoded = tokenizer.decode(outputs[0], skip_special_tokens=True)
 
 
 
 
 
 
 
226
 
227
- # ------------------------------------------------------------------
228
- # 4️⃣ Extract response robustly
229
- # ------------------------------------------------------------------
230
- # Look for the last "AI:" marker and take everything after it
231
- match = re.search(r"AI:\s*(.*)", decoded, flags=re.DOTALL)
232
- response = match.group(1).strip() if match else decoded.strip()
233
 
234
- # Stop before the next Human turn if model over-generated
235
- response = re.split(r"\nHuman:", response)[0].strip()
236
 
237
- # Clean out any duplicated system prompt or extra markers
238
- response = re.sub(rf"{re.escape(system_prompt)}", "", response).strip()
239
- response = re.sub(r"AI:\s*", "", response).strip()
 
240
 
241
- # ------------------------------------------------------------------
242
- # 5️⃣ Handle empty or invalid responses
243
- # ------------------------------------------------------------------
244
- if not response or response.lower() in ["", "human:", "ai:"]:
245
- response = "I'm here with you. Could you tell me a bit more about how you're feeling?"
246
 
247
- # Optional empathy touch 💛
248
- if random.random() < 0.25 and not response.endswith(("💛", "✨", ".", "?", "!")):
249
- response += " 💛"
250
 
251
- # ------------------------------------------------------------------
252
- # 6️⃣ Update chat history & return
253
- # ------------------------------------------------------------------
254
- chat_history.append({"user": user_input, "bot": response})
255
- return response, chat_history
 
 
 
 
256
 
257
  # ------------------------------
258
- # 🔑 Gradio Authentication Handlers
259
  # ------------------------------
260
-
261
- def handle_login(username, password, current_state):
262
- user_id, status_message = authenticate_user(username, password)
263
-
264
  if user_id:
265
- # Get history in tuple format
266
- history = get_history(user_id)
267
-
268
- current_state.update({
269
- 'logged_in': True,
270
- 'user_id': user_id,
271
- 'username': username,
272
- 'history': history, # Store history in tuple format
273
- })
274
-
275
- # Convert history to Gradio 'messages' format for display
276
- gradio_history = history_to_gradio_format(history)
277
-
278
- # Return components: status, auth_panel hide, chat_panel show, chatbot history (gradio format), state
279
- return status_message, gr.update(visible=False), gr.update(visible=True), gradio_history, current_state, f"Logged in as: **{username}**"
280
- else:
281
- # Return components: status, auth_panel show, chat_panel hide, empty history, state, user status (no change)
282
- return status_message, gr.update(visible=True), gr.update(visible=False), [], current_state, gr.update()
283
 
284
- def handle_register(username, password, current_state):
285
- user_id, status_message = register_user(username, password)
286
-
287
  if user_id:
288
- current_state.update({
289
- 'logged_in': True,
290
- 'user_id': user_id,
291
- 'username': username,
292
- 'history': [], # Store history in tuple format
293
- })
294
- # Return components: status, auth_panel hide, chat_panel show, empty history (gradio format), state
295
- return status_message, gr.update(visible=False), gr.update(visible=True), [], current_state, f"Logged in as: **{username}**"
296
- else:
297
- # Return components: status, auth_panel show, chat_panel hide, empty history, state, user status (no change)
298
- return status_message, gr.update(visible=True), gr.update(visible=False), [], current_state, gr.update()
299
 
300
  def handle_logout():
301
- # Reset all components and states
302
  return (
303
- "Logged out successfully. Please log in again.",
304
- gr.update(visible=True), # Show auth panel
305
- gr.update(visible=False), # Hide chat panel
306
- [], # Clear chatbot history
307
- {'logged_in': False, 'user_id': None, 'username': None, 'history': []}, # Reset state
308
- gr.update(value=""), # Clear user input box
309
- "Logged in as:" # Reset user status label
310
  )
311
 
312
- def handle_clear_chat(current_state):
313
- # Only clear history for the currently logged-in user, but keep the session alive
314
- if current_state.get('logged_in'):
315
- user_id = current_state['user_id']
316
- save_history(user_id, []) # Save empty history (tuple format)
317
- current_state['history'] = []
318
- return [], current_state # Return empty Gradio history
319
- return [], current_state
320
 
 
 
 
 
 
 
 
321
 
322
  # ------------------------------
323
  # 🧱 Gradio UI
324
  # ------------------------------
325
-
326
- def process_chat(user_message, chat_history):
327
- # Ensure consistent history format before sending to model
328
- if chat_history is None:
329
- chat_history = []
330
- elif isinstance(chat_history, list) and len(chat_history) > 0 and isinstance(chat_history[0], (list, tuple)):
331
- # Convert tuple-based history into dict-based
332
- chat_history = [{"user": u, "bot": b} for u, b in chat_history]
333
-
334
- # Call model generator
335
- response, updated_history = generate_response(user_message, chat_history, model, tokenizer, system_prompt)
336
- return response, updated_history
337
-
338
  with gr.Blocks(title="SupportPal Chatbot") as demo:
339
- storage_mode = "☁️ MongoDB Connected" if use_mongo else "💾 Using Local Storage (Dev Only)"
340
-
341
- # State component to hold session data (logged_in status, user_id, history in tuple format)
342
- session_state = gr.State(
343
- value={'logged_in': False, 'user_id': None, 'username': None, 'history': []}
344
- )
345
 
346
- gr.Markdown(f"## 🤖 SupportPal - Empathetic Chatbot\n**Persistence Status:** {storage_mode}")
 
347
 
348
- # --- Authentication Panel (Visible by default) ---
349
  with gr.Row(visible=True) as auth_panel:
350
- with gr.Column(scale=1):
351
  auth_status = gr.Markdown("Please log in or register to start chatting.")
352
  username_box = gr.Textbox(label="Username")
353
  password_box = gr.Textbox(label="Password", type="password")
354
-
355
  with gr.Row():
356
  login_btn = gr.Button("Login", variant="primary")
357
  register_btn = gr.Button("Register", variant="secondary")
358
 
359
- # --- Chat Panel (Hidden by default) ---
360
  with gr.Column(visible=False) as chat_panel:
361
- # TARGET FOR USER STATUS UPDATE (Issue 1 fix)
362
- user_status = gr.Markdown("Logged in as:")
363
-
364
- # NOTE: Added type='messages' to avoid the deprecation warning
365
- chatbot_ui = gr.Chatbot(height=400, type='messages')
366
-
367
  with gr.Row():
368
- msg = gr.Textbox(label="Type your message here...", scale=4)
369
- send_btn = gr.Button("Send", scale=1, variant="primary")
370
-
371
  with gr.Row():
372
- clear_btn = gr.Button("Clear History", variant="secondary")
373
  logout_btn = gr.Button("Logout", variant="stop")
374
 
375
- # --- Authentication Flow (Issue 1 fix: add user_status output) ---
376
- login_btn.click(
377
- fn=handle_login,
378
- inputs=[username_box, password_box, session_state],
379
- outputs=[auth_status, auth_panel, chat_panel, chatbot_ui, session_state, user_status]
380
- )
381
 
382
- register_btn.click(
383
- fn=handle_register,
384
- inputs=[username_box, password_box, session_state],
385
- outputs=[auth_status, auth_panel, chat_panel, chatbot_ui, session_state, user_status]
386
- )
387
-
388
- # --- Chat Flow (FIXED) ---
389
-
390
- # 1. Action triggered by clicking the Send button
391
- send_btn.click(
392
- fn=generate_response,
393
- inputs=[msg, session_state],
394
- outputs=[chatbot_ui, session_state, auth_status] # chatbot_ui now expects 'messages' dict format
395
- ).then(
396
- # Clear input box after sending
397
- fn=lambda: gr.update(value=""),
398
- inputs=None,
399
- outputs=[msg]
400
- )
401
 
402
- # 2. Action triggered by hitting Enter in the Textbox (using submit, not click)
403
- msg.submit(
404
- fn=process_chat,
405
- inputs=[msg, session_state],
406
- outputs=[chatbot_ui, session_state, auth_status] # chatbot_ui now expects 'messages' dict format
407
- ).then(
408
- # Clear input box after sending
409
- fn=lambda: gr.update(value=""),
410
- inputs=None,
411
- outputs=[msg]
412
- )
413
 
414
- # Clear and Logout handlers (Issue 1 fix: add user_status output for logout)
415
- clear_btn.click(
416
- fn=handle_clear_chat,
417
- inputs=[session_state],
418
- outputs=[chatbot_ui, session_state]
419
- )
420
-
421
- logout_btn.click(
422
- fn=handle_logout,
423
- inputs=None,
424
- outputs=[auth_status, auth_panel, chat_panel, chatbot_ui, session_state, msg, user_status]
425
- )
426
 
427
  demo.launch()
 
8
  import json
9
  import re
10
  import random
 
11
  import hashlib
12
+ from typing import List, Tuple, Dict, Any
13
 
14
+ # ------------------------------
15
+ # ⚙️ Global Config
16
+ # ------------------------------
17
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
18
+ MODEL_NAME = "bhushanrocks/supportpal-dialoGPT-v3"
19
 
20
  # ------------------------------
21
+ # 🔐 MongoDB Setup & Local Fallback
22
  # ------------------------------
23
  MONGO_URI = os.getenv("MONGO_URI")
24
  use_mongo = False
 
36
  use_mongo = True
37
  print("✅ Connected to MongoDB for persistence and authentication.")
38
  except Exception as e:
 
 
39
  print(f"⚠️ MongoDB unavailable: {e}. Using local JSON storage.")
40
  else:
41
  print("⚠️ MONGO_URI not set. Using local JSON storage.")
42
 
 
 
 
43
  LOCAL_USERS_FILE = "local_users.json"
44
  LOCAL_CHATS_FILE = "local_chats.json"
45
 
46
+
47
  def load_local_data(filepath):
48
  if os.path.exists(filepath):
49
  with open(filepath, "r") as f:
50
  return json.load(f)
51
  return {}
52
 
53
+
54
  def save_local_data(data, filepath):
55
  with open(filepath, "w") as f:
56
  json.dump(data, f, indent=4)
57
 
58
+
59
  local_users = load_local_data(LOCAL_USERS_FILE)
60
  local_chats = load_local_data(LOCAL_CHATS_FILE)
61
 
62
+
63
  def hash_password(password):
64
  return hashlib.sha256(password.encode()).hexdigest()
65
 
66
  # ------------------------------
67
+ # 🔑 Auth Functions
68
  # ------------------------------
 
69
  def authenticate_user(username, password):
70
+ hashed = hash_password(password)
 
71
  if use_mongo:
72
  user_data = users_collection.find_one({"username": username})
73
  else:
74
  user_data = local_users.get(username)
75
 
76
+ if user_data and user_data["password"] == hashed:
77
+ return user_data["user_id"], f"Welcome back, {username}!"
78
  return None, "Invalid username or password."
79
 
80
+
81
  def register_user(username, password):
82
+ hashed = hash_password(password)
83
  new_user_id = str(uuid.uuid4())
84
+
85
  if use_mongo:
86
  if users_collection.find_one({"username": username}):
87
  return None, "Username already taken."
88
+ users_collection.insert_one({"username": username, "password": hashed, "user_id": new_user_id})
89
  else:
90
  if username in local_users:
91
  return None, "Username already taken."
92
+ local_users[username] = {"username": username, "password": hashed, "user_id": new_user_id}
93
  save_local_data(local_users, LOCAL_USERS_FILE)
94
+
95
  return new_user_id, f"Account created! Welcome, {username}."
96
 
97
  # ------------------------------
98
+ # 💾 Chat History Handling
99
  # ------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
100
  def get_history(user_id):
 
101
  if not user_id:
102
  return []
 
103
  if use_mongo:
104
  convo = chats_collection.find_one({"user_id": user_id})
 
105
  return convo.get("history", [])
106
+ return local_chats.get(user_id, [])
107
+
108
 
109
+ def save_history(user_id, history):
 
110
  if not user_id:
111
  return
 
112
  if use_mongo:
113
+ chats_collection.update_one({"user_id": user_id}, {"$set": {"history": history}}, upsert=True)
 
 
 
 
114
  else:
115
  local_chats[user_id] = history
116
  save_local_data(local_chats, LOCAL_CHATS_FILE)
117
 
118
+
119
+ def history_to_gradio_format(history: List[Tuple[str, str]]):
120
+ gradio_msgs = []
121
+ for user_msg, bot_msg in history:
122
+ if user_msg:
123
+ gradio_msgs.append({"role": "user", "content": user_msg})
124
+ if bot_msg:
125
+ gradio_msgs.append({"role": "assistant", "content": bot_msg})
126
+ return gradio_msgs
127
+
128
  # ------------------------------
129
+ # 🤗 Load Model
130
  # ------------------------------
 
 
131
  tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
132
 
133
  try:
 
134
  config = PeftConfig.from_pretrained(MODEL_NAME)
 
 
 
135
  base_model = AutoModelForCausalLM.from_pretrained(
136
+ config.base_model_name_or_path,
137
+ torch_dtype=torch.float16 if DEVICE == "cuda" else None,
138
  ).to(DEVICE)
 
 
139
  model = PeftModel.from_pretrained(base_model, MODEL_NAME)
140
+ print(f"✅ Loaded PEFT model from {MODEL_NAME}")
 
141
  except Exception as e:
142
+ print(f"⚠️ Failed to load PEFT adapters, fallback: {e}")
 
143
  model = AutoModelForCausalLM.from_pretrained(MODEL_NAME).to(DEVICE)
144
 
145
  model.eval()
146
 
147
  # ------------------------------
148
+ # 💬 Chat Logic
149
  # ------------------------------
150
+ def generate_response(user_input: str, state_dict: dict):
151
+ """Main generation logic."""
152
+ if not state_dict.get("logged_in"):
153
+ return [], state_dict, "Please log in to start chatting."
154
+
155
+ user_id = state_dict["user_id"]
156
+ history = state_dict.get("history", [])
157
+
158
+ # Define system prompt here
159
+ system_prompt = (
160
+ "You are SupportPal — a kind, compassionate AI that listens patiently, "
161
+ "validates emotions, and gently helps people feel understood. "
162
+ "Avoid talking about yourself, keep responses concise, warm, and natural."
163
+ )
164
 
165
+ # Keep only the last 5 turns
166
+ context_turns = history[-5:]
 
 
 
167
 
168
+ # Build dialogue text
 
 
169
  prompt_turns = []
170
+ for turn in context_turns:
171
+ if isinstance(turn, dict):
172
+ prompt_turns.append(f"Human: {turn.get('user','')}\nAI: {turn.get('bot','')}")
 
 
 
 
173
  elif isinstance(turn, (list, tuple)) and len(turn) == 2:
174
+ prompt_turns.append(f"Human: {turn[0]}\nAI: {turn[1]}")
 
 
 
 
 
175
 
176
  dialogue = system_prompt.strip() + "\n\n" + "\n".join(prompt_turns)
177
+ dialogue += f"\nHuman: {user_input}\nAI:"
178
+
179
+ # Tokenize and generate
180
+ inputs = tokenizer(dialogue, return_tensors="pt", truncation=True, max_length=512).to(model.device)
 
 
 
 
 
 
 
 
 
 
 
 
181
  with torch.no_grad():
182
  outputs = model.generate(
183
  **inputs,
184
+ max_new_tokens=200,
185
+ temperature=0.7,
186
+ top_k=50,
187
  top_p=0.9,
 
188
  do_sample=True,
189
  pad_token_id=tokenizer.eos_token_id,
190
  )
191
 
192
  decoded = tokenizer.decode(outputs[0], skip_special_tokens=True)
193
+ response = decoded.split("AI:")[-1].strip()
194
+
195
+ # Clean and finalize
196
+ response = re.sub(r"You are SupportPal.*", "", response, flags=re.DOTALL).strip()
197
+ response = re.sub(r"\b(I am|I'm) glad\b.*?\.", "", response)
198
+ response = re.sub(r"\b(I hope|I wish)\b.*?\.", "", response)
199
+ response = response.replace("AI:", "").strip()
200
 
201
+ if not response:
202
+ response = "I'm here with you. Tell me more about what’s on your mind. 💛"
 
 
 
 
203
 
204
+ if random.random() < 0.25 and not any(x in response for x in ["💛", "✨"]):
205
+ response += " " + random.choice(["💛", "✨", "You’re not alone in this."])
206
 
207
+ # Save and return
208
+ history.append((user_input, response))
209
+ save_history(user_id, history)
210
+ state_dict["history"] = history
211
 
212
+ gradio_history = history_to_gradio_format(history)
213
+ return gradio_history, state_dict, ""
 
 
 
214
 
 
 
 
215
 
216
+ def process_chat(user_message, state_dict):
217
+ """Wrapper used for Gradio callbacks."""
218
+ if not state_dict:
219
+ state_dict = {"logged_in": False, "user_id": None, "username": None, "history": []}
220
+
221
+ if not state_dict.get("logged_in"):
222
+ return [], state_dict, "Please log in to start chatting."
223
+
224
+ return generate_response(user_message, state_dict)
225
 
226
  # ------------------------------
227
+ # 🔐 Login / Register / Logout
228
  # ------------------------------
229
+ def handle_login(username, password, state):
230
+ user_id, status = authenticate_user(username, password)
 
 
231
  if user_id:
232
+ history = get_history(user_id)
233
+ state.update({"logged_in": True, "user_id": user_id, "username": username, "history": history})
234
+ gr_history = history_to_gradio_format(history)
235
+ return status, gr.update(visible=False), gr.update(visible=True), gr_history, state, f"Logged in as: **{username}**"
236
+ return status, gr.update(visible=True), gr.update(visible=False), [], state, gr.update()
237
+
 
 
 
 
 
 
 
 
 
 
 
 
238
 
239
+ def handle_register(username, password, state):
240
+ user_id, status = register_user(username, password)
 
241
  if user_id:
242
+ state.update({"logged_in": True, "user_id": user_id, "username": username, "history": []})
243
+ return status, gr.update(visible=False), gr.update(visible=True), [], state, f"Logged in as: **{username}**"
244
+ return status, gr.update(visible=True), gr.update(visible=False), [], state, gr.update()
245
+
 
 
 
 
 
 
 
246
 
247
  def handle_logout():
 
248
  return (
249
+ "Logged out successfully.",
250
+ gr.update(visible=True),
251
+ gr.update(visible=False),
252
+ [],
253
+ {"logged_in": False, "user_id": None, "username": None, "history": []},
254
+ gr.update(value=""),
255
+ "Logged in as:",
256
  )
257
 
 
 
 
 
 
 
 
 
258
 
259
+ def handle_clear_chat(state):
260
+ if state.get("logged_in"):
261
+ user_id = state["user_id"]
262
+ save_history(user_id, [])
263
+ state["history"] = []
264
+ return [], state
265
+ return [], state
266
 
267
  # ------------------------------
268
  # 🧱 Gradio UI
269
  # ------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  with gr.Blocks(title="SupportPal Chatbot") as demo:
271
+ storage_mode = "☁️ MongoDB Connected" if use_mongo else "💾 Local Storage"
 
 
 
 
 
272
 
273
+ session_state = gr.State(value={"logged_in": False, "user_id": None, "username": None, "history": []})
274
+ gr.Markdown(f"## 🤖 SupportPal — Empathetic Chatbot\n**Persistence Mode:** {storage_mode}")
275
 
276
+ # Auth Panel
277
  with gr.Row(visible=True) as auth_panel:
278
+ with gr.Column():
279
  auth_status = gr.Markdown("Please log in or register to start chatting.")
280
  username_box = gr.Textbox(label="Username")
281
  password_box = gr.Textbox(label="Password", type="password")
 
282
  with gr.Row():
283
  login_btn = gr.Button("Login", variant="primary")
284
  register_btn = gr.Button("Register", variant="secondary")
285
 
286
+ # Chat Panel
287
  with gr.Column(visible=False) as chat_panel:
288
+ user_status = gr.Markdown("Logged in as:")
289
+ chatbot_ui = gr.Chatbot(height=400, type="messages")
 
 
 
 
290
  with gr.Row():
291
+ msg = gr.Textbox(label="Your message...", scale=4)
292
+ send_btn = gr.Button("Send", variant="primary", scale=1)
 
293
  with gr.Row():
294
+ clear_btn = gr.Button("Clear Chat", variant="secondary")
295
  logout_btn = gr.Button("Logout", variant="stop")
296
 
297
+ # Bind events
298
+ login_btn.click(handle_login, [username_box, password_box, session_state],
299
+ [auth_status, auth_panel, chat_panel, chatbot_ui, session_state, user_status])
300
+ register_btn.click(handle_register, [username_box, password_box, session_state],
301
+ [auth_status, auth_panel, chat_panel, chatbot_ui, session_state, user_status])
 
302
 
303
+ send_btn.click(process_chat, [msg, session_state],
304
+ [chatbot_ui, session_state, auth_status]).then(lambda: gr.update(value=""), None, [msg])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
 
306
+ msg.submit(process_chat, [msg, session_state],
307
+ [chatbot_ui, session_state, auth_status]).then(lambda: gr.update(value=""), None, [msg])
 
 
 
 
 
 
 
 
 
308
 
309
+ clear_btn.click(handle_clear_chat, [session_state], [chatbot_ui, session_state])
310
+ logout_btn.click(handle_logout, None,
311
+ [auth_status, auth_panel, chat_panel, chatbot_ui, session_state, msg, user_status])
 
 
 
 
 
 
 
 
 
312
 
313
  demo.launch()