XiaoBai1221 commited on
Commit
df89a78
·
1 Parent(s): 9746ac7

feat: 修復emotion flow即時響應,添加emotion_callback機制改善前端UI更新

Browse files
app.py CHANGED
@@ -587,7 +587,16 @@ async def websocket_endpoint_with_jwt(
587
  async def _do_process_and_send():
588
  try:
589
  logger.info(f"🚀 開始處理訊息: user_id={user_id}, chat_id={chat_id}")
590
- response = await handle_message(user_message, user_id, chat_id, messages_for_handler, request_id=request_id)
 
 
 
 
 
 
 
 
 
591
  logger.info(f"📥 handle_message 返回: type={type(response)}, response={response}")
592
 
593
  # 【優化】處理空回應:轉換為帶情緒的 dict 格式
@@ -1086,6 +1095,9 @@ async def websocket_endpoint_with_jwt(
1086
  # 如果有轉錄文字,送給 AI Agent 處理
1087
  if transcription:
1088
  logger.info(f"🤖 處理即時轉錄結果: {transcription}")
 
 
 
1089
 
1090
  # === 方案 B:語音情緒辨識(情緒分佈驗證 + 智能回退)===
1091
  audio_emotion = None
@@ -1139,9 +1151,6 @@ async def websocket_endpoint_with_jwt(
1139
  client_info.pop("audio_buffer", None)
1140
  manager.set_client_info(user_id, client_info)
1141
 
1142
- # 通知前端開始思考
1143
- await websocket.send_json({"type": "typing", "message": "thinking"})
1144
-
1145
  # 異步處理對話邏輯
1146
  async def _process_realtime_chat():
1147
  chat_id = message_data.get("chat_id")
@@ -1169,6 +1178,15 @@ async def websocket_endpoint_with_jwt(
1169
  # 取得語言設定
1170
  language = client_info.get("language", "auto")
1171
 
 
 
 
 
 
 
 
 
 
1172
  # 處理對話(透過 handle_message,自動處理 pipeline)
1173
  response = await handle_message(
1174
  transcription,
@@ -1176,7 +1194,8 @@ async def websocket_endpoint_with_jwt(
1176
  chat_id,
1177
  [], # messages 參數(會自動從數據庫載入)
1178
  audio_emotion=audio_emotion, # 傳遞音頻情緒
1179
- language=language # 傳遞語言設定(新增)
 
1180
  )
1181
 
1182
  # 發送回應
@@ -1250,7 +1269,7 @@ async def websocket_endpoint_with_jwt(
1250
  # -----------------------------
1251
  # 消息處理與AI
1252
  # -----------------------------
1253
- async def handle_message(user_message, user_id, chat_id, messages, request_id: str = None, audio_emotion: dict = None, language: str = None):
1254
  logger.info(f"📥 handle_message: 收到訊息='{user_message}', user_id={user_id}, audio_emotion={audio_emotion}, language={language}")
1255
 
1256
  # 指令優先,避免進入管線造成不必要延遲
@@ -1346,7 +1365,7 @@ async def handle_message(user_message, user_id, chat_id, messages, request_id: s
1346
  ai_timeout=20.0, # AI回應超時 (30 → 20)
1347
  )
1348
  logger.info(f"⚙️ 準備調用 ChatPipeline.process,user_message='{user_message}', audio_emotion={audio_emotion}, language={language}")
1349
- res: PipelineResult = await pipeline.process(user_message, user_id=user_id, chat_id=chat_id, request_id=request_id, audio_emotion=audio_emotion, language=language)
1350
  logger.info(f"⚙️ ChatPipeline.process 完成,結果='{res.text}', is_fallback={res.is_fallback}, reason={res.reason}")
1351
 
1352
  # 檢查是否有工具元數據
 
587
  async def _do_process_and_send():
588
  try:
589
  logger.info(f"🚀 開始處理訊息: user_id={user_id}, chat_id={chat_id}")
590
+
591
+ async def _on_text_emotion(em: str, cm: bool):
592
+ logger.info(f"📤 [即時回調] 發送 text emotion_detected: {em}, care_mode={cm}")
593
+ await websocket.send_json({
594
+ "type": "emotion_detected",
595
+ "emotion": em,
596
+ "care_mode": cm
597
+ })
598
+
599
+ response = await handle_message(user_message, user_id, chat_id, messages_for_handler, request_id=request_id, emotion_callback=_on_text_emotion)
600
  logger.info(f"📥 handle_message 返回: type={type(response)}, response={response}")
601
 
602
  # 【優化】處理空回應:轉換為帶情緒的 dict 格式
 
1095
  # 如果有轉錄文字,送給 AI Agent 處理
1096
  if transcription:
1097
  logger.info(f"🤖 處理即時轉錄結果: {transcription}")
1098
+
1099
+ # 立即通知前端開始思考,提升即時響應感
1100
+ await websocket.send_json({"type": "typing", "message": "thinking"})
1101
 
1102
  # === 方案 B:語音情緒辨識(情緒分佈驗證 + 智能回退)===
1103
  audio_emotion = None
 
1151
  client_info.pop("audio_buffer", None)
1152
  manager.set_client_info(user_id, client_info)
1153
 
 
 
 
1154
  # 異步處理對話邏輯
1155
  async def _process_realtime_chat():
1156
  chat_id = message_data.get("chat_id")
 
1178
  # 取得語言設定
1179
  language = client_info.get("language", "auto")
1180
 
1181
+ # 發送即時情緒的回調函數
1182
+ async def _on_emotion_detected(em: str, cm: bool):
1183
+ logger.info(f"📤 [即時回調] 發送 emotion_detected: {em}, care_mode={cm}")
1184
+ await websocket.send_json({
1185
+ "type": "emotion_detected",
1186
+ "emotion": em,
1187
+ "care_mode": cm
1188
+ })
1189
+
1190
  # 處理對話(透過 handle_message,自動處理 pipeline)
1191
  response = await handle_message(
1192
  transcription,
 
1194
  chat_id,
1195
  [], # messages 參數(會自動從數據庫載入)
1196
  audio_emotion=audio_emotion, # 傳遞音頻情緒
1197
+ language=language, # 傳遞語言設定(新增)
1198
+ emotion_callback=_on_emotion_detected
1199
  )
1200
 
1201
  # 發送回應
 
1269
  # -----------------------------
1270
  # 消息處理與AI
1271
  # -----------------------------
1272
+ async def handle_message(user_message, user_id, chat_id, messages, request_id: str = None, audio_emotion: dict = None, language: str = None, emotion_callback=None):
1273
  logger.info(f"📥 handle_message: 收到訊息='{user_message}', user_id={user_id}, audio_emotion={audio_emotion}, language={language}")
1274
 
1275
  # 指令優先,避免進入管線造成不必要延遲
 
1365
  ai_timeout=20.0, # AI回應超時 (30 → 20)
1366
  )
1367
  logger.info(f"⚙️ 準備調用 ChatPipeline.process,user_message='{user_message}', audio_emotion={audio_emotion}, language={language}")
1368
+ res: PipelineResult = await pipeline.process(user_message, user_id=user_id, chat_id=chat_id, request_id=request_id, audio_emotion=audio_emotion, language=language, emotion_callback=emotion_callback)
1369
  logger.info(f"⚙️ ChatPipeline.process 完成,結果='{res.text}', is_fallback={res.is_fallback}, reason={res.reason}")
1370
 
1371
  # 檢查是否有工具元數據
core/pipeline.py CHANGED
@@ -209,6 +209,7 @@ class ChatPipeline:
209
  request_id: Optional[str] = None,
210
  audio_emotion: Optional[Dict[str, Any]] = None,
211
  language: Optional[str] = None,
 
212
  ) -> PipelineResult:
213
  if not user_message or not user_message.strip():
214
  return PipelineResult(
@@ -269,6 +270,13 @@ class ChatPipeline:
269
  logger.info(f"💙 用戶 {user_id} 在關懷模式中,跳過工具調用,使用關懷 AI")
270
  # 直接用關懷模式 AI 回應(不檢測意圖,不調用工具)
271
  care_emotion = EmotionCareManager.get_care_emotion(user_id, chat_id)
 
 
 
 
 
 
 
272
  ai_res = await self._with_timeout(
273
  self._ai_generator(
274
  user_message,
@@ -301,6 +309,13 @@ class ChatPipeline:
301
  ):
302
  logger.warning(f"⚠️ 偵測到極端情緒 [{emotion_value}](置信度: {emotion_confidence:.2f}),進入關懷模式")
303
  # 立即使用關懷模式 AI 回應
 
 
 
 
 
 
 
304
  ai_res = await self._with_timeout(
305
  self._ai_generator(
306
  user_message,
@@ -325,6 +340,12 @@ class ChatPipeline:
325
  exit_hint = "\n\n💙 關懷模式已啟動。說「我沒事了」可以退出。"
326
  return PipelineResult(text=text + exit_hint, is_fallback=False, meta={"care_mode": True, "emotion": emotion_value})
327
 
 
 
 
 
 
 
328
  # 3) 有功能 → 功能處理(限時)
329
  if has_feature and intent_data:
330
  feat_res = await self._with_timeout(
 
209
  request_id: Optional[str] = None,
210
  audio_emotion: Optional[Dict[str, Any]] = None,
211
  language: Optional[str] = None,
212
+ emotion_callback = None,
213
  ) -> PipelineResult:
214
  if not user_message or not user_message.strip():
215
  return PipelineResult(
 
270
  logger.info(f"💙 用戶 {user_id} 在關懷模式中,跳過工具調用,使用關懷 AI")
271
  # 直接用關懷模式 AI 回應(不檢測意圖,不調用工具)
272
  care_emotion = EmotionCareManager.get_care_emotion(user_id, chat_id)
273
+ final_emotion = care_emotion or emotion_value
274
+ if emotion_callback:
275
+ try:
276
+ await emotion_callback(final_emotion, True)
277
+ except Exception as e:
278
+ logger.warning(f"emotion_callback 錯誤: {e}")
279
+
280
  ai_res = await self._with_timeout(
281
  self._ai_generator(
282
  user_message,
 
309
  ):
310
  logger.warning(f"⚠️ 偵測到極端情緒 [{emotion_value}](置信度: {emotion_confidence:.2f}),進入關懷模式")
311
  # 立即使用關懷模式 AI 回應
312
+
313
+ if emotion_callback:
314
+ try:
315
+ await emotion_callback(emotion_value, True)
316
+ except Exception as e:
317
+ logger.warning(f"emotion_callback 錯誤: {e}")
318
+
319
  ai_res = await self._with_timeout(
320
  self._ai_generator(
321
  user_message,
 
340
  exit_hint = "\n\n💙 關懷模式已啟動。說「我沒事了」可以退出。"
341
  return PipelineResult(text=text + exit_hint, is_fallback=False, meta={"care_mode": True, "emotion": emotion_value})
342
 
343
+ if emotion_callback:
344
+ try:
345
+ await emotion_callback(emotion_value, False)
346
+ except Exception as e:
347
+ logger.warning(f"emotion_callback 錯誤: {e}")
348
+
349
  # 3) 有功能 → 功能處理(限時)
350
  if has_feature and intent_data:
351
  feat_res = await self._with_timeout(
tests/test_heartbeat.py CHANGED
@@ -2,6 +2,10 @@
2
  測試 websocket/heartbeat.py 心跳機制
3
  """
4
 
 
 
 
 
5
  import pytest
6
  import time
7
  from websocket.heartbeat import HeartbeatManager, HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT
 
2
  測試 websocket/heartbeat.py 心跳機制
3
  """
4
 
5
+ import os
6
+ import sys
7
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
8
+
9
  import pytest
10
  import time
11
  from websocket.heartbeat import HeartbeatManager, HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT
tests/test_websocket_manager.py CHANGED
@@ -2,6 +2,10 @@
2
  測試 websocket/manager.py 連線管理器
3
  """
4
 
 
 
 
 
5
  import pytest
6
  from datetime import datetime, timedelta
7
  from websocket.manager import ConnectionManager
 
2
  測試 websocket/manager.py 連線管理器
3
  """
4
 
5
+ import os
6
+ import sys
7
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
8
+
9
  import pytest
10
  from datetime import datetime, timedelta
11
  from websocket.manager import ConnectionManager