michon commited on
Commit
7872dc7
Β·
1 Parent(s): c9e2b06

update websocket again

Browse files
Files changed (1) hide show
  1. mrrrme/backend/websocket.py +111 -163
mrrrme/backend/websocket.py CHANGED
@@ -1,6 +1,5 @@
1
  """
2
- MrrrMe Backend - WebSocket Handler (FIXED - NO CRASHES)
3
- Complete error handling to prevent connection drops
4
  """
5
 
6
  from fastapi import WebSocket, WebSocketDisconnect
@@ -11,10 +10,10 @@ import cv2
11
  import io
12
  from PIL import Image
13
  import requests
14
- from .models.loader import (
15
- face_processor, text_analyzer, whisper_worker,
16
- voice_worker, llm_generator, fusion_engine, models_ready
17
- )
18
  from .session.manager import validate_token, save_message, load_user_history
19
  from .session.summary import generate_session_summary
20
  from .auth.database import get_db_connection
@@ -25,13 +24,7 @@ from .processing.speech import process_speech_end
25
  AVATAR_API = get_avatar_api_url()
26
 
27
  async def websocket_endpoint(websocket: WebSocket):
28
- """
29
- Main WebSocket endpoint handler
30
-
31
- βœ… FIXED: Comprehensive error handling
32
- βœ… FIXED: Logs every step
33
- βœ… FIXED: Never crashes unexpectedly
34
- """
35
 
36
  print("\n" + "="*80, flush=True)
37
  print("[WebSocket] πŸ”Œ NEW CONNECTION", flush=True)
@@ -54,42 +47,39 @@ async def websocket_endpoint(websocket: WebSocket):
54
  user_preferences = {"voice": "female", "language": "en"}
55
 
56
  try:
57
- # ===== STEP 1: AUTHENTICATION =====
58
  print("[WebSocket] ⏳ Waiting for auth message...", flush=True)
59
 
60
  try:
61
  auth_msg = await asyncio.wait_for(websocket.receive_json(), timeout=10.0)
62
  print(f"[WebSocket] πŸ“¨ Auth message received: {auth_msg.get('type')}", flush=True)
63
  except asyncio.TimeoutError:
64
- print("[WebSocket] ❌ Auth timeout (10s)", flush=True)
65
- await websocket.close(code=1008, reason="Authentication timeout")
66
- return
67
- except Exception as e:
68
- print(f"[WebSocket] ❌ Auth receive error: {e}", flush=True)
69
  return
70
 
71
  if auth_msg.get("type") != "auth":
72
- print(f"[WebSocket] ❌ Wrong message type: {auth_msg.get('type')}", flush=True)
73
- await websocket.send_json({"type": "error", "message": "Authentication required"})
74
- await websocket.close(code=1008, reason="Auth required")
75
  return
76
 
77
  token = auth_msg.get("token")
78
  print(f"[WebSocket] πŸ”‘ Validating token: {token[:10] if token else 'None'}...", flush=True)
79
 
80
  if not token:
81
- print(f"[WebSocket] ❌ No token provided", flush=True)
82
- await websocket.send_json({"type": "error", "message": "No token provided"})
83
- await websocket.close(code=1008, reason="No token")
84
  return
85
 
86
- # Validate token
87
  session_data = validate_token(token)
88
 
89
  if not session_data:
90
- print(f"[WebSocket] ❌ Invalid token", flush=True)
91
- await websocket.send_json({"type": "error", "message": "Invalid session - please login again"})
92
- await websocket.close(code=1008, reason="Invalid token")
93
  return
94
 
95
  session_id = session_data['session_id']
@@ -98,114 +88,101 @@ async def websocket_endpoint(websocket: WebSocket):
98
 
99
  print(f"[WebSocket] βœ… Token validated for user: {username} (session: {session_id})", flush=True)
100
 
101
- # Get user summary
102
  try:
103
- user_summary = session_data.get('summary') or validate_token(token).get('summary')
104
- if not user_summary:
105
- conn = get_db_connection()
106
- cursor = conn.cursor()
107
- cursor.execute("SELECT summary_text FROM user_summaries WHERE user_id = ?", (user_id,))
108
- summary_row = cursor.fetchone()
109
- user_summary = summary_row[0] if summary_row else None
110
- conn.close()
111
  except Exception as e:
112
- print(f"[WebSocket] ⚠️ Error loading summary: {e}", flush=True)
113
  user_summary = None
114
 
