tharunchndrn's picture
Update backend_app/flows.py
67efe6c verified
from __future__ import annotations
from typing import Dict, List
import re
from .email_service import send_contact_email
from .suggestions import (
default_suggestions,
suggestions_for_intent,
suggestions_from_text,
)
class FlowManager:
"""
Manages lightweight session state for:
- Contact flow (collect message + email)
- Language flow (ask region -> ask language)
"""
def __init__(self):
self.sessions: Dict[str, Dict] = {}
# ---------- Suggestions ----------
def default_suggestions(self) -> List[str]:
return default_suggestions()
# ---------- Session helpers ----------
def _get(self, session_id: str) -> Dict:
if session_id not in self.sessions:
self.sessions[session_id] = {
"mode": "normal", # normal | contact_wait_msg | contact_wait_email | lang_wait_region | lang_wait_language
"contact_msg": None,
"lang": None, # preferred language (string), e.g. "Sinhala"
"region": None, # store region (string) if provided
}
return self.sessions[session_id]
# ---------- Intents ----------
def _detect_intents(self, text: str) -> List[str]:
t = (text or "").lower()
intents: List[str] = []
if any(k in t for k in ["contact", "support", "help desk", "reach", "email us", "contact us"]):
intents.append("contact")
if any(k in t for k in ["change response language", "change language", "language", "switch language", "translate"]):
intents.append("language")
if any(k in t for k in ["service", "services", "what do you do", "features", "what is syslink", "about"]):
intents.append("services")
return intents or ["rag"]
# ---------- Main entry ----------
def handle_message(self, session_id: str, user_message: str) -> Dict:
"""
Returns dict:
{
"action": "flow" | "rag",
"answer": "...",
"suggestions": [...],
"lang": optional preferred language for RAG
}
"""
state = self._get(session_id)
msg = (user_message or "").strip()
# 1) In-flow handling first
if state["mode"].startswith("contact_"):
return self._handle_contact_flow(state, msg)
if state["mode"].startswith("lang_"):
return self._handle_language_flow(state, msg)
# 2) Detect intent(s)
intents = self._detect_intents(msg)
# If user typed a custom prompt, provide new related suggestions
dynamic_suggestions = suggestions_from_text(msg)
# 3) Multi-intent handling
# If user asks "contact + language" together, do language first, then contact
if "contact" in intents and "language" in intents:
state["mode"] = "lang_wait_region"
state["next_after_lang"] = "contact"
return {
"action": "flow",
"answer": "Sure — first, tell me your region/country.",
"suggestions": [],
"lang": state.get("lang"),
}
# 4) Single intent handling
if "language" in intents:
state["mode"] = "lang_wait_region"
state["next_after_lang"] = None
return {
"action": "flow",
"answer": "Sure — tell me your region/country.",
"suggestions": [],
"lang": state.get("lang"),
}
if "contact" in intents:
state["mode"] = "contact_wait_msg"
return {
"action": "flow",
"answer": "Sure — please type your message for our team.",
"suggestions": suggestions_for_intent("contact"),
"lang": state.get("lang"),
}
if "services" in intents:
return {
"action": "rag",
"answer": "",
"suggestions": suggestions_for_intent("services"),
"lang": state.get("lang"),
}
# 5) Default: RAG
return {
"action": "rag",
"answer": "",
"suggestions": dynamic_suggestions,
"lang": state.get("lang"),
}
# ---------- Contact flow ----------
def _handle_contact_flow(self, state: Dict, msg: str) -> Dict:
if state["mode"] == "contact_wait_msg":
state["contact_msg"] = msg
state["mode"] = "contact_wait_email"
return {
"action": "flow",
"answer": "Thanks. Now please enter your email address.",
"suggestions": [],
"lang": state.get("lang"),
}
if state["mode"] == "contact_wait_email":
if not self._is_valid_email(msg):
return {
"action": "flow",
"answer": "That email doesn’t look valid. Please type a valid email (example: name@gmail.com).",
"suggestions": [],
"lang": state.get("lang"),
}
email = msg
message = state.get("contact_msg") or ""
result = send_contact_email(user_email=email, user_message=message)
# Reset flow state
state["mode"] = "normal"
state["contact_msg"] = None
if result.get("ok"):
return {
"action": "flow",
"answer": "✅ Sent! Thanks — our team will contact you soon.",
"suggestions": default_suggestions(),
"lang": state.get("lang"),
}
return {
"action": "flow",
"answer": (
"✅ I saved your message, but email sending isn’t configured yet on the server.\n"
"Our team can still contact you using the details you provided."
),
"suggestions": default_suggestions(),
"lang": state.get("lang"),
}
# fallback
state["mode"] = "normal"
return {"action": "rag", "answer": "", "suggestions": default_suggestions(), "lang": state.get("lang")}
def _is_valid_email(self, s: str) -> bool:
return bool(re.match(r"^[^@\s]+@[^@\s]+\.[^@\s]+$", (s or "").strip()))
# ---------- Language flow (NO mapping) ----------
def _handle_language_flow(self, state: Dict, msg: str) -> Dict:
# Step 1: ask region
if state["mode"] == "lang_wait_region":
state["region"] = msg
state["mode"] = "lang_wait_language"
return {
"action": "flow",
"answer": "Thanks. What language would you like me to respond in? (Type it, e.g., Sinhala / Tamil / English)",
"suggestions": ["Sinhala", "Tamil", "English"],
"lang": state.get("lang"),
}
# Step 2: set language
if state["mode"] == "lang_wait_language":
chosen = (msg or "").strip()
if not chosen:
return {
"action": "flow",
"answer": "Please type the language you want (example: Sinhala / Tamil / English).",
"suggestions": ["Sinhala", "Tamil", "English"],
"lang": state.get("lang"),
}
state["lang"] = chosen
state["mode"] = "normal"
# If user wanted contact after language, jump into contact flow
next_after = state.pop("next_after_lang", None)
if next_after == "contact":
state["mode"] = "contact_wait_msg"
return {
"action": "flow",
"answer": f"✅ Done. I’ll reply in {state['lang']} from now on.\nNow, please type your message for our team.",
"suggestions": [],
"lang": state.get("lang"),
}
return {
"action": "flow",
"answer": f"✅ Done. I’ll reply in {state['lang']} from now on.",
"suggestions": default_suggestions(),
"lang": state.get("lang"),
}
# fallback
state["mode"] = "normal"
return {"action": "rag", "answer": "", "suggestions": default_suggestions(), "lang": state.get("lang")}