Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -129,7 +129,7 @@ MENU_CONFIG = {
|
|
129 |
'name': 'Main Menu',
|
130 |
'valid_options': ['1', '2', '3', '4'],
|
131 |
'option_descriptions': {
|
132 |
-
'1': '
|
133 |
'2': 'Browse Categories',
|
134 |
'3': 'Download Catalog',
|
135 |
'4': 'Chat with Veterinary AI Assistant'
|
@@ -275,7 +275,7 @@ async def download_voice_file(media_url: str, filename: str) -> str:
|
|
275 |
return None
|
276 |
|
277 |
async def transcribe_voice_with_openai(file_path: str) -> str:
|
278 |
-
"""Transcribe voice file using OpenAI Whisper with
|
279 |
try:
|
280 |
# Check if file exists and has content
|
281 |
if not os.path.exists(file_path):
|
@@ -289,11 +289,19 @@ async def transcribe_voice_with_openai(file_path: str) -> str:
|
|
289 |
|
290 |
logger.info(f"[Transcribe] Transcribing file: {file_path} (size: {file_size} bytes)")
|
291 |
|
292 |
-
#
|
293 |
system_prompt = """
|
294 |
-
You are transcribing voice messages for Apex Biotical Veterinary WhatsApp Assistant.
|
295 |
|
296 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
297 |
|
298 |
PRODUCT NAMES (exact spelling required):
|
299 |
- Hydropex, Respira Aid Plus, Heposel, Bromacid, Hexatox
|
@@ -302,25 +310,22 @@ PRODUCT NAMES (exact spelling required):
|
|
302 |
- Apvita Plus, B-G Aspro-C, EC-Immune, Liverpex, Symodex
|
303 |
- Respira Aid, Adek Gold, Immuno DX
|
304 |
|
305 |
-
ENGLISH NUMBERS: one->1, two->2, three->3, four->4, five->5, six->6, seven->7, eight->8, nine->9, ten->10
|
306 |
-
|
307 |
-
URDU NUMBERS: aik->1, ek->1, do->2, teen->3, char->4, panch->5, cheh->6, saat->7, aath->8, nau->9, das->10
|
308 |
-
|
309 |
MENU COMMANDS:
|
310 |
-
-
|
311 |
-
-
|
|
|
312 |
|
313 |
GREETINGS:
|
314 |
- English: hi, hello, hey, good morning, good afternoon, good evening
|
315 |
- Urdu: salam, assalamu alaikum, adaab, namaste, khuda hafiz
|
316 |
|
317 |
TRANSCRIPTION RULES:
|
318 |
-
1.
|
319 |
-
2.
|
320 |
-
3.
|
321 |
-
4.
|
322 |
-
5.
|
323 |
-
6.
|
324 |
|
325 |
EXAMPLES:
|
326 |
- "hydropex" -> "hydropex"
|
@@ -329,12 +334,10 @@ EXAMPLES:
|
|
329 |
- "main menu" -> "main"
|
330 |
- "salam" -> "salam"
|
331 |
- "search products" -> "search products"
|
332 |
-
-
|
333 |
-
- "kitne products hain" -> "kitne products hain"
|
334 |
-
- Genuinely unclear audio -> "unclear audio"
|
335 |
"""
|
336 |
|
337 |
-
# First attempt with
|
338 |
with open(file_path, 'rb') as audio_file:
|
339 |
transcript = openai.Audio.transcribe(
|
340 |
model="whisper-1",
|
@@ -353,8 +356,6 @@ EXAMPLES:
|
|
353 |
urdu_system_prompt = """
|
354 |
You are transcribing Urdu voice messages for Apex Biotical Veterinary WhatsApp Assistant.
|
355 |
|
356 |
-
FOCUS: The user will speak in Urdu, English, or a mix of both. Be intelligent and natural.
|
357 |
-
|
358 |
PRODUCT NAMES (Urdu/English):
|
359 |
- ہائیڈروپیکس (Hydropex)
|
360 |
- ریسپیرا ایڈ پلس (Respira Aid Plus)
|
@@ -367,11 +368,12 @@ PRODUCT NAMES (Urdu/English):
|
|
367 |
- فائٹو سال (PHYTO-SAL)
|
368 |
- مائیکوپیکس سپر (Mycopex Super)
|
369 |
|
370 |
-
URDU NUMBERS
|
371 |
- ایک (1), دو (2), تین (3), چار (4), پانچ (5)
|
372 |
- چھ (6), سات (7), آٹھ (8), نو (9), دس (10)
|
373 |
- گیارہ (11), بارہ (12), تیرہ (13), چودہ (14), پندرہ (15)
|
374 |
- سولہ (16), سترہ (17), اٹھارہ (18), انیس (19), بیس (20)
|
|
|
375 |
|
376 |
URDU GREETINGS:
|
377 |
- سلام (salam), السلام علیکم (assalamu alaikum)
|
@@ -383,12 +385,11 @@ URDU MENU COMMANDS:
|
|
383 |
- کیٹلاگ (catalog), رابطہ (contact), دستیابی (availability)
|
384 |
|
385 |
TRANSCRIPTION RULES:
|
386 |
-
1.
|
387 |
-
2.
|
388 |
-
3.
|
389 |
4. Preserve product names exactly
|
390 |
-
5.
|
391 |
-
6. Be natural and conversational
|
392 |
"""
|
393 |
|
394 |
with open(file_path, 'rb') as audio_file:
|
@@ -401,16 +402,59 @@ TRANSCRIPTION RULES:
|
|
401 |
|
402 |
transcribed_text = transcript.text.strip()
|
403 |
logger.info(f"[Transcribe] Second attempt transcribed (Urdu): '{transcribed_text}'")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
404 |
|
405 |
-
# Final check for
|
406 |
if not transcribed_text or len(transcribed_text.strip()) < 2:
|
407 |
logger.warning(f"[Transcribe] Very short or empty transcription: '{transcribed_text}'")
|
408 |
return "unclear audio"
|
409 |
|
410 |
-
# Check for
|
|
|
|
|
|
|
|
|
|
|
411 |
special_char_ratio = len(re.findall(r'[^\w\s]', transcribed_text)) / len(transcribed_text)
|
412 |
-
if special_char_ratio > 0.
|
413 |
-
logger.warning(f"[Transcribe] Too many special characters
|
414 |
return "unclear audio"
|
415 |
|
416 |
return transcribed_text
|
@@ -431,80 +475,6 @@ def process_voice_input(text: str) -> str:
|
|
431 |
# Remove extra whitespace
|
432 |
processed_text = re.sub(r'\s+', ' ', processed_text)
|
433 |
|
434 |
-
# Remove all leading/trailing quotes and dots
|
435 |
-
processed_text = re.sub(r'^["\']*[. ]*', '', processed_text)
|
436 |
-
processed_text = re.sub(r'["\']*[. ]*$', '', processed_text)
|
437 |
-
|
438 |
-
# Remove repeated phrases (if two sentences are very similar, keep only one)
|
439 |
-
# Split by sentence-ending punctuation
|
440 |
-
from rapidfuzz import fuzz
|
441 |
-
sentences = re.split(r'[.!?]+', processed_text)
|
442 |
-
unique_sentences = []
|
443 |
-
for s in sentences:
|
444 |
-
s = s.strip()
|
445 |
-
if not s:
|
446 |
-
continue
|
447 |
-
# Only add if not very similar to any already added
|
448 |
-
if not any(fuzz.ratio(s, us) > 85 for us in unique_sentences):
|
449 |
-
unique_sentences.append(s)
|
450 |
-
processed_text = '. '.join(unique_sentences)
|
451 |
-
|
452 |
-
# Remove any remaining duplicate words/phrases
|
453 |
-
words = processed_text.split()
|
454 |
-
if len(words) > 5:
|
455 |
-
mid = len(words) // 2
|
456 |
-
first_half = ' '.join(words[:mid])
|
457 |
-
second_half = ' '.join(words[mid:])
|
458 |
-
if fuzz.ratio(first_half, second_half) > 85:
|
459 |
-
processed_text = first_half
|
460 |
-
|
461 |
-
# Fix special characters and encoding issues
|
462 |
-
processed_text = clean_special_characters(processed_text)
|
463 |
-
|
464 |
-
return processed_text
|
465 |
-
|
466 |
-
def clean_special_characters(text: str) -> str:
|
467 |
-
"""Clean special characters and fix encoding issues"""
|
468 |
-
if not text:
|
469 |
-
return text
|
470 |
-
|
471 |
-
# Fix common encoding issues
|
472 |
-
replacements = {
|
473 |
-
'â€"': '–', # Fix en dash
|
474 |
-
'â€"': '—', # Fix em dash
|
475 |
-
'’': "'", # Fix apostrophe
|
476 |
-
'“': '"', # Fix opening quote
|
477 |
-
'â€': '"', # Fix closing quote
|
478 |
-
'…': '...', # Fix ellipsis
|
479 |
-
'•': '•', # Fix bullet
|
480 |
-
'‰': '°', # Fix degree symbol
|
481 |
-
'′': '′', # Fix prime
|
482 |
-
'″': '″', # Fix double prime
|
483 |
-
'‼': '™', # Fix trademark
|
484 |
-
'‮': '®', # Fix registered
|
485 |
-
'
': '©', # Fix copyright
|
486 |
-
'â€': '–', # Generic fix for en dash
|
487 |
-
'â€"': '—', # Generic fix for em dash
|
488 |
-
'’': "'", # Generic fix for apostrophe
|
489 |
-
'“': '"', # Generic fix for opening quote
|
490 |
-
'â€': '"', # Generic fix for closing quote
|
491 |
-
'…': '...', # Generic fix for ellipsis
|
492 |
-
'•': '•', # Generic fix for bullet
|
493 |
-
'‰': '°', # Generic fix for degree symbol
|
494 |
-
}
|
495 |
-
|
496 |
-
# Apply replacements
|
497 |
-
for old_char, new_char in replacements.items():
|
498 |
-
text = text.replace(old_char, new_char)
|
499 |
-
|
500 |
-
# Remove any remaining problematic characters
|
501 |
-
text = re.sub(r'â€[^"]*', '', text) # Remove any remaining †patterns
|
502 |
-
|
503 |
-
# Clean up multiple spaces
|
504 |
-
text = re.sub(r'\s+', ' ', text)
|
505 |
-
|
506 |
-
return text.strip()
|
507 |
-
|
508 |
# Basic punctuation cleanup
|
509 |
processed_text = processed_text.replace(' ,', ',').replace(' .', '.')
|
510 |
|
@@ -630,19 +600,6 @@ def get_veterinary_product_matches(query: str) -> List[Dict[str, Any]]:
|
|
630 |
logger.info(f"[Veterinary Search] Skipping menu selection: '{normalized_query}'")
|
631 |
return []
|
632 |
|
633 |
-
# Check if this is a mode of action query
|
634 |
-
mode_of_action_keywords = ['mode of action', 'mechanism', 'how does it work', 'what does it do', 'how it works', 'mood of action']
|
635 |
-
is_mode_of_action_query = any(keyword in normalized_query for keyword in mode_of_action_keywords)
|
636 |
-
|
637 |
-
# Extract product name from mode of action query
|
638 |
-
if is_mode_of_action_query:
|
639 |
-
# Try to extract product name from the query
|
640 |
-
for _, row in products_df.iterrows():
|
641 |
-
product_name = str(row.get('Product Name', '')).lower()
|
642 |
-
if product_name in normalized_query:
|
643 |
-
logger.info(f"[Veterinary Search] Mode of action query for product: {product_name}")
|
644 |
-
return [row.to_dict()]
|
645 |
-
|
646 |
scored_matches = []
|
647 |
|
648 |
# Veterinary-specific query expansion
|
@@ -663,29 +620,27 @@ def get_veterinary_product_matches(query: str) -> List[Dict[str, Any]]:
|
|
663 |
if category_key in normalized_query:
|
664 |
expanded_queries.extend(categories)
|
665 |
|
666 |
-
#
|
667 |
veterinary_variations = {
|
668 |
-
'hydropex': ['hydropex', 'hydro pex', '
|
669 |
-
'heposel': ['heposel', 'hepo sel', '
|
670 |
-
'bromacid': ['bromacid', 'brom acid', '
|
671 |
-
'respira aid': ['respira aid', 'respira aid plus', '
|
672 |
-
'hexatox': ['hexatox', 'hexa tox', '
|
673 |
-
'apma fort': ['apma fort', '
|
674 |
-
'para c': ['para c', 'para c.e', '
|
675 |
'tribiotic': ['tribiotic', 'antibiotic', 'respiratory infection'],
|
676 |
-
'phyto-sal': ['phyto-sal', '
|
677 |
-
'mycopex': ['mycopex', '
|
678 |
-
'oftilex': ['oftilex', '
|
679 |
-
'biscomin': ['biscomin', '
|
680 |
-
'apvita': ['apvita', '
|
681 |
-
'bg aspro': ['bg aspro', '
|
682 |
-
'ec-immune': ['ec-immune', '
|
683 |
'liverpex': ['liverpex', 'liver', 'metabolic'],
|
684 |
'symodex': ['symodex', 'multivitamin', 'vitamin'],
|
685 |
-
'adek gold': ['adek gold', '
|
686 |
-
'immuno dx': ['immuno dx', '
|
687 |
-
'apex': ['apex', 'aapex', 'apex biotical'],
|
688 |
-
'apex biotical': ['apex biotical', 'apex', 'aapex']
|
689 |
}
|
690 |
|
691 |
# Add veterinary variations
|
@@ -723,11 +678,11 @@ def get_veterinary_product_matches(query: str) -> List[Dict[str, Any]]:
|
|
723 |
best_match_type = "exact"
|
724 |
match_details = {"field": field_name, "query": expanded_query}
|
725 |
|
726 |
-
# Fuzzy matching for close matches
|
727 |
for expanded_query in expanded_queries:
|
728 |
if len(expanded_query) > 3: # Only fuzzy match longer queries
|
729 |
score = fuzz.partial_ratio(normalized_query, field_str) * weight
|
730 |
-
if score > best_score and score >
|
731 |
best_score = score
|
732 |
best_match_type = "fuzzy"
|
733 |
match_details = {"field": field_name, "query": expanded_query}
|
@@ -752,59 +707,20 @@ def get_veterinary_product_matches(query: str) -> List[Dict[str, Any]]:
|
|
752 |
return unique_matches
|
753 |
|
754 |
def normalize(text: str) -> str:
|
755 |
-
"""Normalize text for
|
756 |
-
|
757 |
-
|
758 |
-
def apply_intelligent_corrections(query: str) -> str:
|
759 |
-
"""Apply intelligent corrections to common misspellings and variations"""
|
760 |
-
corrections = {
|
761 |
-
# Common misspellings
|
762 |
-
'hydropex': 'hydropex',
|
763 |
-
'respira': 'respira aid plus',
|
764 |
-
'bromacid': 'bromacid',
|
765 |
-
'heposel': 'heposel',
|
766 |
-
'tribiotic': 'tribiotic',
|
767 |
-
'para ce': 'para c.e',
|
768 |
-
'phytosal': 'phyto-sal',
|
769 |
-
'mycopex': 'mycopex super',
|
770 |
-
'eflin': 'eflin kt-20',
|
771 |
-
'salcozine': 'salcozine st-30',
|
772 |
-
'oftilex': 'oftilex ua-10',
|
773 |
-
'biscomin': 'biscomin 10',
|
774 |
-
'apvita': 'apvita plus',
|
775 |
-
'b gaspro': 'b-g aspro-c',
|
776 |
-
'ec immune': 'ec-immune',
|
777 |
-
'liverpex': 'liverpex',
|
778 |
-
'symodex': 'symodex',
|
779 |
-
'adek': 'adek gold',
|
780 |
-
'immuno dx': 'immuno dx',
|
781 |
-
|
782 |
-
# Category corrections
|
783 |
-
'respiratory': 'respiratory',
|
784 |
-
'liver': 'liver health',
|
785 |
-
'antibiotic': 'antibiotics',
|
786 |
-
'vitamin': 'vitamins',
|
787 |
-
'supplement': 'supplements',
|
788 |
-
'immune': 'immune system',
|
789 |
-
|
790 |
-
# Common variations
|
791 |
-
'apex': 'apex biotical',
|
792 |
-
'aapex': 'apex biotical',
|
793 |
-
}
|
794 |
|
795 |
-
|
|
|
796 |
|
797 |
-
#
|
798 |
-
|
799 |
-
return corrections[normalized_query]
|
800 |
|
801 |
-
#
|
802 |
-
|
803 |
-
if wrong in normalized_query:
|
804 |
-
return normalized_query.replace(wrong, correct)
|
805 |
|
806 |
-
|
807 |
-
return query
|
808 |
|
809 |
# Enhanced context management with veterinary domain awareness
|
810 |
class VeterinaryContextManager:
|
@@ -895,16 +811,13 @@ class VeterinaryContextManager:
|
|
895 |
context_manager = VeterinaryContextManager()
|
896 |
|
897 |
# Enhanced product response with veterinary domain expertise
|
898 |
-
def generate_veterinary_product_response(product_info: Dict[str, Any], user_context: Dict[str, Any]
|
899 |
"""Generate comprehensive veterinary product response with intelligent information handling"""
|
900 |
|
901 |
def clean_text(text):
|
902 |
if pd.isna(text) or text is None:
|
903 |
return "Not specified"
|
904 |
-
|
905 |
-
# Apply special character cleaning
|
906 |
-
cleaned = clean_special_characters(cleaned)
|
907 |
-
return cleaned
|
908 |
|
909 |
# Extract product details
|
910 |
product_name = clean_text(product_info.get('Product Name', ''))
|
@@ -925,38 +838,18 @@ def generate_veterinary_product_response(product_info: Dict[str, Any], user_cont
|
|
925 |
except Exception as e:
|
926 |
logger.warning(f"Error checking PDF link for {product_name}: {e}")
|
927 |
|
928 |
-
# Build the response
|
929 |
-
|
930 |
-
response = f"""🧪 *نام:* {product_name}
|
931 |
-
📦 *قسم:* {product_type}
|
932 |
-
🏥 *زمرہ:* {category}
|
933 |
-
💊 *استعمال:* {indications}"""
|
934 |
-
|
935 |
-
# Add PDF link if available, in the requested format
|
936 |
-
if pdf_link:
|
937 |
-
response += f"\n\n📄 پروڈکٹ بروشر دستیاب ہے\n🔗 {product_name} پی ڈی ایف:\n{pdf_link}"
|
938 |
-
|
939 |
-
# Add menu options in Urdu
|
940 |
-
response += f"""
|
941 |
-
|
942 |
-
💬 *دستیاب اختیارات:*
|
943 |
-
1️⃣ ویٹرنری کنسلٹنٹ سے بات کریں
|
944 |
-
2️⃣ دستیابی کے بارے میں پوچھیں
|
945 |
-
3️⃣ مین مینو پر واپس جائیں
|
946 |
-
|
947 |
-
💬 ایک اختیار منتخب کریں یا متعلقہ مصنوعات کے بارے میں پوچھیں"""
|
948 |
-
else:
|
949 |
-
response = f"""🧪 *Name:* {product_name}
|
950 |
📦 *Type:* {product_type}
|
951 |
🏥 *Category:* {category}
|
952 |
💊 *Used For:* {indications}"""
|
953 |
-
|
954 |
-
|
955 |
-
|
956 |
-
|
957 |
-
|
958 |
-
|
959 |
-
|
960 |
|
961 |
💬 *Available Actions:*
|
962 |
1️⃣ Talk to Veterinary Consultant
|
@@ -973,8 +866,6 @@ def clean_text_for_pdf(text: str) -> str:
|
|
973 |
return "N/A"
|
974 |
|
975 |
cleaned = str(text)
|
976 |
-
# Apply special character cleaning first
|
977 |
-
cleaned = clean_special_characters(cleaned)
|
978 |
# Remove or replace problematic characters for PDF
|
979 |
cleaned = cleaned.replace('â€"', '-').replace('â€"', '"').replace('’', "'")
|
980 |
cleaned = cleaned.replace('“', '"').replace('â€', '"').replace('…', '...')
|
@@ -1803,36 +1694,7 @@ async def process_incoming_message(from_number: str, msg: dict):
|
|
1803 |
# Handle voice messages FIRST - before checking message_body
|
1804 |
if message_type in ['audio', 'voice'] or media_type == 'audio':
|
1805 |
logger.info(f"[Process] Processing voice message from {from_number}")
|
1806 |
-
|
1807 |
-
transcribed_text = msg.get('transcribed_text') or ''
|
1808 |
-
# Only use menu navigation logic for strict commands
|
1809 |
-
menu_commands = ['main', 'menu', 'start', 'home', 'back', '1', '2', '3', '4', '5']
|
1810 |
-
if transcribed_text.lower().strip() in menu_commands:
|
1811 |
-
# Existing menu navigation logic (unchanged)
|
1812 |
-
if reply_language == 'ur':
|
1813 |
-
welcome_msg = (
|
1814 |
-
"🤖 ویٹرنری ورچوئل اسسٹنٹ\n\n"
|
1815 |
-
"آپ مجھ سے پوچھ سکتے ہیں:\n"
|
1816 |
-
"* ویٹرنری سوالات\n"
|
1817 |
-
"* پروڈکٹ کی سفارشات\n"
|
1818 |
-
"* علاج کے مشورے\n"
|
1819 |
-
"* عمومی معلومات\n\n"
|
1820 |
-
"💬 'main' لکھ کر مین مینو پر واپس جائیں۔"
|
1821 |
-
)
|
1822 |
-
else:
|
1823 |
-
welcome_msg = generate_veterinary_welcome_message()
|
1824 |
-
send_whatsjet_message(from_number, welcome_msg)
|
1825 |
-
context_manager.update_context(
|
1826 |
-
from_number,
|
1827 |
-
current_state='main_menu',
|
1828 |
-
current_menu='main_menu',
|
1829 |
-
current_menu_options=list(MENU_CONFIG['main_menu']['option_descriptions'].values()),
|
1830 |
-
reply_language=reply_language
|
1831 |
-
)
|
1832 |
-
return
|
1833 |
-
# All other voice queries go to OpenAI/gpt-4o
|
1834 |
-
user_context = context_manager.get_context(from_number)
|
1835 |
-
await handle_general_query_with_ai(from_number, transcribed_text, user_context, reply_language)
|
1836 |
return
|
1837 |
|
1838 |
# For text messages, check if body exists
|
@@ -1841,41 +1703,23 @@ async def process_incoming_message(from_number: str, msg: dict):
|
|
1841 |
return
|
1842 |
|
1843 |
message_body = message_body.strip()
|
|
|
1844 |
logger.info(f"[Process] Processing {message_type} message from {from_number}: {message_body}")
|
1845 |
|
1846 |
# Get user context
|
1847 |
user_context = context_manager.get_context(from_number)
|
1848 |
current_state = user_context.get('current_state', 'main_menu')
|
|
|
|
|
1849 |
context_manager.update_context(from_number, last_message=message_body)
|
|
|
|
|
1850 |
logger.info(f"[Process] Current state: {current_state}, Message: '{message_body}' from {from_number}")
|
1851 |
-
|
|
|
1852 |
if not message_body:
|
1853 |
return
|
1854 |
-
|
1855 |
-
# LANGUAGE DETECTION FOR TEXT MESSAGES - STRICTLY ENGLISH OR URDU ONLY
|
1856 |
-
reply_language = 'en' # Default to English
|
1857 |
-
try:
|
1858 |
-
detected_lang = detect(message_body)
|
1859 |
-
logger.info(f"[Process] Raw detected language: {detected_lang}")
|
1860 |
-
if detected_lang in ['en', 'ur']:
|
1861 |
-
reply_language = detected_lang
|
1862 |
-
logger.info(f"[Process] Valid language detected: {detected_lang}")
|
1863 |
-
else:
|
1864 |
-
reply_language = 'en'
|
1865 |
-
logger.warning(f"[Process] Invalid language '{detected_lang}' detected - forcing to English")
|
1866 |
-
urdu_arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]')
|
1867 |
-
islamic_greetings = ['assalamu', 'assalam', 'salam', 'salaam', 'adaab', 'namaste', 'khuda', 'allah']
|
1868 |
-
has_urdu_chars = bool(urdu_arabic_pattern.search(message_body))
|
1869 |
-
has_islamic_greeting = any(greeting in message_body.lower() for greeting in islamic_greetings)
|
1870 |
-
if has_urdu_chars or has_islamic_greeting:
|
1871 |
-
detected_lang = 'ur'
|
1872 |
-
reply_language = 'ur'
|
1873 |
-
logger.info(f"[Process] Overriding language detection to Urdu due to Arabic/Urdu characters or Islamic greeting")
|
1874 |
-
logger.info(f"[Process] Final language set to: {reply_language}")
|
1875 |
-
except Exception as e:
|
1876 |
-
logger.warning(f"[Process] Language detection failed: {e}, defaulting to English")
|
1877 |
-
reply_language = 'en'
|
1878 |
-
|
1879 |
# Check for greetings with multilingual support
|
1880 |
if is_greeting(message_body):
|
1881 |
# Check if user is currently in AI chat mode - if so, don't trigger menu mode
|
@@ -1886,337 +1730,45 @@ async def process_incoming_message(from_number: str, msg: dict):
|
|
1886 |
return
|
1887 |
else:
|
1888 |
# Only trigger menu mode if not in AI chat mode
|
1889 |
-
|
1890 |
-
if reply_language == 'ur':
|
1891 |
-
welcome_msg = (
|
1892 |
-
"🤖 ویٹرنری ورچوئل اسسٹنٹ\n\n"
|
1893 |
-
"آپ مجھ سے پوچھ سکتے ہیں:\n"
|
1894 |
-
"* ویٹرنری سوالات\n"
|
1895 |
-
"* پروڈکٹ کی سفارشات\n"
|
1896 |
-
"* علاج کے مشورے\n"
|
1897 |
-
"* عمومی معلومات\n\n"
|
1898 |
-
"💬 'main' لکھ کر مین مینو پر واپس جائیں۔"
|
1899 |
-
)
|
1900 |
-
else:
|
1901 |
-
welcome_msg = generate_veterinary_welcome_message()
|
1902 |
-
|
1903 |
-
send_whatsjet_message(from_number, welcome_msg)
|
1904 |
-
context_manager.update_context(
|
1905 |
-
from_number,
|
1906 |
-
current_state='main_menu',
|
1907 |
-
current_menu='main_menu',
|
1908 |
-
current_menu_options=list(MENU_CONFIG['main_menu']['option_descriptions'].values()),
|
1909 |
-
reply_language=reply_language
|
1910 |
-
)
|
1911 |
-
return
|
1912 |
-
|
1913 |
-
# 🎯 PRIORITY 1: Navigation commands - work from ANY state
|
1914 |
-
# Check for "main" command - now works for both text and voice
|
1915 |
-
if current_state != 'main_menu' and current_state != 'ai_chat_mode': # Only check for main if not already in main menu and not in AI chat mode
|
1916 |
-
mapped_navigation = process_intelligent_voice_command(message_body, current_state, user_context)
|
1917 |
-
if mapped_navigation == 'main':
|
1918 |
-
logger.info(f"[Process] Navigation command detected: '{message_body}' -> 'main'")
|
1919 |
-
# Generate welcome message in detected language
|
1920 |
-
if reply_language == 'ur':
|
1921 |
-
welcome_msg = (
|
1922 |
-
"🤖 ویٹرنری ورچوئل اسسٹنٹ\n\n"
|
1923 |
-
"آپ مجھ سے پوچھ سکتے ہیں:\n"
|
1924 |
-
"* ویٹرنری سوالات\n"
|
1925 |
-
"* پروڈکٹ کی سفارشات\n"
|
1926 |
-
"* علاج کے مشورے\n"
|
1927 |
-
"* عمومی معلومات\n\n"
|
1928 |
-
"💬 'main' لکھ کر مین مینو پر واپس جائیں۔"
|
1929 |
-
)
|
1930 |
-
else:
|
1931 |
-
welcome_msg = generate_veterinary_welcome_message()
|
1932 |
-
|
1933 |
send_whatsjet_message(from_number, welcome_msg)
|
1934 |
context_manager.update_context(
|
1935 |
from_number,
|
1936 |
current_state='main_menu',
|
1937 |
current_menu='main_menu',
|
1938 |
-
current_menu_options=list(MENU_CONFIG['main_menu']['option_descriptions'].values())
|
1939 |
-
reply_language=reply_language
|
1940 |
-
)
|
1941 |
-
return
|
1942 |
-
|
1943 |
-
# Also check for text-based navigation commands
|
1944 |
-
if message_body.lower() in ['main', 'menu', 'start', 'home', 'back']:
|
1945 |
-
# Generate welcome message in detected language
|
1946 |
-
if reply_language == 'ur':
|
1947 |
-
welcome_msg = (
|
1948 |
-
"🤖 ویٹرنری ورچوئل اسسٹنٹ\n\n"
|
1949 |
-
"آپ مجھ سے پوچھ سکتے ہیں:\n"
|
1950 |
-
"* ویٹرنری سوالات\n"
|
1951 |
-
"* پروڈکٹ کی سفارشات\n"
|
1952 |
-
"* علاج کے مشورے\n"
|
1953 |
-
"* عمومی معلومات\n\n"
|
1954 |
-
"💬 'main' لکھ کر مین مینو پر واپس جائیں۔"
|
1955 |
-
)
|
1956 |
-
else:
|
1957 |
-
welcome_msg = generate_veterinary_welcome_message()
|
1958 |
-
|
1959 |
-
send_whatsjet_message(from_number, welcome_msg)
|
1960 |
-
context_manager.update_context(
|
1961 |
-
from_number,
|
1962 |
-
current_state='main_menu',
|
1963 |
-
current_menu='main_menu',
|
1964 |
-
current_menu_options=list(MENU_CONFIG['main_menu']['option_descriptions'].values()),
|
1965 |
-
reply_language=reply_language
|
1966 |
-
)
|
1967 |
-
return
|
1968 |
-
|
1969 |
-
# 🎯 PRIORITY 2: State-specific handling (contact_request, availability_request, ai_chat_mode, clarification)
|
1970 |
-
if current_state == 'contact_request':
|
1971 |
-
await handle_contact_request_response(from_number, message_body)
|
1972 |
-
return
|
1973 |
-
elif current_state == 'availability_request':
|
1974 |
-
await handle_availability_request_response(from_number, message_body)
|
1975 |
-
return
|
1976 |
-
elif current_state == 'ai_chat_mode':
|
1977 |
-
await handle_ai_chat_mode(from_number, message_body, reply_language)
|
1978 |
-
return
|
1979 |
-
elif user_context.get('awaiting_clarification', False):
|
1980 |
-
# Handle clarification responses
|
1981 |
-
await handle_clarification_response(from_number, message_body, user_context)
|
1982 |
-
return
|
1983 |
-
|
1984 |
-
# 🎯 PRIORITY 3: Menu selection validation and handling
|
1985 |
-
if current_state == 'main_menu':
|
1986 |
-
# Validate menu selection
|
1987 |
-
is_valid, validation_message = validate_menu_selection(message_body, current_state, user_context)
|
1988 |
-
|
1989 |
-
if not is_valid:
|
1990 |
-
send_whatsjet_message(from_number, validation_message)
|
1991 |
-
return
|
1992 |
-
|
1993 |
-
# Handle valid menu selection
|
1994 |
-
if message_body == '1':
|
1995 |
-
# Complete Products List - Display all 23 products
|
1996 |
-
await display_all_products(from_number)
|
1997 |
-
return
|
1998 |
-
|
1999 |
-
elif message_body == '2':
|
2000 |
-
# Category Browse - Show all categories
|
2001 |
-
categories = get_all_categories()
|
2002 |
-
if reply_language == 'ur':
|
2003 |
-
message = "📂 *زمرے براؤز کریں:*\n\n"
|
2004 |
-
for i, category in enumerate(categories, 1):
|
2005 |
-
message += f"{format_number_with_emoji(i)} {category}\n"
|
2006 |
-
message += "\n💬 اپنا انتخاب لکھیں یا 'main' لکھ کر مین مینو پر واپس جائیں۔"
|
2007 |
-
else:
|
2008 |
-
message = "📂 *Browse Categories:*\n\n"
|
2009 |
-
for i, category in enumerate(categories, 1):
|
2010 |
-
message += f"{format_number_with_emoji(i)} {category}\n"
|
2011 |
-
message += "\n💬 Type your choice or 'main' to return to main menu"
|
2012 |
-
|
2013 |
-
send_whatsjet_message(from_number, message)
|
2014 |
-
context_manager.update_context(
|
2015 |
-
from_number,
|
2016 |
-
current_state='category_selection',
|
2017 |
-
current_menu='category_selection',
|
2018 |
-
current_menu_options=categories,
|
2019 |
-
available_categories=categories,
|
2020 |
-
reply_language=reply_language
|
2021 |
-
)
|
2022 |
-
return
|
2023 |
-
|
2024 |
-
elif message_body == '3':
|
2025 |
-
# Catalog Download
|
2026 |
-
await send_catalog_pdf(from_number)
|
2027 |
-
return
|
2028 |
-
|
2029 |
-
elif message_body == '4':
|
2030 |
-
# AI Chat Mode
|
2031 |
-
context_manager.update_context(
|
2032 |
-
from_number,
|
2033 |
-
current_state='ai_chat_mode',
|
2034 |
-
current_menu='ai_chat_mode',
|
2035 |
-
current_menu_options=['main'],
|
2036 |
-
reply_language=reply_language
|
2037 |
)
|
2038 |
-
if reply_language == 'ur':
|
2039 |
-
message = (
|
2040 |
-
"🤖 ویٹرنری ورچوئل اسسٹنٹ\n\n"
|
2041 |
-
"آپ مجھ سے پوچھ سکتے ہیں:\n"
|
2042 |
-
"* ویٹرنری سوالات\n"
|
2043 |
-
"* پروڈکٹ کی سفارشات\n"
|
2044 |
-
"* علاج کے مشورے\n"
|
2045 |
-
"* عمومی معلومات\n\n"
|
2046 |
-
"💬 'main' لکھ کر مین مینو پر واپس جائیں۔"
|
2047 |
-
)
|
2048 |
-
else:
|
2049 |
-
message = (
|
2050 |
-
"🤖 Veterinary Virtual Assistant\n\n"
|
2051 |
-
"You can ask me about:\n"
|
2052 |
-
"* Veterinary questions\n"
|
2053 |
-
"* Product recommendations\n"
|
2054 |
-
"* Treatment advice\n"
|
2055 |
-
"* General information\n\n"
|
2056 |
-
"💬 Type 'main' to return to main menu"
|
2057 |
-
)
|
2058 |
-
send_whatsjet_message(from_number, message)
|
2059 |
return
|
2060 |
|
2061 |
-
# 🎯 PRIORITY 4: Category selection handling
|
2062 |
-
elif current_state == 'category_selection':
|
2063 |
-
await handle_category_selection(message_body, from_number)
|
2064 |
-
return
|
2065 |
-
|
2066 |
-
|
2067 |
-
|
2068 |
-
# 🎯 PRIORITY 5: Product inquiry handling (for direct product searches)
|
2069 |
-
elif current_state == 'product_inquiry':
|
2070 |
-
# Check for voice commands first
|
2071 |
-
mapped_command = process_intelligent_voice_command(message_body, current_state, user_context)
|
2072 |
-
if mapped_command != message_body:
|
2073 |
-
logger.info(f"[Voice Command] Processing: '{message_body}' in state: {current_state}")
|
2074 |
-
message_body = mapped_command
|
2075 |
-
logger.info(f"[Voice Command] Mapped to: '{message_body}'")
|
2076 |
-
|
2077 |
-
# Search for products
|
2078 |
-
matches = get_veterinary_product_matches(message_body)
|
2079 |
-
|
2080 |
-
if matches:
|
2081 |
-
logger.info(f"[Process] Product query detected from menu state '{current_state}': '{message_body}' -> Found {len(matches)} products")
|
2082 |
-
|
2083 |
-
# Apply intelligent corrections
|
2084 |
-
corrected_query = apply_intelligent_corrections(message_body)
|
2085 |
-
if corrected_query != message_body:
|
2086 |
-
logger.info(f"[Query] Applied correction: '{message_body}' -> '{corrected_query}'")
|
2087 |
-
matches = get_veterinary_product_matches(corrected_query)
|
2088 |
-
|
2089 |
-
# Handle product matches
|
2090 |
-
if len(matches) == 1:
|
2091 |
-
# Single product match - show detailed info
|
2092 |
-
product = matches[0]
|
2093 |
-
context_manager.update_context(from_number, current_product=product)
|
2094 |
-
await send_product_with_image(from_number, product, user_context)
|
2095 |
-
|
2096 |
-
# Send follow-up options
|
2097 |
-
await handle_veterinary_product_followup(message_body, from_number)
|
2098 |
-
|
2099 |
-
elif len(matches) <= 5:
|
2100 |
-
# Multiple products - show list
|
2101 |
-
if reply_language == 'ur':
|
2102 |
-
response = f"🔍 *{len(matches)} پروڈکٹ ملے:*\n\n"
|
2103 |
-
else:
|
2104 |
-
response = f"🔍 *Found {len(matches)} products:*\n\n"
|
2105 |
-
|
2106 |
-
for i, product in enumerate(matches, 1):
|
2107 |
-
product_name = product.get('Product Name', 'N/A')
|
2108 |
-
category = product.get('Category', 'N/A')
|
2109 |
-
if reply_language == 'ur':
|
2110 |
-
response += f"{format_number_with_emoji(i)} {product_name} ({category})\n"
|
2111 |
-
else:
|
2112 |
-
response += f"{format_number_with_emoji(i)} {product_name} ({category})\n"
|
2113 |
-
|
2114 |
-
if reply_language == 'ur':
|
2115 |
-
response += "\n💬 اپنا انتخاب لکھیں یا 'main' لکھ کر مین مینو پر واپس جائیں۔"
|
2116 |
-
else:
|
2117 |
-
response += "\n💬 Type your choice or 'main' to return to main menu"
|
2118 |
-
|
2119 |
-
send_whatsjet_message(from_number, response)
|
2120 |
-
context_manager.update_context(
|
2121 |
-
from_number,
|
2122 |
-
current_state='product_selection',
|
2123 |
-
current_menu='product_selection',
|
2124 |
-
current_menu_options=[p.get('Product Name', 'N/A') for p in matches],
|
2125 |
-
reply_language=reply_language
|
2126 |
-
)
|
2127 |
-
|
2128 |
-
else:
|
2129 |
-
# Too many matches - ask for clarification
|
2130 |
-
if reply_language == 'ur':
|
2131 |
-
response = f"🔍 *{len(matches)} سے زیادہ پروڈکٹ ملے۔ براہ کرم مزید تفصیل دیں:*\n\n"
|
2132 |
-
else:
|
2133 |
-
response = f"🔍 *Found {len(matches)}+ products. Please be more specific:*\n\n"
|
2134 |
-
|
2135 |
-
# Show first 10 matches as examples
|
2136 |
-
for i, product in enumerate(matches[:10], 1):
|
2137 |
-
product_name = product.get('Product Name', 'N/A')
|
2138 |
-
category = product.get('Category', 'N/A')
|
2139 |
-
if reply_language == 'ur':
|
2140 |
-
response += f"{format_number_with_emoji(i)} {product_name} ({category})\n"
|
2141 |
-
else:
|
2142 |
-
response += f"{format_number_with_emoji(i)} {product_name} ({category})\n"
|
2143 |
-
|
2144 |
-
if reply_language == 'ur':
|
2145 |
-
response += "\n💬 براہ کرم مزید تفصیل دیں یا 'main' لکھ کر مین مینو پر واپس جائیں۔"
|
2146 |
-
else:
|
2147 |
-
response += "\n💬 Please provide more details or type 'main' to return to main menu"
|
2148 |
-
|
2149 |
-
send_whatsjet_message(from_number, response)
|
2150 |
-
context_manager.update_context(from_number, awaiting_clarification=True)
|
2151 |
-
|
2152 |
-
else:
|
2153 |
-
# No matches found - use OpenAI for intelligent response
|
2154 |
-
await handle_general_query_with_ai(from_number, message_body, user_context, reply_language)
|
2155 |
-
return
|
2156 |
-
|
2157 |
-
# 🎯 PRIORITY 6: All other queries - use OpenAI for intelligent response
|
2158 |
-
await handle_general_query_with_ai(from_number, message_body, user_context, reply_language)
|
2159 |
-
return
|
2160 |
-
|
2161 |
# 🎯 PRIORITY 1: Navigation commands - work from ANY state
|
2162 |
# Check for "main" command - now works for both text and voice
|
2163 |
if current_state != 'main_menu' and current_state != 'ai_chat_mode': # Only check for main if not already in main menu and not in AI chat mode
|
2164 |
mapped_navigation = process_intelligent_voice_command(message_body, current_state, user_context)
|
2165 |
if mapped_navigation == 'main':
|
2166 |
logger.info(f"[Process] Navigation command detected: '{message_body}' -> 'main'")
|
2167 |
-
|
2168 |
-
if reply_language == 'ur':
|
2169 |
-
welcome_msg = (
|
2170 |
-
"🩺 *اپیکس بائیوٹیکل ویٹرنری اسسٹنٹ*\n\n"
|
2171 |
-
"آپ کا خیر مقدم ہے! میں آپ کی ویٹرنری مصنوعات کے بارے میں مدد کر سکتا ہوں۔\n\n"
|
2172 |
-
"📋 *مین مینو:*\n"
|
2173 |
-
"1️⃣ مصنوعات تلاش کریں\n"
|
2174 |
-
"2️⃣ زمرے براؤز کریں\n"
|
2175 |
-
"3️⃣ کیٹلاگ ڈاؤن لوڈ کریں\n"
|
2176 |
-
"4️⃣ اے آئی چیٹ موڈ\n\n"
|
2177 |
-
"💬 اپنا انتخاب لکھیں یا 'main' لکھ کر مین مینو پر واپس جائیں۔"
|
2178 |
-
)
|
2179 |
-
else:
|
2180 |
-
welcome_msg = generate_veterinary_welcome_message()
|
2181 |
-
|
2182 |
send_whatsjet_message(from_number, welcome_msg)
|
2183 |
context_manager.update_context(
|
2184 |
from_number,
|
2185 |
current_state='main_menu',
|
2186 |
current_menu='main_menu',
|
2187 |
-
current_menu_options=list(MENU_CONFIG['main_menu']['option_descriptions'].values())
|
2188 |
-
reply_language=reply_language
|
2189 |
)
|
2190 |
return
|
2191 |
|
2192 |
# Also check for text-based navigation commands
|
2193 |
if message_body.lower() in ['main', 'menu', 'start', 'home', 'back']:
|
2194 |
-
|
2195 |
-
if reply_language == 'ur':
|
2196 |
-
welcome_msg = (
|
2197 |
-
"🩺 *اپیکس بائیوٹیکل ویٹرنری اسسٹنٹ*\n\n"
|
2198 |
-
"آپ کا خیر مقدم ہے! میں آپ کی ویٹرنری مصنوعات کے بارے میں مدد کر سکتا ہوں۔\n\n"
|
2199 |
-
"📋 *مین مینو:*\n"
|
2200 |
-
"1️⃣ مصنوعات تلاش کریں\n"
|
2201 |
-
"2️⃣ زمرے براؤز کریں\n"
|
2202 |
-
"3️⃣ کیٹلاگ ڈاؤن لوڈ کریں\n"
|
2203 |
-
"4️⃣ اے آئی چیٹ موڈ\n\n"
|
2204 |
-
"💬 اپنا انتخاب لکھیں یا 'main' لکھ کر مین مینو پر واپس جائیں۔"
|
2205 |
-
)
|
2206 |
-
else:
|
2207 |
-
welcome_msg = generate_veterinary_welcome_message()
|
2208 |
-
|
2209 |
send_whatsjet_message(from_number, welcome_msg)
|
2210 |
context_manager.update_context(
|
2211 |
from_number,
|
2212 |
current_state='main_menu',
|
2213 |
current_menu='main_menu',
|
2214 |
-
current_menu_options=list(MENU_CONFIG['main_menu']['option_descriptions'].values())
|
2215 |
-
reply_language=reply_language
|
2216 |
)
|
2217 |
return
|
2218 |
|
2219 |
-
# 🎯 PRIORITY 2: State-specific handling (contact_request, availability_request, ai_chat_mode
|
2220 |
if current_state == 'contact_request':
|
2221 |
await handle_contact_request_response(from_number, message_body)
|
2222 |
return
|
@@ -2226,29 +1778,8 @@ async def process_incoming_message(from_number: str, msg: dict):
|
|
2226 |
elif current_state == 'ai_chat_mode':
|
2227 |
await handle_ai_chat_mode(from_number, message_body, reply_language)
|
2228 |
return
|
2229 |
-
elif user_context.get('awaiting_clarification', False):
|
2230 |
-
# Handle clarification responses
|
2231 |
-
await handle_clarification_response(from_number, message_body, user_context)
|
2232 |
-
return
|
2233 |
-
|
2234 |
-
# 🎯 PRIORITY 3: Intelligent product queries from any menu state
|
2235 |
-
# Check if the message is about a product from CSV, regardless of current menu
|
2236 |
-
products = get_veterinary_product_matches(message_body)
|
2237 |
-
|
2238 |
-
if products:
|
2239 |
-
logger.info(f"[Process] Product query detected from menu state '{current_state}': '{message_body}' -> Found {len(products)} products")
|
2240 |
-
|
2241 |
-
# If user is in a specific menu but asks about a product, handle it intelligently
|
2242 |
-
if current_state in ['main_menu', 'category_selection_menu', 'category_products_menu', 'all_products_menu', 'product_inquiry', 'intelligent_products_menu']:
|
2243 |
-
# Use intelligent product inquiry to handle the product query
|
2244 |
-
await handle_intelligent_product_inquiry(from_number, message_body, user_context, reply_language)
|
2245 |
-
return
|
2246 |
-
else:
|
2247 |
-
# For other menu states, still handle product queries but remind about menu context
|
2248 |
-
await handle_intelligent_product_inquiry(from_number, message_body, user_context, reply_language)
|
2249 |
-
return
|
2250 |
|
2251 |
-
# 🎯 PRIORITY
|
2252 |
if current_state in ['main_menu', 'category_selection_menu', 'category_products_menu', 'all_products_menu', 'product_inquiry', 'intelligent_products_menu']:
|
2253 |
# Validate menu selection
|
2254 |
is_valid, error_msg = validate_menu_selection(message_body, current_state, user_context)
|
@@ -2290,12 +1821,12 @@ async def process_incoming_message(from_number: str, msg: dict):
|
|
2290 |
reply_language='ur'
|
2291 |
)
|
2292 |
message = (
|
2293 |
-
"🤖 ویٹرنری
|
2294 |
"آپ مجھ سے پوچھ سکتے ہیں:\n"
|
2295 |
-
"
|
2296 |
-
"
|
2297 |
-
"
|
2298 |
-
"
|
2299 |
"💬 'main' لکھ کر مین مینو پر واپس جائیں۔"
|
2300 |
)
|
2301 |
send_whatsjet_message(from_number, message)
|
@@ -2318,24 +1849,26 @@ async def process_incoming_message(from_number: str, msg: dict):
|
|
2318 |
send_whatsjet_message(from_number, get_menu_validation_message(current_state, user_context))
|
2319 |
elif current_state == 'all_products_menu':
|
2320 |
# Handle product selection from all products
|
2321 |
-
|
2322 |
-
|
2323 |
-
|
2324 |
-
|
2325 |
-
|
2326 |
-
|
2327 |
-
|
2328 |
-
|
2329 |
-
|
2330 |
-
|
2331 |
-
|
2332 |
-
|
|
|
|
|
2333 |
else:
|
2334 |
-
send_whatsjet_message(from_number,
|
2335 |
-
return
|
2336 |
elif current_state == 'product_inquiry':
|
2337 |
await handle_veterinary_product_followup(message_body, from_number)
|
2338 |
elif current_state == 'intelligent_products_menu':
|
|
|
2339 |
available_products = user_context.get('available_products', [])
|
2340 |
if message_body.isdigit() and 1 <= int(message_body) <= len(available_products):
|
2341 |
selected_product = available_products[int(message_body) - 1]
|
@@ -2353,7 +1886,7 @@ async def process_incoming_message(from_number: str, msg: dict):
|
|
2353 |
return
|
2354 |
return # Exit after handling menu selection
|
2355 |
|
2356 |
-
# 🎯 PRIORITY
|
2357 |
# This ensures users can say product names like "hydropex", "respira aid plus", etc. from any menu
|
2358 |
logger.info(f"[Process] Checking for product name in message: '{message_body}' from state: {current_state}")
|
2359 |
products = get_veterinary_product_matches(message_body)
|
@@ -2463,254 +1996,52 @@ async def process_incoming_message(from_number: str, msg: dict):
|
|
2463 |
|
2464 |
async def handle_general_query_with_ai(from_number: str, query: str, user_context: dict, reply_language: str = 'en'):
|
2465 |
"""Handle general queries with OpenAI intelligence"""
|
2466 |
-
|
|
|
|
|
2467 |
|
2468 |
try:
|
2469 |
-
# Let OpenAI handle all queries intelligently first
|
2470 |
-
if OPENAI_API_KEY and products_df is not None and not products_df.empty:
|
2471 |
-
try:
|
2472 |
-
# Get all products data for context
|
2473 |
-
all_products = products_df.to_dict('records')
|
2474 |
-
|
2475 |
-
# Create comprehensive context for AI
|
2476 |
-
products_context = ""
|
2477 |
-
if all_products:
|
2478 |
-
products_context = "Available Veterinary Products:\n"
|
2479 |
-
for i, product in enumerate(all_products[:50], 1): # Limit to first 50 products for context
|
2480 |
-
product_name = product.get('Product Name', 'N/A')
|
2481 |
-
category = product.get('Category', 'N/A')
|
2482 |
-
target_species = product.get('Target Species', 'N/A')
|
2483 |
-
products_context += f"{i}. {product_name} - {category} ({target_species})\n"
|
2484 |
-
|
2485 |
-
# Expert-level AI prompt for comprehensive veterinary assistance
|
2486 |
-
prompt = f"""
|
2487 |
-
You are an EXPERT veterinary pharmaceutical assistant for Apex Biotical, with deep knowledge of veterinary medicine, animal health, and pharmaceutical products. You help users on WhatsApp with professional, accurate, and context-aware responses.
|
2488 |
-
|
2489 |
-
USER QUERY: "{query}"
|
2490 |
-
|
2491 |
-
AVAILABLE PRODUCTS DATABASE:
|
2492 |
-
{products_context}
|
2493 |
-
|
2494 |
-
EXPERT ANALYSIS FRAMEWORK:
|
2495 |
-
|
2496 |
-
1. **QUERY TYPE DETECTION:**
|
2497 |
-
- COUNT/QUANTITY queries: "how many", "count", "total", "number of"
|
2498 |
-
- PRODUCT INQUIRIES: specific product names, categories, or therapeutic areas
|
2499 |
-
- COMPANY INQUIRIES: "apex", "company", "about", "who are you"
|
2500 |
-
- VETERINARY ADVICE: medical questions, treatment recommendations, dosage
|
2501 |
-
- GENERAL QUESTIONS: greetings, navigation, help requests
|
2502 |
-
|
2503 |
-
2. **CONTEXT-AWARE RESPONSE STRATEGY:**
|
2504 |
-
- For COUNT queries: Provide exact numbers with category breakdowns
|
2505 |
-
- For PRODUCT queries: List relevant products with therapeutic benefits, target species, and key features
|
2506 |
-
- For COMPANY queries: Professional company overview with expertise areas
|
2507 |
-
- For VETERINARY ADVICE: Evidence-based recommendations with product suggestions
|
2508 |
-
- For GENERAL queries: Helpful guidance with menu navigation
|
2509 |
-
|
2510 |
-
3. **EXPERT KNOWLEDGE INTEGRATION:**
|
2511 |
-
- Veterinary pharmacology and therapeutics
|
2512 |
-
- Animal health and disease management
|
2513 |
-
- Pharmaceutical product applications
|
2514 |
-
- Professional veterinary communication
|
2515 |
-
- Regulatory and safety considerations
|
2516 |
-
|
2517 |
-
RESPONSE GUIDELINES:
|
2518 |
-
- Be PROFESSIONAL, EXPERT, and HELPFUL
|
2519 |
-
- Use appropriate veterinary terminology
|
2520 |
-
- Include relevant emojis for clarity
|
2521 |
-
- Provide accurate, evidence-based information
|
2522 |
-
- Always include navigation instructions
|
2523 |
-
- Show understanding of user's intent and context
|
2524 |
-
- Be concise but comprehensive
|
2525 |
-
- Maintain professional veterinary standards
|
2526 |
-
|
2527 |
-
IMPORTANT: Demonstrate expert-level veterinary knowledge while being accessible and helpful. Always provide accurate information from the database and professional veterinary guidance.
|
2528 |
-
"""
|
2529 |
-
|
2530 |
-
response = openai.ChatCompletion.create(
|
2531 |
-
model="gpt-4o",
|
2532 |
-
messages=[{"role": "user", "content": prompt}],
|
2533 |
-
temperature=0.3, # Lower temperature for more consistent, expert responses
|
2534 |
-
max_tokens=600 # Increased for more comprehensive expert responses
|
2535 |
-
)
|
2536 |
-
|
2537 |
-
ai_response = response.choices[0].message.content.strip()
|
2538 |
-
|
2539 |
-
# Ensure the response includes navigation instructions
|
2540 |
-
if 'main' not in ai_response.lower():
|
2541 |
-
if reply_language == 'ur':
|
2542 |
-
ai_response += "\n\n💬 *براہ کرم 'main' لکھ کر مین مینو پر جائیں*"
|
2543 |
-
else:
|
2544 |
-
ai_response += "\n\n💬 *Please type 'main' to go to main menu*"
|
2545 |
-
|
2546 |
-
# Translate response if needed (ENGLISH/URDU ONLY)
|
2547 |
-
if reply_language == 'ur':
|
2548 |
-
try:
|
2549 |
-
# Only translate from English to Urdu - no other languages
|
2550 |
-
translated_response = GoogleTranslator(source='en', target='ur').translate(ai_response)
|
2551 |
-
send_whatsjet_message(from_number, translated_response)
|
2552 |
-
except Exception as e:
|
2553 |
-
logger.error(f"[AI] Translation error: {e}")
|
2554 |
-
send_whatsjet_message(from_number, ai_response)
|
2555 |
-
else:
|
2556 |
-
send_whatsjet_message(from_number, ai_response)
|
2557 |
-
|
2558 |
-
# Add to conversation history
|
2559 |
-
context_manager.add_to_history(from_number, query, ai_response)
|
2560 |
-
return
|
2561 |
-
|
2562 |
-
except Exception as e:
|
2563 |
-
logger.error(f"[AI] Error handling query with OpenAI: {e}")
|
2564 |
-
# Fall back to existing logic if OpenAI fails
|
2565 |
-
pass
|
2566 |
-
|
2567 |
-
# Check if this is a non-product query that should get a simple response
|
2568 |
-
non_product_keywords = ['what is', 'who are', 'where is', 'when', 'why', 'how to', 'can you', 'do you', 'is this', 'are you']
|
2569 |
-
is_simple_query = any(keyword in query.lower() for keyword in non_product_keywords)
|
2570 |
-
|
2571 |
-
# Check for company-related queries
|
2572 |
-
company_queries = ['apex', 'aapex', 'apex biotical', 'company', 'about', 'who are you', 'what are you']
|
2573 |
-
is_company_query = any(keyword in query.lower() for keyword in company_queries)
|
2574 |
-
|
2575 |
-
# Check for irrelevant or unclear queries
|
2576 |
-
irrelevant_keywords = ['weather', 'football', 'cricket', 'movie', 'music', 'food', 'restaurant', 'hotel', 'travel', 'shopping', 'fashion', 'beauty', 'sports', 'game', 'entertainment']
|
2577 |
-
is_irrelevant_query = any(keyword in query.lower() for keyword in irrelevant_keywords)
|
2578 |
-
|
2579 |
-
if is_irrelevant_query:
|
2580 |
-
response = get_irrelevant_query_response(query, user_context.get('current_state', 'main_menu'), reply_language)
|
2581 |
-
send_whatsjet_message(from_number, response)
|
2582 |
-
return
|
2583 |
-
|
2584 |
-
if is_company_query:
|
2585 |
-
if reply_language == 'ur':
|
2586 |
-
response = (
|
2587 |
-
"🏥 *Apex Biotical Solutions*\n\n"
|
2588 |
-
"ہم ایک پیشہ ور ویٹرنری فارماسیوٹیکل کمپنی ہیں جو مندرجہ ذیل میں مہارت رکھتے ہیں:\n\n"
|
2589 |
-
"📦 *ہماری مصنوعات:*\n"
|
2590 |
-
"• سانس کی مدد (Respira Aid Plus, Bromacid)\n"
|
2591 |
-
"• جگر کی صحت (Heposel, Liverpex)\n"
|
2592 |
-
"• مدافعتی نظام (EC-Immune)\n"
|
2593 |
-
"• اینٹی بائیوٹکس (Tribiotic, Para C.E)\n"
|
2594 |
-
"• وٹامنز اور سپلیمنٹس (Symodex, Adek Gold)\n\n"
|
2595 |
-
"💬 *مصنوعات دیکھنے کے لیے:*\n"
|
2596 |
-
"• 'main' لکھ کر مین مینو پر جائیں"
|
2597 |
-
)
|
2598 |
-
else:
|
2599 |
-
response = (
|
2600 |
-
"🏥 *Apex Biotical Solutions*\n\n"
|
2601 |
-
"We are a leading veterinary pharmaceutical company specializing in:\n\n"
|
2602 |
-
"📦 *Our Products:*\n"
|
2603 |
-
"• Respiratory support (Respira Aid Plus, Bromacid)\n"
|
2604 |
-
"• Liver health (Heposel, Liverpex)\n"
|
2605 |
-
"• Immune system (EC-Immune)\n"
|
2606 |
-
"• Antibiotics (Tribiotic, Para C.E)\n"
|
2607 |
-
"• Vitamins & supplements (Symodex, Adek Gold)\n\n"
|
2608 |
-
"🌍 *Our Focus:*\n"
|
2609 |
-
"• Professional veterinary solutions\n"
|
2610 |
-
"• Quality pharmaceutical products\n"
|
2611 |
-
"• Comprehensive animal healthcare\n\n"
|
2612 |
-
"💬 *To explore our products:*\n"
|
2613 |
-
"• Type 'main' to see the main menu\n"
|
2614 |
-
"• Type a product name (e.g., 'hydropex', 'respira aid plus')"
|
2615 |
-
)
|
2616 |
-
send_whatsjet_message(from_number, response)
|
2617 |
-
return
|
2618 |
-
|
2619 |
if not OPENAI_API_KEY:
|
2620 |
-
|
2621 |
-
|
2622 |
-
"❌ *AI assistance دستیاب نہیں ہے*\n\n💬 *براہ کرم 'main' لکھ کر مین مینو پر جائیں*")
|
2623 |
-
else:
|
2624 |
-
send_whatsjet_message(from_number,
|
2625 |
-
"❌ AI assistance is not available. Please type 'main' for the menu.")
|
2626 |
return
|
2627 |
|
2628 |
# Create context-aware prompt
|
2629 |
current_state = user_context.get('current_state', 'main_menu')
|
2630 |
current_product = user_context.get('current_product')
|
2631 |
|
2632 |
-
#
|
2633 |
-
|
2634 |
-
|
2635 |
-
|
2636 |
-
|
2637 |
-
# Create comprehensive context for AI
|
2638 |
-
products_context = ""
|
2639 |
-
if all_products:
|
2640 |
-
products_context = "Available Veterinary Products:\n"
|
2641 |
-
for i, product in enumerate(all_products[:30], 1): # Limit to first 30 products for context
|
2642 |
-
product_name = product.get('Product Name', 'N/A')
|
2643 |
-
category = product.get('Category', 'N/A')
|
2644 |
-
products_context += f"{i}. {product_name} - {category}\n"
|
2645 |
-
|
2646 |
-
# Expert-level prompt for comprehensive veterinary assistance
|
2647 |
prompt = f"""
|
2648 |
-
You are
|
2649 |
-
|
2650 |
-
USER QUERY: "{query}"
|
2651 |
-
CURRENT STATE: {current_state}
|
2652 |
-
CURRENT PRODUCT: {current_product.get('Product Name', 'None') if current_product else 'None'}
|
2653 |
|
2654 |
-
|
2655 |
-
{
|
|
|
2656 |
|
2657 |
-
|
2658 |
-
|
2659 |
-
|
2660 |
-
|
2661 |
-
- PRODUCT INQUIRIES: specific product names, categories, or therapeutic areas
|
2662 |
-
- COMPANY INQUIRIES: "apex", "company", "about", "who are you"
|
2663 |
-
- VETERINARY ADVICE: medical questions, treatment recommendations, dosage
|
2664 |
-
- GENERAL QUESTIONS: greetings, navigation, help requests
|
2665 |
-
|
2666 |
-
2. **CONTEXT-AWARE RESPONSE STRATEGY:**
|
2667 |
-
- For COUNT queries: Provide exact numbers with category breakdowns
|
2668 |
-
- For PRODUCT queries: List relevant products with therapeutic benefits, target species, and key features
|
2669 |
-
- For COMPANY queries: Professional company overview with expertise areas
|
2670 |
-
- For VETERINARY ADVICE: Evidence-based recommendations with product suggestions
|
2671 |
-
- For GENERAL queries: Helpful guidance with menu navigation
|
2672 |
-
|
2673 |
-
3. **EXPERT KNOWLEDGE INTEGRATION:**
|
2674 |
-
- Veterinary pharmacology and therapeutics
|
2675 |
-
- Animal health and disease management
|
2676 |
-
- Pharmaceutical product applications
|
2677 |
-
- Professional veterinary communication
|
2678 |
-
- Regulatory and safety considerations
|
2679 |
-
|
2680 |
-
RESPONSE GUIDELINES:
|
2681 |
-
- Be PROFESSIONAL, EXPERT, and HELPFUL
|
2682 |
-
- Use appropriate veterinary terminology
|
2683 |
-
- Include relevant emojis for clarity
|
2684 |
-
- Provide accurate, evidence-based information
|
2685 |
-
- Always include navigation instructions
|
2686 |
-
- Show understanding of user's intent and context
|
2687 |
-
- Be concise but comprehensive
|
2688 |
-
- Maintain professional veterinary standards
|
2689 |
-
|
2690 |
-
IMPORTANT: Demonstrate expert-level veterinary knowledge while being accessible and helpful. Always provide accurate information from the database and professional veterinary guidance.
|
2691 |
"""
|
2692 |
|
2693 |
response = openai.ChatCompletion.create(
|
2694 |
model="gpt-4o",
|
2695 |
messages=[{"role": "user", "content": prompt}],
|
2696 |
-
temperature=0.
|
2697 |
-
max_tokens=
|
2698 |
)
|
2699 |
|
2700 |
ai_response = response.choices[0].message.content.strip()
|
2701 |
|
2702 |
-
#
|
2703 |
-
if 'main' not in ai_response.lower():
|
2704 |
-
if reply_language == 'ur':
|
2705 |
-
ai_response += "\n\n💬 *براہ کرم 'main' لکھ کر مین مینو پر جائیں*"
|
2706 |
-
else:
|
2707 |
-
ai_response += "\n\n💬 *Please type 'main' to go to main menu*"
|
2708 |
-
|
2709 |
-
# Translate response if needed (ENGLISH/URDU ONLY)
|
2710 |
if reply_language == 'ur':
|
2711 |
try:
|
2712 |
-
|
2713 |
-
translated_response = GoogleTranslator(source='en', target='ur').translate(ai_response)
|
2714 |
send_whatsjet_message(from_number, translated_response)
|
2715 |
except Exception as e:
|
2716 |
logger.error(f"[AI] Translation error: {e}")
|
@@ -2723,13 +2054,9 @@ IMPORTANT: Demonstrate expert-level veterinary knowledge while being accessible
|
|
2723 |
|
2724 |
except Exception as e:
|
2725 |
logger.error(f"[AI] Error handling general query: {e}")
|
2726 |
-
#
|
2727 |
-
|
2728 |
-
|
2729 |
-
else:
|
2730 |
-
error_msg = "❌ *An error occurred*\n\n💬 *Please type 'main' to go to main menu*"
|
2731 |
-
|
2732 |
-
send_whatsjet_message(from_number, error_msg)
|
2733 |
context_manager.update_context(
|
2734 |
from_number,
|
2735 |
current_state='main_menu',
|
@@ -2737,58 +2064,6 @@ IMPORTANT: Demonstrate expert-level veterinary knowledge while being accessible
|
|
2737 |
current_menu_options=list(MENU_CONFIG['main_menu']['option_descriptions'].values())
|
2738 |
)
|
2739 |
|
2740 |
-
async def handle_clarification_response(from_number: str, response: str, user_context: dict):
|
2741 |
-
"""Handle user response to clarification questions"""
|
2742 |
-
try:
|
2743 |
-
clean_response = response.strip().lower()
|
2744 |
-
|
2745 |
-
if clean_response in ['yes', 'y', 'apex', 'apex biotical', 'apex biotical solution']:
|
2746 |
-
# User confirmed they want Apex Biotical - provide company information
|
2747 |
-
company_info = (
|
2748 |
-
"🏥 *Apex Biotical Solutions*\n\n"
|
2749 |
-
"We are a leading veterinary pharmaceutical company specializing in:\n\n"
|
2750 |
-
"📦 *Our Products:*\n"
|
2751 |
-
"• Respiratory support (Respira Aid Plus, Bromacid)\n"
|
2752 |
-
"• Liver health (Heposel, Liverpex)\n"
|
2753 |
-
"• Immune system (EC-Immune)\n"
|
2754 |
-
"• Antibiotics (Tribiotic, Para C.E)\n"
|
2755 |
-
"• Vitamins & supplements (Symodex, Adek Gold)\n\n"
|
2756 |
-
"🌍 *Our Focus:*\n"
|
2757 |
-
"• Professional veterinary solutions\n"
|
2758 |
-
"• Quality pharmaceutical products\n"
|
2759 |
-
"• Comprehensive animal healthcare\n\n"
|
2760 |
-
"💬 *To explore our products:*\n"
|
2761 |
-
"• Type a product name (e.g., 'hydropex', 'respira aid plus')\n"
|
2762 |
-
"• Type 'main' to see the main menu\n"
|
2763 |
-
"• Ask about specific symptoms or conditions"
|
2764 |
-
)
|
2765 |
-
|
2766 |
-
send_whatsjet_message(from_number, company_info)
|
2767 |
-
|
2768 |
-
# Update context
|
2769 |
-
user_context['awaiting_clarification'] = False
|
2770 |
-
user_context.pop('clarification_product', None)
|
2771 |
-
context_manager.update_context(from_number, **user_context)
|
2772 |
-
|
2773 |
-
else:
|
2774 |
-
# User didn't confirm - use OpenAI for intelligent response
|
2775 |
-
if OPENAI_API_KEY:
|
2776 |
-
await handle_ai_chat_mode(from_number, response, 'en')
|
2777 |
-
else:
|
2778 |
-
send_whatsjet_message(from_number, "💬 *Type 'main' to go to main menu or ask another question.*")
|
2779 |
-
|
2780 |
-
# Clear clarification context
|
2781 |
-
user_context['awaiting_clarification'] = False
|
2782 |
-
user_context.pop('clarification_product', None)
|
2783 |
-
context_manager.update_context(from_number, **user_context)
|
2784 |
-
|
2785 |
-
except Exception as e:
|
2786 |
-
logger.error(f"[Clarification] Error: {str(e)}")
|
2787 |
-
send_whatsjet_message(from_number, "❌ *An error occurred. Please type 'main' to go to main menu.*")
|
2788 |
-
user_context['awaiting_clarification'] = False
|
2789 |
-
user_context.pop('clarification_product', None)
|
2790 |
-
context_manager.update_context(from_number, **user_context)
|
2791 |
-
|
2792 |
async def handle_contact_request(from_number: str):
|
2793 |
"""Handle contact request"""
|
2794 |
try:
|
@@ -3298,14 +2573,13 @@ Response:
|
|
3298 |
else:
|
3299 |
ai_response += "\n\n💬 *Type 'main' to return to main menu*"
|
3300 |
|
3301 |
-
# Translate response if needed
|
3302 |
if reply_language == 'ur':
|
3303 |
try:
|
3304 |
# Get all product and category names
|
3305 |
product_names = [str(p.get('Product Name', '')) for p in all_products if p.get('Product Name')]
|
3306 |
category_names = list(set([str(p.get('Category', '')) for p in all_products if p.get('Category')]))
|
3307 |
-
|
3308 |
-
translated_response = GoogleTranslator(source='en', target='ur').translate(ai_response)
|
3309 |
# Restore English terms
|
3310 |
translated_response = restore_english_terms(translated_response, ai_response, product_names, category_names)
|
3311 |
send_whatsjet_message(from_number, translated_response)
|
@@ -3522,10 +2796,7 @@ def generate_veterinary_product_response_with_media(product_info: Dict[str, Any]
|
|
3522 |
def clean_text(text):
|
3523 |
if pd.isna(text) or text is None:
|
3524 |
return "Not specified"
|
3525 |
-
|
3526 |
-
# Apply special character cleaning
|
3527 |
-
cleaned = clean_special_characters(cleaned)
|
3528 |
-
return cleaned
|
3529 |
|
3530 |
product_name = clean_text(product_info.get('Product Name', ''))
|
3531 |
product_type = clean_text(product_info.get('Type', ''))
|
@@ -3572,7 +2843,7 @@ def ensure_images_dir():
|
|
3572 |
logger.info(f"[Image] Ensured images directory exists: {images_dir}")
|
3573 |
|
3574 |
# New feature: Send product image with caption (product details)
|
3575 |
-
async def send_product_image_with_caption(from_number: str, product: Dict[str, Any], user_context: Dict[str, Any]
|
3576 |
"""
|
3577 |
Send product image (if available) with product details as caption in a single WhatsApp message.
|
3578 |
Only uses cPanel images from https://amgocus.com/uploads/images/
|
@@ -3580,7 +2851,7 @@ async def send_product_image_with_caption(from_number: str, product: Dict[str, A
|
|
3580 |
"""
|
3581 |
ensure_images_dir()
|
3582 |
product_name = product.get('Product Name', 'Unknown Product')
|
3583 |
-
details = generate_veterinary_product_response(product, user_context
|
3584 |
|
3585 |
logger.info(f"[Product] Processing cPanel image for product: {product_name}")
|
3586 |
|
@@ -3682,7 +2953,7 @@ async def test_product_image_with_caption(phone: str):
|
|
3682 |
product = products_df.iloc[0].to_dict()
|
3683 |
user_context = {}
|
3684 |
|
3685 |
-
await send_product_image_with_caption(phone, product, user_context
|
3686 |
|
3687 |
return {
|
3688 |
"success": True,
|
@@ -4047,9 +3318,11 @@ async def handle_veterinary_product_followup(selection: str, from_number: str) -
|
|
4047 |
current_menu='contact_request',
|
4048 |
current_menu_options=['Provide contact details']
|
4049 |
)
|
|
|
4050 |
elif selection == '2':
|
4051 |
# Inquire about Product Availability
|
4052 |
await handle_availability_inquiry(from_number, user_context)
|
|
|
4053 |
elif selection == '3':
|
4054 |
welcome_msg = generate_veterinary_welcome_message(from_number, user_context)
|
4055 |
send_whatsjet_message(from_number, welcome_msg)
|
@@ -4174,17 +3447,14 @@ async def handle_voice_message_complete(from_number: str, msg: dict):
|
|
4174 |
detected_lang = detect(transcribed_text)
|
4175 |
logger.info(f"[Voice] Raw detected language: {detected_lang}")
|
4176 |
|
4177 |
-
# STRICTLY ENGLISH OR URDU ONLY -
|
|
|
4178 |
if detected_lang in ['en', 'ur']:
|
4179 |
reply_language = detected_lang
|
4180 |
-
logger.info(f"[Voice] Valid language detected: {detected_lang}")
|
4181 |
else:
|
4182 |
-
#
|
4183 |
reply_language = 'en'
|
4184 |
-
logger.warning(f"[Voice]
|
4185 |
-
# If it's clearly not English/Urdu, mark as unclear
|
4186 |
-
if detected_lang not in ['en', 'ur', 'unknown']:
|
4187 |
-
logger.warning(f"[Voice] Non-English/Urdu language detected: {detected_lang}")
|
4188 |
|
4189 |
# Check if text contains Urdu/Arabic characters or Islamic greetings
|
4190 |
urdu_arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]')
|
@@ -4204,12 +3474,11 @@ async def handle_voice_message_complete(from_number: str, msg: dict):
|
|
4204 |
logger.warning(f"[Voice] Language detection failed: {e}, defaulting to English")
|
4205 |
reply_language = 'en'
|
4206 |
|
4207 |
-
# For Urdu voice notes, translate to English for processing
|
4208 |
processing_text = transcribed_text
|
4209 |
if reply_language == 'ur' and detected_lang == 'ur':
|
4210 |
try:
|
4211 |
logger.info(f"[Voice] Translating Urdu voice note to English for processing")
|
4212 |
-
# Only translate from Urdu to English - no other languages
|
4213 |
translated_text = GoogleTranslator(source='ur', target='en').translate(transcribed_text)
|
4214 |
processing_text = translated_text
|
4215 |
logger.info(f"[Voice] Translated to English: {translated_text}")
|
@@ -4217,17 +3486,6 @@ async def handle_voice_message_complete(from_number: str, msg: dict):
|
|
4217 |
logger.error(f"[Voice] Translation failed: {e}")
|
4218 |
# If translation fails, use original text
|
4219 |
processing_text = transcribed_text
|
4220 |
-
elif detected_lang not in ['en', 'ur']:
|
4221 |
-
# If language is not English or Urdu, reject it
|
4222 |
-
logger.warning(f"[Voice] Non-English/Urdu language detected: {detected_lang}")
|
4223 |
-
send_whatsjet_message(from_number,
|
4224 |
-
"🎤 *Voice Message Issue*\n\n"
|
4225 |
-
"I can only process voice messages in English or Urdu.\n\n"
|
4226 |
-
"💡 *Please:*\n"
|
4227 |
-
"• Speak in English or Urdu only\n"
|
4228 |
-
"• Send a text message instead\n"
|
4229 |
-
"• Type 'main' to see menu options")
|
4230 |
-
return
|
4231 |
|
4232 |
# Determine reply language - always respond in English or Urdu
|
4233 |
if detected_lang == 'ur':
|
@@ -4239,22 +3497,33 @@ async def handle_voice_message_complete(from_number: str, msg: dict):
|
|
4239 |
logger.info(f"[Voice] Reply language set to: {reply_language}")
|
4240 |
|
4241 |
# Check if this is a greeting in voice note (check both original and translated)
|
4242 |
-
user_context = context_manager.get_context(from_number)
|
4243 |
-
current_state = user_context.get('current_state', 'main_menu')
|
4244 |
if is_greeting(transcribed_text) or is_greeting(processing_text):
|
4245 |
logger.info(f"[Voice] Greeting detected in voice note: {transcribed_text}")
|
|
|
|
|
|
|
|
|
|
|
4246 |
if current_state == 'ai_chat_mode':
|
4247 |
logger.info(f"[Voice] User is in AI chat mode, treating greeting as AI query instead of menu trigger")
|
|
|
4248 |
await handle_general_query_with_ai(from_number, processing_text, user_context, reply_language)
|
4249 |
return
|
4250 |
else:
|
|
|
4251 |
welcome_msg = generate_veterinary_welcome_message(from_number, user_context)
|
4252 |
send_whatsjet_message(from_number, welcome_msg)
|
4253 |
context_manager.update_context(from_number, current_state='main_menu', current_menu='main_menu', current_menu_options=list(MENU_CONFIG['main_menu']['option_descriptions'].values()))
|
4254 |
return
|
4255 |
-
|
4256 |
-
|
4257 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
4258 |
|
4259 |
except Exception as e:
|
4260 |
logger.error(f"[Voice] Error processing voice message: {e}")
|
@@ -4430,7 +3699,7 @@ async def test_send_product_image(phone: str, product_name: str = "Bromacid"):
|
|
4430 |
user_context = context_manager.get_context(phone)
|
4431 |
|
4432 |
logger.info(f"[Test] Testing send_product_image_with_caption for product: {product_name}")
|
4433 |
-
await send_product_image_with_caption(phone, product, user_context
|
4434 |
|
4435 |
return {
|
4436 |
"status": "sent",
|
@@ -4443,53 +3712,181 @@ async def test_send_product_image(phone: str, product_name: str = "Bromacid"):
|
|
4443 |
return {"error": str(e)}
|
4444 |
|
4445 |
async def handle_intelligent_product_inquiry(from_number: str, query: str, user_context: dict, reply_language: str = 'en'):
|
4446 |
-
"""Handle product inquiry with OpenAI intelligence and media support
|
4447 |
try:
|
|
|
4448 |
products = get_veterinary_product_matches(query)
|
|
|
4449 |
if products:
|
|
|
4450 |
if len(products) > 1:
|
4451 |
-
#
|
4452 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4453 |
for i, product in enumerate(products, 1):
|
4454 |
-
product_name = product.get('Product Name', '
|
4455 |
-
category = product.get('Category', '')
|
4456 |
-
|
4457 |
-
message += f"{i}
|
4458 |
-
|
4459 |
-
|
4460 |
-
|
4461 |
-
|
4462 |
-
|
4463 |
-
|
4464 |
-
|
4465 |
-
|
4466 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4467 |
context_manager.update_context(
|
4468 |
-
from_number,
|
4469 |
current_state='intelligent_products_menu',
|
4470 |
current_menu='intelligent_products_menu',
|
4471 |
-
current_menu_options=[
|
4472 |
-
available_products=products
|
|
|
4473 |
)
|
4474 |
-
|
|
|
|
|
|
|
4475 |
else:
|
4476 |
-
# Single product found - show
|
4477 |
selected_product = products[0]
|
4478 |
context_manager.update_context(
|
4479 |
-
from_number,
|
4480 |
-
current_product=selected_product,
|
4481 |
current_state='product_inquiry',
|
4482 |
current_menu='product_inquiry',
|
4483 |
current_menu_options=list(MENU_CONFIG['product_inquiry']['option_descriptions'].values())
|
4484 |
)
|
|
|
|
|
4485 |
await send_product_image_with_caption(from_number, selected_product, user_context)
|
4486 |
-
|
|
|
|
|
|
|
4487 |
else:
|
4488 |
-
#
|
4489 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4490 |
except Exception as e:
|
4491 |
-
logger.error(f"Error in
|
4492 |
-
|
|
|
|
|
|
|
4493 |
|
4494 |
async def handle_contact_request(from_number: str):
|
4495 |
"""Handle contact request"""
|
@@ -4543,4 +3940,4 @@ if __name__ == "__main__":
|
|
4543 |
|
4544 |
# Launch FastAPI app
|
4545 |
import uvicorn
|
4546 |
-
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|
129 |
'name': 'Main Menu',
|
130 |
'valid_options': ['1', '2', '3', '4'],
|
131 |
'option_descriptions': {
|
132 |
+
'1': 'Search Products',
|
133 |
'2': 'Browse Categories',
|
134 |
'3': 'Download Catalog',
|
135 |
'4': 'Chat with Veterinary AI Assistant'
|
|
|
275 |
return None
|
276 |
|
277 |
async def transcribe_voice_with_openai(file_path: str) -> str:
|
278 |
+
"""Transcribe voice file using OpenAI Whisper with comprehensive veterinary domain system prompt"""
|
279 |
try:
|
280 |
# Check if file exists and has content
|
281 |
if not os.path.exists(file_path):
|
|
|
289 |
|
290 |
logger.info(f"[Transcribe] Transcribing file: {file_path} (size: {file_size} bytes)")
|
291 |
|
292 |
+
# Comprehensive system prompt for veterinary WhatsApp assistant
|
293 |
system_prompt = """
|
294 |
+
You are transcribing voice messages for Apex Biotical Veterinary WhatsApp Assistant. This is a professional veterinary products chatbot.
|
295 |
|
296 |
+
CRITICAL: TRANSCRIBE ONLY ENGLISH OR URDU SPEECH - NOTHING ELSE
|
297 |
+
|
298 |
+
IMPORTANT RULES:
|
299 |
+
1. ONLY transcribe English or Urdu speech
|
300 |
+
2. If you hear unclear audio, transcribe as English
|
301 |
+
3. If you hear mixed languages, transcribe as English
|
302 |
+
4. Never transcribe gibberish or random characters
|
303 |
+
5. If audio is unclear, transcribe as "unclear audio"
|
304 |
+
6. Keep transcriptions simple and clean
|
305 |
|
306 |
PRODUCT NAMES (exact spelling required):
|
307 |
- Hydropex, Respira Aid Plus, Heposel, Bromacid, Hexatox
|
|
|
310 |
- Apvita Plus, B-G Aspro-C, EC-Immune, Liverpex, Symodex
|
311 |
- Respira Aid, Adek Gold, Immuno DX
|
312 |
|
|
|
|
|
|
|
|
|
313 |
MENU COMMANDS:
|
314 |
+
- Numbers: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
|
315 |
+
- Navigation: main, menu, back, home, start
|
316 |
+
- Options: option, number, choice, select
|
317 |
|
318 |
GREETINGS:
|
319 |
- English: hi, hello, hey, good morning, good afternoon, good evening
|
320 |
- Urdu: salam, assalamu alaikum, adaab, namaste, khuda hafiz
|
321 |
|
322 |
TRANSCRIPTION RULES:
|
323 |
+
1. Transcribe exactly what you hear in English or Urdu
|
324 |
+
2. Convert numbers to digits (one->1, two->2, etc.)
|
325 |
+
3. Preserve product names exactly
|
326 |
+
4. If unclear, transcribe as "unclear audio"
|
327 |
+
5. Keep it simple and clean
|
328 |
+
6. No random characters or mixed languages
|
329 |
|
330 |
EXAMPLES:
|
331 |
- "hydropex" -> "hydropex"
|
|
|
334 |
- "main menu" -> "main"
|
335 |
- "salam" -> "salam"
|
336 |
- "search products" -> "search products"
|
337 |
+
- Unclear audio -> "unclear audio"
|
|
|
|
|
338 |
"""
|
339 |
|
340 |
+
# First attempt with comprehensive system prompt
|
341 |
with open(file_path, 'rb') as audio_file:
|
342 |
transcript = openai.Audio.transcribe(
|
343 |
model="whisper-1",
|
|
|
356 |
urdu_system_prompt = """
|
357 |
You are transcribing Urdu voice messages for Apex Biotical Veterinary WhatsApp Assistant.
|
358 |
|
|
|
|
|
359 |
PRODUCT NAMES (Urdu/English):
|
360 |
- ہائیڈروپیکس (Hydropex)
|
361 |
- ریسپیرا ایڈ پلس (Respira Aid Plus)
|
|
|
368 |
- فائٹو سال (PHYTO-SAL)
|
369 |
- مائیکوپیکس سپر (Mycopex Super)
|
370 |
|
371 |
+
URDU NUMBERS:
|
372 |
- ایک (1), دو (2), تین (3), چار (4), پانچ (5)
|
373 |
- چھ (6), سات (7), آٹھ (8), نو (9), دس (10)
|
374 |
- گیارہ (11), بارہ (12), تیرہ (13), چودہ (14), پندرہ (15)
|
375 |
- سولہ (16), سترہ (17), اٹھارہ (18), انیس (19), بیس (20)
|
376 |
+
- اکیس (21), بائیس (22), تئیس (23)
|
377 |
|
378 |
URDU GREETINGS:
|
379 |
- سلام (salam), السلام علیکم (assalamu alaikum)
|
|
|
385 |
- کیٹلاگ (catalog), رابطہ (contact), دستیابی (availability)
|
386 |
|
387 |
TRANSCRIPTION RULES:
|
388 |
+
1. Transcribe Urdu words in Urdu script
|
389 |
+
2. Convert Urdu numbers to digits
|
390 |
+
3. Handle mixed Urdu-English speech
|
391 |
4. Preserve product names exactly
|
392 |
+
5. Convert menu selections to numbers
|
|
|
393 |
"""
|
394 |
|
395 |
with open(file_path, 'rb') as audio_file:
|
|
|
402 |
|
403 |
transcribed_text = transcript.text.strip()
|
404 |
logger.info(f"[Transcribe] Second attempt transcribed (Urdu): '{transcribed_text}'")
|
405 |
+
|
406 |
+
# Third attempt with mixed language prompt if still failing
|
407 |
+
if not transcribed_text or len(transcribed_text.strip()) < 2:
|
408 |
+
logger.warning(f"[Transcribe] Second attempt failed, trying with mixed language prompt")
|
409 |
+
|
410 |
+
mixed_system_prompt = """
|
411 |
+
You are transcribing voice messages for a veterinary products WhatsApp assistant. The user may speak in English, Urdu, or a mix of both languages.
|
412 |
+
|
413 |
+
PRODUCT NAMES (exact spelling required):
|
414 |
+
Hydropex, Respira Aid Plus, Heposel, Bromacid, Hexatox, APMA Fort, Para C.E, Tribiotic, PHYTO-SAL, Mycopex Super, Eflin KT-20, Salcozine ST-30, Oftilex UA-10, Biscomin 10, Apvita Plus, B-G Aspro-C, EC-Immune, Liverpex, Symodex, Respira Aid, Adek Gold, Immuno DX
|
415 |
+
|
416 |
+
NUMBERS (convert to digits):
|
417 |
+
English: one->1, two->2, three->3, etc.
|
418 |
+
Urdu: aik->1, ek->1, do->2, teen->3, etc.
|
419 |
+
|
420 |
+
MENU COMMANDS:
|
421 |
+
main, menu, back, home, start, option, number, search, browse, download, catalog, contact, availability
|
422 |
+
|
423 |
+
GREETINGS:
|
424 |
+
hi, hello, salam, assalamu alaikum, adaab, namaste
|
425 |
+
|
426 |
+
TRANSCRIPTION RULES:
|
427 |
+
1. Transcribe exactly what you hear
|
428 |
+
2. Convert numbers to digits
|
429 |
+
3. Preserve product names exactly
|
430 |
+
4. Handle both languages
|
431 |
+
5. Convert menu selections to numbers
|
432 |
+
"""
|
433 |
+
|
434 |
+
with open(file_path, 'rb') as audio_file:
|
435 |
+
transcript = openai.Audio.transcribe(
|
436 |
+
model="whisper-1",
|
437 |
+
file=audio_file,
|
438 |
+
prompt=mixed_system_prompt
|
439 |
+
)
|
440 |
+
|
441 |
+
transcribed_text = transcript.text.strip()
|
442 |
+
logger.info(f"[Transcribe] Third attempt (mixed) transcribed: '{transcribed_text}'")
|
443 |
|
444 |
+
# Final check for empty transcription or unclear audio
|
445 |
if not transcribed_text or len(transcribed_text.strip()) < 2:
|
446 |
logger.warning(f"[Transcribe] Very short or empty transcription: '{transcribed_text}'")
|
447 |
return "unclear audio"
|
448 |
|
449 |
+
# Check for gibberish or mixed characters
|
450 |
+
if len(transcribed_text) > 10 and not re.search(r'[a-zA-Z\u0600-\u06FF]', transcribed_text):
|
451 |
+
logger.warning(f"[Transcribe] Gibberish detected: '{transcribed_text}'")
|
452 |
+
return "unclear audio"
|
453 |
+
|
454 |
+
# Check for too many special characters
|
455 |
special_char_ratio = len(re.findall(r'[^\w\s]', transcribed_text)) / len(transcribed_text)
|
456 |
+
if special_char_ratio > 0.3:
|
457 |
+
logger.warning(f"[Transcribe] Too many special characters: '{transcribed_text}'")
|
458 |
return "unclear audio"
|
459 |
|
460 |
return transcribed_text
|
|
|
475 |
# Remove extra whitespace
|
476 |
processed_text = re.sub(r'\s+', ' ', processed_text)
|
477 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
478 |
# Basic punctuation cleanup
|
479 |
processed_text = processed_text.replace(' ,', ',').replace(' .', '.')
|
480 |
|
|
|
600 |
logger.info(f"[Veterinary Search] Skipping menu selection: '{normalized_query}'")
|
601 |
return []
|
602 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
603 |
scored_matches = []
|
604 |
|
605 |
# Veterinary-specific query expansion
|
|
|
620 |
if category_key in normalized_query:
|
621 |
expanded_queries.extend(categories)
|
622 |
|
623 |
+
# Common veterinary product variations
|
624 |
veterinary_variations = {
|
625 |
+
'hydropex': ['hydropex', 'hydro pex', 'electrolyte', 'dehydration', 'heat stress'],
|
626 |
+
'heposel': ['heposel', 'hepo sel', 'liver tonic', 'hepatoprotective'],
|
627 |
+
'bromacid': ['bromacid', 'brom acid', 'respiratory', 'mucolytic'],
|
628 |
+
'respira aid': ['respira aid', 'respira aid plus', 'respiratory support'],
|
629 |
+
'hexatox': ['hexatox', 'hexa tox', 'liver support', 'kidney support'],
|
630 |
+
'apma fort': ['apma fort', 'mycotoxin', 'liver support'],
|
631 |
+
'para c': ['para c', 'para c.e', 'heat stress', 'paracetamol'],
|
632 |
'tribiotic': ['tribiotic', 'antibiotic', 'respiratory infection'],
|
633 |
+
'phyto-sal': ['phyto-sal', 'phytogenic', 'vitamin supplement'],
|
634 |
+
'mycopex': ['mycopex', 'mycotoxin binder', 'mold'],
|
635 |
+
'oftilex': ['oftilex', 'ofloxacin', 'antibiotic'],
|
636 |
+
'biscomin': ['biscomin', 'oxytetracycline', 'injectable'],
|
637 |
+
'apvita': ['apvita', 'vitamin b', 'amino acid'],
|
638 |
+
'bg aspro': ['bg aspro', 'aspirin', 'vitamin c'],
|
639 |
+
'ec-immune': ['ec-immune', 'immune', 'immunity'],
|
640 |
'liverpex': ['liverpex', 'liver', 'metabolic'],
|
641 |
'symodex': ['symodex', 'multivitamin', 'vitamin'],
|
642 |
+
'adek gold': ['adek gold', 'vitamin', 'multivitamin'],
|
643 |
+
'immuno dx': ['immuno dx', 'immune', 'antioxidant']
|
|
|
|
|
644 |
}
|
645 |
|
646 |
# Add veterinary variations
|
|
|
678 |
best_match_type = "exact"
|
679 |
match_details = {"field": field_name, "query": expanded_query}
|
680 |
|
681 |
+
# Fuzzy matching for close matches
|
682 |
for expanded_query in expanded_queries:
|
683 |
if len(expanded_query) > 3: # Only fuzzy match longer queries
|
684 |
score = fuzz.partial_ratio(normalized_query, field_str) * weight
|
685 |
+
if score > best_score and score > 70:
|
686 |
best_score = score
|
687 |
best_match_type = "fuzzy"
|
688 |
match_details = {"field": field_name, "query": expanded_query}
|
|
|
707 |
return unique_matches
|
708 |
|
709 |
def normalize(text: str) -> str:
|
710 |
+
"""Normalize text for search"""
|
711 |
+
if not text:
|
712 |
+
return ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
713 |
|
714 |
+
# Convert to lowercase and remove extra whitespace
|
715 |
+
normalized = text.lower().strip()
|
716 |
|
717 |
+
# Remove special characters but keep spaces
|
718 |
+
normalized = re.sub(r'[^\w\s]', '', normalized)
|
|
|
719 |
|
720 |
+
# Replace multiple spaces with single space
|
721 |
+
normalized = re.sub(r'\s+', ' ', normalized)
|
|
|
|
|
722 |
|
723 |
+
return normalized
|
|
|
724 |
|
725 |
# Enhanced context management with veterinary domain awareness
|
726 |
class VeterinaryContextManager:
|
|
|
811 |
context_manager = VeterinaryContextManager()
|
812 |
|
813 |
# Enhanced product response with veterinary domain expertise
|
814 |
+
def generate_veterinary_product_response(product_info: Dict[str, Any], user_context: Dict[str, Any]) -> str:
|
815 |
"""Generate comprehensive veterinary product response with intelligent information handling"""
|
816 |
|
817 |
def clean_text(text):
|
818 |
if pd.isna(text) or text is None:
|
819 |
return "Not specified"
|
820 |
+
return str(text).strip()
|
|
|
|
|
|
|
821 |
|
822 |
# Extract product details
|
823 |
product_name = clean_text(product_info.get('Product Name', ''))
|
|
|
838 |
except Exception as e:
|
839 |
logger.warning(f"Error checking PDF link for {product_name}: {e}")
|
840 |
|
841 |
+
# Build the response
|
842 |
+
response = f"""🧪 *Name:* {product_name}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
843 |
📦 *Type:* {product_type}
|
844 |
🏥 *Category:* {category}
|
845 |
💊 *Used For:* {indications}"""
|
846 |
+
|
847 |
+
# Add PDF link if available, in the requested format
|
848 |
+
if pdf_link:
|
849 |
+
response += f"\n\n📄 Product Brochure Available\n🔗 {product_name} PDF:\n{pdf_link}"
|
850 |
+
|
851 |
+
# Add menu options
|
852 |
+
response += f"""
|
853 |
|
854 |
💬 *Available Actions:*
|
855 |
1️⃣ Talk to Veterinary Consultant
|
|
|
866 |
return "N/A"
|
867 |
|
868 |
cleaned = str(text)
|
|
|
|
|
869 |
# Remove or replace problematic characters for PDF
|
870 |
cleaned = cleaned.replace('â€"', '-').replace('â€"', '"').replace('’', "'")
|
871 |
cleaned = cleaned.replace('“', '"').replace('â€', '"').replace('…', '...')
|
|
|
1694 |
# Handle voice messages FIRST - before checking message_body
|
1695 |
if message_type in ['audio', 'voice'] or media_type == 'audio':
|
1696 |
logger.info(f"[Process] Processing voice message from {from_number}")
|
1697 |
+
await handle_voice_message_complete(from_number, msg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1698 |
return
|
1699 |
|
1700 |
# For text messages, check if body exists
|
|
|
1703 |
return
|
1704 |
|
1705 |
message_body = message_body.strip()
|
1706 |
+
|
1707 |
logger.info(f"[Process] Processing {message_type} message from {from_number}: {message_body}")
|
1708 |
|
1709 |
# Get user context
|
1710 |
user_context = context_manager.get_context(from_number)
|
1711 |
current_state = user_context.get('current_state', 'main_menu')
|
1712 |
+
|
1713 |
+
# Update context with last message for intelligent responses
|
1714 |
context_manager.update_context(from_number, last_message=message_body)
|
1715 |
+
|
1716 |
+
# Debug logging
|
1717 |
logger.info(f"[Process] Current state: {current_state}, Message: '{message_body}' from {from_number}")
|
1718 |
+
|
1719 |
+
# Handle text messages
|
1720 |
if not message_body:
|
1721 |
return
|
1722 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1723 |
# Check for greetings with multilingual support
|
1724 |
if is_greeting(message_body):
|
1725 |
# Check if user is currently in AI chat mode - if so, don't trigger menu mode
|
|
|
1730 |
return
|
1731 |
else:
|
1732 |
# Only trigger menu mode if not in AI chat mode
|
1733 |
+
welcome_msg = generate_veterinary_welcome_message()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1734 |
send_whatsjet_message(from_number, welcome_msg)
|
1735 |
context_manager.update_context(
|
1736 |
from_number,
|
1737 |
current_state='main_menu',
|
1738 |
current_menu='main_menu',
|
1739 |
+
current_menu_options=list(MENU_CONFIG['main_menu']['option_descriptions'].values())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1740 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1741 |
return
|
1742 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1743 |
# 🎯 PRIORITY 1: Navigation commands - work from ANY state
|
1744 |
# Check for "main" command - now works for both text and voice
|
1745 |
if current_state != 'main_menu' and current_state != 'ai_chat_mode': # Only check for main if not already in main menu and not in AI chat mode
|
1746 |
mapped_navigation = process_intelligent_voice_command(message_body, current_state, user_context)
|
1747 |
if mapped_navigation == 'main':
|
1748 |
logger.info(f"[Process] Navigation command detected: '{message_body}' -> 'main'")
|
1749 |
+
welcome_msg = generate_veterinary_welcome_message()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1750 |
send_whatsjet_message(from_number, welcome_msg)
|
1751 |
context_manager.update_context(
|
1752 |
from_number,
|
1753 |
current_state='main_menu',
|
1754 |
current_menu='main_menu',
|
1755 |
+
current_menu_options=list(MENU_CONFIG['main_menu']['option_descriptions'].values())
|
|
|
1756 |
)
|
1757 |
return
|
1758 |
|
1759 |
# Also check for text-based navigation commands
|
1760 |
if message_body.lower() in ['main', 'menu', 'start', 'home', 'back']:
|
1761 |
+
welcome_msg = generate_veterinary_welcome_message()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1762 |
send_whatsjet_message(from_number, welcome_msg)
|
1763 |
context_manager.update_context(
|
1764 |
from_number,
|
1765 |
current_state='main_menu',
|
1766 |
current_menu='main_menu',
|
1767 |
+
current_menu_options=list(MENU_CONFIG['main_menu']['option_descriptions'].values())
|
|
|
1768 |
)
|
1769 |
return
|
1770 |
|
1771 |
+
# 🎯 PRIORITY 2: State-specific handling (contact_request, availability_request, ai_chat_mode)
|
1772 |
if current_state == 'contact_request':
|
1773 |
await handle_contact_request_response(from_number, message_body)
|
1774 |
return
|
|
|
1778 |
elif current_state == 'ai_chat_mode':
|
1779 |
await handle_ai_chat_mode(from_number, message_body, reply_language)
|
1780 |
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1781 |
|
1782 |
+
# 🎯 PRIORITY 3: Menu selections - check if this is a valid menu selection for current state
|
1783 |
if current_state in ['main_menu', 'category_selection_menu', 'category_products_menu', 'all_products_menu', 'product_inquiry', 'intelligent_products_menu']:
|
1784 |
# Validate menu selection
|
1785 |
is_valid, error_msg = validate_menu_selection(message_body, current_state, user_context)
|
|
|
1821 |
reply_language='ur'
|
1822 |
)
|
1823 |
message = (
|
1824 |
+
"🤖 ویٹرنری اے آئی اسسٹنٹ\n\n"
|
1825 |
"آپ مجھ سے پوچھ سکتے ہیں:\n"
|
1826 |
+
"• ویٹرنری سوالات\n"
|
1827 |
+
"• پروڈکٹ کی سفارشات\n"
|
1828 |
+
"• علاج کے مشورے\n"
|
1829 |
+
"• عمومی معلومات\n\n"
|
1830 |
"💬 'main' لکھ کر مین مینو پر واپس جائیں۔"
|
1831 |
)
|
1832 |
send_whatsjet_message(from_number, message)
|
|
|
1849 |
send_whatsjet_message(from_number, get_menu_validation_message(current_state, user_context))
|
1850 |
elif current_state == 'all_products_menu':
|
1851 |
# Handle product selection from all products
|
1852 |
+
if products_df is not None and not products_df.empty:
|
1853 |
+
all_products = products_df.to_dict('records')
|
1854 |
+
if message_body.isdigit() and 1 <= int(message_body) <= len(all_products):
|
1855 |
+
selected_product = all_products[int(message_body) - 1]
|
1856 |
+
context_manager.update_context(
|
1857 |
+
from_number,
|
1858 |
+
current_product=selected_product,
|
1859 |
+
current_state='product_inquiry',
|
1860 |
+
current_menu='product_inquiry',
|
1861 |
+
current_menu_options=list(MENU_CONFIG['product_inquiry']['option_descriptions'].values())
|
1862 |
+
)
|
1863 |
+
await send_product_image_with_caption(from_number, selected_product, user_context)
|
1864 |
+
else:
|
1865 |
+
send_whatsjet_message(from_number, get_menu_validation_message(current_state, user_context))
|
1866 |
else:
|
1867 |
+
send_whatsjet_message(from_number, "❌ No products available. Type 'main' to return to main menu.")
|
|
|
1868 |
elif current_state == 'product_inquiry':
|
1869 |
await handle_veterinary_product_followup(message_body, from_number)
|
1870 |
elif current_state == 'intelligent_products_menu':
|
1871 |
+
# Handle product selection from intelligent products menu
|
1872 |
available_products = user_context.get('available_products', [])
|
1873 |
if message_body.isdigit() and 1 <= int(message_body) <= len(available_products):
|
1874 |
selected_product = available_products[int(message_body) - 1]
|
|
|
1886 |
return
|
1887 |
return # Exit after handling menu selection
|
1888 |
|
1889 |
+
# 🎯 PRIORITY 4: Product names - works from ANY menu state
|
1890 |
# This ensures users can say product names like "hydropex", "respira aid plus", etc. from any menu
|
1891 |
logger.info(f"[Process] Checking for product name in message: '{message_body}' from state: {current_state}")
|
1892 |
products = get_veterinary_product_matches(message_body)
|
|
|
1996 |
|
1997 |
async def handle_general_query_with_ai(from_number: str, query: str, user_context: dict, reply_language: str = 'en'):
|
1998 |
"""Handle general queries with OpenAI intelligence"""
|
1999 |
+
# Force Urdu replies for Option 4
|
2000 |
+
reply_language = 'ur'
|
2001 |
+
logger.info(f"[AI General] Forcing reply_language to Urdu for Option 4.")
|
2002 |
|
2003 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2004 |
if not OPENAI_API_KEY:
|
2005 |
+
send_whatsjet_message(from_number,
|
2006 |
+
"❌ AI assistance is not available. Please try searching for a specific product or type 'main' for the menu.")
|
|
|
|
|
|
|
|
|
2007 |
return
|
2008 |
|
2009 |
# Create context-aware prompt
|
2010 |
current_state = user_context.get('current_state', 'main_menu')
|
2011 |
current_product = user_context.get('current_product')
|
2012 |
|
2013 |
+
# --- SYSTEM PROMPT FOR GENERAL/OUT-OF-MENU QUERIES ---
|
2014 |
+
# This prompt ensures the assistant answers professionally, accurately, and helpfully.
|
2015 |
+
# If the query is about products, list all relevant products (not just one) with brief details.
|
2016 |
+
# If the query is general, provide a concise, expert veterinary answer.
|
2017 |
+
# Clarify when a query is outside the menu system and offer to return to the main menu if needed.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2018 |
prompt = f"""
|
2019 |
+
You are a professional veterinary product assistant for Apex Biotical, helping users on WhatsApp.
|
2020 |
+
Always answer in a clear, accurate, and helpful manner.
|
|
|
|
|
|
|
2021 |
|
2022 |
+
User Query: "{query}"
|
2023 |
+
Current State: {current_state}
|
2024 |
+
Current Product: {current_product.get('Product Name', 'None') if current_product else 'None'}
|
2025 |
|
2026 |
+
If the user asks about products (e.g., 'poultry products', 'respiratory medicine'), list ALL relevant products from the database with a short description for each. If there are many, summarize or group them.
|
2027 |
+
If the user asks a general veterinary question, provide a concise, expert answer.
|
2028 |
+
If the query is outside the menu system, politely clarify and offer to return to the main menu (type 'main').
|
2029 |
+
Always keep responses professional, concise, and user-friendly.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2030 |
"""
|
2031 |
|
2032 |
response = openai.ChatCompletion.create(
|
2033 |
model="gpt-4o",
|
2034 |
messages=[{"role": "user", "content": prompt}],
|
2035 |
+
temperature=0.7,
|
2036 |
+
max_tokens=300
|
2037 |
)
|
2038 |
|
2039 |
ai_response = response.choices[0].message.content.strip()
|
2040 |
|
2041 |
+
# Translate response if needed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2042 |
if reply_language == 'ur':
|
2043 |
try:
|
2044 |
+
translated_response = GoogleTranslator(source='auto', target='ur').translate(ai_response)
|
|
|
2045 |
send_whatsjet_message(from_number, translated_response)
|
2046 |
except Exception as e:
|
2047 |
logger.error(f"[AI] Translation error: {e}")
|
|
|
2054 |
|
2055 |
except Exception as e:
|
2056 |
logger.error(f"[AI] Error handling general query: {e}")
|
2057 |
+
# Instead of sending a generic error, return to main menu
|
2058 |
+
welcome_msg = generate_veterinary_welcome_message()
|
2059 |
+
send_whatsjet_message(from_number, welcome_msg)
|
|
|
|
|
|
|
|
|
2060 |
context_manager.update_context(
|
2061 |
from_number,
|
2062 |
current_state='main_menu',
|
|
|
2064 |
current_menu_options=list(MENU_CONFIG['main_menu']['option_descriptions'].values())
|
2065 |
)
|
2066 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2067 |
async def handle_contact_request(from_number: str):
|
2068 |
"""Handle contact request"""
|
2069 |
try:
|
|
|
2573 |
else:
|
2574 |
ai_response += "\n\n💬 *Type 'main' to return to main menu*"
|
2575 |
|
2576 |
+
# Translate response if needed
|
2577 |
if reply_language == 'ur':
|
2578 |
try:
|
2579 |
# Get all product and category names
|
2580 |
product_names = [str(p.get('Product Name', '')) for p in all_products if p.get('Product Name')]
|
2581 |
category_names = list(set([str(p.get('Category', '')) for p in all_products if p.get('Category')]))
|
2582 |
+
translated_response = GoogleTranslator(source='auto', target='ur').translate(ai_response)
|
|
|
2583 |
# Restore English terms
|
2584 |
translated_response = restore_english_terms(translated_response, ai_response, product_names, category_names)
|
2585 |
send_whatsjet_message(from_number, translated_response)
|
|
|
2796 |
def clean_text(text):
|
2797 |
if pd.isna(text) or text is None:
|
2798 |
return "Not specified"
|
2799 |
+
return str(text).strip()
|
|
|
|
|
|
|
2800 |
|
2801 |
product_name = clean_text(product_info.get('Product Name', ''))
|
2802 |
product_type = clean_text(product_info.get('Type', ''))
|
|
|
2843 |
logger.info(f"[Image] Ensured images directory exists: {images_dir}")
|
2844 |
|
2845 |
# New feature: Send product image with caption (product details)
|
2846 |
+
async def send_product_image_with_caption(from_number: str, product: Dict[str, Any], user_context: Dict[str, Any]):
|
2847 |
"""
|
2848 |
Send product image (if available) with product details as caption in a single WhatsApp message.
|
2849 |
Only uses cPanel images from https://amgocus.com/uploads/images/
|
|
|
2851 |
"""
|
2852 |
ensure_images_dir()
|
2853 |
product_name = product.get('Product Name', 'Unknown Product')
|
2854 |
+
details = generate_veterinary_product_response(product, user_context)
|
2855 |
|
2856 |
logger.info(f"[Product] Processing cPanel image for product: {product_name}")
|
2857 |
|
|
|
2953 |
product = products_df.iloc[0].to_dict()
|
2954 |
user_context = {}
|
2955 |
|
2956 |
+
await send_product_image_with_caption(phone, product, user_context)
|
2957 |
|
2958 |
return {
|
2959 |
"success": True,
|
|
|
3318 |
current_menu='contact_request',
|
3319 |
current_menu_options=['Provide contact details']
|
3320 |
)
|
3321 |
+
|
3322 |
elif selection == '2':
|
3323 |
# Inquire about Product Availability
|
3324 |
await handle_availability_inquiry(from_number, user_context)
|
3325 |
+
|
3326 |
elif selection == '3':
|
3327 |
welcome_msg = generate_veterinary_welcome_message(from_number, user_context)
|
3328 |
send_whatsjet_message(from_number, welcome_msg)
|
|
|
3447 |
detected_lang = detect(transcribed_text)
|
3448 |
logger.info(f"[Voice] Raw detected language: {detected_lang}")
|
3449 |
|
3450 |
+
# STRICTLY ENGLISH OR URDU ONLY - NO OTHER LANGUAGES
|
3451 |
+
# Only allow English and Urdu, reject everything else
|
3452 |
if detected_lang in ['en', 'ur']:
|
3453 |
reply_language = detected_lang
|
|
|
3454 |
else:
|
3455 |
+
# Force any other language to English
|
3456 |
reply_language = 'en'
|
3457 |
+
logger.warning(f"[Voice] Detected language '{detected_lang}' is not English or Urdu, forcing to English")
|
|
|
|
|
|
|
3458 |
|
3459 |
# Check if text contains Urdu/Arabic characters or Islamic greetings
|
3460 |
urdu_arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]')
|
|
|
3474 |
logger.warning(f"[Voice] Language detection failed: {e}, defaulting to English")
|
3475 |
reply_language = 'en'
|
3476 |
|
3477 |
+
# For Urdu voice notes, translate to English for processing
|
3478 |
processing_text = transcribed_text
|
3479 |
if reply_language == 'ur' and detected_lang == 'ur':
|
3480 |
try:
|
3481 |
logger.info(f"[Voice] Translating Urdu voice note to English for processing")
|
|
|
3482 |
translated_text = GoogleTranslator(source='ur', target='en').translate(transcribed_text)
|
3483 |
processing_text = translated_text
|
3484 |
logger.info(f"[Voice] Translated to English: {translated_text}")
|
|
|
3486 |
logger.error(f"[Voice] Translation failed: {e}")
|
3487 |
# If translation fails, use original text
|
3488 |
processing_text = transcribed_text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3489 |
|
3490 |
# Determine reply language - always respond in English or Urdu
|
3491 |
if detected_lang == 'ur':
|
|
|
3497 |
logger.info(f"[Voice] Reply language set to: {reply_language}")
|
3498 |
|
3499 |
# Check if this is a greeting in voice note (check both original and translated)
|
|
|
|
|
3500 |
if is_greeting(transcribed_text) or is_greeting(processing_text):
|
3501 |
logger.info(f"[Voice] Greeting detected in voice note: {transcribed_text}")
|
3502 |
+
|
3503 |
+
# Check if user is currently in AI chat mode - if so, don't trigger menu mode
|
3504 |
+
user_context = context_manager.get_context(from_number)
|
3505 |
+
current_state = user_context.get('current_state', 'main_menu')
|
3506 |
+
|
3507 |
if current_state == 'ai_chat_mode':
|
3508 |
logger.info(f"[Voice] User is in AI chat mode, treating greeting as AI query instead of menu trigger")
|
3509 |
+
# Treat greeting as a general query in AI chat mode
|
3510 |
await handle_general_query_with_ai(from_number, processing_text, user_context, reply_language)
|
3511 |
return
|
3512 |
else:
|
3513 |
+
# Only trigger menu mode if not in AI chat mode
|
3514 |
welcome_msg = generate_veterinary_welcome_message(from_number, user_context)
|
3515 |
send_whatsjet_message(from_number, welcome_msg)
|
3516 |
context_manager.update_context(from_number, current_state='main_menu', current_menu='main_menu', current_menu_options=list(MENU_CONFIG['main_menu']['option_descriptions'].values()))
|
3517 |
return
|
3518 |
+
|
3519 |
+
# Process the translated text using the same strict state-based logic as text messages
|
3520 |
+
# This ensures voice messages follow the same menu and state rules as text messages
|
3521 |
+
await process_incoming_message(from_number, {
|
3522 |
+
'body': processing_text, # Use translated text for processing
|
3523 |
+
'type': 'text',
|
3524 |
+
'reply_language': reply_language,
|
3525 |
+
'original_transcription': transcribed_text # Keep original for context
|
3526 |
+
})
|
3527 |
|
3528 |
except Exception as e:
|
3529 |
logger.error(f"[Voice] Error processing voice message: {e}")
|
|
|
3699 |
user_context = context_manager.get_context(phone)
|
3700 |
|
3701 |
logger.info(f"[Test] Testing send_product_image_with_caption for product: {product_name}")
|
3702 |
+
await send_product_image_with_caption(phone, product, user_context)
|
3703 |
|
3704 |
return {
|
3705 |
"status": "sent",
|
|
|
3712 |
return {"error": str(e)}
|
3713 |
|
3714 |
async def handle_intelligent_product_inquiry(from_number: str, query: str, user_context: dict, reply_language: str = 'en'):
|
3715 |
+
"""Handle product inquiry with OpenAI intelligence and media support"""
|
3716 |
try:
|
3717 |
+
# First try direct product search
|
3718 |
products = get_veterinary_product_matches(query)
|
3719 |
+
|
3720 |
if products:
|
3721 |
+
# Check if this is a broad/category query (multiple products found)
|
3722 |
if len(products) > 1:
|
3723 |
+
# Use OpenAI to generate a professional summary and list all products
|
3724 |
+
if OPENAI_API_KEY:
|
3725 |
+
try:
|
3726 |
+
# Create a comprehensive prompt for multiple products
|
3727 |
+
products_info = []
|
3728 |
+
for i, product in enumerate(products, 1):
|
3729 |
+
product_name = product.get('Product Name', 'N/A')
|
3730 |
+
category = product.get('Category', 'N/A')
|
3731 |
+
target_species = product.get('Target Species', 'N/A')
|
3732 |
+
products_info.append(f"{i}. {product_name} - {category} ({target_species})")
|
3733 |
+
|
3734 |
+
products_text = "\n".join(products_info)
|
3735 |
+
|
3736 |
+
prompt = f"""
|
3737 |
+
You are a professional veterinary product assistant for Apex Biotical. The user asked about "{query}" and we found {len(products)} relevant products.
|
3738 |
+
|
3739 |
+
Available Products:
|
3740 |
+
{products_text}
|
3741 |
+
|
3742 |
+
Please provide:
|
3743 |
+
1. A professional, welcoming response acknowledging their query
|
3744 |
+
2. A brief summary of what these products are for (if it's a category like "poultry products", explain the category)
|
3745 |
+
3. List all products with their numbers and brief descriptions
|
3746 |
+
4. Clear instructions on how to proceed
|
3747 |
+
|
3748 |
+
Format your response professionally with emojis and clear structure. Keep it concise but informative.
|
3749 |
+
"""
|
3750 |
+
|
3751 |
+
response = openai.ChatCompletion.create(
|
3752 |
+
model="gpt-4o",
|
3753 |
+
messages=[{"role": "user", "content": prompt}],
|
3754 |
+
temperature=0.7,
|
3755 |
+
max_tokens=400
|
3756 |
+
)
|
3757 |
+
|
3758 |
+
ai_response = response.choices[0].message['content'].strip()
|
3759 |
+
|
3760 |
+
# Add instructions for selection
|
3761 |
+
selection_instructions = (
|
3762 |
+
f"\n\n💬 *To view detailed information about any product, reply with its number (1-{len(products)})*\n"
|
3763 |
+
"💬 *Type 'main' to return to the main menu*"
|
3764 |
+
)
|
3765 |
+
|
3766 |
+
full_response = ai_response + selection_instructions
|
3767 |
+
|
3768 |
+
# Translate response if needed
|
3769 |
+
if reply_language == 'ur':
|
3770 |
+
try:
|
3771 |
+
translated_response = GoogleTranslator(source='auto', target='ur').translate(full_response)
|
3772 |
+
send_whatsjet_message(from_number, translated_response)
|
3773 |
+
except Exception as e:
|
3774 |
+
logger.error(f"[AI] Translation error: {e}")
|
3775 |
+
send_whatsjet_message(from_number, full_response)
|
3776 |
+
else:
|
3777 |
+
send_whatsjet_message(from_number, full_response)
|
3778 |
+
|
3779 |
+
# Store the product list in context for selection handling
|
3780 |
+
context_manager.update_context(
|
3781 |
+
from_number,
|
3782 |
+
current_state='intelligent_products_menu',
|
3783 |
+
current_menu='intelligent_products_menu',
|
3784 |
+
current_menu_options=[f"Product {i+1}" for i in range(len(products))],
|
3785 |
+
available_products=products,
|
3786 |
+
last_query=query
|
3787 |
+
)
|
3788 |
+
|
3789 |
+
# Add to conversation history
|
3790 |
+
context_manager.add_to_history(from_number, query, full_response)
|
3791 |
+
return
|
3792 |
+
|
3793 |
+
except Exception as e:
|
3794 |
+
logger.error(f"[AI] Error generating product summary: {e}")
|
3795 |
+
# Fall back to simple listing if AI fails
|
3796 |
+
pass
|
3797 |
+
|
3798 |
+
# Fallback: Simple listing without AI
|
3799 |
+
message = f"🔍 *Found {len(products)} products matching '{query}':*\n\n"
|
3800 |
+
|
3801 |
for i, product in enumerate(products, 1):
|
3802 |
+
product_name = product.get('Product Name', 'N/A')
|
3803 |
+
category = product.get('Category', 'N/A')
|
3804 |
+
target_species = product.get('Target Species', 'N/A')
|
3805 |
+
message += f"{format_number_with_emoji(i)} {product_name}\n"
|
3806 |
+
message += f" 📦 {category} ({target_species})\n\n"
|
3807 |
+
|
3808 |
+
message += (
|
3809 |
+
f"💬 *To view detailed information about any product, reply with its number (1-{len(products)})*\n"
|
3810 |
+
"💬 *Type 'main' to return to the main menu*"
|
3811 |
+
)
|
3812 |
+
|
3813 |
+
# Translate response if needed
|
3814 |
+
if reply_language == 'ur':
|
3815 |
+
try:
|
3816 |
+
translated_message = GoogleTranslator(source='auto', target='ur').translate(message)
|
3817 |
+
send_whatsjet_message(from_number, translated_message)
|
3818 |
+
except Exception as e:
|
3819 |
+
logger.error(f"[AI] Translation error: {e}")
|
3820 |
+
send_whatsjet_message(from_number, message)
|
3821 |
+
else:
|
3822 |
+
send_whatsjet_message(from_number, message)
|
3823 |
+
|
3824 |
+
# Store the product list in context for selection handling
|
3825 |
context_manager.update_context(
|
3826 |
+
from_number,
|
3827 |
current_state='intelligent_products_menu',
|
3828 |
current_menu='intelligent_products_menu',
|
3829 |
+
current_menu_options=[f"Product {i+1}" for i in range(len(products))],
|
3830 |
+
available_products=products,
|
3831 |
+
last_query=query
|
3832 |
)
|
3833 |
+
|
3834 |
+
# Add to conversation history
|
3835 |
+
context_manager.add_to_history(from_number, query, message)
|
3836 |
+
|
3837 |
else:
|
3838 |
+
# Single product found - show detailed information with media support
|
3839 |
selected_product = products[0]
|
3840 |
context_manager.update_context(
|
3841 |
+
from_number,
|
3842 |
+
current_product=selected_product,
|
3843 |
current_state='product_inquiry',
|
3844 |
current_menu='product_inquiry',
|
3845 |
current_menu_options=list(MENU_CONFIG['product_inquiry']['option_descriptions'].values())
|
3846 |
)
|
3847 |
+
|
3848 |
+
# Send product image with caption using the new function
|
3849 |
await send_product_image_with_caption(from_number, selected_product, user_context)
|
3850 |
+
|
3851 |
+
# Add to conversation history
|
3852 |
+
context_manager.add_to_history(from_number, query, f"Product inquiry for {selected_product.get('Product Name', 'Unknown')}")
|
3853 |
+
|
3854 |
else:
|
3855 |
+
# Enhanced "not found" response with veterinary suggestions
|
3856 |
+
message = (
|
3857 |
+
"❌ *Product Not Found*\n\n"
|
3858 |
+
f"🔍 *We couldn't find '{query}' in our veterinary database.*\n\n"
|
3859 |
+
"💡 *Try these alternatives:*\n"
|
3860 |
+
"• Check spelling (e.g., 'Hydropex' not 'Hydro pex')\n"
|
3861 |
+
"• Search by symptoms (e.g., 'respiratory', 'liver support')\n"
|
3862 |
+
"• Search by category (e.g., 'antibiotic', 'vitamin')\n"
|
3863 |
+
"• Search by species (e.g., 'poultry', 'livestock')\n\n"
|
3864 |
+
"🏥 *Popular Veterinary Products:*\n"
|
3865 |
+
"• Hydropex (Electrolyte supplement)\n"
|
3866 |
+
"• Heposel (Liver tonic)\n"
|
3867 |
+
"• Bromacid (Respiratory support)\n"
|
3868 |
+
"• Tribiotic (Antibiotic)\n"
|
3869 |
+
"• Symodex (Multivitamin)\n\n"
|
3870 |
+
"💬 *Type 'main' to return to main menu or try another search.*"
|
3871 |
+
)
|
3872 |
+
|
3873 |
+
# Translate response if needed
|
3874 |
+
if reply_language == 'ur':
|
3875 |
+
try:
|
3876 |
+
translated_message = GoogleTranslator(source='auto', target='ur').translate(message)
|
3877 |
+
send_whatsjet_message(from_number, translated_message)
|
3878 |
+
except Exception as e:
|
3879 |
+
logger.error(f"[AI] Translation error: {e}")
|
3880 |
+
send_whatsjet_message(from_number, message)
|
3881 |
+
else:
|
3882 |
+
send_whatsjet_message(from_number, message)
|
3883 |
+
|
3884 |
except Exception as e:
|
3885 |
+
logger.error(f"Error in product inquiry: {e}")
|
3886 |
+
# Instead of sending a generic error, return to main menu
|
3887 |
+
welcome_msg = generate_veterinary_welcome_message(from_number, user_context)
|
3888 |
+
send_whatsjet_message(from_number, welcome_msg)
|
3889 |
+
context_manager.update_context(from_number, current_state='main_menu', current_menu='main_menu', current_menu_options=list(MENU_CONFIG['main_menu']['option_descriptions'].values()))
|
3890 |
|
3891 |
async def handle_contact_request(from_number: str):
|
3892 |
"""Handle contact request"""
|
|
|
3940 |
|
3941 |
# Launch FastAPI app
|
3942 |
import uvicorn
|
3943 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|