Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -289,19 +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 |
-
# 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 -
|
297 |
|
298 |
IMPORTANT RULES:
|
299 |
1. ONLY transcribe English or Urdu speech
|
300 |
-
2. If you hear
|
301 |
-
3. If you hear
|
302 |
4. Never transcribe gibberish or random characters
|
303 |
-
5.
|
304 |
-
6.
|
305 |
|
306 |
PRODUCT NAMES (exact spelling required):
|
307 |
- Hydropex, Respira Aid Plus, Heposel, Bromacid, Hexatox
|
@@ -320,12 +320,12 @@ GREETINGS:
|
|
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.
|
329 |
|
330 |
EXAMPLES:
|
331 |
- "hydropex" -> "hydropex"
|
@@ -334,6 +334,7 @@ EXAMPLES:
|
|
334 |
- "main menu" -> "main"
|
335 |
- "salam" -> "salam"
|
336 |
- "search products" -> "search products"
|
|
|
337 |
- Unclear audio -> "unclear audio"
|
338 |
"""
|
339 |
|
@@ -475,6 +476,53 @@ def process_voice_input(text: str) -> str:
|
|
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 |
|
@@ -832,7 +880,10 @@ def generate_veterinary_product_response(product_info: Dict[str, Any], user_cont
|
|
832 |
def clean_text(text):
|
833 |
if pd.isna(text) or text is None:
|
834 |
return "Not specified"
|
835 |
-
|
|
|
|
|
|
|
836 |
|
837 |
# Extract product details
|
838 |
product_name = clean_text(product_info.get('Product Name', ''))
|
@@ -881,6 +932,8 @@ def clean_text_for_pdf(text: str) -> str:
|
|
881 |
return "N/A"
|
882 |
|
883 |
cleaned = str(text)
|
|
|
|
|
884 |
# Remove or replace problematic characters for PDF
|
885 |
cleaned = cleaned.replace('â€"', '-').replace('â€"', '"').replace('’', "'")
|
886 |
cleaned = cleaned.replace('“', '"').replace('â€', '"').replace('…', '...')
|
@@ -1783,7 +1836,7 @@ async def process_incoming_message(from_number: str, msg: dict):
|
|
1783 |
)
|
1784 |
return
|
1785 |
|
1786 |
-
# 🎯 PRIORITY 2: State-specific handling (contact_request, availability_request, ai_chat_mode)
|
1787 |
if current_state == 'contact_request':
|
1788 |
await handle_contact_request_response(from_number, message_body)
|
1789 |
return
|
@@ -1793,8 +1846,29 @@ async def process_incoming_message(from_number: str, msg: dict):
|
|
1793 |
elif current_state == 'ai_chat_mode':
|
1794 |
await handle_ai_chat_mode(from_number, message_body, reply_language)
|
1795 |
return
|
|
|
|
|
|
|
|
|
1796 |
|
1797 |
-
# 🎯 PRIORITY 3:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1798 |
if current_state in ['main_menu', 'category_selection_menu', 'category_products_menu', 'all_products_menu', 'product_inquiry', 'intelligent_products_menu']:
|
1799 |
# Validate menu selection
|
1800 |
is_valid, error_msg = validate_menu_selection(message_body, current_state, user_context)
|
@@ -1897,8 +1971,16 @@ async def process_incoming_message(from_number: str, msg: dict):
|
|
1897 |
await send_product_image_with_caption(from_number, selected_product, user_context)
|
1898 |
return
|
1899 |
else:
|
1900 |
-
|
1901 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1902 |
return # Exit after handling menu selection
|
1903 |
|
1904 |
# 🎯 PRIORITY 4: Product names - works from ANY menu state
|
@@ -2074,10 +2156,11 @@ Response Guidelines:
|
|
2074 |
else:
|
2075 |
ai_response += "\n\n💬 *Please type 'main' to go to main menu*"
|
2076 |
|
2077 |
-
# Translate response if needed
|
2078 |
if reply_language == 'ur':
|
2079 |
try:
|
2080 |
-
|
|
|
2081 |
send_whatsjet_message(from_number, translated_response)
|
2082 |
except Exception as e:
|
2083 |
logger.error(f"[AI] Translation error: {e}")
|
@@ -2104,6 +2187,54 @@ Response Guidelines:
|
|
2104 |
current_menu_options=list(MENU_CONFIG['main_menu']['option_descriptions'].values())
|
2105 |
)
|
2106 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2107 |
async def handle_contact_request(from_number: str):
|
2108 |
"""Handle contact request"""
|
2109 |
try:
|
@@ -2613,13 +2744,14 @@ Response:
|
|
2613 |
else:
|
2614 |
ai_response += "\n\n💬 *Type 'main' to return to main menu*"
|
2615 |
|
2616 |
-
# Translate response if needed
|
2617 |
if reply_language == 'ur':
|
2618 |
try:
|
2619 |
# Get all product and category names
|
2620 |
product_names = [str(p.get('Product Name', '')) for p in all_products if p.get('Product Name')]
|
2621 |
category_names = list(set([str(p.get('Category', '')) for p in all_products if p.get('Category')]))
|
2622 |
-
|
|
|
2623 |
# Restore English terms
|
2624 |
translated_response = restore_english_terms(translated_response, ai_response, product_names, category_names)
|
2625 |
send_whatsjet_message(from_number, translated_response)
|
@@ -2836,7 +2968,10 @@ def generate_veterinary_product_response_with_media(product_info: Dict[str, Any]
|
|
2836 |
def clean_text(text):
|
2837 |
if pd.isna(text) or text is None:
|
2838 |
return "Not specified"
|
2839 |
-
|
|
|
|
|
|
|
2840 |
|
2841 |
product_name = clean_text(product_info.get('Product Name', ''))
|
2842 |
product_type = clean_text(product_info.get('Type', ''))
|
@@ -3487,14 +3622,17 @@ async def handle_voice_message_complete(from_number: str, msg: dict):
|
|
3487 |
detected_lang = detect(transcribed_text)
|
3488 |
logger.info(f"[Voice] Raw detected language: {detected_lang}")
|
3489 |
|
3490 |
-
# STRICTLY ENGLISH OR URDU ONLY -
|
3491 |
-
# Only allow English and Urdu, reject everything else
|
3492 |
if detected_lang in ['en', 'ur']:
|
3493 |
reply_language = detected_lang
|
|
|
3494 |
else:
|
3495 |
-
#
|
3496 |
reply_language = 'en'
|
3497 |
-
logger.warning(f"[Voice]
|
|
|
|
|
|
|
3498 |
|
3499 |
# Check if text contains Urdu/Arabic characters or Islamic greetings
|
3500 |
urdu_arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]')
|
@@ -3514,11 +3652,12 @@ async def handle_voice_message_complete(from_number: str, msg: dict):
|
|
3514 |
logger.warning(f"[Voice] Language detection failed: {e}, defaulting to English")
|
3515 |
reply_language = 'en'
|
3516 |
|
3517 |
-
# For Urdu voice notes, translate to English for processing
|
3518 |
processing_text = transcribed_text
|
3519 |
if reply_language == 'ur' and detected_lang == 'ur':
|
3520 |
try:
|
3521 |
logger.info(f"[Voice] Translating Urdu voice note to English for processing")
|
|
|
3522 |
translated_text = GoogleTranslator(source='ur', target='en').translate(transcribed_text)
|
3523 |
processing_text = translated_text
|
3524 |
logger.info(f"[Voice] Translated to English: {translated_text}")
|
@@ -3526,6 +3665,17 @@ async def handle_voice_message_complete(from_number: str, msg: dict):
|
|
3526 |
logger.error(f"[Voice] Translation failed: {e}")
|
3527 |
# If translation fails, use original text
|
3528 |
processing_text = transcribed_text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3529 |
|
3530 |
# Determine reply language - always respond in English or Urdu
|
3531 |
if detected_lang == 'ur':
|
@@ -3556,13 +3706,14 @@ async def handle_voice_message_complete(from_number: str, msg: dict):
|
|
3556 |
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()))
|
3557 |
return
|
3558 |
|
3559 |
-
# Process the
|
3560 |
-
# This ensures voice messages
|
3561 |
await process_incoming_message(from_number, {
|
3562 |
'body': processing_text, # Use translated text for processing
|
3563 |
'type': 'text',
|
3564 |
'reply_language': reply_language,
|
3565 |
-
'original_transcription': transcribed_text # Keep original for context
|
|
|
3566 |
})
|
3567 |
|
3568 |
except Exception as e:
|
@@ -3892,10 +4043,11 @@ Format your response professionally with emojis and clear structure. Keep it con
|
|
3892 |
|
3893 |
full_response = ai_response + selection_instructions
|
3894 |
|
3895 |
-
# Translate response if needed
|
3896 |
if reply_language == 'ur':
|
3897 |
try:
|
3898 |
-
|
|
|
3899 |
send_whatsjet_message(from_number, translated_response)
|
3900 |
except Exception as e:
|
3901 |
logger.error(f"[AI] Translation error: {e}")
|
@@ -3937,10 +4089,11 @@ Format your response professionally with emojis and clear structure. Keep it con
|
|
3937 |
"💬 *Type 'main' to return to the main menu*"
|
3938 |
)
|
3939 |
|
3940 |
-
# Translate response if needed
|
3941 |
if reply_language == 'ur':
|
3942 |
try:
|
3943 |
-
|
|
|
3944 |
send_whatsjet_message(from_number, translated_message)
|
3945 |
except Exception as e:
|
3946 |
logger.error(f"[AI] Translation error: {e}")
|
@@ -3974,9 +4127,9 @@ Format your response professionally with emojis and clear structure. Keep it con
|
|
3974 |
|
3975 |
# If it's a mode of action query, provide detailed mechanism information
|
3976 |
if is_mode_of_action_query:
|
3977 |
-
product_name = selected_product.get('Product Name', 'Unknown')
|
3978 |
-
composition = selected_product.get('Composition', 'Not specified')
|
3979 |
-
indications = selected_product.get('Indications', 'Not specified')
|
3980 |
|
3981 |
mode_of_action_response = (
|
3982 |
f"🧪 *{product_name} - Mode of Action*\n\n"
|
@@ -3997,13 +4150,52 @@ Format your response professionally with emojis and clear structure. Keep it con
|
|
3997 |
context_manager.add_to_history(from_number, query, f"Product inquiry for {selected_product.get('Product Name', 'Unknown')}")
|
3998 |
|
3999 |
else:
|
4000 |
-
#
|
4001 |
-
if
|
4002 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4003 |
else:
|
4004 |
-
|
4005 |
-
|
4006 |
-
|
|
|
|
|
|
|
|
|
4007 |
|
4008 |
except Exception as e:
|
4009 |
logger.error(f"Error in product inquiry: {e}")
|
|
|
289 |
|
290 |
logger.info(f"[Transcribe] Transcribing file: {file_path} (size: {file_size} bytes)")
|
291 |
|
292 |
+
# Comprehensive system prompt for veterinary WhatsApp assistant - ENGLISH AND URDU ONLY
|
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 - REJECT ALL OTHER LANGUAGES
|
297 |
|
298 |
IMPORTANT RULES:
|
299 |
1. ONLY transcribe English or Urdu speech
|
300 |
+
2. If you hear any other language, transcribe as "unclear audio"
|
301 |
+
3. If you hear unclear audio, transcribe as "unclear audio"
|
302 |
4. Never transcribe gibberish or random characters
|
303 |
+
5. Keep transcriptions simple and clean
|
304 |
+
6. Reject non-English/Urdu languages completely
|
305 |
|
306 |
PRODUCT NAMES (exact spelling required):
|
307 |
- Hydropex, Respira Aid Plus, Heposel, Bromacid, Hexatox
|
|
|
320 |
- Urdu: salam, assalamu alaikum, adaab, namaste, khuda hafiz
|
321 |
|
322 |
TRANSCRIPTION RULES:
|
323 |
+
1. Transcribe exactly what you hear in English or Urdu ONLY
|
324 |
2. Convert numbers to digits (one->1, two->2, etc.)
|
325 |
3. Preserve product names exactly
|
326 |
+
4. If unclear or non-English/Urdu, transcribe as "unclear audio"
|
327 |
5. Keep it simple and clean
|
328 |
+
6. Reject all other languages
|
329 |
|
330 |
EXAMPLES:
|
331 |
- "hydropex" -> "hydropex"
|
|
|
334 |
- "main menu" -> "main"
|
335 |
- "salam" -> "salam"
|
336 |
- "search products" -> "search products"
|
337 |
+
- Non-English/Urdu speech -> "unclear audio"
|
338 |
- Unclear audio -> "unclear audio"
|
339 |
"""
|
340 |
|
|
|
476 |
# Remove extra whitespace
|
477 |
processed_text = re.sub(r'\s+', ' ', processed_text)
|
478 |
|
479 |
+
# Fix special characters and encoding issues
|
480 |
+
processed_text = clean_special_characters(processed_text)
|
481 |
+
|
482 |
+
return processed_text
|
483 |
+
|
484 |
+
def clean_special_characters(text: str) -> str:
|
485 |
+
"""Clean special characters and fix encoding issues"""
|
486 |
+
if not text:
|
487 |
+
return text
|
488 |
+
|
489 |
+
# Fix common encoding issues
|
490 |
+
replacements = {
|
491 |
+
'â€"': '–', # Fix en dash
|
492 |
+
'â€"': '—', # Fix em dash
|
493 |
+
'’': "'", # Fix apostrophe
|
494 |
+
'“': '"', # Fix opening quote
|
495 |
+
'â€': '"', # Fix closing quote
|
496 |
+
'…': '...', # Fix ellipsis
|
497 |
+
'•': '•', # Fix bullet
|
498 |
+
'‰': '°', # Fix degree symbol
|
499 |
+
'′': '′', # Fix prime
|
500 |
+
'″': '″', # Fix double prime
|
501 |
+
'‼': '™', # Fix trademark
|
502 |
+
'‮': '®', # Fix registered
|
503 |
+
'
': '©', # Fix copyright
|
504 |
+
'â€': '–', # Generic fix for en dash
|
505 |
+
'â€"': '—', # Generic fix for em dash
|
506 |
+
'’': "'", # Generic fix for apostrophe
|
507 |
+
'“': '"', # Generic fix for opening quote
|
508 |
+
'â€': '"', # Generic fix for closing quote
|
509 |
+
'…': '...', # Generic fix for ellipsis
|
510 |
+
'•': '•', # Generic fix for bullet
|
511 |
+
'‰': '°', # Generic fix for degree symbol
|
512 |
+
}
|
513 |
+
|
514 |
+
# Apply replacements
|
515 |
+
for old_char, new_char in replacements.items():
|
516 |
+
text = text.replace(old_char, new_char)
|
517 |
+
|
518 |
+
# Remove any remaining problematic characters
|
519 |
+
text = re.sub(r'â€[^"]*', '', text) # Remove any remaining †patterns
|
520 |
+
|
521 |
+
# Clean up multiple spaces
|
522 |
+
text = re.sub(r'\s+', ' ', text)
|
523 |
+
|
524 |
+
return text.strip()
|
525 |
+
|
526 |
# Basic punctuation cleanup
|
527 |
processed_text = processed_text.replace(' ,', ',').replace(' .', '.')
|
528 |
|
|
|
880 |
def clean_text(text):
|
881 |
if pd.isna(text) or text is None:
|
882 |
return "Not specified"
|
883 |
+
cleaned = str(text).strip()
|
884 |
+
# Apply special character cleaning
|
885 |
+
cleaned = clean_special_characters(cleaned)
|
886 |
+
return cleaned
|
887 |
|
888 |
# Extract product details
|
889 |
product_name = clean_text(product_info.get('Product Name', ''))
|
|
|
932 |
return "N/A"
|
933 |
|
934 |
cleaned = str(text)
|
935 |
+
# Apply special character cleaning first
|
936 |
+
cleaned = clean_special_characters(cleaned)
|
937 |
# Remove or replace problematic characters for PDF
|
938 |
cleaned = cleaned.replace('â€"', '-').replace('â€"', '"').replace('’', "'")
|
939 |
cleaned = cleaned.replace('“', '"').replace('â€', '"').replace('…', '...')
|
|
|
1836 |
)
|
1837 |
return
|
1838 |
|
1839 |
+
# 🎯 PRIORITY 2: State-specific handling (contact_request, availability_request, ai_chat_mode, clarification)
|
1840 |
if current_state == 'contact_request':
|
1841 |
await handle_contact_request_response(from_number, message_body)
|
1842 |
return
|
|
|
1846 |
elif current_state == 'ai_chat_mode':
|
1847 |
await handle_ai_chat_mode(from_number, message_body, reply_language)
|
1848 |
return
|
1849 |
+
elif user_context.get('awaiting_clarification', False):
|
1850 |
+
# Handle clarification responses
|
1851 |
+
await handle_clarification_response(from_number, message_body, user_context)
|
1852 |
+
return
|
1853 |
|
1854 |
+
# 🎯 PRIORITY 3: Intelligent product queries from any menu state
|
1855 |
+
# Check if the message is about a product from CSV, regardless of current menu
|
1856 |
+
products = get_veterinary_product_matches(message_body)
|
1857 |
+
|
1858 |
+
if products:
|
1859 |
+
logger.info(f"[Process] Product query detected from menu state '{current_state}': '{message_body}' -> Found {len(products)} products")
|
1860 |
+
|
1861 |
+
# If user is in a specific menu but asks about a product, handle it intelligently
|
1862 |
+
if current_state in ['main_menu', 'category_selection_menu', 'category_products_menu', 'all_products_menu', 'product_inquiry', 'intelligent_products_menu']:
|
1863 |
+
# Use intelligent product inquiry to handle the product query
|
1864 |
+
await handle_intelligent_product_inquiry(from_number, message_body, user_context, reply_language)
|
1865 |
+
return
|
1866 |
+
else:
|
1867 |
+
# For other menu states, still handle product queries but remind about menu context
|
1868 |
+
await handle_intelligent_product_inquiry(from_number, message_body, user_context, reply_language)
|
1869 |
+
return
|
1870 |
+
|
1871 |
+
# 🎯 PRIORITY 4: Menu selections - check if this is a valid menu selection for current state
|
1872 |
if current_state in ['main_menu', 'category_selection_menu', 'category_products_menu', 'all_products_menu', 'product_inquiry', 'intelligent_products_menu']:
|
1873 |
# Validate menu selection
|
1874 |
is_valid, error_msg = validate_menu_selection(message_body, current_state, user_context)
|
|
|
1971 |
await send_product_image_with_caption(from_number, selected_product, user_context)
|
1972 |
return
|
1973 |
else:
|
1974 |
+
# Check if the invalid input might be a product query
|
1975 |
+
products = get_veterinary_product_matches(message_body)
|
1976 |
+
if products:
|
1977 |
+
logger.info(f"[Process] Invalid menu selection but product found: '{message_body}' -> Handling as product query")
|
1978 |
+
await handle_intelligent_product_inquiry(from_number, message_body, user_context, reply_language)
|
1979 |
+
return
|
1980 |
+
else:
|
1981 |
+
# Show menu validation message with guidance
|
1982 |
+
send_whatsjet_message(from_number, get_menu_validation_message(current_state, user_context))
|
1983 |
+
return
|
1984 |
return # Exit after handling menu selection
|
1985 |
|
1986 |
# 🎯 PRIORITY 4: Product names - works from ANY menu state
|
|
|
2156 |
else:
|
2157 |
ai_response += "\n\n💬 *Please type 'main' to go to main menu*"
|
2158 |
|
2159 |
+
# Translate response if needed (ENGLISH/URDU ONLY)
|
2160 |
if reply_language == 'ur':
|
2161 |
try:
|
2162 |
+
# Only translate from English to Urdu - no other languages
|
2163 |
+
translated_response = GoogleTranslator(source='en', target='ur').translate(ai_response)
|
2164 |
send_whatsjet_message(from_number, translated_response)
|
2165 |
except Exception as e:
|
2166 |
logger.error(f"[AI] Translation error: {e}")
|
|
|
2187 |
current_menu_options=list(MENU_CONFIG['main_menu']['option_descriptions'].values())
|
2188 |
)
|
2189 |
|
2190 |
+
async def handle_clarification_response(from_number: str, response: str, user_context: dict):
|
2191 |
+
"""Handle user response to clarification questions"""
|
2192 |
+
try:
|
2193 |
+
clean_response = response.strip().lower()
|
2194 |
+
|
2195 |
+
if clean_response in ['yes', 'y', 'apex', 'apex biotical', 'apex biotical solution']:
|
2196 |
+
# User confirmed they want Apex Biotical
|
2197 |
+
clarification_product = user_context.get('clarification_product', 'apex biotical')
|
2198 |
+
|
2199 |
+
# Search for the product
|
2200 |
+
products = get_veterinary_product_matches(clarification_product)
|
2201 |
+
|
2202 |
+
if products:
|
2203 |
+
# Found the product - show detailed information
|
2204 |
+
product = products[0]
|
2205 |
+
await send_product_with_image(from_number, product, user_context)
|
2206 |
+
|
2207 |
+
# Update context
|
2208 |
+
user_context['current_product'] = product
|
2209 |
+
user_context['current_state'] = 'product_detail'
|
2210 |
+
user_context['awaiting_clarification'] = False
|
2211 |
+
user_context.pop('clarification_product', None)
|
2212 |
+
context_manager.update_context(from_number, **user_context)
|
2213 |
+
else:
|
2214 |
+
# Product not found even after clarification
|
2215 |
+
send_whatsjet_message(from_number, "❌ *Product not found. Please type 'main' to go to main menu.*")
|
2216 |
+
user_context['awaiting_clarification'] = False
|
2217 |
+
user_context.pop('clarification_product', None)
|
2218 |
+
context_manager.update_context(from_number, **user_context)
|
2219 |
+
else:
|
2220 |
+
# User didn't confirm - use OpenAI for intelligent response
|
2221 |
+
if OPENAI_API_KEY:
|
2222 |
+
await handle_ai_chat_mode(from_number, response, 'en')
|
2223 |
+
else:
|
2224 |
+
send_whatsjet_message(from_number, "💬 *Type 'main' to go to main menu or ask another question.*")
|
2225 |
+
|
2226 |
+
# Clear clarification context
|
2227 |
+
user_context['awaiting_clarification'] = False
|
2228 |
+
user_context.pop('clarification_product', None)
|
2229 |
+
context_manager.update_context(from_number, **user_context)
|
2230 |
+
|
2231 |
+
except Exception as e:
|
2232 |
+
logger.error(f"[Clarification] Error: {str(e)}")
|
2233 |
+
send_whatsjet_message(from_number, "❌ *An error occurred. Please type 'main' to go to main menu.*")
|
2234 |
+
user_context['awaiting_clarification'] = False
|
2235 |
+
user_context.pop('clarification_product', None)
|
2236 |
+
context_manager.update_context(from_number, **user_context)
|
2237 |
+
|
2238 |
async def handle_contact_request(from_number: str):
|
2239 |
"""Handle contact request"""
|
2240 |
try:
|
|
|
2744 |
else:
|
2745 |
ai_response += "\n\n💬 *Type 'main' to return to main menu*"
|
2746 |
|
2747 |
+
# Translate response if needed (ENGLISH/URDU ONLY)
|
2748 |
if reply_language == 'ur':
|
2749 |
try:
|
2750 |
# Get all product and category names
|
2751 |
product_names = [str(p.get('Product Name', '')) for p in all_products if p.get('Product Name')]
|
2752 |
category_names = list(set([str(p.get('Category', '')) for p in all_products if p.get('Category')]))
|
2753 |
+
# Only translate from English to Urdu - no other languages
|
2754 |
+
translated_response = GoogleTranslator(source='en', target='ur').translate(ai_response)
|
2755 |
# Restore English terms
|
2756 |
translated_response = restore_english_terms(translated_response, ai_response, product_names, category_names)
|
2757 |
send_whatsjet_message(from_number, translated_response)
|
|
|
2968 |
def clean_text(text):
|
2969 |
if pd.isna(text) or text is None:
|
2970 |
return "Not specified"
|
2971 |
+
cleaned = str(text).strip()
|
2972 |
+
# Apply special character cleaning
|
2973 |
+
cleaned = clean_special_characters(cleaned)
|
2974 |
+
return cleaned
|
2975 |
|
2976 |
product_name = clean_text(product_info.get('Product Name', ''))
|
2977 |
product_type = clean_text(product_info.get('Type', ''))
|
|
|
3622 |
detected_lang = detect(transcribed_text)
|
3623 |
logger.info(f"[Voice] Raw detected language: {detected_lang}")
|
3624 |
|
3625 |
+
# STRICTLY ENGLISH OR URDU ONLY - REJECT ALL OTHER LANGUAGES
|
|
|
3626 |
if detected_lang in ['en', 'ur']:
|
3627 |
reply_language = detected_lang
|
3628 |
+
logger.info(f"[Voice] Valid language detected: {detected_lang}")
|
3629 |
else:
|
3630 |
+
# Reject any other language and force to English
|
3631 |
reply_language = 'en'
|
3632 |
+
logger.warning(f"[Voice] Invalid language '{detected_lang}' detected - forcing to English")
|
3633 |
+
# If it's clearly not English/Urdu, mark as unclear
|
3634 |
+
if detected_lang not in ['en', 'ur', 'unknown']:
|
3635 |
+
logger.warning(f"[Voice] Non-English/Urdu language detected: {detected_lang}")
|
3636 |
|
3637 |
# Check if text contains Urdu/Arabic characters or Islamic greetings
|
3638 |
urdu_arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]')
|
|
|
3652 |
logger.warning(f"[Voice] Language detection failed: {e}, defaulting to English")
|
3653 |
reply_language = 'en'
|
3654 |
|
3655 |
+
# For Urdu voice notes, translate to English for processing (ENGLISH/URDU ONLY)
|
3656 |
processing_text = transcribed_text
|
3657 |
if reply_language == 'ur' and detected_lang == 'ur':
|
3658 |
try:
|
3659 |
logger.info(f"[Voice] Translating Urdu voice note to English for processing")
|
3660 |
+
# Only translate from Urdu to English - no other languages
|
3661 |
translated_text = GoogleTranslator(source='ur', target='en').translate(transcribed_text)
|
3662 |
processing_text = translated_text
|
3663 |
logger.info(f"[Voice] Translated to English: {translated_text}")
|
|
|
3665 |
logger.error(f"[Voice] Translation failed: {e}")
|
3666 |
# If translation fails, use original text
|
3667 |
processing_text = transcribed_text
|
3668 |
+
elif detected_lang not in ['en', 'ur']:
|
3669 |
+
# If language is not English or Urdu, reject it
|
3670 |
+
logger.warning(f"[Voice] Non-English/Urdu language detected: {detected_lang}")
|
3671 |
+
send_whatsjet_message(from_number,
|
3672 |
+
"🎤 *Voice Message Issue*\n\n"
|
3673 |
+
"I can only process voice messages in English or Urdu.\n\n"
|
3674 |
+
"💡 *Please:*\n"
|
3675 |
+
"• Speak in English or Urdu only\n"
|
3676 |
+
"• Send a text message instead\n"
|
3677 |
+
"• Type 'main' to see menu options")
|
3678 |
+
return
|
3679 |
|
3680 |
# Determine reply language - always respond in English or Urdu
|
3681 |
if detected_lang == 'ur':
|
|
|
3706 |
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()))
|
3707 |
return
|
3708 |
|
3709 |
+
# Process the transcribed text using the same intelligent system as text messages
|
3710 |
+
# This ensures voice messages get the same intelligent clarification and product search
|
3711 |
await process_incoming_message(from_number, {
|
3712 |
'body': processing_text, # Use translated text for processing
|
3713 |
'type': 'text',
|
3714 |
'reply_language': reply_language,
|
3715 |
+
'original_transcription': transcribed_text, # Keep original for context
|
3716 |
+
'is_voice_message': True # Flag to indicate this came from voice
|
3717 |
})
|
3718 |
|
3719 |
except Exception as e:
|
|
|
4043 |
|
4044 |
full_response = ai_response + selection_instructions
|
4045 |
|
4046 |
+
# Translate response if needed (ENGLISH/URDU ONLY)
|
4047 |
if reply_language == 'ur':
|
4048 |
try:
|
4049 |
+
# Only translate from English to Urdu - no other languages
|
4050 |
+
translated_response = GoogleTranslator(source='en', target='ur').translate(full_response)
|
4051 |
send_whatsjet_message(from_number, translated_response)
|
4052 |
except Exception as e:
|
4053 |
logger.error(f"[AI] Translation error: {e}")
|
|
|
4089 |
"💬 *Type 'main' to return to the main menu*"
|
4090 |
)
|
4091 |
|
4092 |
+
# Translate response if needed (ENGLISH/URDU ONLY)
|
4093 |
if reply_language == 'ur':
|
4094 |
try:
|
4095 |
+
# Only translate from English to Urdu - no other languages
|
4096 |
+
translated_message = GoogleTranslator(source='en', target='ur').translate(message)
|
4097 |
send_whatsjet_message(from_number, translated_message)
|
4098 |
except Exception as e:
|
4099 |
logger.error(f"[AI] Translation error: {e}")
|
|
|
4127 |
|
4128 |
# If it's a mode of action query, provide detailed mechanism information
|
4129 |
if is_mode_of_action_query:
|
4130 |
+
product_name = clean_special_characters(selected_product.get('Product Name', 'Unknown'))
|
4131 |
+
composition = clean_special_characters(selected_product.get('Composition', 'Not specified'))
|
4132 |
+
indications = clean_special_characters(selected_product.get('Indications', 'Not specified'))
|
4133 |
|
4134 |
mode_of_action_response = (
|
4135 |
f"🧪 *{product_name} - Mode of Action*\n\n"
|
|
|
4150 |
context_manager.add_to_history(from_number, query, f"Product inquiry for {selected_product.get('Product Name', 'Unknown')}")
|
4151 |
|
4152 |
else:
|
4153 |
+
# No products found - check if it's an ambiguous query that needs clarification
|
4154 |
+
if any(keyword in clean_query for keyword in ['what is', 'apex', 'immune', 'hydro', 'hepo', 'brom', 'trib', 'symo']):
|
4155 |
+
# Check if it might be about a product but needs clarification
|
4156 |
+
if 'apex' in clean_query:
|
4157 |
+
clarification_message = (
|
4158 |
+
"🤔 *I found a similar product in our database.*\n\n"
|
4159 |
+
"Are you asking about:\n"
|
4160 |
+
"• Apex Biotical Solution\n"
|
4161 |
+
"• Or something else?\n\n"
|
4162 |
+
"💬 *Type 'yes' to continue with Apex Biotical, or specify what you mean.*"
|
4163 |
+
)
|
4164 |
+
send_whatsjet_message(from_number, clarification_message)
|
4165 |
+
user_context['awaiting_clarification'] = True
|
4166 |
+
user_context['clarification_product'] = 'apex biotical'
|
4167 |
+
context_manager.update_context(from_number, **user_context)
|
4168 |
+
return
|
4169 |
+
elif any(product_hint in clean_query for product_hint in ['immune', 'hydro', 'hepo', 'brom', 'trib', 'symo']):
|
4170 |
+
# Use OpenAI for intelligent response about veterinary topics
|
4171 |
+
if OPENAI_API_KEY:
|
4172 |
+
await handle_ai_chat_mode(from_number, query, reply_language)
|
4173 |
+
else:
|
4174 |
+
# Fallback to generic response
|
4175 |
+
if reply_language == 'ur':
|
4176 |
+
message = "❌ *سوال درست نہیں ہے*\n\n💬 *براہ کرم اپنا سوال درست کریں یا 'main' لکھ کر مین مینو پر جائیں*"
|
4177 |
+
else:
|
4178 |
+
message = "❌ *Please correct your question*\n\n💬 *Type 'main' to go to main menu*"
|
4179 |
+
send_whatsjet_message(from_number, message)
|
4180 |
+
else:
|
4181 |
+
# Use OpenAI for general veterinary questions
|
4182 |
+
if OPENAI_API_KEY:
|
4183 |
+
await handle_ai_chat_mode(from_number, query, reply_language)
|
4184 |
+
else:
|
4185 |
+
# Fallback to generic response
|
4186 |
+
if reply_language == 'ur':
|
4187 |
+
message = "❌ *سوال درست نہیں ہے*\n\n💬 *براہ کرم اپنا سوال درست کریں یا 'main' لکھ کر مین مینو پر جائیں*"
|
4188 |
+
else:
|
4189 |
+
message = "❌ *Please correct your question*\n\n💬 *Type 'main' to go to main menu*"
|
4190 |
+
send_whatsjet_message(from_number, message)
|
4191 |
else:
|
4192 |
+
# Generic, professional "not found" response
|
4193 |
+
if reply_language == 'ur':
|
4194 |
+
message = "❌ *سوال درست نہیں ہے*\n\n💬 *براہ کرم اپنا سوال درست کریں یا 'main' لکھ کر مین مینو پر جائیں*"
|
4195 |
+
else:
|
4196 |
+
message = "❌ *Please correct your question*\n\n💬 *Type 'main' to go to main menu*"
|
4197 |
+
|
4198 |
+
send_whatsjet_message(from_number, message)
|
4199 |
|
4200 |
except Exception as e:
|
4201 |
logger.error(f"Error in product inquiry: {e}")
|