NitinBot001 commited on
Commit
5ed8087
·
verified ·
1 Parent(s): 15c82e5

Update conversation_manager.py

Browse files
Files changed (1) hide show
  1. conversation_manager.py +254 -87
conversation_manager.py CHANGED
@@ -2,6 +2,7 @@
2
 
3
  import os
4
  import uuid
 
5
  import logging
6
  from typing import Dict, List, Any, Optional
7
  from datetime import datetime
@@ -16,7 +17,7 @@ load_dotenv()
16
 
17
 
18
  class ConversationManager:
19
- """Enhanced ConversationManager with unique message IDs and flexible chat ID management"""
20
 
21
  def __init__(self):
22
  """Initialize the conversation manager with Supabase client"""
@@ -35,15 +36,38 @@ class ConversationManager:
35
  logger.error(f"Failed to initialize ConversationManager: {e}")
36
  raise
37
 
38
- def generate_chat_id(self) -> str:
39
- """Generate a unique chat ID"""
40
- return f"chat_{uuid.uuid4().hex[:12]}"
 
 
41
 
42
- def generate_message_id(self, chat_id: str) -> int:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  """Generate the next message ID for a specific chat"""
44
  try:
45
  # Get current conversation to find the highest message ID
46
- history = self.get_history(chat_id)
47
 
48
  if not history:
49
  return 1
@@ -58,53 +82,57 @@ class ConversationManager:
58
  return max_id + 1
59
 
60
  except Exception as e:
61
- logger.error(f"Error generating message ID for chat {chat_id}: {e}")
62
  return 1
63
 
64
- def get_history(self, chat_id: str) -> List[Dict[str, Any]]:
65
  """
66
- Retrieve conversation history for a specific chat ID
67
 
68
  Args:
69
  chat_id: The chat ID to retrieve history for
 
70
 
71
  Returns:
72
- List of message dictionaries, empty list if chat doesn't exist
73
  """
74
- if not chat_id:
 
75
  return []
76
 
77
  try:
78
- response = self.supabase.table('conversations').select('history').eq('session_id', chat_id).execute()
79
 
80
  if response.data and len(response.data) > 0:
81
  history = response.data[0].get('history', [])
82
  if isinstance(history, list):
 
83
  return history
84
  else:
85
  logger.warning(f"Invalid history format for chat {chat_id}")
86
  return []
87
  else:
88
- logger.info(f"No history found for chat {chat_id}")
89
  return []
90
 
91
  except Exception as e:
92
- logger.error(f"Error retrieving history for chat {chat_id}: {e}")
93
  return []
94
 
95
- def save_history(self, chat_id: str, history: List[Dict[str, Any]], user_id: str) -> bool:
96
  """
97
- Save conversation history for a specific chat ID
98
 
99
  Args:
100
  chat_id: The chat ID to save history for
 
101
  history: List of message dictionaries
102
 
103
  Returns:
104
  True if successful, False otherwise
105
  """
106
- if not chat_id:
107
- logger.error("Cannot save history: chat_id is empty")
108
  return False
109
 
110
  if not isinstance(history, list):
@@ -112,40 +140,44 @@ class ConversationManager:
112
  return False
113
 
114
  try:
115
- # Check if conversation exists
116
  existing = self.supabase.table('conversations').select('session_id').eq('session_id', chat_id).eq('user_id', user_id).execute()
117
-
 
 
118
  if existing.data and len(existing.data) > 0:
119
  # Update existing conversation
120
  response = self.supabase.table('conversations').update({
121
  'history': history,
122
- 'updated_at': datetime.utcnow().isoformat()
123
- }).eq('session_id', chat_id).execute()
124
 
125
- logger.info(f"Updated history for chat {chat_id}")
126
  else:
127
  # Create new conversation
128
  response = self.supabase.table('conversations').insert({
129
  'session_id': chat_id,
 
130
  'history': history,
131
- 'created_at': datetime.utcnow().isoformat(),
132
- 'updated_at': datetime.utcnow().isoformat()
133
  }).execute()
134
 
135
- logger.info(f"Created new conversation for chat {chat_id}")
136
 
137
  return True
138
 
139
  except Exception as e:
140
- logger.error(f"Error saving history for chat {chat_id}: {e}")
141
  return False
142
 