115
- # Send authenticated confirmation
116
- try:
117
- await websocket.send_json({
118
- "type": "authenticated",
119
- "username": username,
120
- "summary": user_summary
121
- })
122
- print(f"[WebSocket] βœ… Authenticated: {username} (user_id: {user_id})", flush=True)
123
- if user_summary:
124
- print(f"[WebSocket] πŸ“– Loaded summary: {user_summary[:60]}...", flush=True)
125
- except Exception as e:
126
- print(f"[WebSocket] ❌ Failed to send auth confirmation: {e}", flush=True)
127
- return
128
 
129
- # Load conversation history
130
- if llm_generator:
131
- try:
132
- llm_generator.clear_history()
133
- user_history = load_user_history(user_id, limit=10)
134
-
135
- for role, content in user_history:
136
- llm_generator.conversation_history.append({
137
- "role": role,
138
- "content": content
139
- })
140
-
141
- if user_history:
142
- print(f"[WebSocket] πŸ“š Loaded {len(user_history)} messages from history", flush=True)
143
- except Exception as e:
144
- print(f"[WebSocket] ⚠️ Error loading history: {e}", flush=True)
145
 
146
- # Wait for models if needed
147
- if not models_ready:
148
- print("[WebSocket] ⏳ Models still loading, asking client to wait...", flush=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  await websocket.send_json({
150
  "type": "status",
151
- "message": "AI models loading, please wait..."
152
  })
153
 
154
  for i in range(900):
155
- if models_ready:
156
  await websocket.send_json({
157
  "type": "status",
158
- "message": "Models loaded! Ready."
159
  })
 
160
  break
161
  await asyncio.sleep(1)
162
 
163
- if not models_ready:
164
- print("[WebSocket] ❌ Models failed to load within 15 minutes", flush=True)
165
- await websocket.send_json({
166
- "type": "error",
167
- "message": "Models failed to load"
168
- })
169
- await websocket.close(code=1011, reason="Model loading failed")
170
  return
171
 
172
- print(f"[WebSocket] βœ… Models ready, entering message loop for {username}", flush=True)
173
 
174
- # ===== STEP 2: MESSAGE LOOP =====
175
  message_count = 0
176
 
177
  while True:
178
  try:
179
- print(f"[WebSocket] ⏳ Waiting for message from {username}...", flush=True)
 
180
  data = await websocket.receive_json()
181
  message_count += 1
182
  msg_type = data.get("type")
183
 
184
- print(f"[WebSocket] πŸ“¨ Message #{message_count} from {username}: type={msg_type}", flush=True)
185
 
186
- # ============ PREFERENCES UPDATE ============
187
  if msg_type == "preferences":
188
- try:
189
- if "voice" in data:
190
- user_preferences["voice"] = data.get("voice", "female")
191
- if "language" in data:
192
- user_preferences["language"] = data.get("language", "en")
193
- print(f"[Preferences] βœ… {username}: voice={user_preferences.get('voice')}, lang={user_preferences.get('language')}", flush=True)
194
- except Exception as e:
195
- print(f"[Preferences] ❌ Error: {e}", flush=True)
196
  continue
197
 
198
- # ============ GREETING REQUEST ============
199
  elif msg_type == "request_greeting":
200
- print(f"[Greeting] πŸ€– Generating greeting for {username}...", flush=True)
201
 
202
  try:
203
- # Determine greeting text
204
- language = user_preferences.get("language", "en")
205
  if user_summary:
206
- greeting_text = GREETINGS[language]["returning"].format(username=username)
207
  else:
208
- greeting_text = GREETINGS[language]["new"].format(username=username)
209
 
210
  print(f"[Greeting] πŸ‘‹ Text: '{greeting_text}'", flush=True)
211
 
@@ -217,7 +194,7 @@ async def websocket_endpoint(websocket: WebSocket):
217
  voice_pref = user_preferences.get("voice", "female")
218
  lang_pref = user_preferences.get("language", "en")
219
 
220
- print(f"[Greeting] πŸ”Š Requesting TTS (voice={voice_pref}, lang={lang_pref})...", flush=True)
221
 
222
  avatar_response = requests.post(
223
  f"{AVATAR_API}/speak",
@@ -235,10 +212,10 @@ async def websocket_endpoint(websocket: WebSocket):
235
  visemes = avatar_data.get("visemes")
236
  print(f"[Greeting] βœ… TTS generated", flush=True)
237
  else:
238
- print(f"[Greeting] ⚠️ TTS failed: {avatar_response.status_code}", flush=True)
239
 
240
  except requests.exceptions.ConnectionError:
241
- print(f"[Greeting] ⚠️ Avatar service not available (will send text-only)", flush=True)
242
  except Exception as tts_err:
243
  print(f"[Greeting] ⚠️ TTS error: {tts_err}", flush=True)
244
 
@@ -257,39 +234,29 @@ async def websocket_endpoint(websocket: WebSocket):
257
  else:
258
  response_data["text_only"] = True
259
 
260
- # Send greeting
261
- print(f"[Greeting] πŸ“€ Sending response to {username}...", flush=True)
262
  await websocket.send_json(response_data)
263
- print(f"[Greeting] βœ… Sent to {username}", flush=True)
264
 
265
- # Save to database
266
  save_message(session_id, "assistant", greeting_text, "Neutral")
267
 
268
- except Exception as greeting_err:
269
- print(f"[Greeting] ❌ Error: {greeting_err}", flush=True)
270
  import traceback
271
  traceback.print_exc()
272
-
273
- try:
274
- await websocket.send_json({
275
- "type": "error",
276
- "message": f"Greeting failed: {str(greeting_err)}"
277
- })
278
- except:
279
- pass
280
 
281
- # ============ VIDEO FRAME ============
282
  elif msg_type == "video_frame":
283
  try:
284
  img_data = base64.b64decode(data["frame"].split(",")[1])
285
  img = Image.open(io.BytesIO(img_data))
286
  frame = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
287
 
288
- processed_frame, result = face_processor.process_frame(frame)
289
- face_emotion = face_processor.get_last_emotion() or "Neutral"
290
- face_confidence = face_processor.get_last_confidence() or 0.0
291
- face_probs = face_processor.get_last_probs()
292
- face_quality = getattr(face_processor, 'get_last_quality', lambda: 0.5)()
293
 
294
  await websocket.send_json({
295
  "type": "face_emotion",
@@ -301,23 +268,20 @@ async def websocket_endpoint(websocket: WebSocket):
301
  except Exception as e:
302
  print(f"[Video] ❌ Error: {e}", flush=True)
303
 
304
- # ============ AUDIO CHUNK ============
305
  elif msg_type == "audio_chunk":
306
  try:
307
  audio_data = base64.b64decode(data["audio"])
308
-
309
- # Convert to float32
310
  audio_array = np.frombuffer(audio_data, dtype=np.int16).astype(np.float32) / 32768.0
311
 
312
- # Feed to whisper
313
- if whisper_worker:
314
- whisper_worker.add_audio(audio_array)
315
 
316
  audio_buffer.append(audio_array)
317
 
318
  if len(audio_buffer) >= 5:
319
- if voice_worker:
320
- voice_probs, voice_emotion = voice_worker.get_probs()
321
  await websocket.send_json({
322
  "type": "voice_emotion",
323
  "emotion": voice_emotion
@@ -327,10 +291,10 @@ async def websocket_endpoint(websocket: WebSocket):
327
  except Exception as e:
328
  print(f"[Audio] ❌ Error: {e}", flush=True)
329
 
330
- # ============ SPEECH END ============
331
  elif msg_type == "speech_end":
332
  transcription = data.get("text", "").strip()
333
- print(f"\n[Speech] 🎀 User finished: '{transcription}'", flush=True)
334
 
335
  try:
336
  await process_speech_end(
@@ -338,60 +302,44 @@ async def websocket_endpoint(websocket: WebSocket):
338
  username, user_summary, user_preferences
339
  )
340
  except Exception as e:
341
- print(f"[Speech] ❌ Processing error: {e}", flush=True)
342
  import traceback
343
  traceback.print_exc()
344
 
345
- try:
346
- await websocket.send_json({
347
- "type": "error",
348
- "message": f"Processing failed: {str(e)}"
349
- })
350
- except:
351
- pass
352
 
353
  else:
354
- print(f"[WebSocket] ⚠️ Unknown message type: {msg_type}", flush=True)
355
 
356
  except WebSocketDisconnect:
357
- print(f"[WebSocket] πŸ”Œ {username} disconnected gracefully", flush=True)
358
  break
359
 
360
- except Exception as loop_err:
361
- print(f"[WebSocket] ⚠️ Error in message loop: {loop_err}", flush=True)
362
  import traceback
363
  traceback.print_exc()
364
-
365
- try:
366
- await websocket.send_json({
367
- "type": "error",
368
- "message": f"Server error: {str(loop_err)}"
369
- })
370
- except:
371
- pass
372
-
373
- # Don't break - continue receiving messages
374
  await asyncio.sleep(0.1)
375
 
376
  except WebSocketDisconnect:
377
  print(f"[WebSocket] πŸ”Œ {username or 'User'} disconnected", flush=True)
378
 
379
  except Exception as outer_err:
380
- print(f"[WebSocket] ❌ Fatal error: {outer_err}", flush=True)
381
  import traceback
382
  traceback.print_exc()
383
 
384
  finally:
385
- # Generate summary on disconnect
386
  if session_id and user_id and username:
387
  print(f"[WebSocket] πŸ“ Generating summary for {username}...", flush=True)
388
  try:
389
  summary = await generate_session_summary(session_id, user_id)
390
  if summary:
391
- print(f"[Summary] βœ… Saved for {username}", flush=True)
392
- else:
393
- print(f"[Summary] ⏭️ Skipped (not enough messages)", flush=True)
394
- except Exception as summary_err:
395
- print(f"[Summary] ❌ Error: {summary_err}", flush=True)
396
 
397
- print(f"[WebSocket] πŸ‘‹ Connection fully closed for {username or 'Unknown'}", flush=True)
 
1
  """
2
+ MrrrMe Backend - WebSocket Handler (FIXED - GLOBAL VARIABLE BUG)
 
3
  """
4
 
5
  from fastapi import WebSocket, WebSocketDisconnect
 
10
  import io
11
  from PIL import Image
12
  import requests
13
+
14
+ # βœ… FIX: Import the MODULE, not the variable
15
+ from . import models as models_module
16
+
17
  from .session.manager import validate_token, save_message, load_user_history
18
  from .session.summary import generate_session_summary
19
  from .auth.database import get_db_connection
 
24
  AVATAR_API = get_avatar_api_url()
25
 
26
  async def websocket_endpoint(websocket: WebSocket):
27
+ """Main WebSocket endpoint handler"""
 
 
 
 
 
 
28
 
29
  print("\n" + "="*80, flush=True)
30
  print("[WebSocket] πŸ”Œ NEW CONNECTION", flush=True)
 
47
  user_preferences = {"voice": "female", "language": "en"}
48
 
49
  try:
50
+ # ===== AUTHENTICATION =====
51
  print("[WebSocket] ⏳ Waiting for auth message...", flush=True)
52
 
53
  try:
54
  auth_msg = await asyncio.wait_for(websocket.receive_json(), timeout=10.0)
55
  print(f"[WebSocket] πŸ“¨ Auth message received: {auth_msg.get('type')}", flush=True)
56
  except asyncio.TimeoutError:
57
+ print("[WebSocket] ❌ Auth timeout", flush=True)
58
+ await websocket.close(code=1008, reason="Auth timeout")
 
 
 
59
  return
60
 
61
  if auth_msg.get("type") != "auth":
62
+ print(f"[WebSocket] ❌ Wrong type: {auth_msg.get('type')}", flush=True)
63
+ await websocket.send_json({"type": "error", "message": "Auth required"})
64
+ await websocket.close(code=1008)
65
  return
66
 
67
  token = auth_msg.get("token")
68
  print(f"[WebSocket] πŸ”‘ Validating token: {token[:10] if token else 'None'}...", flush=True)
69
 
70
  if not token:
71
+ print("[WebSocket] ❌ No token", flush=True)
72
+ await websocket.send_json({"type": "error", "message": "No token"})
73
+ await websocket.close(code=1008)
74
  return
75
 
76
+ # Validate
77
  session_data = validate_token(token)
78
 
79
  if not session_data:
80
+ print("[WebSocket] ❌ Invalid token", flush=True)
81
+ await websocket.send_json({"type": "error", "message": "Invalid session"})
82
+ await websocket.close(code=1008)
83
  return
84
 
85
  session_id = session_data['session_id']
 
88
 
89
  print(f"[WebSocket] βœ… Token validated for user: {username} (session: {session_id})", flush=True)
90
 
91
+ # Get summary
92
  try:
93
+ conn = get_db_connection()
94
+ cursor = conn.cursor()
95
+ cursor.execute("SELECT summary_text FROM user_summaries WHERE user_id = ?", (user_id,))
96
+ summary_row = cursor.fetchone()
97
+ user_summary = summary_row[0] if summary_row else None
98
+ conn.close()
 
 
99
  except Exception as e:
100
+ print(f"[WebSocket] ⚠️ Summary error: {e}", flush=True)
101
  user_summary = None
102
 
103
+ # Send auth confirmation
104
+ await websocket.send_json({
105
+ "type": "authenticated",
106
+ "username": username,
107
+ "summary": user_summary
108
+ })
 
 
 
 
 
 
 
109
 
110
+ print(f"[WebSocket] βœ… Authenticated: {username} (user_id: {user_id})", flush=True)
111
+ if user_summary:
112
+ print(f"[WebSocket] πŸ“– Loaded summary: {user_summary[:60]}...", flush=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
+ # Load history
115
+ if models_module.llm_generator:
116
+ models_module.llm_generator.clear_history()
117
+ user_history = load_user_history(user_id, limit=10)
118
+
119
+ for role, content in user_history:
120
+ models_module.llm_generator.conversation_history.append({
121
+ "role": role,
122
+ "content": content
123
+ })
124
+
125
+ if user_history:
126
+ print(f"[WebSocket] πŸ“š Loaded {len(user_history)} messages", flush=True)
127
+
128
+ # βœ… FIX: Check models_ready from the module, not imported variable
129
+ if not models_module.models_ready:
130
+ print(f"[WebSocket] ⏳ Models still loading (models_ready={models_module.models_ready}), asking client to wait...", flush=True)
131
  await websocket.send_json({
132
  "type": "status",
133
+ "message": "Models loading..."
134
  })
135
 
136
  for i in range(900):
137
+ if models_module.models_ready:
138
  await websocket.send_json({
139
  "type": "status",
140
+ "message": "Models ready!"
141
  })
142
+ print(f"[WebSocket] βœ… Models became ready after {i}s", flush=True)
143
  break
144
  await asyncio.sleep(1)
145
 
146
+ if not models_module.models_ready:
147
+ print("[WebSocket] ❌ Models timeout", flush=True)
148
+ await websocket.send_json({"type": "error", "message": "Models timeout"})
149
+ await websocket.close(code=1011)
 
 
 
150
  return
151
 
152
+ print(f"[WebSocket] βœ… Models confirmed ready, entering message loop for {username}", flush=True)
153
 
154
+ # ===== MESSAGE LOOP =====
155
  message_count = 0
156
 
157
  while True:
158
  try:
159
+ print(f"[WebSocket] ⏳ Waiting for message from {username} (msg #{message_count+1})...", flush=True)
160
+
161
  data = await websocket.receive_json()
162
  message_count += 1
163
  msg_type = data.get("type")
164
 
165
+ print(f"[WebSocket] πŸ“¨ Message #{message_count}: type={msg_type}", flush=True)
166
 
167
+ # ===== PREFERENCES =====
168
  if msg_type == "preferences":
169
+ if "voice" in data:
170
+ user_preferences["voice"] = data.get("voice", "female")
171
+ if "language" in data:
172
+ user_preferences["language"] = data.get("language", "en")
173
+ print(f"[Preferences] βœ… Updated: {user_preferences}", flush=True)
 
 
 
174
  continue
175
 
176
+ # ===== GREETING =====
177
  elif msg_type == "request_greeting":
178
+ print(f"[Greeting] πŸ€– Generating for {username}...", flush=True)
179
 
180
  try:
181
+ lang = user_preferences.get("language", "en")
 
182
  if user_summary:
183
+ greeting_text = GREETINGS[lang]["returning"].format(username=username)
184
  else:
185
+ greeting_text = GREETINGS[lang]["new"].format(username=username)
186
 
187
  print(f"[Greeting] πŸ‘‹ Text: '{greeting_text}'", flush=True)
188
 
 
194
  voice_pref = user_preferences.get("voice", "female")
195
  lang_pref = user_preferences.get("language", "en")
196
 
197
+ print(f"[Greeting] πŸ”Š Requesting TTS...", flush=True)
198
 
199
  avatar_response = requests.post(
200
  f"{AVATAR_API}/speak",
 
212
  visemes = avatar_data.get("visemes")
213
  print(f"[Greeting] βœ… TTS generated", flush=True)
214
  else:
215
+ print(f"[Greeting] ⚠️ TTS status: {avatar_response.status_code}", flush=True)
216
 
217
  except requests.exceptions.ConnectionError:
218
+ print(f"[Greeting] ⚠️ Avatar offline - text-only", flush=True)
219
  except Exception as tts_err:
220
  print(f"[Greeting] ⚠️ TTS error: {tts_err}", flush=True)
221
 
 
234
  else:
235
  response_data["text_only"] = True
236
 
237
+ print(f"[Greeting] πŸ“€ Sending to {username}...", flush=True)
 
238
  await websocket.send_json(response_data)
239
+ print(f"[Greeting] βœ… Sent", flush=True)
240
 
 
241
  save_message(session_id, "assistant", greeting_text, "Neutral")
242
 
243
+ except Exception as err:
244
+ print(f"[Greeting] ❌ Error: {err}", flush=True)
245
  import traceback
246
  traceback.print_exc()
 
 
 
 
 
 
 
 
247
 
248
+ # ===== VIDEO FRAME =====
249
  elif msg_type == "video_frame":
250
  try:
251
  img_data = base64.b64decode(data["frame"].split(",")[1])
252
  img = Image.open(io.BytesIO(img_data))
253
  frame = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
254
 
255
+ processed_frame, result = models_module.face_processor.process_frame(frame)
256
+ face_emotion = models_module.face_processor.get_last_emotion() or "Neutral"
257
+ face_confidence = models_module.face_processor.get_last_confidence() or 0.0
258
+ face_probs = models_module.face_processor.get_last_probs()
259
+ face_quality = getattr(models_module.face_processor, 'get_last_quality', lambda: 0.5)()
260
 
261
  await websocket.send_json({
262
  "type": "face_emotion",
 
268
  except Exception as e:
269
  print(f"[Video] ❌ Error: {e}", flush=True)
270
 
271
+ # ===== AUDIO CHUNK =====
272
  elif msg_type == "audio_chunk":
273
  try:
274
  audio_data = base64.b64decode(data["audio"])
 
 
275
  audio_array = np.frombuffer(audio_data, dtype=np.int16).astype(np.float32) / 32768.0
276
 
277
+ if models_module.whisper_worker:
278
+ models_module.whisper_worker.add_audio(audio_array)
 
279
 
280
  audio_buffer.append(audio_array)
281
 
282
  if len(audio_buffer) >= 5:
283
+ if models_module.voice_worker:
284
+ voice_probs, voice_emotion = models_module.voice_worker.get_probs()
285
  await websocket.send_json({
286
  "type": "voice_emotion",
287
  "emotion": voice_emotion
 
291
  except Exception as e:
292
  print(f"[Audio] ❌ Error: {e}", flush=True)
293
 
294
+ # ===== SPEECH END =====
295
  elif msg_type == "speech_end":
296
  transcription = data.get("text", "").strip()
297
+ print(f"\n[Speech] 🎀 '{transcription}'", flush=True)
298
 
299
  try:
300
  await process_speech_end(
 
302
  username, user_summary, user_preferences
303
  )
304
  except Exception as e:
305
+ print(f"[Speech] ❌ Error: {e}", flush=True)
306
  import traceback
307
  traceback.print_exc()
308
 
309
+ await websocket.send_json({
310
+ "type": "error",
311
+ "message": f"Error: {str(e)}"
312
+ })
 
 
 
313
 
314
  else:
315
+ print(f"[WebSocket] ⚠️ Unknown type: {msg_type}", flush=True)
316
 
317
  except WebSocketDisconnect:
318
+ print(f"[WebSocket] πŸ”Œ {username} disconnected", flush=True)
319
  break
320
 
321
+ except Exception as err:
322
+ print(f"[WebSocket] ⚠️ Loop error: {err}", flush=True)
323
  import traceback
324
  traceback.print_exc()
 
 
 
 
 
 
 
 
 
 
325
  await asyncio.sleep(0.1)
326
 
327
  except WebSocketDisconnect:
328
  print(f"[WebSocket] πŸ”Œ {username or 'User'} disconnected", flush=True)
329
 
330
  except Exception as outer_err:
331
+ print(f"[WebSocket] ❌ Fatal: {outer_err}", flush=True)
332
  import traceback
333
  traceback.print_exc()
334
 
335
  finally:
 
336
  if session_id and user_id and username:
337
  print(f"[WebSocket] πŸ“ Generating summary for {username}...", flush=True)
338
  try:
339
  summary = await generate_session_summary(session_id, user_id)
340
  if summary:
341
+ print(f"[Summary] βœ… Saved", flush=True)
342
+ except Exception as e:
343
+ print(f"[Summary] ❌ Error: {e}", flush=True)
 
 
344
 
345
+ print(f"[WebSocket] πŸ‘‹ Closed for {username or 'Unknown'}", flush=True)