tharunchndrn commited on
Commit
67efe6c
·
verified ·
1 Parent(s): 7cd5767

Update backend_app/flows.py

Browse files
Files changed (1) hide show
  1. backend_app/flows.py +230 -222
backend_app/flows.py CHANGED
@@ -1,222 +1,230 @@
1
- from __future__ import annotations
2
- from typing import Dict, List, Optional
3
- import re
4
- from .email_service import send_contact_email
5
-
6
- from .suggestions import (
7
- default_suggestions,
8
- suggestions_for_intent,
9
- suggestions_from_text,
10
- )
11
-
12
- class FlowManager:
13
- """
14
- Manages lightweight session state for:
15
- - Contact flow (collect message + email)
16
- - Language flow (choose language/region)
17
- """
18
-
19
- def __init__(self):
20
- # session_id -> state
21
- self.sessions: Dict[str, Dict] = {}
22
-
23
- # ---------- Suggestions ----------
24
- def default_suggestions(self) -> List[str]:
25
- return default_suggestions()
26
-
27
- # ---------- Session helpers ----------
28
- def _get(self, session_id: str) -> Dict:
29
- if session_id not in self.sessions:
30
- self.sessions[session_id] = {
31
- "mode": "normal", # normal | contact_wait_msg | contact_wait_email | lang_wait
32
- "contact_msg": None,
33
- "lang": None, # e.g. "Sinhala", "Tamil", "English"
34
- }
35
- return self.sessions[session_id]
36
-
37
- # ---------- Intents ----------
38
- def _detect_intents(self, text: str) -> List[str]:
39
- t = text.lower()
40
-
41
- intents = []
42
- if any(k in t for k in ["contact", "support", "help desk", "reach", "email us", "contact us"]):
43
- intents.append("contact")
44
- if any(k in t for k in ["language", "sinhala", "tamil", "english", "change language", "translate"]):
45
- intents.append("language")
46
- if any(k in t for k in ["service", "services", "what do you do", "features", "what is syslink", "about"]):
47
- intents.append("services")
48
-
49
- return intents or ["rag"]
50
-
51
- # ---------- Main entry ----------
52
- def handle_message(self, session_id: str, user_message: str) -> Dict:
53
- """
54
- Returns dict:
55
- {
56
- "action": "flow" | "rag",
57
- "answer": "...",
58
- "suggestions": [...]
59
- "lang": optional preferred language for RAG
60
- }
61
- """
62
- state = self._get(session_id)
63
- msg = user_message.strip()
64
-
65
- # 1) If we're in the middle of a flow, handle it first
66
- if state["mode"].startswith("contact_"):
67
- return self._handle_contact_flow(state, msg)
68
-
69
- if state["mode"] == "lang_wait":
70
- return self._handle_language_flow(state, msg)
71
-
72
- # 2) Not in a flow: detect intent(s)
73
- intents = self._detect_intents(msg)
74
-
75
- # If user typed custom prompt, we replace suggestions with new related ones
76
- dynamic_suggestions = suggestions_from_text(msg)
77
-
78
- # 3) Multi-intent handling (2+ in one message)
79
- # We'll handle flow intents first, then allow RAG for remaining.
80
- if "contact" in intents and "language" in intents:
81
- # Ask language first (quick), then contact
82
- state["mode"] = "lang_wait"
83
- return {
84
- "action": "flow",
85
- "answer": "Sure. Which language would you like (Sinhala / Tamil / English)?",
86
- "suggestions": suggestions_for_intent("language"),
87
- "lang": state.get("lang"),
88
- }
89
-
90
- if "language" in intents:
91
- state["mode"] = "lang_wait"
92
- return {
93
- "action": "flow",
94
- "answer": "Sure. Which language would you like (Sinhala / Tamil / English)?",
95
- "suggestions": suggestions_for_intent("language"),
96
- "lang": state.get("lang"),
97
- }
98
-
99
- if "contact" in intents:
100
- state["mode"] = "contact_wait_msg"
101
- return {
102
- "action": "flow",
103
- "answer": "Sure — please type your message for our team.",
104
- "suggestions": suggestions_for_intent("contact"),
105
- "lang": state.get("lang"),
106
- }
107
-
108
- if "services" in intents:
109
- # Let RAG answer, but provide service-related suggestions
110
- return {
111
- "action": "rag",
112
- "answer": "",
113
- "suggestions": suggestions_for_intent("services"),
114
- "lang": state.get("lang"),
115
- }
116
-
117
- # 4) Default: RAG
118
- return {
119
- "action": "rag",
120
- "answer": "",
121
- "suggestions": dynamic_suggestions,
122
- "lang": state.get("lang"),
123
- }
124
-
125
- # ---------- Contact flow ----------
126
- def _handle_contact_flow(self, state: Dict, msg: str) -> Dict:
127
- if state["mode"] == "contact_wait_msg":
128
- state["contact_msg"] = msg
129
- state["mode"] = "contact_wait_email"
130
- return {
131
- "action": "flow",
132
- "answer": "Thanks. Now please enter your email address.",
133
- "suggestions": [],
134
- "lang": state.get("lang"),
135
- }
136
-
137
- if state["mode"] == "contact_wait_email":
138
- if not self._is_valid_email(msg):
139
- return {
140
- "action": "flow",
141
- "answer": "That email doesn’t look valid. Please type a valid email (example: name@gmail.com).",
142
- "suggestions": [],
143
- "lang": state.get("lang"),
144
- }
145
-
146
- # Send email (free SMTP). If not configured, we still store and confirm.
147
- email = msg
148
- message = state.get("contact_msg") or ""
149
-
150
- result = send_contact_email(user_email=email, user_message=message)
151
-
152
- # Reset flow state
153
- state["mode"] = "normal"
154
- state["contact_msg"] = None
155
-
156
- if result["ok"]:
157
- return {
158
- "action": "flow",
159
- "answer": "✅ Sent! Thanks — our team will contact you soon.",
160
- "suggestions": default_suggestions(),
161
- "lang": state.get("lang"),
162
- }
163
-
164
- return {
165
- "action": "flow",
166
- "answer": (
167
- " I saved your message, but email sending isn’t configured yet on the server.\n"
168
- "Our team can still contact you using the details you provided."
169
- ),
170
- "suggestions": default_suggestions(),
171
- "lang": state.get("lang"),
172
- }
173
-
174
- # fallback
175
- state["mode"] = "normal"
176
- return {"action": "rag", "answer": "", "suggestions": default_suggestions(), "lang": state.get("lang")}
177
-
178
- def submit_contact(self, session_id: str, email: str, message: str) -> Dict:
179
- """
180
- Optional endpoint use.
181
- """
182
- state = self._get(session_id)
183
- result = send_contact_email(user_email=email, user_message=message)
184
- if result["ok"]:
185
- return {"ok": True, "message": "Sent"}
186
- return {"ok": False, "message": "Not configured"}
187
-
188
- def _is_valid_email(self, s: str) -> bool:
189
- return bool(re.match(r"^[^@\s]+@[^@\s]+\.[^@\s]+$", s.strip()))
190
-
191
- # ---------- Language flow ----------
192
- def _handle_language_flow(self, state: Dict, msg: str) -> Dict:
193
- t = msg.strip().lower()
194
-
195
- # Accept direct language choice
196
- if "sinhala" in t or t in ["si", "sinhala", "sin"]:
197
- state["lang"] = "Sinhala"
198
- elif "tamil" in t or t in ["ta", "tamil"]:
199
- state["lang"] = "Tamil"
200
- elif "english" in t or t in ["en", "english"]:
201
- state["lang"] = "English"
202
- else:
203
- # Accept region words -> map quickly
204
- # (You can expand this later)
205
- if any(k in t for k in ["sri lanka", "colombo", "kandy", "galle", "jaffna"]):
206
- state["lang"] = "Sinhala"
207
- else:
208
- return {
209
- "action": "flow",
210
- "answer": "Please type the language you want: Sinhala / Tamil / English.",
211
- "suggestions": suggestions_for_intent("language"),
212
- "lang": state.get("lang"),
213
- }
214
-
215
- # Finish language flow
216
- state["mode"] = "normal"
217
- return {
218
- "action": "flow",
219
- "answer": f"✅ Done. I’ll reply in {state['lang']} from now on.",
220
- "suggestions": default_suggestions(),
221
- "lang": state.get("lang"),
222
- }
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict, List
4
+ import re
5
+
6
+ from .email_service import send_contact_email
7
+ from .suggestions import (
8
+ default_suggestions,
9
+ suggestions_for_intent,
10
+ suggestions_from_text,
11
+ )
12
+
13
+
14
+ class FlowManager:
15
+ """
16
+ Manages lightweight session state for:
17
+ - Contact flow (collect message + email)
18
+ - Language flow (ask region -> ask language)
19
+ """
20
+
21
+ def __init__(self):
22
+ self.sessions: Dict[str, Dict] = {}
23
+
24
+ # ---------- Suggestions ----------
25
+ def default_suggestions(self) -> List[str]:
26
+ return default_suggestions()
27
+
28
+ # ---------- Session helpers ----------
29
+ def _get(self, session_id: str) -> Dict:
30
+ if session_id not in self.sessions:
31
+ self.sessions[session_id] = {
32
+ "mode": "normal", # normal | contact_wait_msg | contact_wait_email | lang_wait_region | lang_wait_language
33
+ "contact_msg": None,
34
+ "lang": None, # preferred language (string), e.g. "Sinhala"
35
+ "region": None, # store region (string) if provided
36
+ }
37
+ return self.sessions[session_id]
38
+
39
+ # ---------- Intents ----------
40
+ def _detect_intents(self, text: str) -> List[str]:
41
+ t = (text or "").lower()
42
+
43
+ intents: List[str] = []
44
+ if any(k in t for k in ["contact", "support", "help desk", "reach", "email us", "contact us"]):
45
+ intents.append("contact")
46
+ if any(k in t for k in ["change response language", "change language", "language", "switch language", "translate"]):
47
+ intents.append("language")
48
+ if any(k in t for k in ["service", "services", "what do you do", "features", "what is syslink", "about"]):
49
+ intents.append("services")
50
+
51
+ return intents or ["rag"]
52
+
53
+ # ---------- Main entry ----------
54
+ def handle_message(self, session_id: str, user_message: str) -> Dict:
55
+ """
56
+ Returns dict:
57
+ {
58
+ "action": "flow" | "rag",
59
+ "answer": "...",
60
+ "suggestions": [...],
61
+ "lang": optional preferred language for RAG
62
+ }
63
+ """
64
+ state = self._get(session_id)
65
+ msg = (user_message or "").strip()
66
+
67
+ # 1) In-flow handling first
68
+ if state["mode"].startswith("contact_"):
69
+ return self._handle_contact_flow(state, msg)
70
+
71
+ if state["mode"].startswith("lang_"):
72
+ return self._handle_language_flow(state, msg)
73
+
74
+ # 2) Detect intent(s)
75
+ intents = self._detect_intents(msg)
76
+
77
+ # If user typed a custom prompt, provide new related suggestions
78
+ dynamic_suggestions = suggestions_from_text(msg)
79
+
80
+ # 3) Multi-intent handling
81
+ # If user asks "contact + language" together, do language first, then contact
82
+ if "contact" in intents and "language" in intents:
83
+ state["mode"] = "lang_wait_region"
84
+ state["next_after_lang"] = "contact"
85
+ return {
86
+ "action": "flow",
87
+ "answer": "Sure — first, tell me your region/country.",
88
+ "suggestions": [],
89
+ "lang": state.get("lang"),
90
+ }
91
+
92
+ # 4) Single intent handling
93
+ if "language" in intents:
94
+ state["mode"] = "lang_wait_region"
95
+ state["next_after_lang"] = None
96
+ return {
97
+ "action": "flow",
98
+ "answer": "Sure — tell me your region/country.",
99
+ "suggestions": [],
100
+ "lang": state.get("lang"),
101
+ }
102
+
103
+ if "contact" in intents:
104
+ state["mode"] = "contact_wait_msg"
105
+ return {
106
+ "action": "flow",
107
+ "answer": "Sure — please type your message for our team.",
108
+ "suggestions": suggestions_for_intent("contact"),
109
+ "lang": state.get("lang"),
110
+ }
111
+
112
+ if "services" in intents:
113
+ return {
114
+ "action": "rag",
115
+ "answer": "",
116
+ "suggestions": suggestions_for_intent("services"),
117
+ "lang": state.get("lang"),
118
+ }
119
+
120
+ # 5) Default: RAG
121
+ return {
122
+ "action": "rag",
123
+ "answer": "",
124
+ "suggestions": dynamic_suggestions,
125
+ "lang": state.get("lang"),
126
+ }
127
+
128
+ # ---------- Contact flow ----------
129
+ def _handle_contact_flow(self, state: Dict, msg: str) -> Dict:
130
+ if state["mode"] == "contact_wait_msg":
131
+ state["contact_msg"] = msg
132
+ state["mode"] = "contact_wait_email"
133
+ return {
134
+ "action": "flow",
135
+ "answer": "Thanks. Now please enter your email address.",
136
+ "suggestions": [],
137
+ "lang": state.get("lang"),
138
+ }
139
+
140
+ if state["mode"] == "contact_wait_email":
141
+ if not self._is_valid_email(msg):
142
+ return {
143
+ "action": "flow",
144
+ "answer": "That email doesn’t look valid. Please type a valid email (example: name@gmail.com).",
145
+ "suggestions": [],
146
+ "lang": state.get("lang"),
147
+ }
148
+
149
+ email = msg
150
+ message = state.get("contact_msg") or ""
151
+
152
+ result = send_contact_email(user_email=email, user_message=message)
153
+
154
+ # Reset flow state
155
+ state["mode"] = "normal"
156
+ state["contact_msg"] = None
157
+
158
+ if result.get("ok"):
159
+ return {
160
+ "action": "flow",
161
+ "answer": "✅ Sent! Thanks — our team will contact you soon.",
162
+ "suggestions": default_suggestions(),
163
+ "lang": state.get("lang"),
164
+ }
165
+
166
+ return {
167
+ "action": "flow",
168
+ "answer": (
169
+ "✅ I saved your message, but email sending isn’t configured yet on the server.\n"
170
+ "Our team can still contact you using the details you provided."
171
+ ),
172
+ "suggestions": default_suggestions(),
173
+ "lang": state.get("lang"),
174
+ }
175
+
176
+ # fallback
177
+ state["mode"] = "normal"
178
+ return {"action": "rag", "answer": "", "suggestions": default_suggestions(), "lang": state.get("lang")}
179
+
180
+ def _is_valid_email(self, s: str) -> bool:
181
+ return bool(re.match(r"^[^@\s]+@[^@\s]+\.[^@\s]+$", (s or "").strip()))
182
+
183
+ # ---------- Language flow (NO mapping) ----------
184
+ def _handle_language_flow(self, state: Dict, msg: str) -> Dict:
185
+ # Step 1: ask region
186
+ if state["mode"] == "lang_wait_region":
187
+ state["region"] = msg
188
+ state["mode"] = "lang_wait_language"
189
+ return {
190
+ "action": "flow",
191
+ "answer": "Thanks. What language would you like me to respond in? (Type it, e.g., Sinhala / Tamil / English)",
192
+ "suggestions": ["Sinhala", "Tamil", "English"],
193
+ "lang": state.get("lang"),
194
+ }
195
+
196
+ # Step 2: set language
197
+ if state["mode"] == "lang_wait_language":
198
+ chosen = (msg or "").strip()
199
+ if not chosen:
200
+ return {
201
+ "action": "flow",
202
+ "answer": "Please type the language you want (example: Sinhala / Tamil / English).",
203
+ "suggestions": ["Sinhala", "Tamil", "English"],
204
+ "lang": state.get("lang"),
205
+ }
206
+
207
+ state["lang"] = chosen
208
+ state["mode"] = "normal"
209
+
210
+ # If user wanted contact after language, jump into contact flow
211
+ next_after = state.pop("next_after_lang", None)
212
+ if next_after == "contact":
213
+ state["mode"] = "contact_wait_msg"
214
+ return {
215
+ "action": "flow",
216
+ "answer": f"✅ Done. I’ll reply in {state['lang']} from now on.\nNow, please type your message for our team.",
217
+ "suggestions": [],
218
+ "lang": state.get("lang"),
219
+ }
220
+
221
+ return {
222
+ "action": "flow",
223
+ "answer": f"✅ Done. I’ll reply in {state['lang']} from now on.",
224
+ "suggestions": default_suggestions(),
225
+ "lang": state.get("lang"),
226
+ }
227
+
228
+ # fallback
229
+ state["mode"] = "normal"
230
+ return {"action": "rag", "answer": "", "suggestions": default_suggestions(), "lang": state.get("lang")}