Spaces:
Runtime error
Runtime error
| import gradio as gr | |
| import re | |
| import json | |
| import time | |
| from datetime import datetime | |
| # Simple language detection function instead of using transformers | |
| def simple_detect_language(text): | |
| # Check for Arabic characters | |
| arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF]+') | |
| if arabic_pattern.search(text): | |
| return "ar" | |
| return "en" | |
| # Import customer service enhancements | |
| try: | |
| from customer_service_enhancements import ( | |
| ENHANCED_CUSTOMER_SERVICE_PHRASES_AR, | |
| ENHANCED_CUSTOMER_SERVICE_PHRASES_EN, | |
| BANKING_FAQS_AR, | |
| BANKING_FAQS_EN, | |
| BANKING_GLOSSARY_AR, | |
| BANKING_GLOSSARY_EN, | |
| get_enhanced_response, | |
| handle_banking_faq, | |
| offer_satisfaction_survey, | |
| get_banking_term_definition | |
| ) | |
| CUSTOMER_SERVICE_ENHANCEMENTS_AVAILABLE = True | |
| except ImportError: | |
| CUSTOMER_SERVICE_ENHANCEMENTS_AVAILABLE = False | |
| # Fallback customer service phrases | |
| ENHANCED_CUSTOMER_SERVICE_PHRASES_AR = { | |
| "greeting": [ | |
| "مرحبًا بك في بنك أم درمان الوطني! كيف يمكنني مساعدتك اليوم؟", | |
| "أهلاً بك في خدمة العملاء الافتراضية لبنك أم درمان الوطني. كيف يمكنني خدمتك؟" | |
| ], | |
| "thanks": [ | |
| "شكرًا لتواصلك مع بنك أم درمان الوطني!", | |
| "نشكرك على ثقتك في خدماتنا المصرفية." | |
| ], | |
| "follow_up": [ | |
| "هل هناك خدمة مصرفية أخرى يمكنني مساعدتك بها اليوم؟", | |
| "هل لديك أي استفسارات أخرى حول منتجاتنا أو خدماتنا المصرفية؟" | |
| ] | |
| } | |
| ENHANCED_CUSTOMER_SERVICE_PHRASES_EN = { | |
| "greeting": [ | |
| "Welcome to Omdurman National Bank! How may I assist you today?", | |
| "Hello and welcome to ONB virtual customer service. How can I help you?" | |
| ], | |
| "thanks": [ | |
| "Thank you for contacting Omdurman National Bank!", | |
| "We appreciate your trust in our banking services." | |
| ], | |
| "follow_up": [ | |
| "Is there any other banking service I can assist you with today?", | |
| "Do you have any other questions about our products or banking services?" | |
| ] | |
| } | |
| # Omdurman National Bank-specific guidelines in Arabic | |
| ONB_GUIDELINES_AR = { | |
| "balance": "يمكنك التحقق من رصيدك عبر الإنترنت أو عبر تطبيق الهاتف الخاص ببنك أم درمان الوطني. <a href='#' onclick='window.open(\"https://onb.sd/balance\", \"_blank\")'>افحص رصيدك الآن</a>", | |
| "lost_card": "في حالة فقدان البطاقة، اتصل بالرقم <a href='tel:249123456789'>249-123-456-789</a> فورًا أو <a href='#' onclick='window.open(\"https://onb.sd/block-card\", \"_blank\")'>أوقف البطاقة عبر الإنترنت</a>.", | |
| "loan": "شروط القرض تشمل الحد الأدنى للدخل (5000 جنيه سوداني) وتاريخ ائتماني جيد. <a href='#' onclick='window.open(\"https://onb.sd/loans\", \"_blank\")'>تقدم بطلب قرض الآن</a>", | |
| "transfer": "لتحويل الأموال، استخدم <a href='#' onclick='window.open(\"https://onb.sd/mobile-app\", \"_blank\")'>تطبيق الهاتف</a> أو <a href='#' onclick='window.open(\"https://onb.sd/online-banking\", \"_blank\")'>الخدمة المصرفية عبر الإنترنت</a>.", | |
| "new_account": "لفتح حساب جديد، قم بزيارة أقرب فرع مع جواز سفرك أو هويتك الوطنية. <a href='#' onclick='window.open(\"https://onb.sd/new-account\", \"_blank\")'>احجز موعدًا الآن</a>", | |
| "interest_rates": "أسعار الفائدة على الودائع تتراوح بين 5% إلى 10% سنويًا. <a href='#' onclick='window.open(\"https://onb.sd/rates\", \"_blank\")'>اطلع على جميع الأسعار</a>", | |
| "branches": "فروعنا موجودة في أم درمان، الخرطوم، وبورتسودان. <a href='#' onclick='window.open(\"https://onb.sd/branches\", \"_blank\")'>اعثر على أقرب فرع</a>", | |
| "working_hours": "ساعات العمل من 8 صباحًا إلى 3 مساءً من الأحد إلى الخميس. <a href='#' onclick='window.open(\"https://onb.sd/hours\", \"_blank\")'>تحقق من ساعات العمل الخاصة</a>", | |
| "contact": "الاتصال بنا على الرقم <a href='tel:249123456789'>249-123-456-789</a> أو عبر البريد الإلكتروني <a href='mailto:[email protected]'>[email protected]</a>. <a href='#' onclick='window.open(\"https://onb.sd/contact\", \"_blank\")'>نموذج الاتصال</a>" | |
| } | |
| # Omdurman National Bank-specific guidelines in English | |
| ONB_GUIDELINES_EN = { | |
| "balance": "You can check your balance online or via the ONB mobile app. <a href='#' onclick='window.open(\"https://onb.sd/balance\", \"_blank\")'>Check your balance now</a>", | |
| "lost_card": "In case of a lost card, call <a href='tel:249123456789'>249-123-456-789</a> immediately or <a href='#' onclick='window.open(\"https://onb.sd/block-card\", \"_blank\")'>block your card online</a>.", | |
| "loan": "Loan requirements include minimum income (5000 SDG) and good credit history. <a href='#' onclick='window.open(\"https://onb.sd/loans\", \"_blank\")'>Apply for a loan now</a>", | |
| "transfer": "To transfer funds, use the <a href='#' onclick='window.open(\"https://onb.sd/mobile-app\", \"_blank\")'>mobile app</a> or <a href='#' onclick='window.open(\"https://onb.sd/online-banking\", \"_blank\")'>online banking service</a>.", | |
| "new_account": "To open a new account, visit your nearest branch with your passport or national ID. <a href='#' onclick='window.open(\"https://onb.sd/new-account\", \"_blank\")'>Book an appointment now</a>", | |
| "interest_rates": "Interest rates on deposits range from 5% to 10% annually. <a href='#' onclick='window.open(\"https://onb.sd/rates\", \"_blank\")'>View all rates</a>", | |
| "branches": "Our branches are located in Omdurman, Khartoum, and Port Sudan. <a href='#' onclick='window.open(\"https://onb.sd/branches\", \"_blank\")'>Find your nearest branch</a>", | |
| "working_hours": "Working hours are from 8 AM to 3 PM, Sunday to Thursday. <a href='#' onclick='window.open(\"https://onb.sd/hours\", \"_blank\")'>Check special hours</a>", | |
| "contact": "Contact us at <a href='tel:249123456789'>249-123-456-789</a> or via email at <a href='mailto:[email protected]'>[email protected]</a>. <a href='#' onclick='window.open(\"https://onb.sd/contact\", \"_blank\")'>Contact form</a>" | |
| } | |
| # Quick action buttons in Arabic | |
| QUICK_ACTIONS_AR = [ | |
| {"text": "تحقق من الرصيد", "intent": "balance"}, | |
| {"text": "الإبلاغ عن بطاقة مفقودة", "intent": "lost_card"}, | |
| {"text": "معلومات القرض", "intent": "loan"}, | |
| {"text": "تحويل الأموال", "intent": "transfer"}, | |
| {"text": "فتح حساب جديد", "intent": "new_account"}, | |
| {"text": "أسعار الفائدة", "intent": "interest_rates"}, | |
| {"text": "مواقع الفروع", "intent": "branches"}, | |
| {"text": "ساعات العمل", "intent": "working_hours"}, | |
| {"text": "اتصل بنا", "intent": "contact"} | |
| ] | |
| # Quick action buttons in English | |
| QUICK_ACTIONS_EN = [ | |
| {"text": "Check Balance", "intent": "balance"}, | |
| {"text": "Report Lost Card", "intent": "lost_card"}, | |
| {"text": "Loan Information", "intent": "loan"}, | |
| {"text": "Transfer Funds", "intent": "transfer"}, | |
| {"text": "Open New Account", "intent": "new_account"}, | |
| {"text": "Interest Rates", "intent": "interest_rates"}, | |
| {"text": "Branch Locations", "intent": "branches"}, | |
| {"text": "Working Hours", "intent": "working_hours"}, | |
| {"text": "Contact Us", "intent": "contact"} | |
| ] | |
| # Menu options in both languages | |
| MENU_AR = """ | |
| قائمة الخدمات المصرفية: | |
| 1. رصيد - استعلام عن رصيد حسابك | |
| 2. بطاقة - الإبلاغ عن بطاقة مفقودة | |
| 3. قرض - معلومات عن القروض | |
| 4. تحويل - تحويل الأموال | |
| 5. حساب - فتح حساب جديد | |
| 6. فائدة - أسعار الفائدة | |
| 7. فرع - مواقع الفروع | |
| 8. ساعات - ساعات العمل | |
| 9. اتصال - معلومات الاتصال | |
| """ | |
| MENU_EN = """ | |
| Banking Services Menu: | |
| 1. balance - Check your account balance | |
| 2. card - Report a lost card | |
| 3. loan - Information about loans | |
| 4. transfer - Transfer funds | |
| 5. account - Open a new account | |
| 6. interest - Interest rates | |
| 7. branch - Branch locations | |
| 8. hours - Working hours | |
| 9. contact - Contact information | |
| """ | |
| # Map intents to keywords (enhanced) | |
| INTENT_KEYWORDS = { | |
| "balance": ["balance", "check balance", "account balance", "how much", "رصيد", "حساب", "كم المبلغ", "1"], | |
| "lost_card": ["lost", "card", "stolen", "missing", "فقدت", "بطاقة", "مسروقة", "ضائعة", "2"], | |
| "loan": ["loan", "borrow", "borrowing", "credit", "قرض", "استدانة", "إئتمان", "3"], | |
| "transfer": ["transfer", "send money", "payment", "تحويل", "ارسال", "دفع", "4"], | |
| "new_account": ["account", "open", "create", "new", "حساب", "فتح", "جديد", "إنشاء", "5"], | |
| "interest_rates": ["interest", "rate", "rates", "return", "فائدة", "نسبة", "عائد", "6"], | |
| "branches": ["branch", "location", "where", "office", "فرع", "موقع", "أين", "مكتب", "7"], | |
| "working_hours": ["hours", "time", "open", "close", "ساعات", "وقت", "مفتوح", "مغلق", "8"], | |
| "contact": ["contact", "phone", "email", "call", "اتصال", "هاتف", "بريد", "اتصل", "9"] | |
| } | |
| # Function to get a random phrase from the customer service phrases | |
| def get_random_phrase(category, language): | |
| import random | |
| if language == "ar": | |
| return random.choice(ENHANCED_CUSTOMER_SERVICE_PHRASES_AR[category]) | |
| else: | |
| return random.choice(ENHANCED_CUSTOMER_SERVICE_PHRASES_EN[category]) | |
| def classify_intent(message: str): | |
| # Check for menu request | |
| menu_keywords = ["menu", "options", "help", "قائمة", "خيارات", "مساعدة"] | |
| message_lower = message.lower() | |
| for keyword in menu_keywords: | |
| if keyword in message_lower: | |
| return "menu" | |
| # Use keyword matching for intent classification | |
| for intent_key, keywords in INTENT_KEYWORDS.items(): | |
| for keyword in keywords: | |
| if keyword.lower() in message_lower: | |
| return intent_key | |
| return "unknown" | |
| # Function to log customer interactions | |
| def log_interaction(user_message, bot_response, intent, language): | |
| timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
| log_entry = { | |
| "timestamp": timestamp, | |
| "user_message": user_message, | |
| "bot_response": bot_response, | |
| "intent": intent, | |
| "language": language | |
| } | |
| try: | |
| with open("/home/ubuntu/banking_chatbot/interaction_logs.jsonl", "a") as f: | |
| f.write(json.dumps(log_entry) + "\n") | |
| except Exception as e: | |
| print(f"Error logging interaction: {e}") | |
| def respond(message: str): | |
| if not message.strip(): | |
| return { | |
| "ar": "الرجاء كتابة سؤالك.", | |
| "en": "Please type your question." | |
| } | |
| # Detect language using simple function | |
| language = simple_detect_language(message) | |
| # Classify the user's intent using keyword matching | |
| intent = classify_intent(message) | |
| # Prepare responses in both languages | |
| responses = { | |
| "ar": "", | |
| "en": "" | |
| } | |
| # Special handling for menu request | |
| if intent == "menu": | |
| responses["ar"] = MENU_AR | |
| responses["en"] = MENU_EN | |
| log_interaction(message, responses[language], "menu", language) | |
| return responses | |
| # Check if it's a banking FAQ | |
| if CUSTOMER_SERVICE_ENHANCEMENTS_AVAILABLE: | |
| faq_answer = handle_banking_faq(message, language) | |
| if faq_answer: | |
| # Add a greeting phrase at the beginning | |
| greeting_ar = get_random_phrase("greeting", "ar") | |
| greeting_en = get_random_phrase("greeting", "en") | |
| # Add a follow-up phrase at the end | |
| follow_up_ar = get_random_phrase("follow_up", "ar") | |
| follow_up_en = get_random_phrase("follow_up", "en") | |
| # Combine all parts | |
| responses["ar"] = f"{greeting_ar}<br><br>{faq_answer}<br><br>{follow_up_ar}" | |
| responses["en"] = f"{greeting_en}<br><br>{faq_answer}<br><br>{follow_up_en}" | |
| log_interaction(message, responses[language], "faq", language) | |
| return responses | |
| # Check if it's a banking term definition request | |
| if CUSTOMER_SERVICE_ENHANCEMENTS_AVAILABLE: | |
| term_definition = get_banking_term_definition(message, language) | |
| if term_definition: | |
| # Add a greeting phrase at the beginning | |
| greeting_ar = get_random_phrase("greeting", "ar") | |
| greeting_en = get_random_phrase("greeting", "en") | |
| # Add a follow-up phrase at the end | |
| follow_up_ar = get_random_phrase("follow_up", "ar") | |
| follow_up_en = get_random_phrase("follow_up", "en") | |
| # Combine all parts | |
| responses["ar"] = f"{greeting_ar}<br><br>{term_definition}<br><br>{follow_up_ar}" | |
| responses["en"] = f"{greeting_en}<br><br>{term_definition}<br><br>{follow_up_en}" | |
| log_interaction(message, responses[language], "term", language) | |
| return responses | |
| # If intent is recognized, return the corresponding response | |
| if intent != "unknown": | |
| if CUSTOMER_SERVICE_ENHANCEMENTS_AVAILABLE: | |
| # Use enhanced response if available | |
| responses["ar"] = get_enhanced_response(intent, "ar") | |
| responses["en"] = get_enhanced_response(intent, "en") | |
| else: | |
| # Add a greeting phrase at the beginning | |
| greeting_ar = get_random_phrase("greeting", "ar") | |
| greeting_en = get_random_phrase("greeting", "en") | |
| # Add the main response | |
| main_response_ar = ONB_GUIDELINES_AR.get(intent, "عذرًا، لم يتم التعرف على الخيار المحدد.") | |
| main_response_en = ONB_GUIDELINES_EN.get(intent, "Sorry, the selected option was not recognized.") | |
| # Add a follow-up phrase at the end | |
| follow_up_ar = get_random_phrase("follow_up", "ar") | |
| follow_up_en = get_random_phrase("follow_up", "en") | |
| # Combine all parts | |
| responses["ar"] = f"{greeting_ar}<br><br>{main_response_ar}<br><br>{follow_up_ar}" | |
| responses["en"] = f"{greeting_en}<br><br>{main_response_en}<br><br>{follow_up_en}" | |
| else: | |
| # Default response if no intent is matched - show menu | |
| responses["ar"] = "عذرًا، لم أفهم سؤالك. إليك قائمة بالخدمات المتاحة:" + MENU_AR | |
| responses["en"] = "Sorry, I didn't understand your question. Here's a menu of available services:" + MENU_EN | |
| # Log the interaction | |
| log_interaction(message, responses[language], intent, language) | |
| return responses | |
| # Custom CSS for better UI | |
| custom_css = """ | |
| .gradio-container { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| } | |
| .chat-message { | |
| padding: 1rem; | |
| border-radius: 10px; | |
| margin-bottom: 1rem; | |
| max-width: 80%; | |
| } | |
| .user-message { | |
| background-color: #e6f7ff; | |
| margin-left: auto; | |
| text-align: right; | |
| } | |
| .bot-message { | |
| background-color: #f0f0f0; | |
| margin-right: auto; | |
| text-align: left; | |
| } | |
| .bot-message-ar { | |
| background-color: #f0f0f0; | |
| margin-left: auto; | |
| text-align: right; | |
| } | |
| .header-section { | |
| background-color: #1a5276; | |
| color: white; | |
| padding: 1rem; | |
| border-radius: 10px; | |
| margin-bottom: 1rem; | |
| text-align: center; | |
| } | |
| .footer-section { | |
| font-size: 0.8rem; | |
| text-align: center; | |
| margin-top: 2rem; | |
| color: #666; | |
| } | |
| .lang-selector { | |
| text-align: right; | |
| margin-bottom: 1rem; | |
| } | |
| .menu-button { | |
| margin-top: 0.5rem; | |
| } | |
| .quick-actions { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 0.5rem; | |
| margin: 1rem 0; | |
| } | |
| .quick-action-button { | |
| background-color: #1a5276; | |
| color: white; | |
| border: none; | |
| border-radius: 20px; | |
| padding: 0.5rem 1rem; | |
| cursor: pointer; | |
| font-size: 0.9rem; | |
| transition: background-color 0.3s; | |
| } | |
| .quick-action-button:hover { | |
| background-color: #2980b9; | |
| } | |
| .chat-container { | |
| border: 1px solid #ddd; | |
| border-radius: 10px; | |
| padding: 1rem; | |
| background-color: #f9f9f9; | |
| } | |
| .typing-indicator { | |
| display: inline-block; | |
| width: 50px; | |
| text-align: left; | |
| } | |
| .typing-indicator span { | |
| display: inline-block; | |
| width: 8px; | |
| height: 8px; | |
| background-color: #1a5276; | |
| border-radius: 50%; | |
| margin-right: 5px; | |
| animation: typing 1s infinite; | |
| } | |
| .typing-indicator span:nth-child(2) { | |
| animation-delay: 0.2s; | |
| } | |
| .typing-indicator span:nth-child(3) { | |
| animation-delay: 0.4s; | |
| } | |
| @keyframes typing { | |
| 0%, 100% { | |
| transform: translateY(0); | |
| } | |
| 50% { | |
| transform: translateY(-5px); | |
| } | |
| } | |
| .live-agent-button { | |
| background-color: #27ae60; | |
| color: white; | |
| border: none; | |
| border-radius: 5px; | |
| padding: 0.5rem 1rem; | |
| cursor: pointer; | |
| font-size: 0.9rem; | |
| margin-top: 1rem; | |
| transition: background-color 0.3s; | |
| } | |
| .live-agent-button:hover { | |
| background-color: #2ecc71; | |
| } | |
| /* Add custom styling for links */ | |
| a { | |
| color: #2980b9; | |
| text-decoration: none; | |
| font-weight: bold; | |
| } | |
| a:hover { | |
| text-decoration: underline; | |
| } | |
| /* Add styling for action buttons */ | |
| .action-button { | |
| display: inline-block; | |
| background-color: #3498db; | |
| color: white; | |
| padding: 0.5rem 1rem; | |
| border-radius: 5px; | |
| margin: 0.5rem 0; | |
| text-decoration: none; | |
| } | |
| .action-button:hover { | |
| background-color: #2980b9; | |
| text-decoration: none; | |
| } | |
| /* Styling for satisfaction survey */ | |
| .satisfaction-survey { | |
| background-color: #f8f9fa; | |
| border: 1px solid #ddd; | |
| border-radius: 10px; | |
| padding: 1rem; | |
| margin-top: 1rem; | |
| } | |
| .survey-question { | |
| margin-bottom: 1rem; | |
| } | |
| .rating-options { | |
| display: flex; | |
| justify-content: space-between; | |
| margin-bottom: 0.5rem; | |
| } | |
| .survey-submit { | |
| background-color: #1a5276; | |
| color: white; | |
| border: none; | |
| border-radius: 5px; | |
| padding: 0.5rem 1rem; | |
| cursor: pointer; | |
| font-size: 0.9rem; | |
| margin-top: 1rem; | |
| } | |
| .survey-submit:hover { | |
| background-color: #2980b9; | |
| } | |
| """ | |
| # Custom JavaScript for enhanced functionality | |
| custom_js = """ | |
| function simulateTyping(message, elementId, delay = 30) { | |
| const element = document.getElementById(elementId); | |
| if (!element) return; | |
| element.innerHTML = ""; | |
| let i = 0; | |
| function type() { | |
| if (i < message.length) { | |
| element.innerHTML += message.charAt(i); | |
| i++; | |
| setTimeout(type, delay); | |
| } | |
| } | |
| type(); | |
| } | |
| // Function to show typing indicator | |
| function showTypingIndicator() { | |
| const chatbox = document.getElementById('chatbox'); | |
| if (!chatbox) return; | |
| const typingIndicator = document.createElement('div'); | |
| typingIndicator.className = 'typing-indicator'; | |
| typingIndicator.id = 'typing-indicator'; | |
| typingIndicator.innerHTML = '<span></span><span></span><span></span>'; | |
| chatbox.appendChild(typingIndicator); | |
| chatbox.scrollTop = chatbox.scrollHeight; | |
| } | |
| // Function to hide typing indicator | |
| function hideTypingIndicator() { | |
| const typingIndicator = document.getElementById('typing-indicator'); | |
| if (typingIndicator) { | |
| typingIndicator.remove(); | |
| } | |
| } | |
| // Function to connect with a live agent | |
| function connectLiveAgent() { | |
| alert('Connecting to a live customer service agent. Please wait a moment...'); | |
| // In a real implementation, this would initiate a connection to a live agent system | |
| } | |
| // Function to show satisfaction survey | |
| function showSatisfactionSurvey(surveyHtml) { | |
| const chatbox = document.getElementById('chatbox'); | |
| if (!chatbox) return; | |
| const surveyDiv = document.createElement('div'); | |
| surveyDiv.innerHTML = surveyHtml; | |
| chatbox.appendChild(surveyDiv); | |
| chatbox.scrollTop = chatbox.scrollHeight; | |
| } | |
| // Function to submit survey | |
| function submitSurvey() { | |
| const form = document.getElementById('satisfaction-form'); | |
| if (!form) return; | |
| // In a real implementation, this would send the survey data to a server | |
| alert('Thank you for your feedback!'); | |
| form.style.display = 'none'; | |
| } | |
| """ | |
| # Chat interface with enhanced UI | |
| with gr.Blocks(css=custom_css, js=custom_js) as demo: | |
| # Store conversation history | |
| state = gr.State(value=[]) | |
| # Store selected language | |
| selected_lang = gr.State(value="ar") | |
| # Store user name for personalization | |
| user_name = gr.State(value=None) | |
| with gr.Row(elem_classes="header-section"): | |
| with gr.Column(): | |
| gr.Markdown("# Omdurman National Bank | بنك أم درمان الوطني") | |
| gr.Markdown("### Virtual Banking Assistant | المساعد المصرفي الافتراضي") | |
| with gr.Row(): | |
| with gr.Column(elem_classes="lang-selector"): | |
| language_btn = gr.Radio( | |
| ["العربية", "English"], | |
| value="العربية", | |
| label="Language | اللغة" | |
| ) | |
| with gr.Row(elem_classes="chat-container"): | |
| chat_box = gr.HTML(elem_id="chatbox", value="<div style='height: 400px; overflow-y: auto;'></div>") | |
| # Quick action buttons (will be populated based on language) | |
| with gr.Row(elem_classes="quick-actions", visible=True) as quick_actions_container: | |
| quick_action_buttons = [] | |
| for i in range(9): # Create 9 buttons (one for each intent) | |
| button = gr.Button("", visible=False, elem_classes="quick-action-button") | |
| quick_action_buttons.append(button) | |
| with gr.Row(): | |
| with gr.Column(scale=8): | |
| text_input = gr.Textbox( | |
| placeholder="Type your question here | اكتب سؤالك هنا", | |
| label="", | |
| elem_id="chat-input" | |
| ) | |
| with gr.Column(scale=1): | |
| submit_btn = gr.Button("Send | إرسال", variant="primary") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| menu_btn = gr.Button("Show Menu | إظهار القائمة", elem_classes="menu-button") | |
| with gr.Column(scale=1): | |
| live_agent_btn = gr.Button("Connect to Live Agent | الاتصال بوكيل حي", elem_classes="live-agent-button") | |
| with gr.Column(scale=1): | |
| survey_btn = gr.Button("Feedback | تقييم الخدمة", elem_classes="menu-button") | |
| with gr.Row(elem_classes="footer-section"): | |
| gr.Markdown("© 2025 Omdurman National Bank. All Rights Reserved. | جميع الحقوق محفوظة لبنك أم درمان الوطني ٢٠٢٥ ©") | |
| # Update language state and quick action buttons when language is changed | |
| def update_language_and_buttons(lang): | |
| language_code = "ar" if lang == "العربية" else "en" | |
| # Get the appropriate quick actions based on language | |
| quick_actions = QUICK_ACTIONS_AR if language_code == "ar" else QUICK_ACTIONS_EN | |
| # Update button visibility and text | |
| button_updates = [] | |
| for i, button in enumerate(quick_action_buttons): | |
| if i < len(quick_actions): | |
| button_updates.append(gr.Button.update(visible=True, value=quick_actions[i]["text"])) | |
| else: | |
| button_updates.append(gr.Button.update(visible=False)) | |
| return [language_code] + button_updates | |
| # Connect language button to update function | |
| outputs = [selected_lang] + quick_action_buttons | |
| language_btn.change( | |
| fn=update_language_and_buttons, | |
| inputs=language_btn, | |
| outputs=outputs | |
| ) | |
| # Function to add message to chat | |
| def add_message_to_chat(message, is_user, lang): | |
| # Create a JavaScript function to add the message to the chat | |
| alignment = "right" if (is_user or (not is_user and lang == "ar")) else "left" | |
| background = "#e6f7ff" if is_user else "#f0f0f0" | |
| js_code = f""" | |
| (function() {{ | |
| const chatbox = document.getElementById('chatbox').querySelector('div'); | |
| const messageDiv = document.createElement('div'); | |
| messageDiv.style.padding = '1rem'; | |
| messageDiv.style.borderRadius = '10px'; | |
| messageDiv.style.marginBottom = '1rem'; | |
| messageDiv.style.maxWidth = '80%'; | |
| messageDiv.style.backgroundColor = '{background}'; | |
| messageDiv.style.marginLeft = '{alignment === "right" ? "auto" : "0"}'; | |
| messageDiv.style.marginRight = '{alignment === "left" ? "auto" : "0"}'; | |
| messageDiv.style.textAlign = '{alignment}'; | |
| messageDiv.innerHTML = `{message}`; | |
| chatbox.appendChild(messageDiv); | |
| chatbox.scrollTop = chatbox.scrollHeight; | |
| }})(); | |
| """ | |
| return js_code | |
| # Handle message submission with typing effect | |
| def on_submit(message, chat_history, lang, name): | |
| if not message.strip(): | |
| return "", chat_history, "", name | |
| # Check if this is a name introduction | |
| name_patterns = [ | |
| r"my name is (\w+)", | |
| r"i am (\w+)", | |
| r"i'm (\w+)", | |
| r"اسمي (\w+)", | |
| r"أنا (\w+)" | |
| ] | |
| for pattern in name_patterns: | |
| match = re.search(pattern, message.lower()) | |
| if match: | |
| name = match.group(1) | |
| break | |
| # Add user message to chat | |
| user_js = add_message_to_chat(message, True, lang) | |
| # Show typing indicator | |
| typing_js = "showTypingIndicator();" | |
| # Get response | |
| responses = respond(message) | |
| # Select response based on language | |
| response = responses[lang] | |
| # Personalize response if name is available | |
| if name and CUSTOMER_SERVICE_ENHANCEMENTS_AVAILABLE: | |
| if lang == "ar": | |
| response = response.replace("مرحبًا", f"مرحبًا {name}") | |
| else: | |
| response = response.replace("Welcome", f"Welcome {name}") | |
| response = response.replace("Hello", f"Hello {name}") | |
| # Hide typing indicator and add bot response | |
| bot_js = f""" | |
| setTimeout(function() {{ | |
| hideTypingIndicator(); | |
| {add_message_to_chat(response, False, lang)} | |
| }}, 1000); | |
| """ | |
| # Combine all JavaScript | |
| combined_js = user_js + typing_js + bot_js | |
| return "", chat_history, combined_js, name | |
| # Handle menu button click | |
| def show_menu(chat_history, lang): | |
| menu_responses = { | |
| "ar": MENU_AR, | |
| "en": MENU_EN | |
| } | |
| # Get menu text | |
| menu_text = menu_responses[lang] | |
| # Add system message showing the menu | |
| js_code = add_message_to_chat(menu_text.replace("\n", "<br>"), False, lang) | |
| return chat_history, js_code | |
| # Handle quick action button clicks | |
| def handle_quick_action(button_index, chat_history, lang, name): | |
| # Get the appropriate quick actions based on language | |
| quick_actions = QUICK_ACTIONS_AR if lang == "ar" else QUICK_ACTIONS_EN | |
| if button_index < len(quick_actions): | |
| # Get the intent for this button | |
| intent = quick_actions[button_index]["intent"] | |
| # Get the response for this intent | |
| if CUSTOMER_SERVICE_ENHANCEMENTS_AVAILABLE: | |
| # Use enhanced response if available | |
| response_ar = get_enhanced_response(intent, "ar", name) | |
| response_en = get_enhanced_response(intent, "en", name) | |
| else: | |
| # Add a greeting phrase at the beginning | |
| greeting_ar = get_random_phrase("greeting", "ar") | |
| greeting_en = get_random_phrase("greeting", "en") | |
| # Add the main response | |
| main_response_ar = ONB_GUIDELINES_AR.get(intent, "") | |
| main_response_en = ONB_GUIDELINES_EN.get(intent, "") | |
| # Add a follow-up phrase at the end | |
| follow_up_ar = get_random_phrase("follow_up", "ar") | |
| follow_up_en = get_random_phrase("follow_up", "en") | |
| # Combine all parts | |
| response_ar = f"{greeting_ar}<br><br>{main_response_ar}<br><br>{follow_up_ar}" | |
| response_en = f"{greeting_en}<br><br>{main_response_en}<br><br>{follow_up_en}" | |
| responses = { | |
| "ar": response_ar, | |
| "en": response_en | |
| } | |
| # Select response based on language | |
| response = responses[lang] | |
| # Personalize response if name is available | |
| if name: | |
| if lang == "ar": | |
| response = response.replace("مرحبًا", f"مرحبًا {name}") | |
| else: | |
| response = response.replace("Welcome", f"Welcome {name}") | |
| response = response.replace("Hello", f"Hello {name}") | |
| # Add button text as user message | |
| button_text = quick_actions[button_index]["text"] | |
| user_js = add_message_to_chat(button_text, True, lang) | |
| # Show typing indicator | |
| typing_js = "showTypingIndicator();" | |
| # Hide typing indicator and add bot response | |
| bot_js = f""" | |
| setTimeout(function() {{ | |
| hideTypingIndicator(); | |
| {add_message_to_chat(response, False, lang)} | |
| }}, 1000); | |
| """ | |
| # Combine all JavaScript | |
| combined_js = user_js + typing_js + bot_js | |
| # Log the interaction | |
| log_interaction(button_text, response, intent, lang) | |
| return chat_history, combined_js | |
| return chat_history, "" | |
| # Handle live agent button click | |
| def connect_to_live_agent(): | |
| return "connectLiveAgent();" | |
| # Handle satisfaction survey button click | |
| def show_satisfaction_survey(lang): | |
| if CUSTOMER_SERVICE_ENHANCEMENTS_AVAILABLE: | |
| survey_html = offer_satisfaction_survey(lang) | |
| return f"showSatisfactionSurvey(`{survey_html}`);" | |
| else: | |
| # Simple survey HTML if enhancements not available | |
| title = "استطلاع رضا العملاء" if lang == "ar" else "Customer Satisfaction Survey" | |
| intro = "نقدر ملاحظاتك!" if lang == "ar" else "We value your feedback!" | |
| submit = "إرسال" if lang == "ar" else "Submit" | |
| survey_html = f""" | |
| <div class="satisfaction-survey" dir="{('rtl' if lang == 'ar' else 'ltr')}"> | |
| <h3>{title}</h3> | |
| <p>{intro}</p> | |
| <button onclick="submitSurvey()" class="survey-submit">{submit}</button> | |
| </div> | |
| """ | |
| return f"showSatisfactionSurvey(`{survey_html}`);" | |
| # Link inputs and button to response function | |
| submit_btn.click( | |
| fn=on_submit, | |
| inputs=[text_input, state, selected_lang, user_name], | |
| outputs=[text_input, state, chat_box, user_name] | |
| ) | |
| # Link menu button to show menu function | |
| menu_btn.click( | |
| fn=show_menu, | |
| inputs=[state, selected_lang], | |
| outputs=[state, chat_box] | |
| ) | |
| # Link live agent button to connect function | |
| live_agent_btn.click( | |
| fn=connect_to_live_agent, | |
| inputs=[], | |
| outputs=[chat_box] | |
| ) | |
| # Link survey button to show survey function | |
| survey_btn.click( | |
| fn=show_satisfaction_survey, | |
| inputs=[selected_lang], | |
| outputs=[chat_box] | |
| ) | |
| # Link quick action buttons to handler function | |
| for i, button in enumerate(quick_action_buttons): | |
| button.click( | |
| fn=lambda idx=i, s=state, l=selected_lang, n=user_name: handle_quick_action(idx, s, l, n), | |
| inputs=[state, selected_lang, user_name], | |
| outputs=[state, chat_box] | |
| ) | |
| # Also trigger on Enter key | |
| text_input.submit( | |
| fn=on_submit, | |
| inputs=[text_input, state, selected_lang, user_name], | |
| outputs=[text_input, state, chat_box, user_name] | |
| ) | |
| # Initialize the chat with a welcome message | |
| def init_chat(lang): | |
| # Get welcome message based on language | |
| welcome_ar = """ | |
| <div style='text-align: center; margin-bottom: 20px;'> | |
| <img src='https://via.placeholder.com/150?text=ONB+Logo' alt='ONB Logo' style='max-width: 150px;'> | |
| <h3>مرحبًا بك في المساعد المصرفي الافتراضي لبنك أم درمان الوطني!</h3> | |
| <p>يمكنك طرح أي سؤال حول خدماتنا المصرفية أو استخدام أزرار الإجراءات السريعة أدناه.</p> | |
| </div> | |
| """ | |
| welcome_en = """ | |
| <div style='text-align: center; margin-bottom: 20px;'> | |
| <img src='https://via.placeholder.com/150?text=ONB+Logo' alt='ONB Logo' style='max-width: 150px;'> | |
| <h3>Welcome to Omdurman National Bank Virtual Banking Assistant!</h3> | |
| <p>You can ask any question about our banking services or use the quick action buttons below.</p> | |
| </div> | |
| """ | |
| welcome_message = welcome_ar if lang == "ar" else welcome_en | |
| # Add welcome message to chat | |
| js_code = f""" | |
| (function() {{ | |
| const chatbox = document.getElementById('chatbox').querySelector('div'); | |
| chatbox.innerHTML = `{welcome_message}`; | |
| }})(); | |
| """ | |
| # Update quick action buttons | |
| quick_actions = QUICK_ACTIONS_AR if lang == "ar" else QUICK_ACTIONS_EN | |
| button_updates = [] | |
| for i, button in enumerate(quick_action_buttons): | |
| if i < len(quick_actions): | |
| button_updates.append(gr.Button.update(visible=True, value=quick_actions[i]["text"])) | |
| else: | |
| button_updates.append(gr.Button.update(visible=False)) | |
| return [js_code] + button_updates | |
| # Initialize the chat when the app starts | |
| demo.load( | |
| fn=lambda: init_chat("ar"), | |
| inputs=[], | |
| outputs=[chat_box] + quick_action_buttons | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=True # Enable public link | |
| ) | |