Spaces:
Running
Running
File size: 5,433 Bytes
69fb140 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
"""
語音綁定狀態機
管理語音帳號綁定流程(關鍵字匹配,無 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()
|