Spaces:
Runtime error
Runtime error
File size: 35,418 Bytes
7d0b36f |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 |
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
)
|