143
- def add_message(self, chat_id: str, role: str, content: str, image_url: Optional[str] = None) -> Dict[str, Any]:
144
  """
145
  Add a single message to a chat and return the message with its ID
146
 
147
  Args:
148
  chat_id: The chat ID to add the message to
 
149
  role: Message role ('user' or 'assistant')
150
  content: Message content
151
  image_url: Optional image URL
@@ -153,13 +185,13 @@ class ConversationManager:
153
  Returns:
154
  Dictionary containing the message with its assigned ID
155
  """
156
- if not chat_id:
157
- logger.error("Cannot add message: chat_id is empty")
158
  return {}
159
 
160
  try:
161
  # Generate message ID
162
- message_id = self.generate_message_id(chat_id)
163
 
164
  # Create message object
165
  message = {
@@ -174,59 +206,68 @@ class ConversationManager:
174
  message["imageUrl"] = image_url
175
 
176
  # Get current history and add the new message
177
- current_history = self.get_history(chat_id)
178
  current_history.append(message)
179
 
180
  # Save updated history
181
- if self.save_history(chat_id, current_history):
182
- logger.info(f"Added message {message_id} to chat {chat_id}")
183
  return message
184
  else:
185
- logger.error(f"Failed to save message to chat {chat_id}")
186
  return {}
187
 
188
  except Exception as e:
189
- logger.error(f"Error adding message to chat {chat_id}: {e}")
190
  return {}
191
 
192
- def delete_history(self, chat_id: str) -> bool:
193
  """
194
- Delete conversation history for a specific chat ID
195
 
196
  Args:
197
  chat_id: The chat ID to delete
 
198
 
199
  Returns:
200
  True if successful, False otherwise
201
  """
202
- if not chat_id:
203
- logger.error("Cannot delete history: chat_id is empty")
204
  return False
205
 
206
  try:
207
- response = self.supabase.table('conversations').delete().eq('session_id', chat_id).execute()
208
 
209
  # Check if any rows were affected
210
  if hasattr(response, 'data') and response.data is not None:
211
- logger.info(f"Deleted conversation history for chat {chat_id}")
212
  return True
213
  else:
214
- logger.warning(f"No conversation found to delete for chat {chat_id}")
215
  return True # Consider it successful if nothing to delete
216
 
217
  except Exception as e:
218
- logger.error(f"Error deleting history for chat {chat_id}: {e}")
219
  return False
220
 
221
  def get_all_chat_sessions(self, user_id: str) -> List[Dict[str, Any]]:
222
  """
223
- Get all chat sessions with basic information
224
 
 
 
 
225
  Returns:
226
  List of dictionaries with chat session information
227
  """
 
 
 
 
228
  try:
229
- response = self.supabase.table('conversations').select('session_id, history, created_at, updated_at').eq('user_id', user_id).execute()
 
230
  sessions = []
231
  for conv in response.data:
232
  session_id = conv.get('session_id')
@@ -234,7 +275,7 @@ class ConversationManager:
234
  created_at = conv.get('created_at')
235
  updated_at = conv.get('updated_at')
236
 
237
- # Generate title from first user message
238
  title = "New Chat"
239
  if history and len(history) > 0:
240
  first_user_msg = None
@@ -249,7 +290,7 @@ class ConversationManager:
249
  title = content[:35] + '...' if len(content) > 35 else content
250
  elif first_user_msg.get('imageUrl'):
251
  title = "Image Query"
252
-
253
  sessions.append({
254
  "session_id": session_id,
255
  "title": title,
@@ -261,27 +302,29 @@ class ConversationManager:
261
  # Sort by updated_at (most recent first)
262
  sessions.sort(key=lambda x: x.get('updated_at', ''), reverse=True)
263
 
 
264
  return sessions
265
 
266
  except Exception as e:
267
- logger.error(f"Error fetching chat sessions: {e}")
268
  return []
269
 
270
- def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
271
  """
272
- Get information about a specific chat
273
 
274
  Args:
275
  chat_id: The chat ID to get information for
 
276
 
277
  Returns:
278
  Dictionary with chat information
279
  """
280
- if not chat_id:
281
  return {}
282
 
283
  try:
284
- response = self.supabase.table('conversations').select('*').eq('session_id', chat_id).execute()
285
 
286
  if response.data and len(response.data) > 0:
287
  conv = response.data[0]
@@ -289,6 +332,7 @@ class ConversationManager:
289
 
290
  return {
291
  "session_id": conv.get('session_id'),
 
292
  "message_count": len(history),
293
  "created_at": conv.get('created_at'),
294
  "updated_at": conv.get('updated_at'),
@@ -297,82 +341,205 @@ class ConversationManager:
297
  else:
298
  return {
299
  "session_id": chat_id,
 
300
  "message_count": 0,
301
  "exists": False
302
  }
303
 
304
  except Exception as e:
305
- logger.error(f"Error getting chat info for {chat_id}: {e}")
306
  return {
307
  "session_id": chat_id,
 
308
  "error": str(e),
309
  "exists": False
310
  }
311
 
312
- def chat_exists(self, chat_id: str) -> bool:
313
  """
314
- Check if a chat ID exists in the database
315
 
316
  Args:
317
  chat_id: The chat ID to check
 
318
 
319
  Returns:
320
- True if chat exists, False otherwise
321
  """
322
- if not chat_id:
323
  return False
324
 
325
  try:
326
- response = self.supabase.table('conversations').select('session_id').eq('session_id', chat_id).execute()
327
- return bool(response.data and len(response.data) > 0)
 
 
 
 
 
328
 
329
  except Exception as e:
330
- logger.error(f"Error checking if chat {chat_id} exists: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  return False
332
 
333
 
334
  # Example usage and testing
335
  if __name__ == "__main__":
336
- print("=== ConversationManager Test ===")
337
 
338
  try:
339
  # Initialize manager
340
  manager = ConversationManager()
341
 
342
- # Test 1: Create a new chat
343
- test_chat_id = "test_chat_123"
344
- print(f"\n1. Testing with chat ID: {test_chat_id}")
345
 
346
- # Add some messages
347
- msg1 = manager.add_message(test_chat_id, "user", "Hello, I need help with farming")
348
- print(f"Added user message: {msg1}")
349
 
350
- msg2 = manager.add_message(test_chat_id, "assistant", "Hello! I'd be happy to help you with farming. What specific questions do you have?")
351
- print(f"Added assistant message: {msg2}")
 
352
 
353
- msg3 = manager.add_message(test_chat_id, "user", "What crops should I grow?", "https://example.com/image.jpg")
354
- print(f"Added user message with image: {msg3}")
355
 
356
- # Test 2: Retrieve history
357
- print(f"\n2. Retrieved history for {test_chat_id}:")
358
- history = manager.get_history(test_chat_id)
359
  for msg in history:
360
  print(f" Message {msg.get('message_id')}: {msg.get('role')} - {msg.get('content')[:50]}...")
361
 
362
- # Test 3: Get chat info
363
- print(f"\n3. Chat info:")
364
- info = manager.get_chat_info(test_chat_id)
365
- print(f" Exists: {info.get('exists')}, Messages: {info.get('message_count')}")
 
366
 
367
- # Test 4: Auto-generated chat ID
368
- auto_chat_id = manager.generate_chat_id()
369
- print(f"\n4. Auto-generated chat ID: {auto_chat_id}")
 
 
370
 
371
- # Test 5: Get all sessions
372
- print(f"\n5. All chat sessions:")
373
- sessions = manager.get_all_chat_sessions()
374
- for session in sessions[:3]: # Show first 3
375
- print(f" {session.get('session_id')}: {session.get('title')} ({session.get('message_count')} messages)")
376
 
377
  print("\n✅ All tests completed successfully!")
378
 
 
2
 
3
  import os
4
  import uuid
5
+ import hashlib
6
  import logging
7
  from typing import Dict, List, Any, Optional
8
  from datetime import datetime
 
17
 
18
 
19
  class ConversationManager:
20
+ """Enhanced ConversationManager with user authentication and session isolation"""
21
 
22
  def __init__(self):
23
  """Initialize the conversation manager with Supabase client"""
 
36
  logger.error(f"Failed to initialize ConversationManager: {e}")
37
  raise
38
 
39
+ def generate_chat_id(self, user_id: str) -> str:
40
+ """Generate a unique chat ID for a specific user"""
41
+ timestamp = str(int(datetime.utcnow().timestamp()))
42
+ random_part = uuid.uuid4().hex[:8]
43
+ return f"chat_{user_id[:8]}_{timestamp}_{random_part}"
44
 
45
+ def generate_user_id(self, device_info: Optional[str] = None, ip_address: Optional[str] = None) -> str:
46
+ """
47
+ Generate a unique user ID based on device info and/or IP
48
+ This creates a consistent ID for the same device/IP combination
49
+ """
50
+ # Create a hash based on available info
51
+ identifier_string = ""
52
+
53
+ if device_info:
54
+ identifier_string += device_info
55
+ if ip_address:
56
+ identifier_string += ip_address
57
+
58
+ # Fallback to timestamp + random if no device info
59
+ if not identifier_string:
60
+ identifier_string = f"anonymous_{int(datetime.utcnow().timestamp())}_{uuid.uuid4().hex[:8]}"
61
+
62
+ # Create consistent hash
63
+ user_hash = hashlib.sha256(identifier_string.encode()).hexdigest()[:16]
64
+ return f"user_{user_hash}"
65
+
66
+ def generate_message_id(self, chat_id: str, user_id: str) -> int:
67
  """Generate the next message ID for a specific chat"""
68
  try:
69
  # Get current conversation to find the highest message ID
70
+ history = self.get_history(chat_id, user_id)
71
 
72
  if not history:
73
  return 1
 
82
  return max_id + 1
83
 
84
  except Exception as e:
85
+ logger.error(f"Error generating message ID for chat {chat_id}, user {user_id}: {e}")
86
  return 1
87
 
88
+ def get_history(self, chat_id: str, user_id: str) -> List[Dict[str, Any]]:
89
  """
90
+ Retrieve conversation history for a specific chat ID and user
91
 
92
  Args:
93
  chat_id: The chat ID to retrieve history for
94
+ user_id: The user ID (for security/isolation)
95
 
96
  Returns:
97
+ List of message dictionaries, empty list if chat doesn't exist or unauthorized
98
  """
99
+ if not chat_id or not user_id:
100
+ logger.warning("Cannot get history: chat_id or user_id is empty")
101
  return []
102
 
103
  try:
104
+ response = self.supabase.table('conversations').select('history').eq('session_id', chat_id).eq('user_id', user_id).execute()
105
 
106
  if response.data and len(response.data) > 0:
107
  history = response.data[0].get('history', [])
108
  if isinstance(history, list):
109
+ logger.info(f"Retrieved {len(history)} messages for chat {chat_id}, user {user_id}")
110
  return history
111
  else:
112
  logger.warning(f"Invalid history format for chat {chat_id}")
113
  return []
114
  else:
115
+ logger.info(f"No history found for chat {chat_id}, user {user_id}")
116
  return []
117
 
118
  except Exception as e:
119
+ logger.error(f"Error retrieving history for chat {chat_id}, user {user_id}: {e}")
120
  return []
121
 
122
+ def save_history(self, chat_id: str, user_id: str, history: List[Dict[str, Any]]) -> bool:
123
  """
124
+ Save conversation history for a specific chat ID and user
125
 
126
  Args:
127
  chat_id: The chat ID to save history for
128
+ user_id: The user ID (for security/isolation)
129
  history: List of message dictionaries
130
 
131
  Returns:
132
  True if successful, False otherwise
133
  """
134
+ if not chat_id or not user_id:
135
+ logger.error("Cannot save history: chat_id or user_id is empty")
136
  return False
137
 
138
  if not isinstance(history, list):
 
140
  return False
141
 
142
  try:
143
+ # Check if conversation exists for this user
144
  existing = self.supabase.table('conversations').select('session_id').eq('session_id', chat_id).eq('user_id', user_id).execute()
145
+
146
+ current_time = datetime.utcnow().isoformat()
147
+
148
  if existing.data and len(existing.data) > 0:
149
  # Update existing conversation
150
  response = self.supabase.table('conversations').update({
151
  'history': history,
152
+ 'updated_at': current_time
153
+ }).eq('session_id', chat_id).eq('user_id', user_id).execute()
154
 
155
+ logger.info(f"Updated history for chat {chat_id}, user {user_id}")
156
  else:
157
  # Create new conversation
158
  response = self.supabase.table('conversations').insert({
159
  'session_id': chat_id,
160
+ 'user_id': user_id,
161
  'history': history,
162
+ 'created_at': current_time,
163
+ 'updated_at': current_time
164
  }).execute()
165
 
166
+ logger.info(f"Created new conversation for chat {chat_id}, user {user_id}")
167
 
168
  return True
169
 
170
  except Exception as e:
171
+ logger.error(f"Error saving history for chat {chat_id}, user {user_id}: {e}")
172
  return False
173
 
174
+ def add_message(self, chat_id: str, user_id: str, role: str, content: str, image_url: Optional[str] = None) -> Dict[str, Any]:
175
  """
176
  Add a single message to a chat and return the message with its ID
177
 
178
  Args:
179
  chat_id: The chat ID to add the message to
180
+ user_id: The user ID (for security/isolation)
181
  role: Message role ('user' or 'assistant')
182
  content: Message content
183
  image_url: Optional image URL
 
185
  Returns:
186
  Dictionary containing the message with its assigned ID
187
  """
188
+ if not chat_id or not user_id:
189
+ logger.error("Cannot add message: chat_id or user_id is empty")
190
  return {}
191
 
192
  try:
193
  # Generate message ID
194
+ message_id = self.generate_message_id(chat_id, user_id)
195
 
196
  # Create message object
197
  message = {
 
206
  message["imageUrl"] = image_url
207
 
208
  # Get current history and add the new message
209
+ current_history = self.get_history(chat_id, user_id)
210
  current_history.append(message)
211
 
212
  # Save updated history
213
+ if self.save_history(chat_id, user_id, current_history):
214
+ logger.info(f"Added message {message_id} to chat {chat_id}, user {user_id}")
215
  return message
216
  else:
217
+ logger.error(f"Failed to save message to chat {chat_id}, user {user_id}")
218
  return {}
219
 
220
  except Exception as e:
221
+ logger.error(f"Error adding message to chat {chat_id}, user {user_id}: {e}")
222
  return {}
223
 
224
+ def delete_history(self, chat_id: str, user_id: str) -> bool:
225
  """
226
+ Delete conversation history for a specific chat ID and user
227
 
228
  Args:
229
  chat_id: The chat ID to delete
230
+ user_id: The user ID (for security/isolation)
231
 
232
  Returns:
233
  True if successful, False otherwise
234
  """
235
+ if not chat_id or not user_id:
236
+ logger.error("Cannot delete history: chat_id or user_id is empty")
237
  return False
238
 
239
  try:
240
+ response = self.supabase.table('conversations').delete().eq('session_id', chat_id).eq('user_id', user_id).execute()
241
 
242
  # Check if any rows were affected
243
  if hasattr(response, 'data') and response.data is not None:
244
+ logger.info(f"Deleted conversation history for chat {chat_id}, user {user_id}")
245
  return True
246
  else:
247
+ logger.warning(f"No conversation found to delete for chat {chat_id}, user {user_id}")
248
  return True # Consider it successful if nothing to delete
249
 
250
  except Exception as e:
251
+ logger.error(f"Error deleting history for chat {chat_id}, user {user_id}: {e}")
252
  return False
253
 
254
  def get_all_chat_sessions(self, user_id: str) -> List[Dict[str, Any]]:
255
  """
256
+ Get all chat sessions for a specific user
257
 
258
+ Args:
259
+ user_id: The user ID to get sessions for
260
+
261
  Returns:
262
  List of dictionaries with chat session information
263
  """
264
+ if not user_id:
265
+ logger.error("Cannot get chat sessions: user_id is empty")
266
+ return []
267
+
268
  try:
269
+ response = self.supabase.table('conversations').select('session_id, history, created_at, updated_at').eq('user_id', user_id).execute()
270
+
271
  sessions = []
272
  for conv in response.data:
273
  session_id = conv.get('session_id')
 
275
  created_at = conv.get('created_at')
276
  updated_at = conv.get('updated_at')
277
 
278
+ # Generate title from first user message in the history
279
  title = "New Chat"
280
  if history and len(history) > 0:
281
  first_user_msg = None
 
290
  title = content[:35] + '...' if len(content) > 35 else content
291
  elif first_user_msg.get('imageUrl'):
292
  title = "Image Query"
293
+
294
  sessions.append({
295
  "session_id": session_id,
296
  "title": title,
 
302
  # Sort by updated_at (most recent first)
303
  sessions.sort(key=lambda x: x.get('updated_at', ''), reverse=True)
304
 
305
+ logger.info(f"Retrieved {len(sessions)} sessions for user {user_id}")
306
  return sessions
307
 
308
  except Exception as e:
309
+ logger.error(f"Error fetching chat sessions for user {user_id}: {e}")
310
  return []
311
 
312
+ def get_chat_info(self, chat_id: str, user_id: str) -> Dict[str, Any]:
313
  """
314
+ Get information about a specific chat for a specific user
315
 
316
  Args:
317
  chat_id: The chat ID to get information for
318
+ user_id: The user ID (for security/isolation)
319
 
320
  Returns:
321
  Dictionary with chat information
322
  """
323
+ if not chat_id or not user_id:
324
  return {}
325
 
326
  try:
327
+ response = self.supabase.table('conversations').select('*').eq('session_id', chat_id).eq('user_id', user_id).execute()
328
 
329
  if response.data and len(response.data) > 0:
330
  conv = response.data[0]
 
332
 
333
  return {
334
  "session_id": conv.get('session_id'),
335
+ "user_id": conv.get('user_id'),
336
  "message_count": len(history),
337
  "created_at": conv.get('created_at'),
338
  "updated_at": conv.get('updated_at'),
 
341
  else:
342
  return {
343
  "session_id": chat_id,
344
+ "user_id": user_id,
345
  "message_count": 0,
346
  "exists": False
347
  }
348
 
349
  except Exception as e:
350
+ logger.error(f"Error getting chat info for {chat_id}, user {user_id}: {e}")
351
  return {
352
  "session_id": chat_id,
353
+ "user_id": user_id,
354
  "error": str(e),
355
  "exists": False
356
  }
357
 
358
+ def chat_exists(self, chat_id: str, user_id: str) -> bool:
359
  """
360
+ Check if a chat ID exists for a specific user
361
 
362
  Args:
363
  chat_id: The chat ID to check
364
+ user_id: The user ID (for security/isolation)
365
 
366
  Returns:
367
+ True if chat exists and belongs to user, False otherwise
368
  """
369
+ if not chat_id or not user_id:
370
  return False
371
 
372
  try:
373
+ response = self.supabase.table('conversations').select('session_id').eq('session_id', chat_id).eq('user_id', user_id).execute()
374
+ exists = bool(response.data and len(response.data) > 0)
375
+
376
+ if exists:
377
+ logger.info(f"Chat {chat_id} exists for user {user_id}")
378
+
379
+ return exists
380
 
381
  except Exception as e:
382
+ logger.error(f"Error checking if chat {chat_id} exists for user {user_id}: {e}")
383
+ return False
384
+
385
+ def get_user_stats(self, user_id: str) -> Dict[str, Any]:
386
+ """
387
+ Get statistics for a specific user
388
+
389
+ Args:
390
+ user_id: The user ID to get stats for
391
+
392
+ Returns:
393
+ Dictionary with user statistics
394
+ """
395
+ if not user_id:
396
+ return {}
397
+
398
+ try:
399
+ sessions = self.get_all_chat_sessions(user_id)
400
+ total_sessions = len(sessions)
401
+ total_messages = sum(session.get('message_count', 0) for session in sessions)
402
+
403
+ # Calculate recent activity (last 24 hours)
404
+ from datetime import datetime, timedelta
405
+ now = datetime.utcnow()
406
+ yesterday = now - timedelta(days=1)
407
+
408
+ recent_sessions = 0
409
+ for session in sessions:
410
+ updated_at = session.get('updated_at')
411
+ if updated_at:
412
+ try:
413
+ session_time = datetime.fromisoformat(updated_at.replace('Z', '+00:00'))
414
+ if session_time > yesterday:
415
+ recent_sessions += 1
416
+ except:
417
+ pass
418
+
419
+ return {
420
+ "user_id": user_id,
421
+ "total_sessions": total_sessions,
422
+ "total_messages": total_messages,
423
+ "recent_sessions_24h": recent_sessions,
424
+ "average_messages_per_session": round(total_messages / total_sessions, 2) if total_sessions > 0 else 0,
425
+ "first_session": min(session.get('created_at', '') for session in sessions) if sessions else None,
426
+ "last_activity": max(session.get('updated_at', '') for session in sessions) if sessions else None
427
+ }
428
+
429
+ except Exception as e:
430
+ logger.error(f"Error getting user stats for {user_id}: {e}")
431
+ return {"user_id": user_id, "error": str(e)}
432
+
433
+ def delete_all_user_data(self, user_id: str) -> bool:
434
+ """
435
+ Delete all conversation data for a specific user
436
+
437
+ Args:
438
+ user_id: The user ID to delete all data for
439
+
440
+ Returns:
441
+ True if successful, False otherwise
442
+ """
443
+ if not user_id:
444
+ logger.error("Cannot delete user data: user_id is empty")
445
+ return False
446
+
447
+ try:
448
+ response = self.supabase.table('conversations').delete().eq('user_id', user_id).execute()
449
+
450
+ logger.info(f"Deleted all conversation data for user {user_id}")
451
+ return True
452
+
453
+ except Exception as e:
454
+ logger.error(f"Error deleting all user data for {user_id}: {e}")
455
+ return False
456
+
457
+ def migrate_anonymous_sessions(self, old_session_ids: List[str], new_user_id: str) -> bool:
458
+ """
459
+ Migrate anonymous sessions to a specific user ID
460
+ Useful when user decides to create an account
461
+
462
+ Args:
463
+ old_session_ids: List of session IDs to migrate
464
+ new_user_id: The new user ID to assign these sessions to
465
+
466
+ Returns:
467
+ True if successful, False otherwise
468
+ """
469
+ if not old_session_ids or not new_user_id:
470
+ logger.error("Cannot migrate sessions: missing session_ids or user_id")
471
+ return False
472
+
473
+ try:
474
+ migrated_count = 0
475
+
476
+ for session_id in old_session_ids:
477
+ try:
478
+ # Update the user_id for this session
479
+ response = self.supabase.table('conversations').update({
480
+ 'user_id': new_user_id,
481
+ 'updated_at': datetime.utcnow().isoformat()
482
+ }).eq('session_id', session_id).execute()
483
+
484
+ migrated_count += 1
485
+
486
+ except Exception as e:
487
+ logger.error(f"Failed to migrate session {session_id}: {e}")
488
+ continue
489
+
490
+ logger.info(f"Migrated {migrated_count} sessions to user {new_user_id}")
491
+ return migrated_count > 0
492
+
493
+ except Exception as e:
494
+ logger.error(f"Error migrating sessions to user {new_user_id}: {e}")
495
  return False
496
 
497
 
498
  # Example usage and testing
499
  if __name__ == "__main__":
500
+ print("=== ConversationManager with User Authentication Test ===")
501
 
502
  try:
503
  # Initialize manager
504
  manager = ConversationManager()
505
 
506
+ # Test user ID generation
507
+ test_user_id = manager.generate_user_id("test_device_info", "192.168.1.100")
508
+ print(f"\n1. Generated user ID: {test_user_id}")
509
 
510
+ # Create a new chat for this user
511
+ test_chat_id = manager.generate_chat_id(test_user_id)
512
+ print(f"2. Generated chat ID: {test_chat_id}")
513
 
514
+ # Add some messages
515
+ msg1 = manager.add_message(test_chat_id, test_user_id, "user", "Hello, I need help with farming")
516
+ print(f"3. Added user message: {msg1}")
517
 
518
+ msg2 = manager.add_message(test_chat_id, test_user_id, "assistant", "Hello! I'd be happy to help you with farming. What specific questions do you have?")
519
+ print(f"4. Added assistant message: {msg2}")
520
 
521
+ # Test retrieving history
522
+ print(f"\n5. Retrieved history for {test_chat_id}:")
523
+ history = manager.get_history(test_chat_id, test_user_id)
524
  for msg in history:
525
  print(f" Message {msg.get('message_id')}: {msg.get('role')} - {msg.get('content')[:50]}...")
526
 
527
+ # Test getting all sessions for this user
528
+ print(f"\n6. All sessions for user {test_user_id}:")
529
+ sessions = manager.get_all_chat_sessions(test_user_id)
530
+ for session in sessions:
531
+ print(f" {session.get('session_id')}: {session.get('title')} ({session.get('message_count')} messages)")
532
 
533
+ # Test user stats
534
+ print(f"\n7. User statistics:")
535
+ stats = manager.get_user_stats(test_user_id)
536
+ for key, value in stats.items():
537
+ print(f" {key}: {value}")
538
 
539
+ # Test security - try to access with wrong user ID
540
+ print(f"\n8. Security test - trying to access with wrong user ID:")
541
+ wrong_user_history = manager.get_history(test_chat_id, "wrong_user_id")
542
+ print(f" History with wrong user ID: {len(wrong_user_history)} messages (should be 0)")
 
543
 
544
  print("\n✅ All tests completed successfully!")
545