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]}...")