Bloom_Ware / websocket /voice_binding.py
XiaoBai1221's picture
Latest
69fb140
"""
語音綁定狀態機
管理語音帳號綁定流程(關鍵字匹配,無 GPT)
"""
import logging
import time
from datetime import datetime
from typing import Dict, Any, Optional
from fastapi import WebSocket
logger = logging.getLogger("websocket.voice_binding")
class VoiceBindingStateMachine:
"""
語音帳號綁定狀態機(硬編碼關鍵字匹配)
流程:
1. 用戶說「我要綁定語音登入」
2. Agent 回應「好的,你現在要綁定誰?」
3. 用戶提供名稱
4. 系統綁定 speaker_label 到用戶帳號
5. Agent 回應「綁定成功!」
"""
def __init__(self):
# 用戶狀態:{user_id: {state: str, speaker_label: str}}
self.user_states: Dict[str, Dict[str, Any]] = {}
def check_binding_trigger(self, user_id: str, message: str) -> Optional[str]:
"""
檢查是否觸發綁定流程
Returns:
- "TRIGGER": 觸發綁定流程
- "AWAITING_NAME": 等待用戶提供名稱
- None: 不是綁定相關訊息
"""
message_lower = message.lower().replace(" ", "")
# 檢測觸發關鍵字
trigger_keywords = ["綁定語音登入", "語音登入綁定", "綁定語音", "設定語音登入"]
for keyword in trigger_keywords:
if keyword.replace(" ", "") in message_lower:
# 進入等待狀態
self.user_states[user_id] = {
"state": "AWAITING_NAME",
"timestamp": datetime.now()
}
return "TRIGGER"
# 檢查是否在等待名稱狀態
if user_id in self.user_states:
state_info = self.user_states[user_id]
if state_info.get("state") == "AWAITING_NAME":
# 檢查是否超時(5分鐘)
if (datetime.now() - state_info.get("timestamp")).total_seconds() > 300:
del self.user_states[user_id]
return None
return "AWAITING_NAME"
return None
async def handle_binding_flow(
self,
user_id: str,
message: str,
websocket: WebSocket,
voice_service: Optional[Any] = None,
manager: Optional[Any] = None,
) -> bool:
"""
處理綁定流程
Returns:
True: 已處理(不要繼續到 Agent)
False: 未處理(繼續到 Agent)
"""
state = self.check_binding_trigger(user_id, message)
if state == "TRIGGER":
# 用戶觸發綁定 - 先檢查是否已經綁定過
logger.info(f"🎙️ 用戶 {user_id} 觸發語音綁定流程")
# 檢查使用者是否已經綁定過 speaker_label
from core.database import get_user_by_id
try:
user_data = await get_user_by_id(user_id)
if user_data and user_data.get("speaker_label"):
# 已經綁定過了
existing_label = user_data.get("speaker_label")
logger.info(f"⚠️ 用戶 {user_id} 已綁定 speaker_label: {existing_label}")
await websocket.send_json({
"type": "bot_message",
"message": f"你已經綁定過語音了!目前的聲紋標籤是:{existing_label}。如果需要重新綁定,請聯繫管理員。",
"timestamp": time.time()
})
# 清理 FSM 狀態
self.clear_state(user_id)
return True
except Exception as e:
logger.error(f"❌ 檢查使用者綁定狀態失敗: {e}")
await websocket.send_json({
"type": "error",
"message": "系統錯誤,無法檢查綁定狀態"
})
return True
# 未綁定,繼續綁定流程
logger.info(f"✅ 用戶 {user_id} 尚未綁定,啟動綁定流程")
# 標記用戶進入語音綁定等待狀態
if manager:
user_session = manager.get_client_info(user_id) or {}
user_session["voice_binding_pending"] = True
user_session["voice_binding_started_at"] = datetime.now()
manager.set_client_info(user_id, user_session)
await websocket.send_json({
"type": "bot_message",
"message": "好的,請錄製一段語音(約3-5秒),用於建立你的聲紋特徵。系統會自動識別並綁定到你的帳號。",
"timestamp": time.time()
})
await websocket.send_json({
"type": "voice_binding_ready",
"message": "請點擊錄音按鈕開始錄製"
})
return True
elif state == "AWAITING_NAME":
# 這個狀態已不再使用,因為我們改為直接錄音綁定
pass
return False
def clear_state(self, user_id: str):
"""清理用戶狀態"""
self.user_states.pop(user_id, None)
def is_in_binding_flow(self, user_id: str) -> bool:
"""檢查用戶是否在綁定流程中"""
return user_id in self.user_states
# 全局語音綁定狀態機實例
voice_binding_fsm = VoiceBindingStateMachine()