| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import re |
| from typing import Literal |
|
|
| from language_config import has_arabic_script |
|
|
| OpeningTier = Literal["pure_greeting", "connection_check", "greeting_with_substance", "normal"] |
|
|
| |
| SUBSTANCE_MARKERS_LATIN = { |
| "aap", "ap", "meri", "mera", "madad", "help", "kesse", "kaise", "kese", "kaisey", |
| "puchna", "poochna", "admission", "course", "problem", "issue", "sawal", "sawaal", |
| "question", "batana", "batao", "bata", "chahiye", "chahte", "chahti", "karna", |
| "how", "what", "when", "where", "why", "can", "could", "would", "need", "want", |
| "tell", "about", "regarding", "fee", "fees", "enroll", "register", |
| "mujhe", "main", "mein", "hum", "humein", "kya", "kab", "kahan", "kyun", |
| } |
|
|
| |
| SUBSTANCE_MARKERS_URDU = { |
| "آپ", "اپ", "مدد", "مجھے", "میرا", "میری", "مرا", "کیا", "کہاں", "کیوں", "کیسے", |
| "کسے", "بتائیں", "بتاؤ", "پوچھنا", "چاہیے", "ضرورت", "مسئلہ", "مسئله", "سوال", |
| "داخلہ", "کورس", "فیس", "کرنا", "کریں", "سکتے", "سکتا", "سکتی", |
| "بتانا", "رہنمائی", "معلومات", "جاننا", "جانکارى", |
| } |
|
|
| PURE_GREETING_PHRASES_LATIN = { |
| "hi", "hello", "hey", "helo", "hii", "hiii", |
| "salam", "adaab", |
| "assalam u alaikum", "assalamu alaikum", "asalam u alaikum", "assalam o alaikum", |
| "salam u alaikum", "salam alaikum", "assalam alaikum", |
| "alaikum assalam", "walaikum assalam", "walaykum assalam", "walaikum salam", |
| "kher he", "khair he", "khair mubarak", "kher mubarak", "mubarak", |
| "good morning", "good evening", "good afternoon", "good night", |
| "yes", "ok", "okay", "thanks", "thank you", "bye", "goodbye", "shukriya", |
| "ji", "jee", "han", "haan", "nah", "na", |
| } |
|
|
| |
| PURE_GREETING_PHRASES_URDU = { |
| "السلام علیکم", "السلام عليكم", "سلام علیکم", "سلام عليكم", |
| "وعلیکم السلام", "وعليكم السلام", "و علیکم السلام", "و عليكم السلام", |
| "سلام", "السلام", "آداب", "اداب", "خیر ہے", "خیر مبارک", "خیرمبارک", "مبارک", |
| "جی", "جی ہاں", "جی ہاں", "ہاں", "ہان", "شکریہ", "شکریا", "جزاک اللہ", |
| "ہیلو", "ہائے", "ہیلو ہیلو", "سلام سلام", |
| "الحمد للہ", "الحمد لله", "ماشاء اللہ", "ماشاء الله", |
| } |
|
|
| URDU_GREETING_TOKENS = { |
| "السلام", "علیکم", "عليكم", "وعلیکم", "وعليكم", "سلام", "آداب", "اداب", |
| "خیر", "مبارک", "جی", "ہاں", "ہان", "شکریہ", "شکریا", "ہیلو", "ہائے", |
| } |
|
|
| CONNECTION_CHECK_PATTERNS = [ |
| |
| re.compile(r"\bhello\s+hello\b", re.I), |
| re.compile(r"\bhi\s+hi\b", re.I), |
| re.compile(r"\bhey\s+hey\b", re.I), |
| re.compile(r"awa?z\s+aara?hi", re.I), |
| re.compile(r"awa?z\s+aa\s+rahi", re.I), |
| re.compile(r"sun\s+pa\s+rahe", re.I), |
| re.compile(r"sun\s+rahe", re.I), |
| re.compile(r"koi\s+sun\s+raha", re.I), |
| re.compile(r"sunaai\s+de", re.I), |
| re.compile(r"sunai\s+de", re.I), |
| re.compile(r"bol\s+rahe", re.I), |
| |
| re.compile(r"ہیلو\s+ہیلو"), |
| re.compile(r"سلام\s+سلام"), |
| re.compile(r"آواز\s+آ\s*رہی"), |
| re.compile(r"آواز\s+آرہی"), |
| re.compile(r"آواز\s+آ\s*رہی"), |
| re.compile(r"سن\s+پا\s+رہے"), |
| re.compile(r"سن\s+رہے"), |
| re.compile(r"سن\s+رہی"), |
| re.compile(r"کوئی\s+سن\s+رہا"), |
| re.compile(r"سنائی\s+دے"), |
| re.compile(r"سناائی\s+دے"), |
| re.compile(r"بول\s+رہے"), |
| re.compile(r"بول\s+رہی"), |
| re.compile(r"آواز\s+آ\s*رہی\s+ہے"), |
| ] |
|
|
| _GREETING_MIRROR = [ |
| |
| (re.compile(r"assalam\s*u?\s*alaikum|assalamu\s*alaikum|salam\s*u?\s*alaikum", re.I), "وعلیکم السلام"), |
| (re.compile(r"walaikum\s*assalam|walaykum\s*assalam|alaikum\s*assalam", re.I), "السلام علیکم"), |
| (re.compile(r"khair\s*mubarak|kher\s*mubarak", re.I), "مبارک"), |
| (re.compile(r"\bkher\s+he\b|\bkhair\s+he\b", re.I), "مبارک"), |
| (re.compile(r"\badaab\b", re.I), "آداب"), |
| (re.compile(r"\bhello\b", re.I), "ہیلو"), |
| (re.compile(r"\bhi\b|\bhey\b|\bhelo\b", re.I), "ہیلو"), |
| (re.compile(r"\bsalam\b", re.I), "وعلیکم السلام"), |
| |
| (re.compile(r"السلام\s*علیکم|السلام\s*عليكم|سلام\s*علیکم|سلام\s*عليكم"), "وعلیکم السلام"), |
| (re.compile(r"وعلیکم\s*السلام|وعليكم\s*السلام"), "السلام علیکم"), |
| (re.compile(r"خیر\s*مبارک|خیرمبارک"), "مبارک"), |
| (re.compile(r"خیر\s*ہے"), "مبارک"), |
| (re.compile(r"آداب|اداب"), "آداب"), |
| (re.compile(r"ہیلو"), "ہیلو"), |
| (re.compile(r"ہائے"), "ہائے"), |
| (re.compile(r"^سلام$|^\s*سلام\s*$"), "وعلیکم السلام"), |
| (re.compile(r"^جی\s*ہاں$|^ہاں$|^ہان$"), "جی"), |
| (re.compile(r"شکریہ|شکریا"), "خوش آمدید"), |
| ] |
|
|
|
|
| def _strip_punctuation(text: str) -> str: |
| return re.sub(r"[\s\u060C\u061B\u061F\u06D4\.,!?؛:]+", " ", text).strip() |
|
|
|
|
| def _normalize_latin(text: str) -> str: |
| return re.sub(r"\s+", " ", (text or "").strip().lower()) |
|
|
|
|
| def _normalize_urdu(text: str) -> str: |
| return re.sub(r"\s+", " ", _strip_punctuation(text)).strip() |
|
|
|
|
| def _has_substance(text: str) -> bool: |
| if not text: |
| return False |
|
|
| latin_words = set(re.findall(r"[a-zA-Z]+", text.lower())) |
| if latin_words & SUBSTANCE_MARKERS_LATIN: |
| return True |
|
|
| urdu_words = set(re.findall(r"[\u0600-\u06FF]+", text)) |
| if urdu_words & SUBSTANCE_MARKERS_URDU: |
| return True |
|
|
| if "?" in text or "؟" in text: |
| lowered = text.lower() |
| if any(m in lowered for m in SUBSTANCE_MARKERS_LATIN): |
| return True |
| if any(m in text for m in SUBSTANCE_MARKERS_URDU): |
| return True |
|
|
| return False |
|
|
|
|
| def _is_pure_greeting_latin(text: str) -> bool: |
| normalized = _normalize_latin(text) |
| if not normalized: |
| return False |
| if normalized in PURE_GREETING_PHRASES_LATIN: |
| return True |
| words = normalized.split() |
| filler = {"ji", "jee", "han", "haan"} |
| if words and all(w in PURE_GREETING_PHRASES_LATIN or w in filler for w in words): |
| return True |
| return False |
|
|
|
|
| def _is_pure_greeting_urdu(text: str) -> bool: |
| if not has_arabic_script(text): |
| return False |
|
|
| normalized = _normalize_urdu(text) |
| if not normalized: |
| return False |
|
|
| if normalized in PURE_GREETING_PHRASES_URDU: |
| return True |
|
|
| compact = normalized.replace("و ", "و") |
| if compact in PURE_GREETING_PHRASES_URDU: |
| return True |
|
|
| words = re.findall(r"[\u0600-\u06FF]+", normalized) |
| if words and all(w in URDU_GREETING_TOKENS or w == "ہے" for w in words): |
| if set(words) <= (URDU_GREETING_TOKENS | {"ہے"}): |
| return True |
|
|
| return False |
|
|
|
|
| def _is_pure_greeting(text: str) -> bool: |
| return _is_pure_greeting_latin(text) or _is_pure_greeting_urdu(text) |
|
|
|
|
| def _is_connection_check(text: str) -> bool: |
| for pattern in CONNECTION_CHECK_PATTERNS: |
| if pattern.search(text): |
| return True |
|
|
| normalized_latin = _normalize_latin(text) |
| if normalized_latin.count("hello") >= 2 or normalized_latin.count("hi") >= 2: |
| return True |
|
|
| normalized_urdu = _normalize_urdu(text) |
| if normalized_urdu.count("ہیلو") >= 2 or normalized_urdu.count("سلام") >= 2: |
| return True |
|
|
| return False |
|
|
|
|
| def _message_length(text: str) -> int: |
| stripped = _strip_punctuation(text) |
| return len(stripped.replace(" ", "")) |
|
|
|
|
| def classify_opening_message(text: str, turn_count: int, language_locked: bool) -> OpeningTier: |
| """Classify message tier for early-turn routing.""" |
| if turn_count > 3 or language_locked: |
| return "normal" |
|
|
| if _has_substance(text): |
| return "greeting_with_substance" |
|
|
| if _is_connection_check(text): |
| return "connection_check" |
|
|
| if _is_pure_greeting(text): |
| return "pure_greeting" |
|
|
| if _message_length(text) < 12 and not _has_substance(text): |
| return "pure_greeting" |
|
|
| return "normal" |
|
|
|
|
| def _is_audio_connection_check(text: str) -> bool: |
| latin = text.lower() |
| if any(c in latin for c in ("awaaz", "awaz", "sun")): |
| return True |
| return any( |
| marker in text |
| for marker in ("آواز", "سن", "سنائی", "سناائی", "بول رہ") |
| ) |
|
|
|
|
| def get_short_reply(tier: OpeningTier, text: str, greeting_streak: int = 0) -> str: |
| """ |
| Deterministic short reply — no LLM. Urdu-only mode: all replies in Urdu. |
| |
| Issue 8 fix: the previous connection_check fallback branch returned the |
| English string "Yes, I can hear you." when _is_audio_connection_check() |
| was False (e.g. bare "ہیلو ہیلو" repetition). This produced an English |
| TTS response in a Urdu-only session. Both branches now return Urdu. |
| """ |
| if tier == "connection_check": |
| if _is_audio_connection_check(text): |
| return "جی ہاں، آواز آ رہی ہے۔" |
| |
| return "جی، میں سن رہا ہوں۔" |
|
|
| if tier == "pure_greeting": |
| for pattern, reply in _GREETING_MIRROR: |
| if pattern.search(text): |
| return reply |
| if greeting_streak >= 2: |
| return "جی، میں آپ کی کیسے مدد کر سکتا ہوں؟" |
| if has_arabic_script(text): |
| return "وعلیکم السلام" |
| |
| return "ہیلو" |
|
|
| |
| return "جی" |
|
|
|
|
| def should_short_circuit(tier: OpeningTier) -> bool: |
| return tier in ("pure_greeting", "connection_check") |