173 lines
6.7 KiB
Python
173 lines
6.7 KiB
Python
import requests
|
|
import logging
|
|
from .models import Fanpage, Flow, Node, Edge, ChatSession, MessageLog
|
|
from ai.local_ai_api import LocalAIApi
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
def send_fb_message(psid, access_token, message_content):
|
|
"""
|
|
Sends a message to a Facebook user via the Graph API.
|
|
message_content: dict matching Facebook's message object (e.g., {"text": "..."})
|
|
"""
|
|
url = f"https://graph.facebook.com/v12.0/me/messages?access_token={access_token}"
|
|
payload = {
|
|
"recipient": {"id": psid},
|
|
"message": message_content
|
|
}
|
|
try:
|
|
response = requests.post(url, json=payload)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
except Exception as e:
|
|
logger.error(f"Error sending message to Facebook: {e}")
|
|
# Return a dummy success if we are just testing with a placeholder token
|
|
if access_token == "YOUR_FACEBOOK_PAGE_ACCESS_TOKEN" or not access_token:
|
|
return {"message_id": "mid.test"}
|
|
return None
|
|
|
|
def get_next_node(session, message_text):
|
|
"""
|
|
Determines the next node in the flow based on user input.
|
|
Returns None if no matching edge is found.
|
|
"""
|
|
fanpage = session.fanpage
|
|
|
|
if not session.current_node:
|
|
# Start of conversation: find the default flow and its start node
|
|
flow = Flow.objects.filter(fanpage=fanpage, is_default=True).first()
|
|
if not flow:
|
|
flow = Flow.objects.filter(fanpage=fanpage).first()
|
|
|
|
if flow:
|
|
return Node.objects.filter(flow=flow, is_start_node=True).first()
|
|
return None
|
|
|
|
# We are in an active session, look for a matching edge
|
|
edges = Edge.objects.filter(source_node=session.current_node)
|
|
message_text_clean = message_text.strip().lower()
|
|
|
|
for edge in edges:
|
|
if edge.condition.lower() == message_text_clean:
|
|
return edge.target_node
|
|
|
|
# If no matching edge, return None to trigger AI fallback
|
|
return None
|
|
|
|
def get_ai_fallback_response(message_text, session):
|
|
"""
|
|
Generates an AI-powered response when no flow edge matches.
|
|
"""
|
|
fanpage = session.fanpage
|
|
# Get last 5 messages for context
|
|
recent_logs = MessageLog.objects.filter(session=session).order_by('-timestamp')[:5]
|
|
history = []
|
|
# Reverse to get chronological order
|
|
for log in reversed(list(recent_logs)):
|
|
role = "user" if log.sender_type == 'user' else "assistant"
|
|
history.append({"role": role, "content": log.message_text})
|
|
|
|
system_prompt = f"You are a helpful AI assistant for the Facebook Page '{fanpage.name}'. "
|
|
system_prompt += "Your goal is to answer questions and help users in a friendly manner. "
|
|
|
|
if session.current_node:
|
|
system_prompt += f"The user is currently at the stage '{session.current_node.name}' in our automated flow, but just asked something else."
|
|
|
|
messages = [{"role": "system", "content": system_prompt}] + history
|
|
|
|
logger.info(f"Triggering AI fallback for session {session.id}")
|
|
response = LocalAIApi.create_response({
|
|
"input": messages
|
|
})
|
|
|
|
if response.get("success"):
|
|
ai_text = LocalAIApi.extract_text(response)
|
|
if ai_text:
|
|
return ai_text
|
|
|
|
return "I'm sorry, I couldn't understand that. How else can I help you today?"
|
|
|
|
def handle_webhook_event(data):
|
|
"""
|
|
Main entry point for processing Facebook webhook POST data.
|
|
"""
|
|
if data.get('object') != 'page':
|
|
logger.info(f"Webhook event received but 'object' is not 'page': {data.get('object')}")
|
|
return
|
|
|
|
for entry in data.get('entry', []):
|
|
for messaging_event in entry.get('messaging', []):
|
|
sender_id = messaging_event.get('sender', {}).get('id')
|
|
recipient_id = messaging_event.get('recipient', {}).get('id')
|
|
|
|
logger.info(f"Processing messaging event: sender={sender_id}, recipient={recipient_id}")
|
|
|
|
if not sender_id or not recipient_id:
|
|
continue
|
|
|
|
# 1. Identify Fanpage
|
|
try:
|
|
fanpage = Fanpage.objects.get(page_id=recipient_id, is_active=True)
|
|
except Fanpage.DoesNotExist:
|
|
logger.warning(f"Received event for unknown or inactive Page ID: {recipient_id}")
|
|
continue
|
|
|
|
# 2. Extract Message
|
|
message_text = ""
|
|
if 'message' in messaging_event:
|
|
message_text = messaging_event['message'].get('text', "")
|
|
elif 'postback' in messaging_event:
|
|
# Handle button clicks
|
|
message_text = messaging_event['postback'].get('payload', "")
|
|
|
|
if not message_text:
|
|
logger.info("No message text or postback payload found in event.")
|
|
continue
|
|
|
|
# 3. Get or Create Session
|
|
session, created = ChatSession.objects.get_or_create(
|
|
psid=sender_id,
|
|
fanpage=fanpage
|
|
)
|
|
|
|
# 4. Log User Message
|
|
MessageLog.objects.create(
|
|
session=session,
|
|
sender_type='user',
|
|
message_text=message_text
|
|
)
|
|
logger.info(f"Logged user message: {message_text[:20]}...")
|
|
|
|
# 5. Determine Next Node
|
|
next_node = get_next_node(session, message_text)
|
|
|
|
if next_node:
|
|
# 6. Send Flow Reply
|
|
bot_text = next_node.content.get('text', '[Non-text message]')
|
|
MessageLog.objects.create(
|
|
session=session,
|
|
sender_type='bot',
|
|
message_text=bot_text
|
|
)
|
|
|
|
# Update Session
|
|
session.current_node = next_node
|
|
session.save()
|
|
|
|
# Actual delivery attempt
|
|
send_fb_message(sender_id, fanpage.access_token, next_node.content)
|
|
logger.info(f"Sent flow reply: {bot_text[:20]}...")
|
|
else:
|
|
# 9. Trigger AI Fallback
|
|
ai_reply_text = get_ai_fallback_response(message_text, session)
|
|
|
|
# Log AI Response first so it shows up even if delivery fails
|
|
MessageLog.objects.create(
|
|
session=session,
|
|
sender_type='bot',
|
|
message_text=ai_reply_text
|
|
)
|
|
|
|
# Actual delivery attempt
|
|
send_fb_message(sender_id, fanpage.access_token, {"text": ai_reply_text})
|
|
logger.info(f"Sent AI fallback reply: {ai_reply_text[:20]}...") |