|
""" |
|
LINE Bot 服務 - 整合業務查詢功能 |
|
處理來自 LINE 官方帳號的用戶訊息 |
|
""" |
|
|
|
import logging |
|
from typing import Dict, Any, Optional |
|
from backend.services.business_query_service import BusinessQueryService |
|
from backend.services.database_service import DatabaseService |
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
class LineBotService: |
|
"""LINE Bot 服務類""" |
|
|
|
def __init__(self): |
|
self.business_query_service = BusinessQueryService() |
|
self.db_service = DatabaseService() |
|
|
|
def handle_text_message(self, user_id: str, message_text: str, display_name: str = None) -> Dict[str, Any]: |
|
""" |
|
處理 LINE 用戶的文字訊息 |
|
|
|
Args: |
|
user_id: LINE 用戶ID |
|
message_text: 用戶發送的訊息內容 |
|
display_name: 用戶顯示名稱(可選) |
|
|
|
Returns: |
|
包含回應訊息和相關資料的字典 |
|
""" |
|
try: |
|
|
|
logger.info(f"收到來自用戶 {user_id} 的訊息: {message_text}") |
|
|
|
|
|
if message_text.startswith('##test##'): |
|
return self._handle_deepseek_test(user_id, message_text[8:].strip()) |
|
|
|
|
|
if message_text.lower() in ['help', '幫助', '說明', '指令']: |
|
return self._handle_help_command(user_id) |
|
|
|
elif message_text.lower() in ['menu', '選單', '功能']: |
|
return self._handle_menu_command(user_id) |
|
|
|
|
|
self._ensure_user_profile(user_id, display_name) |
|
|
|
|
|
query_result = self.business_query_service.process_user_query(message_text, user_id) |
|
|
|
|
|
response = { |
|
"type": "text", |
|
"text": query_result["response_message"], |
|
"user_id": user_id, |
|
"success": query_result["success"], |
|
"intent": query_result["intent"], |
|
"confidence": query_result["confidence"] |
|
} |
|
|
|
|
|
if query_result["success"] and query_result["data"]: |
|
response["quick_reply"] = self._generate_quick_reply_buttons(query_result["intent"]) |
|
|
|
return response |
|
|
|
except Exception as e: |
|
logger.error(f"處理 LINE 訊息時發生錯誤: {str(e)}") |
|
return { |
|
"type": "text", |
|
"text": "抱歉,系統暫時無法處理您的請求,請稍後再試。", |
|
"user_id": user_id, |
|
"success": False, |
|
"error": str(e) |
|
} |
|
|
|
def _handle_help_command(self, user_id: str) -> Dict[str, Any]: |
|
"""處理幫助指令""" |
|
help_message = self.business_query_service.get_help_message() |
|
|
|
return { |
|
"type": "text", |
|
"text": help_message, |
|
"user_id": user_id, |
|
"success": True, |
|
"intent": "help" |
|
} |
|
|
|
def _handle_menu_command(self, user_id: str) -> Dict[str, Any]: |
|
"""處理選單指令""" |
|
menu_message = """ |
|
🏪 智能查詢系統主選單 |
|
|
|
請選擇您需要的功能: |
|
|
|
1️⃣ 商品查詢 |
|
輸入:查詢商品 [商品名稱] |
|
|
|
2️⃣ 庫存查詢 |
|
輸入:庫存查詢 [商品名稱] |
|
|
|
3️⃣ 訂單查詢 |
|
輸入:我的訂單 |
|
|
|
4️⃣ 低庫存警告 |
|
輸入:低庫存商品 |
|
|
|
5️⃣ 業務統計 |
|
輸入:業務摘要 |
|
|
|
💡 您也可以直接用自然語言提問! |
|
例如:「iPhone 還有多少庫存?」 |
|
|
|
輸入「幫助」查看詳細說明 |
|
""" |
|
|
|
return { |
|
"type": "text", |
|
"text": menu_message.strip(), |
|
"user_id": user_id, |
|
"success": True, |
|
"intent": "menu", |
|
"quick_reply": { |
|
"items": [ |
|
{"type": "action", "action": {"type": "message", "label": "商品查詢", "text": "查詢商品"}}, |
|
{"type": "action", "action": {"type": "message", "label": "庫存查詢", "text": "庫存查詢"}}, |
|
{"type": "action", "action": {"type": "message", "label": "我的訂單", "text": "我的訂單"}}, |
|
{"type": "action", "action": {"type": "message", "label": "低庫存", "text": "低庫存商品"}}, |
|
{"type": "action", "action": {"type": "message", "label": "業務統計", "text": "業務摘要"}} |
|
] |
|
} |
|
} |
|
|
|
def _ensure_user_profile(self, user_id: str, display_name: str = None): |
|
"""確保用戶資料存在於資料庫中""" |
|
try: |
|
|
|
existing_user = self.db_service.get_user_profile(user_id) |
|
|
|
if not existing_user: |
|
|
|
user_data = { |
|
"user_id": user_id, |
|
"display_name": display_name or f"用戶_{user_id[:8]}", |
|
"created_at": "now()" |
|
} |
|
|
|
success = self.db_service.create_user_profile(user_data) |
|
if success: |
|
logger.info(f"已建立新用戶資料: {user_id}") |
|
else: |
|
logger.warning(f"建立用戶資料失敗: {user_id}") |
|
|
|
except Exception as e: |
|
logger.error(f"處理用戶資料時發生錯誤: {str(e)}") |
|
|
|
def _generate_quick_reply_buttons(self, intent: str) -> Dict[str, Any]: |
|
"""根據查詢意圖生成快速回覆按鈕""" |
|
|
|
if intent == "product_search": |
|
return { |
|
"items": [ |
|
{"type": "action", "action": {"type": "message", "label": "查看庫存", "text": "庫存查詢"}}, |
|
{"type": "action", "action": {"type": "message", "label": "相關商品", "text": "查詢商品"}}, |
|
{"type": "action", "action": {"type": "message", "label": "主選單", "text": "選單"}} |
|
] |
|
} |
|
|
|
elif intent == "inventory_check": |
|
return { |
|
"items": [ |
|
{"type": "action", "action": {"type": "message", "label": "低庫存警告", "text": "低庫存商品"}}, |
|
{"type": "action", "action": {"type": "message", "label": "商品詳情", "text": "查詢商品"}}, |
|
{"type": "action", "action": {"type": "message", "label": "主選單", "text": "選單"}} |
|
] |
|
} |
|
|
|
elif intent == "order_search": |
|
return { |
|
"items": [ |
|
{"type": "action", "action": {"type": "message", "label": "業務統計", "text": "業務摘要"}}, |
|
{"type": "action", "action": {"type": "message", "label": "主選單", "text": "選單"}} |
|
] |
|
} |
|
|
|
else: |
|
return { |
|
"items": [ |
|
{"type": "action", "action": {"type": "message", "label": "商品查詢", "text": "查詢商品"}}, |
|
{"type": "action", "action": {"type": "message", "label": "庫存查詢", "text": "庫存查詢"}}, |
|
{"type": "action", "action": {"type": "message", "label": "主選單", "text": "選單"}} |
|
] |
|
} |
|
|
|
def handle_postback_action(self, user_id: str, postback_data: str) -> Dict[str, Any]: |
|
"""處理 Postback 動作(按鈕點擊等)""" |
|
try: |
|
|
|
if postback_data.startswith("query_"): |
|
query_type = postback_data.replace("query_", "") |
|
|
|
if query_type == "products": |
|
return self.handle_text_message(user_id, "查詢商品") |
|
elif query_type == "inventory": |
|
return self.handle_text_message(user_id, "庫存查詢") |
|
elif query_type == "orders": |
|
return self.handle_text_message(user_id, "我的訂單") |
|
elif query_type == "summary": |
|
return self.handle_text_message(user_id, "業務摘要") |
|
|
|
|
|
return self._handle_menu_command(user_id) |
|
|
|
except Exception as e: |
|
logger.error(f"處理 Postback 動作時發生錯誤: {str(e)}") |
|
return { |
|
"type": "text", |
|
"text": "抱歉,無法處理此操作。", |
|
"user_id": user_id, |
|
"success": False, |
|
"error": str(e) |
|
} |
|
|
|
def get_user_activity_summary(self, user_id: str, days: int = 7) -> Dict[str, Any]: |
|
"""取得用戶活動摘要""" |
|
try: |
|
|
|
|
|
|
|
summary = { |
|
"user_id": user_id, |
|
"period_days": days, |
|
"total_queries": 0, |
|
"most_used_features": [], |
|
"last_activity": None |
|
} |
|
|
|
return { |
|
"success": True, |
|
"data": summary |
|
} |
|
|
|
except Exception as e: |
|
logger.error(f"取得用戶活動摘要時發生錯誤: {str(e)}") |
|
return { |
|
"success": False, |
|
"error": str(e) |
|
} |
|
|
|
def _handle_deepseek_test(self, user_id: str, test_message: str) -> Dict[str, Any]: |
|
""" |
|
🔧 後門測試:直接與 DeepSeek V3 聊天 |
|
使用方式:在 LINE 中發送 "##test##你好" |
|
""" |
|
try: |
|
import asyncio |
|
import httpx |
|
from backend.config import settings |
|
|
|
if not test_message: |
|
return { |
|
"type": "text", |
|
"text": "🔧 DeepSeek V3 測試模式\n\n使用方式:##test##[您的訊息]\n\n範例:##test##你好,請介紹一下自己", |
|
"user_id": user_id, |
|
"success": True, |
|
"intent": "deepseek_test" |
|
} |
|
|
|
|
|
def sync_call_deepseek(): |
|
return asyncio.run(self._call_deepseek_api(test_message)) |
|
|
|
try: |
|
response_text = sync_call_deepseek() |
|
|
|
return { |
|
"type": "text", |
|
"text": f"🤖 DeepSeek V3 回應:\n\n{response_text}", |
|
"user_id": user_id, |
|
"success": True, |
|
"intent": "deepseek_test" |
|
} |
|
|
|
except Exception as api_error: |
|
return { |
|
"type": "text", |
|
"text": f"❌ DeepSeek V3 API 錯誤:\n{str(api_error)}\n\n請檢查:\n1. API Key 設定\n2. 網路連線\n3. OpenRouter 服務狀態", |
|
"user_id": user_id, |
|
"success": False, |
|
"intent": "deepseek_test", |
|
"error": str(api_error) |
|
} |
|
|
|
except Exception as e: |
|
logger.error(f"DeepSeek 測試錯誤: {str(e)}") |
|
return { |
|
"type": "text", |
|
"text": f"❌ 測試功能錯誤:{str(e)}", |
|
"user_id": user_id, |
|
"success": False, |
|
"intent": "deepseek_test", |
|
"error": str(e) |
|
} |
|
|
|
async def _call_deepseek_api(self, message: str) -> str: |
|
""" |
|
直接調用 DeepSeek V3 API |
|
""" |
|
from backend.config import settings |
|
|
|
api_key = settings.OPENROUTER_API_KEY |
|
model = settings.OPENROUTER_MODEL |
|
|
|
if not api_key: |
|
return "❌ OPENROUTER_API_KEY 未設定" |
|
|
|
headers = { |
|
"Authorization": f"Bearer {api_key}", |
|
"Content-Type": "application/json", |
|
"HTTP-Referer": "https://huggingface.co/spaces", |
|
"X-Title": "LINE Bot DeepSeek Test" |
|
} |
|
|
|
try: |
|
async with httpx.AsyncClient(timeout=30.0) as client: |
|
response = await client.post( |
|
"https://openrouter.ai/api/v1/chat/completions", |
|
headers=headers, |
|
json={ |
|
"model": model, |
|
"messages": [ |
|
{ |
|
"role": "system", |
|
"content": "你是 DeepSeek V3,一個強大的中文 AI 助手。請用繁體中文回應,並在回應開頭說明你是 DeepSeek V3。" |
|
}, |
|
{ |
|
"role": "user", |
|
"content": message |
|
} |
|
], |
|
"temperature": 0.7, |
|
"max_tokens": 500 |
|
} |
|
) |
|
|
|
if response.status_code == 200: |
|
result = response.json() |
|
content = result["choices"][0]["message"]["content"] |
|
return content |
|
else: |
|
return f"❌ API 錯誤 {response.status_code}: {response.text}" |
|
|
|
except httpx.TimeoutException: |
|
return "❌ API 請求超時,請稍後再試" |
|
except Exception as e: |
|
return f"❌ 網路錯誤: {str(e)}" |