DreamStream-1 commited on
Commit
c097eb8
·
verified ·
1 Parent(s): 3ccc5ef

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +311 -0
app.py CHANGED
@@ -2995,6 +2995,317 @@ async def handle_veterinary_product_followup(selection: str, from_number: str) -
2995
  current_menu_options=list(MENU_CONFIG['main_menu']['option_descriptions'].values())
2996
  )
2997
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2998
  if __name__ == "__main__":
2999
  # Launch FastAPI app
3000
  import uvicorn
 
2995
  current_menu_options=list(MENU_CONFIG['main_menu']['option_descriptions'].values())
2996
  )
2997
 
2998
+ # Add or update the following functions in app.py:
2999
+
3000
+ # --- Restore handle_voice_message_complete ---
3001
+ async def handle_voice_message_complete(from_number: str, msg: dict):
3002
+ """Complete voice message processing with OpenAI transcription - treats voice exactly like text"""
3003
+ try:
3004
+ logger.info(f"[Voice] Processing voice message from {from_number}")
3005
+ logger.info(f"[Voice] Message structure: {msg}")
3006
+
3007
+ # Check if OpenAI is available
3008
+ if not OPENAI_API_KEY:
3009
+ send_whatsjet_message(from_number,
3010
+ "🎤 Voice messages require OpenAI API. Please send a text message or type 'main' to see the menu.")
3011
+ return
3012
+
3013
+ # Extract media URL from different possible locations
3014
+ media_url = None
3015
+ logger.info(f"[Voice] Checking media URL locations...")
3016
+
3017
+ if msg.get('media', {}).get('link'):
3018
+ media_url = msg.get('media', {}).get('link')
3019
+ logger.info(f"[Voice] Found media URL in media.link: {media_url}")
3020
+ elif msg.get('media', {}).get('url'):
3021
+ media_url = msg.get('media', {}).get('url')
3022
+ logger.info(f"[Voice] Found media URL in media.url: {media_url}")
3023
+ elif msg.get('url'):
3024
+ media_url = msg.get('url')
3025
+ logger.info(f"[Voice] Found media URL in url: {media_url}")
3026
+ elif msg.get('audio', {}).get('url'):
3027
+ media_url = msg.get('audio', {}).get('url')
3028
+ logger.info(f"[Voice] Found media URL in audio.url: {media_url}")
3029
+ else:
3030
+ logger.error(f"[Voice] No media URL found in message structure")
3031
+ logger.error(f"[Voice] Available fields: {list(msg.keys())}")
3032
+ if 'media' in msg:
3033
+ logger.error(f"[Voice] Media fields: {list(msg['media'].keys())}")
3034
+
3035
+ logger.info(f"[Voice] Final extracted media URL: {media_url}")
3036
+
3037
+ if not media_url:
3038
+ send_whatsjet_message(from_number, "❌ Could not process voice message. Please try again.")
3039
+ return
3040
+
3041
+ # Generate unique filename
3042
+ filename = f"voice_{from_number}_{int(time.time())}.ogg"
3043
+
3044
+ # Download voice file
3045
+ file_path = await download_voice_file(media_url, filename)
3046
+ if not file_path:
3047
+ send_whatsjet_message(from_number, "❌ Failed to download voice message. Please try again.")
3048
+ return
3049
+
3050
+ # Transcribe with OpenAI
3051
+ transcribed_text = await transcribe_voice_with_openai(file_path)
3052
+
3053
+ # Clean up voice file immediately
3054
+ try:
3055
+ os.remove(file_path)
3056
+ except:
3057
+ pass
3058
+
3059
+ # Handle empty or failed transcription
3060
+ if not transcribed_text or transcribed_text.strip() == "":
3061
+ logger.warning(f"[Voice] Empty transcription for {from_number}")
3062
+ send_whatsjet_message(from_number,
3063
+ "🎤 *Voice Message Issue*\n\n"
3064
+ "I couldn't hear anything in your voice message. This can happen due to:\n"
3065
+ "• Very short voice note\n"
3066
+ "• Background noise\n"
3067
+ "• Microphone too far away\n"
3068
+ "• Audio quality issues\n\n"
3069
+ "💡 *Tips for better voice notes:*\n"
3070
+ "• Speak clearly and slowly\n"
3071
+ "• Keep phone close to mouth\n"
3072
+ "• Record in quiet environment\n"
3073
+ "• Make voice note at least 2-3 seconds\n\n"
3074
+ "💬 *You can also:*\n"
3075
+ "• Send a text message\n"
3076
+ "• Type 'main' to see menu options\n"
3077
+ "• Try voice note again")
3078
+ return
3079
+
3080
+ # Process transcribed text with full intelligence
3081
+ logger.info(f"[Voice] Transcribed: {transcribed_text}")
3082
+
3083
+ # Apply transcription error corrections
3084
+ corrected_text = process_voice_input(transcribed_text)
3085
+ if corrected_text != transcribed_text:
3086
+ logger.info(f"[Voice] Applied corrections: '{transcribed_text}' -> '{corrected_text}'")
3087
+ transcribed_text = corrected_text
3088
+
3089
+ # Detect language of transcribed text
3090
+ detected_lang = 'en' # Default to English
3091
+ try:
3092
+ detected_lang = detect(transcribed_text)
3093
+ logger.info(f"[Voice] Detected language: {detected_lang}")
3094
+
3095
+ # Map language codes to supported languages
3096
+ lang_mapping = {
3097
+ 'ur': 'ur', # Urdu
3098
+ 'ar': 'ur', # Arabic (treat as Urdu for Islamic greetings)
3099
+ 'en': 'en', # English
3100
+ 'hi': 'ur', # Hindi (treat as Urdu)
3101
+ 'bn': 'ur', # Bengali (treat as Urdu)
3102
+ 'pa': 'ur', # Punjabi (treat as Urdu)
3103
+ 'id': 'ur', # Indonesian (often misdetected for Urdu/Arabic)
3104
+ 'ms': 'ur', # Malay (often misdetected for Urdu/Arabic)
3105
+ 'tr': 'ur', # Turkish (often misdetected for Urdu/Arabic)
3106
+ }
3107
+
3108
+ # Check if text contains Urdu/Arabic characters or Islamic greetings
3109
+ urdu_arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]')
3110
+ islamic_greetings = ['assalamu', 'assalam', 'salam', 'salaam', 'adaab', 'namaste', 'khuda', 'allah']
3111
+
3112
+ has_urdu_chars = bool(urdu_arabic_pattern.search(transcribed_text))
3113
+ has_islamic_greeting = any(greeting in transcribed_text.lower() for greeting in islamic_greetings)
3114
+
3115
+ if has_urdu_chars or has_islamic_greeting:
3116
+ detected_lang = 'ur'
3117
+ logger.info(f"[Voice] Overriding language detection to Urdu due to Arabic/Urdu characters or Islamic greeting")
3118
+
3119
+ reply_language = lang_mapping.get(detected_lang, 'en')
3120
+ logger.info(f"[Voice] Language '{detected_lang}' mapped to: {reply_language}")
3121
+
3122
+ except Exception as e:
3123
+ logger.warning(f"[Voice] Language detection failed: {e}")
3124
+ reply_language = 'en'
3125
+
3126
+ if reply_language not in ['en', 'ur']:
3127
+ logger.info(f"[Voice] Language '{reply_language}' not supported, defaulting to English")
3128
+ reply_language = 'en'
3129
+
3130
+ # For Urdu voice notes, translate to English for processing
3131
+ processing_text = transcribed_text
3132
+ if reply_language == 'ur' and detected_lang == 'ur':
3133
+ try:
3134
+ logger.info(f"[Voice] Translating Urdu voice note to English for processing")
3135
+ translated_text = GoogleTranslator(source='ur', target='en').translate(transcribed_text)
3136
+ processing_text = translated_text
3137
+ logger.info(f"[Voice] Translated to English: {translated_text}")
3138
+ except Exception as e:
3139
+ logger.error(f"[Voice] Translation failed: {e}")
3140
+ # If translation fails, use original text
3141
+ processing_text = transcribed_text
3142
+
3143
+ # Determine reply language - always respond in English or Urdu
3144
+ if detected_lang == 'ur':
3145
+ reply_language = 'ur' # Urdu voice notes get Urdu replies
3146
+ else:
3147
+ reply_language = 'en' # All other languages get English replies
3148
+
3149
+ logger.info(f"[Voice] Processing text: {processing_text}")
3150
+ logger.info(f"[Voice] Reply language set to: {reply_language}")
3151
+
3152
+ # Check if this is a greeting in voice note (check both original and translated)
3153
+ if is_greeting(transcribed_text) or is_greeting(processing_text):
3154
+ logger.info(f"[Voice] Greeting detected in voice note: {transcribed_text}")
3155
+
3156
+ # Check if user is currently in AI chat mode - if so, don't trigger menu mode
3157
+ user_context = context_manager.get_context(from_number)
3158
+ current_state = user_context.get('current_state', 'main_menu')
3159
+
3160
+ if current_state == 'ai_chat_mode':
3161
+ logger.info(f"[Voice] User is in AI chat mode, treating greeting as AI query instead of menu trigger")
3162
+ # Treat greeting as a general query in AI chat mode
3163
+ await handle_general_query_with_ai(from_number, processing_text, user_context, reply_language)
3164
+ return
3165
+ else:
3166
+ # Only trigger menu mode if not in AI chat mode
3167
+ welcome_msg = generate_veterinary_welcome_message(from_number, user_context)
3168
+ send_whatsjet_message(from_number, welcome_msg)
3169
+ 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()))
3170
+ return
3171
+
3172
+ # Process the translated text using the same strict state-based logic as text messages
3173
+ # This ensures voice messages follow the same menu and state rules as text messages
3174
+ await process_incoming_message(from_number, {
3175
+ 'body': processing_text, # Use translated text for processing
3176
+ 'type': 'text',
3177
+ 'reply_language': reply_language,
3178
+ 'original_transcription': transcribed_text # Keep original for context
3179
+ })
3180
+
3181
+ except Exception as e:
3182
+ logger.error(f"[Voice] Error processing voice message: {e}")
3183
+ logger.error(f"[Voice] Full error details: {str(e)}")
3184
+ import traceback
3185
+ logger.error(f"[Voice] Traceback: {traceback.format_exc()}")
3186
+ send_whatsjet_message(from_number,
3187
+ "❌ Error processing voice message. Please try a text message.")
3188
+
3189
+ # --- Update send_product_image_with_caption to latest version ---
3190
+ async def send_product_image_with_caption(from_number: str, product: Dict[str, Any], user_context: Dict[str, Any]):
3191
+ """
3192
+ Send product image (if available) with product details as caption in a single WhatsApp message.
3193
+ If image is not available, send only the product details as text.
3194
+ Now supports 'Images' column in CSV (Google Drive or direct links).
3195
+ """
3196
+ ensure_images_dir()
3197
+ product_name = product.get('Product Name', 'Unknown Product')
3198
+ details = generate_veterinary_product_response(product, user_context)
3199
+ image_url = product.get('Images', '').strip() if 'Images' in product else ''
3200
+
3201
+ # Force image URL for Respira Aid Plus (use cPanel public URL)
3202
+ if product_name.lower().strip() == "respira aid plus":
3203
+ image_url = "https://amgocus.com/uploads/images/Respira%20Aid%20Plus.jpg"
3204
+
3205
+ logger.info(f"[Product] Processing image for product: {product_name}")
3206
+ logger.info(f"[Product] Image URL from CSV: {image_url}")
3207
+ try:
3208
+ # First, check if we have an image URL from CSV
3209
+ if image_url:
3210
+ # Convert Google Drive link to direct download if needed
3211
+ if 'drive.google.com' in image_url:
3212
+ logger.info(f"[Product] Converting Google Drive link: {image_url}")
3213
+ if '/d/' in image_url:
3214
+ file_id = image_url.split('/d/')[1].split('/')[0]
3215
+ elif 'id=' in image_url:
3216
+ file_id = image_url.split('id=')[1].split('&')[0]
3217
+ else:
3218
+ file_id = ''
3219
+ if file_id:
3220
+ image_url = f"https://drive.google.com/uc?export=download&id={file_id}"
3221
+ logger.info(f"[Product] Converted to direct download URL: {image_url}")
3222
+ # Use the public URL directly for WhatsApp API
3223
+ media_type = 'image/jpeg'
3224
+ filename = f"{product_name.replace(' ', '_')}.jpg"
3225
+ # Test the image URL first
3226
+ try:
3227
+ logger.info(f"[Product] Testing image URL accessibility: {image_url}")
3228
+ headers = {
3229
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
3230
+ 'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8',
3231
+ 'Accept-Language': 'en-US,en;q=0.9',
3232
+ 'Accept-Encoding': 'gzip, deflate, br',
3233
+ 'Connection': 'keep-alive',
3234
+ 'Upgrade-Insecure-Requests': '1'
3235
+ }
3236
+ test_response = requests.head(image_url, headers=headers, timeout=10, allow_redirects=True)
3237
+ if test_response.status_code != 200:
3238
+ logger.warning(f"[Product] Image URL not accessible (status {test_response.status_code}): {image_url}")
3239
+ raise Exception(f"Image URL not accessible: {test_response.status_code}")
3240
+ logger.info(f"[Product] Image URL is accessible")
3241
+ except Exception as e:
3242
+ logger.warning(f"[Product] Failed to test image URL {image_url}: {e}")
3243
+ image_url = None
3244
+ # Send using public URL (not local file)
3245
+ if image_url:
3246
+ logger.info(f"[Product] Attempting to send image from CSV URL for: {product_name}")
3247
+ success = send_whatsjet_message(
3248
+ from_number,
3249
+ details,
3250
+ media_type=media_type,
3251
+ media_path=image_url, # Use public URL directly
3252
+ filename=filename
3253
+ )
3254
+ if success:
3255
+ logger.info(f"[Product] Successfully sent image from CSV link with caption for product: {product_name}")
3256
+ return
3257
+ else:
3258
+ logger.warning(f"[Product] Failed to send image from CSV link, trying fallback: {product_name}")
3259
+ # Fallback 1: Try with a known public test image
3260
+ logger.info(f"[Product] Trying public test image for: {product_name}")
3261
+ test_image_url = "https://www.w3schools.com/w3images/lights.jpg"
3262
+ media_type = 'image/jpeg'
3263
+ filename = f"{product_name.replace(' ', '_')}.jpg"
3264
+ success = send_whatsjet_message(
3265
+ from_number,
3266
+ details,
3267
+ media_type=media_type,
3268
+ media_path=test_image_url,
3269
+ filename=filename
3270
+ )
3271
+ if success:
3272
+ logger.info(f"[Product] Successfully sent test image with caption for product: {product_name}")
3273
+ return
3274
+ # Fallback 2: Try local uploads directory (public URL)
3275
+ logger.info(f"[Product] Trying local uploads directory for: {product_name}")
3276
+ image_path = get_product_image_path(product_name)
3277
+ if image_path and (image_path.startswith('http') or os.path.exists(image_path)):
3278
+ media_type = get_product_image_media_type(image_path)
3279
+ filename = f"{product_name.replace(' ', '_')}.jpg"
3280
+ # If it's already a public URL, use it directly
3281
+ if image_path.startswith('http'):
3282
+ media_path = image_path
3283
+ logger.info(f"[Product] Using existing public URL: {media_path}")
3284
+ else:
3285
+ # Convert local path to public URL
3286
+ media_path = f"https://dreamstream-1-chatbot.hf.space/uploads/{os.path.basename(image_path).replace(' ', '%20')}"
3287
+ logger.info(f"[Product] Converted local path to public URL: {media_path}")
3288
+ success = send_whatsjet_message(
3289
+ from_number,
3290
+ details,
3291
+ media_type=media_type,
3292
+ media_path=media_path, # Use public URL
3293
+ filename=filename
3294
+ )
3295
+ if success:
3296
+ logger.info(f"[Product] Successfully sent image with caption for product: {product_name}")
3297
+ else:
3298
+ logger.warning(f"[Product] Failed to send image, sending text only: {product_name}")
3299
+ send_whatsjet_message(from_number, details)
3300
+ else:
3301
+ # No image available, send text only
3302
+ logger.info(f"[Product] No image available, sending text only for: {product_name}")
3303
+ send_whatsjet_message(from_number, details)
3304
+ except Exception as e:
3305
+ logger.error(f"[Product] Error sending product image with caption: {e}")
3306
+ logger.info(f"[Product] Falling back to text-only message for: {product_name}")
3307
+ send_whatsjet_message(from_number, details)
3308
+
3309
  if __name__ == "__main__":
3310
  # Launch FastAPI app
3311
  import uvicorn