diff --git a/salesflow-saas/backend/app/api/v1/whatsapp_webhook.py b/salesflow-saas/backend/app/api/v1/whatsapp_webhook.py new file mode 100644 index 00000000..ab7d88a7 --- /dev/null +++ b/salesflow-saas/backend/app/api/v1/whatsapp_webhook.py @@ -0,0 +1,135 @@ +""" +WhatsApp Webhook — Dealix AI Revenue OS +Handles incoming WhatsApp messages, verification, and delivery status. +""" +import logging +from typing import Any + +from fastapi import APIRouter, Request, Depends, HTTPException +from fastapi.responses import PlainTextResponse + +from app.database import get_db + +logger = logging.getLogger(__name__) + +router = APIRouter(prefix="/webhooks/whatsapp", tags=["WhatsApp Webhook"]) + + +@router.post("/incoming") +async def handle_incoming(request: Request, db=Depends(get_db)): + """Handle incoming WhatsApp messages from Meta Cloud API or Twilio.""" + try: + body = await request.json() + except Exception: + raise HTTPException(status_code=400, detail="Invalid JSON body") + + phone = "" + message = "" + + # Meta Cloud API format + if "entry" in body: + try: + entry = body["entry"][0] + changes = entry.get("changes", [{}])[0] + value = changes.get("value", {}) + messages = value.get("messages", []) + if messages: + msg = messages[0] + phone = msg.get("from", "") + if msg.get("type") == "text": + message = msg.get("text", {}).get("body", "") + elif msg.get("type") == "interactive": + interactive = msg.get("interactive", {}) + if "button_reply" in interactive: + message = interactive["button_reply"].get("title", "") + elif "list_reply" in interactive: + message = interactive["list_reply"].get("title", "") + else: + message = f"[{msg.get('type', 'unknown')} message]" + except (IndexError, KeyError) as e: + logger.warning(f"Failed to parse Meta webhook: {e}") + return {"status": "ok"} + + # Twilio format + elif "From" in body or "from" in body: + phone = body.get("From", body.get("from", "")).replace("whatsapp:", "") + message = body.get("Body", body.get("body", "")) + + if not phone or not message: + logger.debug("Webhook received but no actionable message") + return {"status": "ok"} + + # Process through WhatsApp Brain + from app.services.whatsapp_brain import whatsapp_brain + + try: + response = await whatsapp_brain.handle_incoming(phone, message, db) + except Exception as e: + logger.error(f"WhatsApp brain error for {phone}: {e}") + response = "عذراً، حدث خطأ. حاول مرة أخرى أو تواصل مع support@dealix.sa" + + # Send response via WhatsApp API + try: + from app.integrations.whatsapp import send_whatsapp_message + await send_whatsapp_message(phone, response) + except Exception as e: + logger.error(f"Failed to send WhatsApp response to {phone}: {e}") + + logger.info(f"[WhatsApp] {phone}: '{message[:50]}...' → response sent") + return {"status": "ok", "phone": phone, "response_length": len(response)} + + +@router.get("/verify") +async def verify_webhook(request: Request): + """Meta webhook verification challenge.""" + params = request.query_params + mode = params.get("hub.mode") + token = params.get("hub.verify_token") + challenge = params.get("hub.challenge") + + import os + verify_token = os.environ.get("WHATSAPP_VERIFY_TOKEN", "dealix-whatsapp-verify-2026") + + if mode == "subscribe" and token == verify_token: + logger.info("WhatsApp webhook verified successfully") + return PlainTextResponse(content=challenge or "", status_code=200) + + logger.warning(f"WhatsApp webhook verification failed: mode={mode}, token={token}") + raise HTTPException(status_code=403, detail="Verification failed") + + +@router.post("/status") +async def delivery_status(request: Request): + """Handle delivery/read status updates from WhatsApp.""" + try: + body = await request.json() + except Exception: + return {"status": "ok"} + + # Meta format + if "entry" in body: + try: + entry = body["entry"][0] + changes = entry.get("changes", [{}])[0] + value = changes.get("value", {}) + statuses = value.get("statuses", []) + + for status in statuses: + recipient = status.get("recipient_id", "") + status_type = status.get("status", "") # sent, delivered, read, failed + timestamp = status.get("timestamp", "") + logger.debug( + f"[WhatsApp Status] {recipient}: {status_type} at {timestamp}" + ) + + # Update message status in database if needed + if status_type == "failed": + errors = status.get("errors", []) + error_msg = errors[0].get("title", "Unknown") if errors else "Unknown" + logger.error( + f"[WhatsApp] Message to {recipient} FAILED: {error_msg}" + ) + except (IndexError, KeyError) as e: + logger.warning(f"Failed to parse status webhook: {e}") + + return {"status": "ok"} diff --git a/salesflow-saas/backend/app/services/comparison_engine.py b/salesflow-saas/backend/app/services/comparison_engine.py new file mode 100644 index 00000000..ce39024f --- /dev/null +++ b/salesflow-saas/backend/app/services/comparison_engine.py @@ -0,0 +1,183 @@ +""" +Comparison Engine — Dealix AI Revenue OS +Competitive comparison data for charts, WhatsApp responses, and sales tools. +""" +import logging +from typing import Any + +logger = logging.getLogger(__name__) + +# Score scale: 0-10 per dimension +COMPETITORS = { + "dealix": { + "name": "Dealix", "name_ar": "ديلكس", + "scores": { + "arabic_support": 10, "whatsapp_native": 10, "ai_scoring": 9, + "pdpl_compliance": 10, "pricing_value": 9, "ease_of_use": 9, + "saudi_market_fit": 10, "deal_exchange": 10, "strategic_deals": 10, + "multi_channel": 9, "reporting": 8, "integrations": 7, + }, + }, + "zoho": { + "name": "Zoho CRM", "name_ar": "زوهو", + "scores": { + "arabic_support": 7, "whatsapp_native": 6, "ai_scoring": 6, + "pdpl_compliance": 5, "pricing_value": 8, "ease_of_use": 7, + "saudi_market_fit": 6, "deal_exchange": 2, "strategic_deals": 1, + "multi_channel": 7, "reporting": 8, "integrations": 9, + }, + }, + "salesforce": { + "name": "Salesforce", "name_ar": "سيلزفورس", + "scores": { + "arabic_support": 3, "whatsapp_native": 2, "ai_scoring": 8, + "pdpl_compliance": 4, "pricing_value": 3, "ease_of_use": 4, + "saudi_market_fit": 4, "deal_exchange": 1, "strategic_deals": 2, + "multi_channel": 7, "reporting": 10, "integrations": 10, + }, + }, + "hubspot": { + "name": "HubSpot", "name_ar": "هب سبوت", + "scores": { + "arabic_support": 2, "whatsapp_native": 3, "ai_scoring": 7, + "pdpl_compliance": 3, "pricing_value": 5, "ease_of_use": 8, + "saudi_market_fit": 3, "deal_exchange": 1, "strategic_deals": 1, + "multi_channel": 8, "reporting": 8, "integrations": 9, + }, + }, + "pipedrive": { + "name": "Pipedrive", "name_ar": "بايب درايف", + "scores": { + "arabic_support": 2, "whatsapp_native": 1, "ai_scoring": 5, + "pdpl_compliance": 2, "pricing_value": 7, "ease_of_use": 9, + "saudi_market_fit": 2, "deal_exchange": 0, "strategic_deals": 0, + "multi_channel": 4, "reporting": 6, "integrations": 6, + }, + }, +} + +DIMENSION_LABELS = { + "arabic_support": {"ar": "دعم العربي", "en": "Arabic Support"}, + "whatsapp_native": {"ar": "واتساب مدمج", "en": "WhatsApp Native"}, + "ai_scoring": {"ar": "ذكاء اصطناعي", "en": "AI Scoring"}, + "pdpl_compliance": {"ar": "حماية البيانات", "en": "PDPL Compliance"}, + "pricing_value": {"ar": "القيمة مقابل السعر", "en": "Pricing Value"}, + "ease_of_use": {"ar": "سهولة الاستخدام", "en": "Ease of Use"}, + "saudi_market_fit": {"ar": "مناسب للسعودية", "en": "Saudi Market Fit"}, + "deal_exchange": {"ar": "تبادل صفقات", "en": "Deal Exchange"}, + "strategic_deals": {"ar": "صفقات استراتيجية", "en": "Strategic Deals"}, + "multi_channel": {"ar": "تعدد القنوات", "en": "Multi-Channel"}, + "reporting": {"ar": "التقارير", "en": "Reporting"}, + "integrations": {"ar": "التكاملات", "en": "Integrations"}, +} + + +class ComparisonEngine: + """Generate comparison data for charts and sales responses.""" + + @staticmethod + def get_chart_data(language: str = "ar") -> dict[str, Any]: + """Data formatted for radar/bar charts on frontend.""" + labels = [ + DIMENSION_LABELS[dim][language] + for dim in DIMENSION_LABELS + ] + datasets = [] + for key, comp in COMPETITORS.items(): + datasets.append({ + "label": comp[f"name_{language}" if f"name_{language}" in comp else "name"], + "data": list(comp["scores"].values()), + "highlight": key == "dealix", + }) + return {"labels": labels, "datasets": datasets, "dimensions": list(DIMENSION_LABELS.keys())} + + @staticmethod + def get_feature_matrix(language: str = "ar") -> dict[str, Any]: + """Feature comparison table data.""" + features = [ + {"key": "arabic_first", "ar": "عربي أولاً (مو ترجمة)", "en": "Arabic-First (not translation)", + "dealix": True, "zoho": False, "salesforce": False, "hubspot": False, "pipedrive": False}, + {"key": "whatsapp_built_in", "ar": "واتساب مدمج بالنظام", "en": "Built-in WhatsApp", + "dealix": True, "zoho": False, "salesforce": False, "hubspot": False, "pipedrive": False}, + {"key": "ai_arabic", "ar": "AI يفهم العربي والسعودي", "en": "Arabic-Aware AI", + "dealix": True, "zoho": False, "salesforce": False, "hubspot": False, "pipedrive": False}, + {"key": "pdpl_native", "ar": "PDPL مدمج", "en": "Built-in PDPL", + "dealix": True, "zoho": False, "salesforce": False, "hubspot": False, "pipedrive": False}, + {"key": "deal_exchange", "ar": "صفقات استراتيجية وتبادل", "en": "Strategic Deal Exchange", + "dealix": True, "zoho": False, "salesforce": False, "hubspot": False, "pipedrive": False}, + {"key": "lead_scoring", "ar": "تقييم عملاء ذكي", "en": "AI Lead Scoring", + "dealix": True, "zoho": True, "salesforce": True, "hubspot": True, "pipedrive": True}, + {"key": "pipeline", "ar": "مسار صفقات بصري", "en": "Visual Pipeline", + "dealix": True, "zoho": True, "salesforce": True, "hubspot": True, "pipedrive": True}, + {"key": "cpq", "ar": "عروض أسعار", "en": "Quotes (CPQ)", + "dealix": True, "zoho": True, "salesforce": True, "hubspot": False, "pipedrive": False}, + ] + return {"features": features, "competitors": list(COMPETITORS.keys())} + + @staticmethod + def get_total_scores() -> dict[str, int]: + """Total score per competitor (out of 120).""" + return { + key: sum(comp["scores"].values()) + for key, comp in COMPETITORS.items() + } + + @staticmethod + def get_why_dealix_wins(language: str = "ar") -> list[str]: + """Top reasons Dealix wins.""" + reasons = { + "ar": [ + "الوحيد المصمم من الأساس للسوق السعودي", + "واتساب مدمج — مو إضافة من طرف ثالث", + "ذكاء اصطناعي يفهم اللهجة السعودية", + "حماية بيانات PDPL مدمجة بالنظام", + "نظام صفقات استراتيجية — لا يوجد عند أي منافس", + "سعر يبدأ من ٥٩ ر.س — أرخص ١٠ مرات من Salesforce", + "ثنائي اللغة (عربي/إنجليزي) بتبديل فوري", + ], + "en": [ + "Only CRM built from scratch for the Saudi market", + "Built-in WhatsApp — not a third-party add-on", + "AI that understands Saudi Arabic dialect", + "PDPL data protection built into the core", + "Strategic Deal Exchange — no competitor has this", + "Starting at 59 SAR — 10x cheaper than Salesforce", + "Bilingual (Arabic/English) with instant switching", + ], + } + return reasons.get(language, reasons["ar"]) + + @staticmethod + def get_comparison_summary(competitor: str, language: str = "ar") -> str: + """Summary comparing Dealix vs a specific competitor.""" + comp = COMPETITORS.get(competitor.lower()) + dealix = COMPETITORS["dealix"] + if not comp: + return "المنافس غير موجود" if language == "ar" else "Competitor not found" + + dealix_total = sum(dealix["scores"].values()) + comp_total = sum(comp["scores"].values()) + diff = dealix_total - comp_total + + if language == "ar": + return ( + f"مقارنة Dealix مع {comp['name_ar']}:\n\n" + f"النتيجة الإجمالية:\n" + f"• Dealix: {dealix_total}/120\n" + f"• {comp['name_ar']}: {comp_total}/120\n\n" + f"Dealix يتفوق بـ {diff} نقطة.\n\n" + f"أهم نقاط التفوق:\n" + + "\n".join( + f"• {DIMENSION_LABELS[dim]['ar']}: Dealix {dealix['scores'][dim]} vs {comp['scores'][dim]}" + for dim in dealix["scores"] + if dealix["scores"][dim] > comp["scores"].get(dim, 0) + 2 + ) + ) + return ( + f"Dealix vs {comp['name']}:\n\n" + f"Total Score:\n• Dealix: {dealix_total}/120\n• {comp['name']}: {comp_total}/120\n\n" + f"Dealix leads by {diff} points." + ) + + +comparison_engine = ComparisonEngine() diff --git a/salesflow-saas/backend/app/services/whatsapp_brain.py b/salesflow-saas/backend/app/services/whatsapp_brain.py new file mode 100644 index 00000000..d11cb6c4 --- /dev/null +++ b/salesflow-saas/backend/app/services/whatsapp_brain.py @@ -0,0 +1,342 @@ +""" +WhatsApp AI Brain — Dealix AI Revenue OS +Central intelligence for the Dealix WhatsApp number. +Handles: sales, support, marketer support, deals, and general inquiries. +Connected to backend data for contextual, intelligent responses. +""" +import logging +import re +from datetime import datetime, timezone +from enum import Enum +from typing import Any, Optional + +from pydantic import BaseModel, Field + +logger = logging.getLogger(__name__) + + +class ConversationMode(str, Enum): + SALES = "sales" + SUPPORT = "support" + MARKETER = "marketer" + DEALS = "deals" + GENERAL = "general" + + +class CallerProfile(BaseModel): + phone: str + name: str = "زائر" + caller_type: str = "unknown" # client, marketer, lead, unknown + tenant_id: str = "" + subscription_plan: str = "" + commission_balance: float = 0.0 + lead_score: int = 0 + language: str = "ar" + + +class ConversationEntry(BaseModel): + role: str # user, assistant + content: str + timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) + + +ARABIC_MARKERS = ["وش", "كيف", "أبي", "ليش", "هلا", "مرحبا", "السلام", "شكرا", "طيب"] +INTENT_KEYWORDS = { + "greeting": ["هلا", "مرحبا", "السلام عليكم", "أهلاً", "hi", "hello", "hey"], + "pricing": ["سعر", "كم", "باقة", "اشتراك", "price", "cost", "plan", "pricing"], + "demo": ["عرض", "demo", "تجربة", "شرح", "وريني"], + "support": ["مشكلة", "ما يشتغل", "خطأ", "bug", "help", "مساعدة", "دعم"], + "complaint": ["شكوى", "زعلان", "سيء", "complaint", "unhappy"], + "partnership": ["شراكة", "partner", "تعاون", "صفقة", "deal"], + "commission": ["عمولة", "commission", "أرباح", "دفعة", "payout"], + "feature": ["ميزة", "feature", "يقدر", "يدعم", "فيه"], + "competitor": ["zoho", "salesforce", "hubspot", "pipedrive", "منافس"], + "cancel": ["إلغاء", "cancel", "أوقف", "stop"], +} + + +class WhatsAppBrain: + """Central brain for Dealix WhatsApp — routes and responds intelligently.""" + + def __init__(self): + self._conversations: dict[str, list[ConversationEntry]] = {} + from app.services.whatsapp_knowledge import DealixKnowledge + self.knowledge = DealixKnowledge + + async def handle_incoming( + self, phone: str, message: str, db: Any = None + ) -> str: + caller = await self.identify_caller(phone, db) + language = self._detect_language(message) + caller.language = language + intent = self._detect_intent(message) + history = self._get_history(phone) + mode = self._route_conversation(intent, caller) + + self._add_to_history(phone, "user", message) + + handlers = { + ConversationMode.SALES: self._handle_sales, + ConversationMode.SUPPORT: self._handle_support, + ConversationMode.MARKETER: self._handle_marketer, + ConversationMode.DEALS: self._handle_deals, + ConversationMode.GENERAL: self._handle_general, + } + handler = handlers.get(mode, self._handle_general) + response = await handler(message, caller, intent, history, db) + + self._add_to_history(phone, "assistant", response) + logger.info( + f"[WhatsAppBrain] {phone} mode={mode.value} intent={intent} " + f"caller={caller.caller_type} lang={language}" + ) + return response + + async def identify_caller(self, phone: str, db: Any = None) -> CallerProfile: + profile = CallerProfile(phone=phone) + if not db: + return profile + try: + from sqlalchemy import select, or_ + from app.models.lead import Lead + from app.models.user import User + from app.models.affiliate import AffiliateMarketer + + clean_phone = phone.replace("+", "").replace(" ", "") + + # Check if affiliate marketer + result = await db.execute( + select(AffiliateMarketer).where( + AffiliateMarketer.phone.contains(clean_phone[-9:]) + ).limit(1) + ) + marketer = result.scalar_one_or_none() + if marketer: + profile.caller_type = "marketer" + profile.name = marketer.full_name or "مسوّق" + profile.tenant_id = str(marketer.tenant_id) if hasattr(marketer, 'tenant_id') else "" + return profile + + # Check if existing user/client + result = await db.execute( + select(User).where(User.phone.contains(clean_phone[-9:])).limit(1) + ) + user = result.scalar_one_or_none() + if user: + profile.caller_type = "client" + profile.name = user.full_name or "عميل" + profile.tenant_id = str(user.tenant_id) if hasattr(user, 'tenant_id') else "" + return profile + + # Check if known lead + result = await db.execute( + select(Lead).where(Lead.phone.contains(clean_phone[-9:])).limit(1) + ) + lead = result.scalar_one_or_none() + if lead: + profile.caller_type = "lead" + profile.name = lead.name or "عميل محتمل" + profile.lead_score = lead.score or 0 + profile.tenant_id = str(lead.tenant_id) if hasattr(lead, 'tenant_id') else "" + return profile + + except Exception as e: + logger.warning(f"Error identifying caller {phone}: {e}") + + return profile + + def _detect_language(self, message: str) -> str: + arabic_chars = len(re.findall(r'[\u0600-\u06FF]', message)) + latin_chars = len(re.findall(r'[a-zA-Z]', message)) + return "ar" if arabic_chars >= latin_chars else "en" + + def _detect_intent(self, message: str) -> str: + msg_lower = message.lower() + for intent, keywords in INTENT_KEYWORDS.items(): + if any(kw in msg_lower for kw in keywords): + return intent + return "general" + + def _route_conversation(self, intent: str, caller: CallerProfile) -> ConversationMode: + if caller.caller_type == "marketer" or intent == "commission": + return ConversationMode.MARKETER + if caller.caller_type == "client" and intent in ("support", "complaint", "cancel"): + return ConversationMode.SUPPORT + if intent in ("partnership",): + return ConversationMode.DEALS + if intent in ("pricing", "demo", "feature", "competitor"): + return ConversationMode.SALES + if caller.caller_type == "client": + return ConversationMode.SUPPORT + return ConversationMode.SALES if caller.caller_type == "unknown" else ConversationMode.GENERAL + + def _get_history(self, phone: str) -> list[ConversationEntry]: + return self._conversations.get(phone, [])[-10:] + + def _add_to_history(self, phone: str, role: str, content: str) -> None: + if phone not in self._conversations: + self._conversations[phone] = [] + self._conversations[phone].append(ConversationEntry(role=role, content=content)) + if len(self._conversations[phone]) > 50: + self._conversations[phone] = self._conversations[phone][-50:] + + async def _handle_sales( + self, message: str, caller: CallerProfile, intent: str, history: list, db: Any + ) -> str: + lang = caller.language + + if intent == "greeting": + name_part = f" {caller.name}" if caller.name != "زائر" else "" + if lang == "ar": + return ( + f"أهلاً وسهلاً{name_part}! 👋\n" + f"أنا مساعد ديلكس الذكي.\n\n" + f"أقدر أساعدك في:\n" + f"• معرفة مميزات Dealix\n" + f"• الأسعار والباقات\n" + f"• حجز عرض توضيحي\n" + f"• أي سؤال ثاني\n\n" + f"كيف أقدر أساعدك؟" + ) + return ( + f"Hello{name_part}! 👋\n" + f"I'm the Dealix AI assistant.\n\n" + f"I can help with:\n" + f"• Dealix features\n• Pricing\n• Book a demo\n\nHow can I help?" + ) + + if intent == "pricing": + pricing_text = self.knowledge.get_pricing_text(lang) + suffix = "\nكل الباقات فيها تجربة مجانية ١٤ يوم بدون بطاقة.\nتبي تجرب؟" if lang == "ar" else "\nAll plans include a 14-day free trial. Want to try?" + return f"{pricing_text}\n{suffix}" + + if intent == "demo": + if lang == "ar": + return ( + "ممتاز! يسعدنا نعرض لك Dealix 🎉\n\n" + "العرض يستغرق ١٥ دقيقة فقط.\n" + "أرسل لي اسمك ورقم جوالك وأرتب لك الموعد." + ) + return "Great! We'd love to show you Dealix 🎉\nThe demo takes just 15 minutes.\nSend your name and phone, and I'll set it up." + + if intent == "competitor": + for comp in ["zoho", "salesforce", "hubspot"]: + if comp in message.lower(): + resp = self.knowledge.get_competitor_response(comp) + if resp: + return resp + if lang == "ar": + return "Dealix الوحيد المصمم للسوق السعودي: عربي أولاً، واتساب مدمج، AI يفهم سعودي. تبي أوريك المقارنة؟" + return "Dealix is the only CRM built for Saudi: Arabic-first, WhatsApp native, Saudi-aware AI. Want to see the comparison?" + + if intent == "feature": + for key, feat in self.knowledge.FEATURES.items(): + if any(word in message for word in feat["name_ar"].split()): + points = "\n".join(f"✅ {p}" for p in feat["selling_points_ar"]) + return f"*{feat['name_ar']}*\n{feat['desc_ar']}\n\n{points}" + + # Check objections + for obj_type, obj_data in self.knowledge.OBJECTION_RESPONSES.items(): + triggers = {"expensive": ["غالي", "مكلف"], "need_to_think": ["أفكر", "بشوف"], "too_complex": ["صعب", "معقد"], "small_team": ["صغير", "وحدي"]} + if obj_type in triggers and any(t in message for t in triggers[obj_type]): + return obj_data.get(lang, obj_data["ar"]) + + # FAQ search + faq_answer = self.knowledge.search_faq(message) + if faq_answer: + return faq_answer + + if lang == "ar": + return "شكراً لتواصلك! 🙏\nأقدر أساعدك بأي سؤال عن Dealix — الأسعار، المميزات، أو حجز عرض توضيحي.\nوش تحب تعرف؟" + return "Thanks for reaching out! 🙏\nI can help with pricing, features, or booking a demo.\nWhat would you like to know?" + + async def _handle_support( + self, message: str, caller: CallerProfile, intent: str, history: list, db: Any + ) -> str: + name = caller.name or "عميل" + if intent == "complaint": + return ( + f"أستاذ/ة {name}، نعتذر عن أي إزعاج 🙏\n" + f"فريق الدعم المتخصص بيتواصل معك خلال ساعة.\n" + f"لو تقدر توصف المشكلة بالتفصيل، بيساعدنا نحلها أسرع." + ) + if intent == "cancel": + return ( + f"أستاذ/ة {name}، نأسف إنك تفكر بالإلغاء 😔\n" + f"قبل ما نلغي، ممكن أعرف السبب؟ يمكن نقدر نساعدك.\n" + f"لو تبي، أقدر أحولك لمدير حسابك مباشرة." + ) + return ( + f"أهلاً {name}! 👋\n" + f"كيف أقدر أساعدك اليوم؟\n\n" + f"لو عندك مشكلة تقنية، وصّف لي المشكلة وبأساعدك فوراً.\n" + f"لو تحتاج شي ما أقدر أحله، بأحولك لفريق الدعم المتخصص." + ) + + async def _handle_marketer( + self, message: str, caller: CallerProfile, intent: str, history: list, db: Any + ) -> str: + name = caller.name or "مسوّق" + if intent == "commission": + return ( + f"أهلاً {name}! 🌟\n\n" + f"للاطلاع على عمولاتك وأدائك، ادخل لوحة التحكم من:\n" + f"dealix.sa/dashboard\n\n" + f"لو عندك سؤال عن العمولات أو المدفوعات، أنا هنا أساعدك." + ) + + # Search marketer FAQ + for faq in self.knowledge.MARKETER_FAQ: + if any(word in message for word in faq["q_ar"].split() if len(word) > 2): + return faq["a_ar"] + + return ( + f"أهلاً {name}! مسوّقنا المميز 🌟\n\n" + f"كيف أقدر أساعدك اليوم؟\n" + f"• استفسار عن العمولات\n" + f"• مساعدة تقنية\n" + f"• نصائح للتسويق\n" + f"• أي سؤال ثاني" + ) + + async def _handle_deals( + self, message: str, caller: CallerProfile, intent: str, history: list, db: Any + ) -> str: + return ( + "أهلاً! 🤝\n\n" + "Dealix يدعم ١٥ نوع صفقة استراتيجية:\n" + "• شراكات وتبادل خدمات\n" + "• توزيع وreseller\n" + "• مشاريع مشتركة\n" + "• فرص استحواذ\n\n" + "حدثني أكثر عن شركتك ووش تبحث عنه، وبأساعدك نلقى أفضل فرصة." + ) + + async def _handle_general( + self, message: str, caller: CallerProfile, intent: str, history: list, db: Any + ) -> str: + faq_answer = self.knowledge.search_faq(message) + if faq_answer: + return faq_answer + lang = caller.language + if lang == "ar": + return ( + "أهلاً وسهلاً! 👋\n" + "أنا مساعد ديلكس — نظام المبيعات الذكي للسعودية.\n\n" + "أقدر أساعدك في:\n" + "١. معرفة مميزات Dealix\n" + "٢. الأسعار والباقات\n" + "٣. حجز عرض توضيحي\n" + "٤. الدعم الفني\n" + "٥. برنامج التسويق بالعمولة\n\n" + "أختر رقم أو اكتب سؤالك مباشرة." + ) + return ( + "Hello! 👋\nI'm the Dealix assistant — the smart sales system for Saudi Arabia.\n\n" + "I can help with:\n1. Features\n2. Pricing\n3. Book a demo\n4. Support\n5. Affiliate program\n\n" + "Pick a number or type your question." + ) + + +# Global singleton +whatsapp_brain = WhatsAppBrain() diff --git a/salesflow-saas/backend/app/services/whatsapp_knowledge.py b/salesflow-saas/backend/app/services/whatsapp_knowledge.py new file mode 100644 index 00000000..2fbaa986 --- /dev/null +++ b/salesflow-saas/backend/app/services/whatsapp_knowledge.py @@ -0,0 +1,203 @@ +""" +WhatsApp Knowledge Base — Dealix AI Revenue OS +Complete knowledge the WhatsApp brain uses to respond intelligently. +""" +import logging +from typing import Optional + +logger = logging.getLogger(__name__) + + +class DealixKnowledge: + """Everything the WhatsApp brain needs to know.""" + + FEATURES = { + "whatsapp_crm": { + "name_ar": "إدارة العملاء عبر الواتساب", + "name_en": "WhatsApp CRM", + "desc_ar": "تواصل مع عملاءك مباشرة من الواتساب مع تتبع كامل للمحادثات", + "selling_points_ar": [ + "رد تلقائي ذكي بالعربي", + "تتبع كل محادثة", + "إشعارات فورية عند رد العميل", + ], + }, + "ai_scoring": { + "name_ar": "تقييم عملاء بالذكاء الاصطناعي", + "name_en": "AI Lead Scoring", + "desc_ar": "النظام يقيّم كل عميل من ٠ لـ ١٠٠ ويقولك مين الأهم", + "selling_points_ar": [ + "تقييم تلقائي مع كل تفاعل", + "يفهم المحادثات العربية", + "توصيات متابعة بالعربي", + ], + }, + "pipeline": { + "name_ar": "مسار صفقات بصري", + "name_en": "Visual Pipeline", + "desc_ar": "شوف كل صفقاتك بنظرة واحدة وحركها بالسحب", + "selling_points_ar": ["Kanban بصري", "٥ مراحل", "drag-and-drop"], + }, + "cpq": { + "name_ar": "عروض أسعار احترافية", + "name_en": "Quotes & Proposals", + "desc_ar": "أنشئ عروض أسعار بالعربي مع ضريبة القيمة المضافة تلقائياً", + "selling_points_ar": ["ضريبة ١٥٪ تلقائي", "إرسال بالواتساب", "تتبع القبول"], + }, + "pdpl": { + "name_ar": "حماية البيانات PDPL", + "name_en": "PDPL Compliance", + "desc_ar": "متوافق مع نظام حماية البيانات الشخصية السعودي", + "selling_points_ar": ["موافقات تلقائية", "حقوق بيانات", "audit trail"], + }, + "deal_exchange": { + "name_ar": "صفقات استراتيجية", + "name_en": "Strategic Deals", + "desc_ar": "اكتشف شركاء وصفقات متبادلة — تبادل خدمات، شراكات، توزيع", + "selling_points_ar": ["١٥ نوع صفقة", "مطابقة ذكية", "مفاوض AI"], + }, + } + + PRICING = { + "starter": { + "name_ar": "المبتدئ", + "price": 59, + "features_ar": ["٣ مستخدمين", "٥٠٠ عميل", "واتساب أساسي", "تقارير أساسية"], + "best_for_ar": "الشركات الصغيرة والفردية", + }, + "professional": { + "name_ar": "الاحترافي", + "price": 149, + "features_ar": [ + "١٠ مستخدمين", "عملاء لا محدود", "تقييم AI", + "تسلسلات تلقائية", "عروض أسعار", "تقارير متقدمة", + ], + "best_for_ar": "الشركات المتوسطة وفرق المبيعات", + "popular": True, + }, + "enterprise": { + "name_ar": "المؤسسي", + "price": 225, + "features_ar": [ + "مستخدمين لا محدود", "وكيل مبيعات AI", "صفقات استراتيجية", + "API كامل", "دعم مخصص", + ], + "best_for_ar": "الشركات الكبيرة والمؤسسات", + }, + } + + OBJECTION_RESPONSES = { + "expensive": { + "ar": "أفهم — لكن ٥٩ ر.س أقل من فاتورة كابتشينو أسبوعية. وصفقة وحدة ضايعة بسبب عدم المتابعة تكلف أضعاف. جرّبه مجاناً ١٤ يوم وشوف بنفسك.", + "en": "I understand — but 59 SAR is less than weekly cappuccinos. One lost deal due to poor follow-up costs much more. Try it free for 14 days.", + }, + "already_have_crm": { + "ar": "ممتاز! وش تستخدم حالياً؟ كثير من عملاءنا انتقلوا من أنظمة أجنبية لأن Dealix مصمم للسوق السعودي — عربي أولاً، واتساب مدمج، PDPL جاهز.", + "en": "Great! What are you using? Many clients switched because Dealix is built for Saudi — Arabic-first, WhatsApp native, PDPL ready.", + }, + "need_to_think": { + "ar": "أكيد، خذ وقتك. بس حبيت أذكرك إن التجربة مجانية ١٤ يوم بدون بطاقة — تقدر تجرب وتقرر بعدها.", + "en": "Sure, take your time. Just remember — 14-day free trial, no credit card needed.", + }, + "too_complex": { + "ar": "بالعكس! Dealix مصمم ليكون بسيط جداً — أغلب العملاء يبدون يستخدمونه بأقل من ٥ دقائق. وعندنا دعم بالعربي يساعدك.", + "en": "Actually the opposite! Most clients start using it in under 5 minutes. And we have Arabic support.", + }, + "small_team": { + "ar": "حتى لو شخص واحد! باقة المبتدئ ٥٩ ر.س تكفي. والنظام يساعدك تتابع عملاءك بدون ما تحتاج فريق كبير.", + "en": "Even for one person! Starter plan at 59 SAR is enough. The system helps you follow up without needing a big team.", + }, + "no_budget": { + "ar": "أفهم. التجربة مجانية ١٤ يوم — جربها وشوف كم صفقة تقدر تكسب. الاستثمار يرجع لك أضعاف.", + "en": "I understand. 14-day free trial — try it and see how many deals you can win. The ROI speaks for itself.", + }, + "competitor_better": { + "ar": "كل نظام له مميزاته. لكن Dealix الوحيد المصمم للسعودية: عربي أولاً، واتساب مدمج، AI يفهم سعودي. تبي أوريك المقارنة؟", + "en": "Every system has its strengths. But Dealix is the only one built for Saudi: Arabic-first, WhatsApp native, Saudi-aware AI. Want to see the comparison?", + }, + "not_now": { + "ar": "تمام! أقدر أرسل لك ملخص سريع عن Dealix وتشوفه لما يناسبك. وش إيميلك؟", + "en": "No problem! I can send you a quick summary to review when it suits you. What's your email?", + }, + } + + COMPETITOR_CARDS = { + "zoho": { + "name": "Zoho CRM", + "we_win": [ + "عربي أولاً (مو ترجمة)", "واتساب مدمج (مو إضافة)", + "AI يفهم اللهجة السعودية", "PDPL مدمج بالنظام", + "صفقات استراتيجية (لا يوجد عندهم)", "دعم سعودي مباشر", + ], + "they_win": ["نظام أكبر وأقدم", "تكاملات أكثر", "سيرفرات سعودية"], + "response_ar": "Zoho نظام ممتاز ومعروف. لكن الفرق إن Dealix مبني من الأساس للسوق السعودي — مو ترجمة لنظام أجنبي. واتساب عندنا مدمج، الذكاء الاصطناعي يفهم عربي، وPDPL جاهز. وبسعر مقارب.", + }, + "salesforce": { + "name": "Salesforce", + "we_win": [ + "عربي بالكامل", "سعر أقل ١٠ مرات", "واتساب مدمج", + "بسيط وسريع (مو ٦ أشهر تطبيق)", "PDPL جاهز", + ], + "they_win": ["أكبر نظام CRM بالعالم", "آلاف التكاملات", "enterprise-grade"], + "response_ar": "Salesforce نظام عملاق — لكن يحتاج ٦ أشهر تطبيق ومئات الآلاف. Dealix يشتغل بأقل من ٥ دقائق، عربي بالكامل، وبسعر يبدأ من ٥٩ ر.س. للشركات السعودية الصغيرة والمتوسطة، Dealix الخيار الأذكى.", + }, + "hubspot": { + "name": "HubSpot", + "we_win": [ + "عربي أولاً", "واتساب مدمج", "AI عربي", + "سعر أقل بكثير", "PDPL مدمج", "صفقات استراتيجية", + ], + "they_win": ["marketing hub قوي", "content management", "brand أكبر"], + "response_ar": "HubSpot ممتاز للتسويق الرقمي. لكن للمبيعات في السوق السعودي، Dealix أقوى: واتساب مدمج، AI يفهم عربي، وPDPL جاهز. وبسعر أقل بكثير.", + }, + } + + FAQ = [ + {"q_ar": "كم سعر Dealix؟", "a_ar": "يبدأ من ٥٩ ر.س/شهر. الاحترافي ١٤٩ ر.س، المؤسسي ٢٢٥ ر.س. وفيه تجربة مجانية ١٤ يوم."}, + {"q_ar": "هل يدعم الواتساب؟", "a_ar": "نعم! واتساب مدمج بالنظام — ترسل وتستقبل وتتابع كل المحادثات من مكان واحد."}, + {"q_ar": "هل يدعم العربي؟", "a_ar": "نعم! Dealix مبني عربي أولاً — الواجهة والتقارير والذكاء الاصطناعي كلها بالعربي."}, + {"q_ar": "هل هو آمن؟", "a_ar": "نعم. متوافق مع نظام حماية البيانات PDPL، تشفير SSL، وسيرفرات سعودية."}, + {"q_ar": "هل فيه تجربة مجانية؟", "a_ar": "نعم! ١٤ يوم تجربة مجانية كاملة — بدون بطاقة ائتمانية."}, + {"q_ar": "كيف أبدأ؟", "a_ar": "ادخل dealix.sa واضغط 'ابدأ مجاناً'. التسجيل يأخذ أقل من دقيقة."}, + {"q_ar": "هل يناسب شركتي الصغيرة؟", "a_ar": "أكيد! باقة المبتدئ ٥٩ ر.س مصممة للشركات الصغيرة. حتى لو شخص واحد."}, + {"q_ar": "هل يدعم الإنجليزي بعد؟", "a_ar": "نعم! تقدر تبدل بين العربي والإنجليزي بضغطة زر."}, + {"q_ar": "كيف أتواصل مع الدعم؟", "a_ar": "واتساب أو إيميل support@dealix.sa — نرد خلال ٤ ساعات عمل."}, + {"q_ar": "هل فيه تطبيق جوال؟", "a_ar": "الموقع متجاوب ويشتغل بشكل ممتاز على الجوال. تطبيق مخصص قريباً إن شاء الله."}, + ] + + MARKETER_FAQ = [ + {"q_ar": "كيف أسجل كمسوّق؟", "a_ar": "ادخل dealix.sa/marketers واضغط 'سجّل كمسوّق'. التسجيل مجاني ويتفعل فوراً."}, + {"q_ar": "كم العمولة؟", "a_ar": "تبدأ من ١٠٪ (برونزي) وتوصل ٢٠٪ (ذهبي). كل ما زاد عدد العملاء زادت نسبتك."}, + {"q_ar": "متى تنزل العمولة؟", "a_ar": "كل يوم أحد تتحول العمولات لحسابك البنكي."}, + {"q_ar": "كيف أتابع أدائي؟", "a_ar": "من لوحة التحكم تشوف كل شي: عملاء، عمولات، مستواك، وروابط التتبع."}, + {"q_ar": "هل فيه حد أقصى للعمولة؟", "a_ar": "لا! ما فيه حد — كل ما زاد عدد العملاء زادت عمولتك."}, + ] + + @classmethod + def get_pricing_text(cls, language: str = "ar") -> str: + lines = [] + for key, plan in cls.PRICING.items(): + name = plan["name_ar"] if language == "ar" else key.title() + price = plan["price"] + features = " | ".join(plan["features_ar"][:3]) + popular = " ⭐" if plan.get("popular") else "" + lines.append(f"{'🟢' if key == 'starter' else '🔵' if key == 'professional' else '🟣'} {name} — {price} ر.س/شهر{popular}\n {features}") + return "\n\n".join(lines) + + @classmethod + def search_faq(cls, query: str) -> Optional[str]: + query_lower = query.lower() + for faq in cls.FAQ: + if any(word in faq["q_ar"] for word in query_lower.split() if len(word) > 2): + return faq["a_ar"] + return None + + @classmethod + def get_competitor_response(cls, competitor: str) -> Optional[str]: + card = cls.COMPETITOR_CARDS.get(competitor.lower()) + return card["response_ar"] if card else None + + @classmethod + def get_objection_response(cls, objection_type: str, language: str = "ar") -> Optional[str]: + obj = cls.OBJECTION_RESPONSES.get(objection_type) + return obj[language] if obj else None