Spaces:
Runtime error
Runtime error
Upload 7 files
Browse files- INSTALLATION.md +96 -0
- README.md +61 -14
- customer_service_enhancements.py +349 -0
- final_chatbot.py +923 -0
- improved_chatbot.py +460 -0
- original_chatbot.py +305 -0
- requirements.txt +1 -8
INSTALLATION.md
ADDED
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Installation Instructions for Omdurman National Bank Chatbot
|
2 |
+
|
3 |
+
This document provides instructions for installing and running the improved Omdurman National Bank chatbot.
|
4 |
+
|
5 |
+
## Prerequisites
|
6 |
+
|
7 |
+
- Python 3.6 or higher
|
8 |
+
- pip (Python package installer)
|
9 |
+
|
10 |
+
## Installation Steps
|
11 |
+
|
12 |
+
1. Create a virtual environment (recommended):
|
13 |
+
```bash
|
14 |
+
python3 -m venv venv
|
15 |
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
16 |
+
```
|
17 |
+
|
18 |
+
2. Install Gradio with minimal dependencies:
|
19 |
+
```bash
|
20 |
+
pip install gradio --no-deps
|
21 |
+
```
|
22 |
+
|
23 |
+
3. Install required dependencies:
|
24 |
+
```bash
|
25 |
+
pip install gradio-client
|
26 |
+
```
|
27 |
+
|
28 |
+
4. For full functionality (if disk space allows):
|
29 |
+
```bash
|
30 |
+
pip install numpy pandas
|
31 |
+
```
|
32 |
+
|
33 |
+
## Running the Chatbot
|
34 |
+
|
35 |
+
### Basic Version
|
36 |
+
To run the basic version of the chatbot:
|
37 |
+
|
38 |
+
```bash
|
39 |
+
python3 final_chatbot.py
|
40 |
+
```
|
41 |
+
|
42 |
+
This will start the chatbot with core functionality. The chatbot will automatically detect if the customer service enhancements module is available and adapt accordingly.
|
43 |
+
|
44 |
+
### Testing Locally
|
45 |
+
|
46 |
+
Once the chatbot is running, you can access it at:
|
47 |
+
- http://localhost:7860
|
48 |
+
|
49 |
+
The chatbot will also generate a public link that you can use to access it from any device.
|
50 |
+
|
51 |
+
## Troubleshooting
|
52 |
+
|
53 |
+
### Disk Space Issues
|
54 |
+
|
55 |
+
If you encounter disk space issues during installation, you can try:
|
56 |
+
|
57 |
+
1. Using the `--no-deps` flag with pip to avoid installing unnecessary dependencies:
|
58 |
+
```bash
|
59 |
+
pip install gradio --no-deps
|
60 |
+
```
|
61 |
+
|
62 |
+
2. Installing only the essential dependencies:
|
63 |
+
```bash
|
64 |
+
pip install gradio-client
|
65 |
+
```
|
66 |
+
|
67 |
+
3. Using a smaller version of the chatbot by removing the customer service enhancements:
|
68 |
+
```bash
|
69 |
+
# Run without importing customer_service_enhancements.py
|
70 |
+
```
|
71 |
+
|
72 |
+
### Missing Modules
|
73 |
+
|
74 |
+
If you encounter "ModuleNotFoundError" messages:
|
75 |
+
|
76 |
+
1. Install the specific missing module:
|
77 |
+
```bash
|
78 |
+
pip install [module_name]
|
79 |
+
```
|
80 |
+
|
81 |
+
2. If the error is related to gradio-client:
|
82 |
+
```bash
|
83 |
+
pip install gradio-client
|
84 |
+
```
|
85 |
+
|
86 |
+
## File Structure
|
87 |
+
|
88 |
+
- `final_chatbot.py`: The main chatbot implementation with all features
|
89 |
+
- `customer_service_enhancements.py`: Additional customer service features (optional)
|
90 |
+
- `original_chatbot.py`: The original chatbot implementation for reference
|
91 |
+
- `README.md`: Overview of the chatbot features and implementation
|
92 |
+
- `INSTALLATION.md`: This installation guide
|
93 |
+
|
94 |
+
## Contact
|
95 |
+
|
96 |
+
For any issues or questions, please contact the Omdurman National Bank IT support team.
|
README.md
CHANGED
@@ -1,14 +1,61 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Omdurman National Bank Chatbot
|
2 |
+
|
3 |
+
This is an improved chatbot for Omdurman National Bank with user-friendly features and external action capabilities.
|
4 |
+
|
5 |
+
## Features
|
6 |
+
|
7 |
+
### User-Friendly Enhancements
|
8 |
+
- Bilingual support (Arabic and English)
|
9 |
+
- Quick action buttons for common banking tasks
|
10 |
+
- Typing indicators for a more natural conversation experience
|
11 |
+
- Enhanced UI with better styling and layout
|
12 |
+
- Welcome message with bank logo
|
13 |
+
|
14 |
+
### External Actions
|
15 |
+
- Clickable links for online banking actions
|
16 |
+
- Phone number links for direct calling
|
17 |
+
- Email links for customer support
|
18 |
+
- Branch location finder
|
19 |
+
- Live agent connection option
|
20 |
+
|
21 |
+
### Customer Service Expertise
|
22 |
+
- Natural language responses with greeting and follow-up phrases
|
23 |
+
- Personalized customer service tone
|
24 |
+
- Interaction logging for quality improvement
|
25 |
+
- Menu of available services
|
26 |
+
|
27 |
+
## Implementation Details
|
28 |
+
|
29 |
+
The chatbot has been implemented with disk space constraints in mind:
|
30 |
+
- Replaced the heavy transformers-based language detection with a lightweight regex-based approach
|
31 |
+
- Used HTML/CSS/JavaScript for enhanced UI features instead of additional dependencies
|
32 |
+
- Implemented all functionality within a single file for easy deployment
|
33 |
+
|
34 |
+
## How to Run
|
35 |
+
|
36 |
+
```bash
|
37 |
+
cd /home/ubuntu/banking_chatbot
|
38 |
+
python3 improved_chatbot.py
|
39 |
+
```
|
40 |
+
|
41 |
+
The chatbot will be available at http://localhost:7860 and will also generate a public link for external access.
|
42 |
+
|
43 |
+
## Comparison with Original Version
|
44 |
+
|
45 |
+
The improved version offers several advantages over the original:
|
46 |
+
1. More user-friendly interface with quick action buttons
|
47 |
+
2. External action links for banking services
|
48 |
+
3. More natural, human-like responses
|
49 |
+
4. Typing indicators for a more engaging experience
|
50 |
+
5. Live agent connection option
|
51 |
+
6. Interaction logging for service improvement
|
52 |
+
7. Reduced dependencies for better performance
|
53 |
+
|
54 |
+
## Customization
|
55 |
+
|
56 |
+
You can customize the chatbot by modifying:
|
57 |
+
- `ONB_GUIDELINES_AR` and `ONB_GUIDELINES_EN` for response content
|
58 |
+
- `QUICK_ACTIONS_AR` and `QUICK_ACTIONS_EN` for quick action buttons
|
59 |
+
- `CUSTOMER_SERVICE_PHRASES_AR` and `CUSTOMER_SERVICE_PHRASES_EN` for tone and style
|
60 |
+
- `custom_css` for visual styling
|
61 |
+
- `custom_js` for interactive behavior
|
customer_service_enhancements.py
ADDED
@@ -0,0 +1,349 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Customer Service Expertise Enhancements for Omdurman National Bank Chatbot
|
3 |
+
|
4 |
+
This module contains additional customer service expertise elements that can be
|
5 |
+
integrated into the main chatbot implementation to provide a more personalized
|
6 |
+
and professional banking experience.
|
7 |
+
"""
|
8 |
+
|
9 |
+
# Enhanced customer service phrases with more banking-specific language
|
10 |
+
ENHANCED_CUSTOMER_SERVICE_PHRASES_AR = {
|
11 |
+
"greeting": [
|
12 |
+
"مرحبًا بك في بنك أم درمان الوطني! كيف يمكنني مساعدتك اليوم؟",
|
13 |
+
"أهلاً بك في خدمة العملاء الافتراضية لبنك أم درمان الوطني. كيف يمكنني خدمتك؟",
|
14 |
+
"مرحبًا! أنا مساعدك المصرفي الشخصي من بنك أم درمان الوطني. كيف يمكنني مساعدتك؟",
|
15 |
+
"شكرًا لاختيارك بنك أم درمان الوطني. كيف يمكنني تلبية احتياجاتك المصرفية اليوم؟"
|
16 |
+
],
|
17 |
+
"thanks": [
|
18 |
+
"شكرًا لتواصلك مع بنك أم درمان الوطني!",
|
19 |
+
"نشكرك على ثقتك في خدماتنا المصرفية.",
|
20 |
+
"سعداء بخدمتك دائمًا في بنك أم درمان الوطني!",
|
21 |
+
"نقدر اختيارك لبنك أم درمان الوطني لتلبية احتياجاتك المالية."
|
22 |
+
],
|
23 |
+
"follow_up": [
|
24 |
+
"هل هناك خدمة مصرفية أخرى يمكنني مساعدتك بها اليوم؟",
|
25 |
+
"هل لديك أي استفسارات أخرى حول منتجاتنا أو خدماتنا المصرفية؟",
|
26 |
+
"هل تحتاج إلى مساعدة في أي معاملات مصرفية أخرى؟",
|
27 |
+
"هل يمكنني مساعدتك في أي شيء آخر لتحسين تجربتك المصرفية معنا؟"
|
28 |
+
],
|
29 |
+
"apology": [
|
30 |
+
"أعتذر عن أي إزعاج قد سببناه لك.",
|
31 |
+
"نأسف على هذه التجربة، ونحن ملتزمون بتحسين خدماتنا.",
|
32 |
+
"أعتذر عن عدم تلبية توقعاتك. دعنا نعمل معًا لإيجاد حل.",
|
33 |
+
"نعتذر عن هذا الخطأ ونقدر صبرك."
|
34 |
+
],
|
35 |
+
"reassurance": [
|
36 |
+
"يمكنك الاطمئنان بأن أموالك في أيدٍ أمينة مع بنك أم درمان الوطني.",
|
37 |
+
"أمن معلوماتك المالية هو أولويتنا القصوى.",
|
38 |
+
"نحن نلتزم بتقديم أفضل الحلول المالية لعملائنا الكرام.",
|
39 |
+
"ثقتك مهمة لنا، ونحن نعمل جاهدين للحفاظ عليها."
|
40 |
+
]
|
41 |
+
}
|
42 |
+
|
43 |
+
ENHANCED_CUSTOMER_SERVICE_PHRASES_EN = {
|
44 |
+
"greeting": [
|
45 |
+
"Welcome to Omdurman National Bank! How may I assist you today?",
|
46 |
+
"Hello and welcome to ONB virtual customer service. How can I help you?",
|
47 |
+
"Greetings! I'm your personal banking assistant from Omdurman National Bank. How can I assist you?",
|
48 |
+
"Thank you for choosing Omdurman National Bank. How may I address your banking needs today?"
|
49 |
+
],
|
50 |
+
"thanks": [
|
51 |
+
"Thank you for contacting Omdurman National Bank!",
|
52 |
+
"We appreciate your trust in our banking services.",
|
53 |
+
"Always a pleasure serving you at Omdurman National Bank!",
|
54 |
+
"We value your choice of Omdurman National Bank for your financial needs."
|
55 |
+
],
|
56 |
+
"follow_up": [
|
57 |
+
"Is there any other banking service I can assist you with today?",
|
58 |
+
"Do you have any other questions about our products or banking services?",
|
59 |
+
"Do you need assistance with any other banking transactions?",
|
60 |
+
"Can I help you with anything else to enhance your banking experience with us?"
|
61 |
+
],
|
62 |
+
"apology": [
|
63 |
+
"I apologize for any inconvenience this may have caused you.",
|
64 |
+
"We're sorry about this experience, and we're committed to improving our services.",
|
65 |
+
"I apologize that we didn't meet your expectations. Let's work together to find a solution.",
|
66 |
+
"We apologize for this error and appreciate your patience."
|
67 |
+
],
|
68 |
+
"reassurance": [
|
69 |
+
"You can rest assured that your money is in safe hands with Omdurman National Bank.",
|
70 |
+
"The security of your financial information is our top priority.",
|
71 |
+
"We are committed to providing the best financial solutions for our valued customers.",
|
72 |
+
"Your trust is important to us, and we work hard to maintain it."
|
73 |
+
]
|
74 |
+
}
|
75 |
+
|
76 |
+
# Banking-specific FAQs
|
77 |
+
BANKING_FAQS_AR = [
|
78 |
+
{
|
79 |
+
"question": "ما هي متطلبات فتح حساب توفير؟",
|
80 |
+
"answer": "لفتح حساب توفير، تحتاج إلى هوية وطنية سارية المفعول أو جواز سفر، وإثبات عنوان (مثل فاتورة مرافق)، والحد الأدنى للإيداع هو 1000 جنيه سوداني."
|
81 |
+
},
|
82 |
+
{
|
83 |
+
"question": "كيف يمكنني تغيير رقم هاتفي المسجل لدى البنك؟",
|
84 |
+
"answer": "لتغيير رقم هاتفك المسجل، يرجى زيارة أقرب فرع مع إثبات هويتك، أو يمكنك تقديم طلب عبر الخدمات المصرفية عبر الإنترنت في قسم 'تحديث المعلومات الشخصية'."
|
85 |
+
},
|
86 |
+
{
|
87 |
+
"question": "ما هي رسوم التحويلات الدولية؟",
|
88 |
+
"answer": "تبدأ رسوم التحويلات الدولية من 25 جنيه سوداني وتختلف حسب البلد المستلم ومبلغ التحويل. للحصول على تفاصيل محددة، يرجى الاطلاع على جدول الرسوم على موقعنا الإلكتروني."
|
89 |
+
},
|
90 |
+
{
|
91 |
+
"question": "هل يمكنني الحصول على قرض إذا كنت متقاعدًا؟",
|
92 |
+
"answer": "نعم، يمكن للمتقاعدين التقدم بطلب للحصول على قروض خاصة مع شروط ميسرة. يجب أن يكون عمرك أقل من 70 عامًا وأن تتلقى معاشك التقاعدي من خلال حساب في بنكنا."
|
93 |
+
},
|
94 |
+
{
|
95 |
+
"question": "كم من الوقت يستغرق إصدار بطاقة ائتمان جديدة؟",
|
96 |
+
"answer": "عادة ما يستغرق إصدار بطاقة ائتمان جديدة من 7 إلى 10 أيام عمل من تاريخ الموافقة على الطلب. يمكنك متابعة حالة طلبك من خلال الخدمات المصرفية عبر الإنترنت."
|
97 |
+
}
|
98 |
+
]
|
99 |
+
|
100 |
+
BANKING_FAQS_EN = [
|
101 |
+
{
|
102 |
+
"question": "What are the requirements for opening a savings account?",
|
103 |
+
"answer": "To open a savings account, you need a valid national ID or passport, proof of address (such as a utility bill), and the minimum deposit is 1000 SDG."
|
104 |
+
},
|
105 |
+
{
|
106 |
+
"question": "How can I change my registered phone number with the bank?",
|
107 |
+
"answer": "To change your registered phone number, please visit your nearest branch with your ID proof, or you can submit a request through online banking in the 'Update Personal Information' section."
|
108 |
+
},
|
109 |
+
{
|
110 |
+
"question": "What are the fees for international transfers?",
|
111 |
+
"answer": "International transfer fees start from 25 SDG and vary depending on the receiving country and transfer amount. For specific details, please check the fee schedule on our website."
|
112 |
+
},
|
113 |
+
{
|
114 |
+
"question": "Can I get a loan if I am retired?",
|
115 |
+
"answer": "Yes, retirees can apply for special loans with favorable terms. You must be under 70 years of age and receive your pension through an account with our bank."
|
116 |
+
},
|
117 |
+
{
|
118 |
+
"question": "How long does it take to issue a new credit card?",
|
119 |
+
"answer": "Issuing a new credit card typically takes 7-10 business days from the date of application approval. You can track your application status through online banking."
|
120 |
+
}
|
121 |
+
]
|
122 |
+
|
123 |
+
# Customer satisfaction survey
|
124 |
+
SATISFACTION_SURVEY_AR = {
|
125 |
+
"title": "استطلاع رضا العملاء",
|
126 |
+
"intro": "نقدر ملاحظاتك! يرجى تقييم تجربتك مع مساعدنا المصرفي الافتراضي.",
|
127 |
+
"questions": [
|
128 |
+
"كيف تقيم سهولة استخدام المساعد المصرفي الافتراضي؟",
|
129 |
+
"هل كانت المعلومات المقدمة مفيدة ودقيقة؟",
|
130 |
+
"هل تمت معالجة استفسارك بشكل فعال؟",
|
131 |
+
"ما مدى احتمالية استخدامك للمساعد المصرفي الافتراضي مرة أخرى؟",
|
132 |
+
"هل لديك أي اقتراحات لتحسين خدمتنا؟"
|
133 |
+
],
|
134 |
+
"ratings": ["ممتاز", "جيد جدًا", "جيد", "مقبول", "ضعيف"],
|
135 |
+
"submit": "إرسال التقييم",
|
136 |
+
"thanks": "شكرًا على ملاحظاتك القيمة!"
|
137 |
+
}
|
138 |
+
|
139 |
+
SATISFACTION_SURVEY_EN = {
|
140 |
+
"title": "Customer Satisfaction Survey",
|
141 |
+
"intro": "We value your feedback! Please rate your experience with our virtual banking assistant.",
|
142 |
+
"questions": [
|
143 |
+
"How would you rate the ease of use of the virtual banking assistant?",
|
144 |
+
"Was the information provided helpful and accurate?",
|
145 |
+
"Was your inquiry handled effectively?",
|
146 |
+
"How likely are you to use the virtual banking assistant again?",
|
147 |
+
"Do you have any suggestions for improving our service?"
|
148 |
+
],
|
149 |
+
"ratings": ["Excellent", "Very Good", "Good", "Fair", "Poor"],
|
150 |
+
"submit": "Submit Rating",
|
151 |
+
"thanks": "Thank you for your valuable feedback!"
|
152 |
+
}
|
153 |
+
|
154 |
+
# Banking terminology glossary
|
155 |
+
BANKING_GLOSSARY_AR = {
|
156 |
+
"حساب جاري": "حساب مصرفي يسمح بالسحب والإيداع المتكرر، عادة بدون فائدة.",
|
157 |
+
"حساب توفير": "حساب مصرفي يدفع فائدة على الأموال المودعة ويشجع على الادخار.",
|
158 |
+
"بطاقة الخصم": "بطاقة تسمح بالدفع الإلكتروني مباشرة من الحساب الجاري.",
|
159 |
+
"بطاقة ائتمان": "بطاقة تسمح بالاقتراض ضمن حد ائتماني محدد مسبقًا.",
|
160 |
+
"معدل الفائدة": "النسبة المئوية للمبلغ الأصلي الذي يدفعه المقترض كرسوم للمقرض.",
|
161 |
+
"الرصيد المتاح": "المبلغ المتاح للسحب من الحساب.",
|
162 |
+
"كشف الحساب": "سجل للمعاملات المالية خلال فترة زمنية محددة.",
|
163 |
+
"الحوالة المصرفية": "تحويل الأموال إلكترونيًا من حساب إلى آخر.",
|
164 |
+
"الرهن العقاري": "قرض لشراء عقار، حيث يكون العقار نفسه ضمانًا للقرض.",
|
165 |
+
"الوديعة لأجل": "إيداع مالي في البنك لفترة زمنية محددة بسعر فائدة ثابت."
|
166 |
+
}
|
167 |
+
|
168 |
+
BANKING_GLOSSARY_EN = {
|
169 |
+
"Current Account": "A bank account that allows frequent withdrawals and deposits, usually without interest.",
|
170 |
+
"Savings Account": "A bank account that pays interest on deposits and encourages saving.",
|
171 |
+
"Debit Card": "A card that allows electronic payment directly from a current account.",
|
172 |
+
"Credit Card": "A card that allows borrowing within a pre-approved credit limit.",
|
173 |
+
"Interest Rate": "The percentage of the principal amount that a borrower pays as a fee to the lender.",
|
174 |
+
"Available Balance": "The amount available for withdrawal from an account.",
|
175 |
+
"Bank Statement": "A record of financial transactions over a specified period.",
|
176 |
+
"Wire Transfer": "Electronic transfer of funds from one account to another.",
|
177 |
+
"Mortgage": "A loan to purchase real estate, where the property itself serves as collateral.",
|
178 |
+
"Fixed Deposit": "A financial deposit in a bank for a specified time period at a fixed interest rate."
|
179 |
+
}
|
180 |
+
|
181 |
+
# Function to get enhanced customer service response
|
182 |
+
def get_enhanced_response(intent, language, user_name=None):
|
183 |
+
"""
|
184 |
+
Generate an enhanced customer service response based on intent and language.
|
185 |
+
|
186 |
+
Args:
|
187 |
+
intent (str): The identified user intent
|
188 |
+
language (str): The language code ('ar' or 'en')
|
189 |
+
user_name (str, optional): The user's name for personalization
|
190 |
+
|
191 |
+
Returns:
|
192 |
+
str: An enhanced response with appropriate customer service elements
|
193 |
+
"""
|
194 |
+
import random
|
195 |
+
|
196 |
+
# Select the appropriate phrases based on language
|
197 |
+
phrases = ENHANCED_CUSTOMER_SERVICE_PHRASES_AR if language == "ar" else ENHANCED_CUSTOMER_SERVICE_PHRASES_EN
|
198 |
+
|
199 |
+
# Get the base response content
|
200 |
+
if language == "ar":
|
201 |
+
base_content = ONB_GUIDELINES_AR.get(intent, "")
|
202 |
+
else:
|
203 |
+
base_content = ONB_GUIDELINES_EN.get(intent, "")
|
204 |
+
|
205 |
+
# Add personalization if user name is provided
|
206 |
+
greeting = random.choice(phrases["greeting"])
|
207 |
+
if user_name:
|
208 |
+
if language == "ar":
|
209 |
+
greeting = greeting.replace("مرحبًا", f"مرحبًا {user_name}")
|
210 |
+
else:
|
211 |
+
greeting = greeting.replace("Welcome", f"Welcome {user_name}")
|
212 |
+
|
213 |
+
# Add reassurance for financial security related intents
|
214 |
+
reassurance = ""
|
215 |
+
if intent in ["balance", "transfer", "loan", "new_account"]:
|
216 |
+
reassurance = f"<br><br>{random.choice(phrases['reassurance'])}"
|
217 |
+
|
218 |
+
# Add follow-up
|
219 |
+
follow_up = random.choice(phrases["follow_up"])
|
220 |
+
|
221 |
+
# Combine all elements
|
222 |
+
enhanced_response = f"{greeting}<br><br>{base_content}{reassurance}<br><br>{follow_up}"
|
223 |
+
|
224 |
+
return enhanced_response
|
225 |
+
|
226 |
+
# Function to handle common banking FAQs
|
227 |
+
def handle_banking_faq(question, language):
|
228 |
+
"""
|
229 |
+
Check if the user's question matches any common banking FAQs and return the answer.
|
230 |
+
|
231 |
+
Args:
|
232 |
+
question (str): The user's question
|
233 |
+
language (str): The language code ('ar' or 'en')
|
234 |
+
|
235 |
+
Returns:
|
236 |
+
str or None: The FAQ answer if found, None otherwise
|
237 |
+
"""
|
238 |
+
# Select the appropriate FAQs based on language
|
239 |
+
faqs = BANKING_FAQS_AR if language == "ar" else BANKING_FAQS_EN
|
240 |
+
|
241 |
+
# Convert question to lowercase for case-insensitive matching
|
242 |
+
question_lower = question.lower()
|
243 |
+
|
244 |
+
# Check each FAQ for a match
|
245 |
+
for faq in faqs:
|
246 |
+
faq_question_lower = faq["question"].lower()
|
247 |
+
|
248 |
+
# Check if the user's question contains the FAQ question keywords
|
249 |
+
keywords = faq_question_lower.split()
|
250 |
+
match_count = sum(1 for keyword in keywords if keyword in question_lower)
|
251 |
+
|
252 |
+
# If more than 50% of keywords match, consider it a match
|
253 |
+
if match_count >= len(keywords) * 0.5:
|
254 |
+
return faq["answer"]
|
255 |
+
|
256 |
+
return None
|
257 |
+
|
258 |
+
# Function to offer satisfaction survey
|
259 |
+
def offer_satisfaction_survey(language):
|
260 |
+
"""
|
261 |
+
Generate HTML for a customer satisfaction survey.
|
262 |
+
|
263 |
+
Args:
|
264 |
+
language (str): The language code ('ar' or 'en')
|
265 |
+
|
266 |
+
Returns:
|
267 |
+
str: HTML for the satisfaction survey
|
268 |
+
"""
|
269 |
+
# Select the appropriate survey based on language
|
270 |
+
survey = SATISFACTION_SURVEY_AR if language == "ar" else SATISFACTION_SURVEY_EN
|
271 |
+
|
272 |
+
# Generate HTML for the survey
|
273 |
+
html = f"""
|
274 |
+
<div class="satisfaction-survey" dir="{('rtl' if language == 'ar' else 'ltr')}">
|
275 |
+
<h3>{survey['title']}</h3>
|
276 |
+
<p>{survey['intro']}</p>
|
277 |
+
<form id="satisfaction-form">
|
278 |
+
"""
|
279 |
+
|
280 |
+
# Add questions
|
281 |
+
for i, question in enumerate(survey['questions']):
|
282 |
+
html += f"""
|
283 |
+
<div class="survey-question">
|
284 |
+
<p>{question}</p>
|
285 |
+
<div class="rating-options">
|
286 |
+
"""
|
287 |
+
|
288 |
+
# Add rating options
|
289 |
+
for rating in survey['ratings']:
|
290 |
+
html += f"""
|
291 |
+
<label>
|
292 |
+
<input type="radio" name="q{i+1}" value="{rating}">
|
293 |
+
{rating}
|
294 |
+
</label>
|
295 |
+
"""
|
296 |
+
|
297 |
+
html += """
|
298 |
+
</div>
|
299 |
+
</div>
|
300 |
+
"""
|
301 |
+
|
302 |
+
# Add text area for suggestions
|
303 |
+
html += f"""
|
304 |
+
<div class="survey-question">
|
305 |
+
<p>{survey['questions'][-1]}</p>
|
306 |
+
<textarea name="suggestions" rows="3" cols="40"></textarea>
|
307 |
+
</div>
|
308 |
+
"""
|
309 |
+
|
310 |
+
# Add submit button
|
311 |
+
html += f"""
|
312 |
+
<button type="button" onclick="submitSurvey()" class="survey-submit">{survey['submit']}</button>
|
313 |
+
</form>
|
314 |
+
</div>
|
315 |
+
|
316 |
+
<script>
|
317 |
+
function submitSurvey() {{
|
318 |
+
alert('{survey['thanks']}');
|
319 |
+
document.getElementById('satisfaction-form').style.display = 'none';
|
320 |
+
}}
|
321 |
+
</script>
|
322 |
+
"""
|
323 |
+
|
324 |
+
return html
|
325 |
+
|
326 |
+
# Function to provide banking term definition
|
327 |
+
def get_banking_term_definition(term, language):
|
328 |
+
"""
|
329 |
+
Get the definition of a banking term from the glossary.
|
330 |
+
|
331 |
+
Args:
|
332 |
+
term (str): The banking term to look up
|
333 |
+
language (str): The language code ('ar' or 'en')
|
334 |
+
|
335 |
+
Returns:
|
336 |
+
str or None: The definition if found, None otherwise
|
337 |
+
"""
|
338 |
+
# Select the appropriate glossary based on language
|
339 |
+
glossary = BANKING_GLOSSARY_AR if language == "ar" else BANKING_GLOSSARY_EN
|
340 |
+
|
341 |
+
# Convert term to lowercase for case-insensitive matching
|
342 |
+
term_lower = term.lower()
|
343 |
+
|
344 |
+
# Check each term in the glossary
|
345 |
+
for glossary_term, definition in glossary.items():
|
346 |
+
if glossary_term.lower() in term_lower or term_lower in glossary_term.lower():
|
347 |
+
return definition
|
348 |
+
|
349 |
+
return None
|
final_chatbot.py
ADDED
@@ -0,0 +1,923 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import re
|
3 |
+
import json
|
4 |
+
import time
|
5 |
+
from datetime import datetime
|
6 |
+
|
7 |
+
# Simple language detection function instead of using transformers
|
8 |
+
def simple_detect_language(text):
|
9 |
+
# Check for Arabic characters
|
10 |
+
arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF]+')
|
11 |
+
if arabic_pattern.search(text):
|
12 |
+
return "ar"
|
13 |
+
return "en"
|
14 |
+
|
15 |
+
# Import customer service enhancements
|
16 |
+
try:
|
17 |
+
from customer_service_enhancements import (
|
18 |
+
ENHANCED_CUSTOMER_SERVICE_PHRASES_AR,
|
19 |
+
ENHANCED_CUSTOMER_SERVICE_PHRASES_EN,
|
20 |
+
BANKING_FAQS_AR,
|
21 |
+
BANKING_FAQS_EN,
|
22 |
+
BANKING_GLOSSARY_AR,
|
23 |
+
BANKING_GLOSSARY_EN,
|
24 |
+
get_enhanced_response,
|
25 |
+
handle_banking_faq,
|
26 |
+
offer_satisfaction_survey,
|
27 |
+
get_banking_term_definition
|
28 |
+
)
|
29 |
+
CUSTOMER_SERVICE_ENHANCEMENTS_AVAILABLE = True
|
30 |
+
except ImportError:
|
31 |
+
CUSTOMER_SERVICE_ENHANCEMENTS_AVAILABLE = False
|
32 |
+
# Fallback customer service phrases
|
33 |
+
ENHANCED_CUSTOMER_SERVICE_PHRASES_AR = {
|
34 |
+
"greeting": [
|
35 |
+
"مرحبًا بك في بنك أم درمان الوطني! كيف يمكنني مساعدتك اليوم؟",
|
36 |
+
"أهلاً بك في خدمة العملاء الافتراضية لبنك أم درمان الوطني. كيف يمكنني خدمتك؟"
|
37 |
+
],
|
38 |
+
"thanks": [
|
39 |
+
"شكرًا لتواصلك مع بنك أم درمان الوطني!",
|
40 |
+
"نشكرك على ثقتك في خدماتنا المصرفية."
|
41 |
+
],
|
42 |
+
"follow_up": [
|
43 |
+
"هل هناك خدمة مصرفية أخرى يمكنني مساعدتك بها اليوم؟",
|
44 |
+
"هل لديك أي استفسارات أخرى حول منتجاتنا أو خدماتنا المصرفية؟"
|
45 |
+
]
|
46 |
+
}
|
47 |
+
ENHANCED_CUSTOMER_SERVICE_PHRASES_EN = {
|
48 |
+
"greeting": [
|
49 |
+
"Welcome to Omdurman National Bank! How may I assist you today?",
|
50 |
+
"Hello and welcome to ONB virtual customer service. How can I help you?"
|
51 |
+
],
|
52 |
+
"thanks": [
|
53 |
+
"Thank you for contacting Omdurman National Bank!",
|
54 |
+
"We appreciate your trust in our banking services."
|
55 |
+
],
|
56 |
+
"follow_up": [
|
57 |
+
"Is there any other banking service I can assist you with today?",
|
58 |
+
"Do you have any other questions about our products or banking services?"
|
59 |
+
]
|
60 |
+
}
|
61 |
+
|
62 |
+
# Omdurman National Bank-specific guidelines in Arabic
|
63 |
+
ONB_GUIDELINES_AR = {
|
64 |
+
"balance": "يمكنك التحقق من رصيدك عبر الإنترنت أو عبر تطبيق الهاتف الخاص ببنك أم درمان الوطني. <a href='#' onclick='window.open(\"https://onb.sd/balance\", \"_blank\")'>افحص رصيدك الآن</a>",
|
65 |
+
"lost_card": "في حالة فقدان البطاقة، اتصل بالرقم <a href='tel:249123456789'>249-123-456-789</a> فورًا أو <a href='#' onclick='window.open(\"https://onb.sd/block-card\", \"_blank\")'>أوقف البطاقة عبر الإنترنت</a>.",
|
66 |
+
"loan": "شروط القرض تشمل الحد الأدنى للدخل (5000 جنيه سوداني) وتاريخ ائتماني جيد. <a href='#' onclick='window.open(\"https://onb.sd/loans\", \"_blank\")'>تقدم بطلب قرض الآن</a>",
|
67 |
+
"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>.",
|
68 |
+
"new_account": "لفتح حساب جديد، قم بزيارة أقرب فرع مع جواز سفرك أو هويتك الوطنية. <a href='#' onclick='window.open(\"https://onb.sd/new-account\", \"_blank\")'>احجز موعدًا الآن</a>",
|
69 |
+
"interest_rates": "أسعار الفائدة على الودائع تتراوح بين 5% إلى 10% سنويًا. <a href='#' onclick='window.open(\"https://onb.sd/rates\", \"_blank\")'>اطلع على جميع الأسعار</a>",
|
70 |
+
"branches": "فروعنا موجودة في أم درمان، الخرطوم، وبورتسودان. <a href='#' onclick='window.open(\"https://onb.sd/branches\", \"_blank\")'>اعثر على أقرب فرع</a>",
|
71 |
+
"working_hours": "ساعات العمل من 8 صباحًا إلى 3 مساءً من الأحد إلى الخميس. <a href='#' onclick='window.open(\"https://onb.sd/hours\", \"_blank\")'>تحقق من ساعات العمل الخاصة</a>",
|
72 |
+
"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>"
|
73 |
+
}
|
74 |
+
|
75 |
+
# Omdurman National Bank-specific guidelines in English
|
76 |
+
ONB_GUIDELINES_EN = {
|
77 |
+
"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>",
|
78 |
+
"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>.",
|
79 |
+
"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>",
|
80 |
+
"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>.",
|
81 |
+
"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>",
|
82 |
+
"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>",
|
83 |
+
"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>",
|
84 |
+
"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>",
|
85 |
+
"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>"
|
86 |
+
}
|
87 |
+
|
88 |
+
# Quick action buttons in Arabic
|
89 |
+
QUICK_ACTIONS_AR = [
|
90 |
+
{"text": "تحقق من الرصيد", "intent": "balance"},
|
91 |
+
{"text": "الإبلاغ عن بطاقة مفقودة", "intent": "lost_card"},
|
92 |
+
{"text": "معلومات القرض", "intent": "loan"},
|
93 |
+
{"text": "تحويل الأموال", "intent": "transfer"},
|
94 |
+
{"text": "فتح حساب جديد", "intent": "new_account"},
|
95 |
+
{"text": "أسعار الفائدة", "intent": "interest_rates"},
|
96 |
+
{"text": "مواقع الفروع", "intent": "branches"},
|
97 |
+
{"text": "ساعات العمل", "intent": "working_hours"},
|
98 |
+
{"text": "اتصل بنا", "intent": "contact"}
|
99 |
+
]
|
100 |
+
|
101 |
+
# Quick action buttons in English
|
102 |
+
QUICK_ACTIONS_EN = [
|
103 |
+
{"text": "Check Balance", "intent": "balance"},
|
104 |
+
{"text": "Report Lost Card", "intent": "lost_card"},
|
105 |
+
{"text": "Loan Information", "intent": "loan"},
|
106 |
+
{"text": "Transfer Funds", "intent": "transfer"},
|
107 |
+
{"text": "Open New Account", "intent": "new_account"},
|
108 |
+
{"text": "Interest Rates", "intent": "interest_rates"},
|
109 |
+
{"text": "Branch Locations", "intent": "branches"},
|
110 |
+
{"text": "Working Hours", "intent": "working_hours"},
|
111 |
+
{"text": "Contact Us", "intent": "contact"}
|
112 |
+
]
|
113 |
+
|
114 |
+
# Menu options in both languages
|
115 |
+
MENU_AR = """
|
116 |
+
قائمة الخدمات المصرفية:
|
117 |
+
1. رصيد - استعلام عن رصيد حسابك
|
118 |
+
2. بطاقة - الإبلاغ عن بطاقة مفقودة
|
119 |
+
3. قرض - معلومات عن القروض
|
120 |
+
4. تحويل - تحويل الأموال
|
121 |
+
5. حساب - فتح حساب جديد
|
122 |
+
6. فائدة - أسعار الفائدة
|
123 |
+
7. فرع - مواقع الفروع
|
124 |
+
8. ساعات - ساعات العمل
|
125 |
+
9. اتصال - معلومات الاتصال
|
126 |
+
"""
|
127 |
+
|
128 |
+
MENU_EN = """
|
129 |
+
Banking Services Menu:
|
130 |
+
1. balance - Check your account balance
|
131 |
+
2. card - Report a lost card
|
132 |
+
3. loan - Information about loans
|
133 |
+
4. transfer - Transfer funds
|
134 |
+
5. account - Open a new account
|
135 |
+
6. interest - Interest rates
|
136 |
+
7. branch - Branch locations
|
137 |
+
8. hours - Working hours
|
138 |
+
9. contact - Contact information
|
139 |
+
"""
|
140 |
+
|
141 |
+
# Map intents to keywords (enhanced)
|
142 |
+
INTENT_KEYWORDS = {
|
143 |
+
"balance": ["balance", "check balance", "account balance", "how much", "رصيد", "حساب", "كم المبلغ", "1"],
|
144 |
+
"lost_card": ["lost", "card", "stolen", "missing", "فقدت", "بطاقة", "مسروقة", "ضائعة", "2"],
|
145 |
+
"loan": ["loan", "borrow", "borrowing", "credit", "قرض", "استدانة", "إئتمان", "3"],
|
146 |
+
"transfer": ["transfer", "send money", "payment", "تحويل", "ارسال", "دفع", "4"],
|
147 |
+
"new_account": ["account", "open", "create", "new", "حساب", "فتح", "جديد", "إنشاء", "5"],
|
148 |
+
"interest_rates": ["interest", "rate", "rates", "return", "فائدة", "نسبة", "عائد", "6"],
|
149 |
+
"branches": ["branch", "location", "where", "office", "فرع", "موقع", "أين", "مكتب", "7"],
|
150 |
+
"working_hours": ["hours", "time", "open", "close", "ساعات", "وقت", "مفتوح", "مغلق", "8"],
|
151 |
+
"contact": ["contact", "phone", "email", "call", "اتصال", "هاتف", "بريد", "اتصل", "9"]
|
152 |
+
}
|
153 |
+
|
154 |
+
# Function to get a random phrase from the customer service phrases
|
155 |
+
def get_random_phrase(category, language):
|
156 |
+
import random
|
157 |
+
if language == "ar":
|
158 |
+
return random.choice(ENHANCED_CUSTOMER_SERVICE_PHRASES_AR[category])
|
159 |
+
else:
|
160 |
+
return random.choice(ENHANCED_CUSTOMER_SERVICE_PHRASES_EN[category])
|
161 |
+
|
162 |
+
def classify_intent(message: str):
|
163 |
+
# Check for menu request
|
164 |
+
menu_keywords = ["menu", "options", "help", "قائمة", "خيارات", "مساعدة"]
|
165 |
+
message_lower = message.lower()
|
166 |
+
|
167 |
+
for keyword in menu_keywords:
|
168 |
+
if keyword in message_lower:
|
169 |
+
return "menu"
|
170 |
+
|
171 |
+
# Use keyword matching for intent classification
|
172 |
+
for intent_key, keywords in INTENT_KEYWORDS.items():
|
173 |
+
for keyword in keywords:
|
174 |
+
if keyword.lower() in message_lower:
|
175 |
+
return intent_key
|
176 |
+
|
177 |
+
return "unknown"
|
178 |
+
|
179 |
+
# Function to log customer interactions
|
180 |
+
def log_interaction(user_message, bot_response, intent, language):
|
181 |
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
182 |
+
log_entry = {
|
183 |
+
"timestamp": timestamp,
|
184 |
+
"user_message": user_message,
|
185 |
+
"bot_response": bot_response,
|
186 |
+
"intent": intent,
|
187 |
+
"language": language
|
188 |
+
}
|
189 |
+
|
190 |
+
try:
|
191 |
+
with open("/home/ubuntu/banking_chatbot/interaction_logs.jsonl", "a") as f:
|
192 |
+
f.write(json.dumps(log_entry) + "\n")
|
193 |
+
except Exception as e:
|
194 |
+
print(f"Error logging interaction: {e}")
|
195 |
+
|
196 |
+
def respond(message: str):
|
197 |
+
if not message.strip():
|
198 |
+
return {
|
199 |
+
"ar": "الرجاء كتابة سؤالك.",
|
200 |
+
"en": "Please type your question."
|
201 |
+
}
|
202 |
+
|
203 |
+
# Detect language using simple function
|
204 |
+
language = simple_detect_language(message)
|
205 |
+
|
206 |
+
# Classify the user's intent using keyword matching
|
207 |
+
intent = classify_intent(message)
|
208 |
+
|
209 |
+
# Prepare responses in both languages
|
210 |
+
responses = {
|
211 |
+
"ar": "",
|
212 |
+
"en": ""
|
213 |
+
}
|
214 |
+
|
215 |
+
# Special handling for menu request
|
216 |
+
if intent == "menu":
|
217 |
+
responses["ar"] = MENU_AR
|
218 |
+
responses["en"] = MENU_EN
|
219 |
+
log_interaction(message, responses[language], "menu", language)
|
220 |
+
return responses
|
221 |
+
|
222 |
+
# Check if it's a banking FAQ
|
223 |
+
if CUSTOMER_SERVICE_ENHANCEMENTS_AVAILABLE:
|
224 |
+
faq_answer = handle_banking_faq(message, language)
|
225 |
+
if faq_answer:
|
226 |
+
# Add a greeting phrase at the beginning
|
227 |
+
greeting_ar = get_random_phrase("greeting", "ar")
|
228 |
+
greeting_en = get_random_phrase("greeting", "en")
|
229 |
+
|
230 |
+
# Add a follow-up phrase at the end
|
231 |
+
follow_up_ar = get_random_phrase("follow_up", "ar")
|
232 |
+
follow_up_en = get_random_phrase("follow_up", "en")
|
233 |
+
|
234 |
+
# Combine all parts
|
235 |
+
responses["ar"] = f"{greeting_ar}<br><br>{faq_answer}<br><br>{follow_up_ar}"
|
236 |
+
responses["en"] = f"{greeting_en}<br><br>{faq_answer}<br><br>{follow_up_en}"
|
237 |
+
|
238 |
+
log_interaction(message, responses[language], "faq", language)
|
239 |
+
return responses
|
240 |
+
|
241 |
+
# Check if it's a banking term definition request
|
242 |
+
if CUSTOMER_SERVICE_ENHANCEMENTS_AVAILABLE:
|
243 |
+
term_definition = get_banking_term_definition(message, language)
|
244 |
+
if term_definition:
|
245 |
+
# Add a greeting phrase at the beginning
|
246 |
+
greeting_ar = get_random_phrase("greeting", "ar")
|
247 |
+
greeting_en = get_random_phrase("greeting", "en")
|
248 |
+
|
249 |
+
# Add a follow-up phrase at the end
|
250 |
+
follow_up_ar = get_random_phrase("follow_up", "ar")
|
251 |
+
follow_up_en = get_random_phrase("follow_up", "en")
|
252 |
+
|
253 |
+
# Combine all parts
|
254 |
+
responses["ar"] = f"{greeting_ar}<br><br>{term_definition}<br><br>{follow_up_ar}"
|
255 |
+
responses["en"] = f"{greeting_en}<br><br>{term_definition}<br><br>{follow_up_en}"
|
256 |
+
|
257 |
+
log_interaction(message, responses[language], "term", language)
|
258 |
+
return responses
|
259 |
+
|
260 |
+
# If intent is recognized, return the corresponding response
|
261 |
+
if intent != "unknown":
|
262 |
+
if CUSTOMER_SERVICE_ENHANCEMENTS_AVAILABLE:
|
263 |
+
# Use enhanced response if available
|
264 |
+
responses["ar"] = get_enhanced_response(intent, "ar")
|
265 |
+
responses["en"] = get_enhanced_response(intent, "en")
|
266 |
+
else:
|
267 |
+
# Add a greeting phrase at the beginning
|
268 |
+
greeting_ar = get_random_phrase("greeting", "ar")
|
269 |
+
greeting_en = get_random_phrase("greeting", "en")
|
270 |
+
|
271 |
+
# Add the main response
|
272 |
+
main_response_ar = ONB_GUIDELINES_AR.get(intent, "عذرًا، لم يتم التعرف على الخيار المحدد.")
|
273 |
+
main_response_en = ONB_GUIDELINES_EN.get(intent, "Sorry, the selected option was not recognized.")
|
274 |
+
|
275 |
+
# Add a follow-up phrase at the end
|
276 |
+
follow_up_ar = get_random_phrase("follow_up", "ar")
|
277 |
+
follow_up_en = get_random_phrase("follow_up", "en")
|
278 |
+
|
279 |
+
# Combine all parts
|
280 |
+
responses["ar"] = f"{greeting_ar}<br><br>{main_response_ar}<br><br>{follow_up_ar}"
|
281 |
+
responses["en"] = f"{greeting_en}<br><br>{main_response_en}<br><br>{follow_up_en}"
|
282 |
+
else:
|
283 |
+
# Default response if no intent is matched - show menu
|
284 |
+
responses["ar"] = "عذرًا، لم أفهم سؤالك. إليك قائمة بالخدمات المتاحة:" + MENU_AR
|
285 |
+
responses["en"] = "Sorry, I didn't understand your question. Here's a menu of available services:" + MENU_EN
|
286 |
+
|
287 |
+
# Log the interaction
|
288 |
+
log_interaction(message, responses[language], intent, language)
|
289 |
+
|
290 |
+
return responses
|
291 |
+
|
292 |
+
# Custom CSS for better UI
|
293 |
+
custom_css = """
|
294 |
+
.gradio-container {
|
295 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
296 |
+
}
|
297 |
+
|
298 |
+
.chat-message {
|
299 |
+
padding: 1rem;
|
300 |
+
border-radius: 10px;
|
301 |
+
margin-bottom: 1rem;
|
302 |
+
max-width: 80%;
|
303 |
+
}
|
304 |
+
|
305 |
+
.user-message {
|
306 |
+
background-color: #e6f7ff;
|
307 |
+
margin-left: auto;
|
308 |
+
text-align: right;
|
309 |
+
}
|
310 |
+
|
311 |
+
.bot-message {
|
312 |
+
background-color: #f0f0f0;
|
313 |
+
margin-right: auto;
|
314 |
+
text-align: left;
|
315 |
+
}
|
316 |
+
|
317 |
+
.bot-message-ar {
|
318 |
+
background-color: #f0f0f0;
|
319 |
+
margin-left: auto;
|
320 |
+
text-align: right;
|
321 |
+
}
|
322 |
+
|
323 |
+
.header-section {
|
324 |
+
background-color: #1a5276;
|
325 |
+
color: white;
|
326 |
+
padding: 1rem;
|
327 |
+
border-radius: 10px;
|
328 |
+
margin-bottom: 1rem;
|
329 |
+
text-align: center;
|
330 |
+
}
|
331 |
+
|
332 |
+
.footer-section {
|
333 |
+
font-size: 0.8rem;
|
334 |
+
text-align: center;
|
335 |
+
margin-top: 2rem;
|
336 |
+
color: #666;
|
337 |
+
}
|
338 |
+
|
339 |
+
.lang-selector {
|
340 |
+
text-align: right;
|
341 |
+
margin-bottom: 1rem;
|
342 |
+
}
|
343 |
+
|
344 |
+
.menu-button {
|
345 |
+
margin-top: 0.5rem;
|
346 |
+
}
|
347 |
+
|
348 |
+
.quick-actions {
|
349 |
+
display: flex;
|
350 |
+
flex-wrap: wrap;
|
351 |
+
gap: 0.5rem;
|
352 |
+
margin: 1rem 0;
|
353 |
+
}
|
354 |
+
|
355 |
+
.quick-action-button {
|
356 |
+
background-color: #1a5276;
|
357 |
+
color: white;
|
358 |
+
border: none;
|
359 |
+
border-radius: 20px;
|
360 |
+
padding: 0.5rem 1rem;
|
361 |
+
cursor: pointer;
|
362 |
+
font-size: 0.9rem;
|
363 |
+
transition: background-color 0.3s;
|
364 |
+
}
|
365 |
+
|
366 |
+
.quick-action-button:hover {
|
367 |
+
background-color: #2980b9;
|
368 |
+
}
|
369 |
+
|
370 |
+
.chat-container {
|
371 |
+
border: 1px solid #ddd;
|
372 |
+
border-radius: 10px;
|
373 |
+
padding: 1rem;
|
374 |
+
background-color: #f9f9f9;
|
375 |
+
}
|
376 |
+
|
377 |
+
.typing-indicator {
|
378 |
+
display: inline-block;
|
379 |
+
width: 50px;
|
380 |
+
text-align: left;
|
381 |
+
}
|
382 |
+
|
383 |
+
.typing-indicator span {
|
384 |
+
display: inline-block;
|
385 |
+
width: 8px;
|
386 |
+
height: 8px;
|
387 |
+
background-color: #1a5276;
|
388 |
+
border-radius: 50%;
|
389 |
+
margin-right: 5px;
|
390 |
+
animation: typing 1s infinite;
|
391 |
+
}
|
392 |
+
|
393 |
+
.typing-indicator span:nth-child(2) {
|
394 |
+
animation-delay: 0.2s;
|
395 |
+
}
|
396 |
+
|
397 |
+
.typing-indicator span:nth-child(3) {
|
398 |
+
animation-delay: 0.4s;
|
399 |
+
}
|
400 |
+
|
401 |
+
@keyframes typing {
|
402 |
+
0%, 100% {
|
403 |
+
transform: translateY(0);
|
404 |
+
}
|
405 |
+
50% {
|
406 |
+
transform: translateY(-5px);
|
407 |
+
}
|
408 |
+
}
|
409 |
+
|
410 |
+
.live-agent-button {
|
411 |
+
background-color: #27ae60;
|
412 |
+
color: white;
|
413 |
+
border: none;
|
414 |
+
border-radius: 5px;
|
415 |
+
padding: 0.5rem 1rem;
|
416 |
+
cursor: pointer;
|
417 |
+
font-size: 0.9rem;
|
418 |
+
margin-top: 1rem;
|
419 |
+
transition: background-color 0.3s;
|
420 |
+
}
|
421 |
+
|
422 |
+
.live-agent-button:hover {
|
423 |
+
background-color: #2ecc71;
|
424 |
+
}
|
425 |
+
|
426 |
+
/* Add custom styling for links */
|
427 |
+
a {
|
428 |
+
color: #2980b9;
|
429 |
+
text-decoration: none;
|
430 |
+
font-weight: bold;
|
431 |
+
}
|
432 |
+
|
433 |
+
a:hover {
|
434 |
+
text-decoration: underline;
|
435 |
+
}
|
436 |
+
|
437 |
+
/* Add styling for action buttons */
|
438 |
+
.action-button {
|
439 |
+
display: inline-block;
|
440 |
+
background-color: #3498db;
|
441 |
+
color: white;
|
442 |
+
padding: 0.5rem 1rem;
|
443 |
+
border-radius: 5px;
|
444 |
+
margin: 0.5rem 0;
|
445 |
+
text-decoration: none;
|
446 |
+
}
|
447 |
+
|
448 |
+
.action-button:hover {
|
449 |
+
background-color: #2980b9;
|
450 |
+
text-decoration: none;
|
451 |
+
}
|
452 |
+
|
453 |
+
/* Styling for satisfaction survey */
|
454 |
+
.satisfaction-survey {
|
455 |
+
background-color: #f8f9fa;
|
456 |
+
border: 1px solid #ddd;
|
457 |
+
border-radius: 10px;
|
458 |
+
padding: 1rem;
|
459 |
+
margin-top: 1rem;
|
460 |
+
}
|
461 |
+
|
462 |
+
.survey-question {
|
463 |
+
margin-bottom: 1rem;
|
464 |
+
}
|
465 |
+
|
466 |
+
.rating-options {
|
467 |
+
display: flex;
|
468 |
+
justify-content: space-between;
|
469 |
+
margin-bottom: 0.5rem;
|
470 |
+
}
|
471 |
+
|
472 |
+
.survey-submit {
|
473 |
+
background-color: #1a5276;
|
474 |
+
color: white;
|
475 |
+
border: none;
|
476 |
+
border-radius: 5px;
|
477 |
+
padding: 0.5rem 1rem;
|
478 |
+
cursor: pointer;
|
479 |
+
font-size: 0.9rem;
|
480 |
+
margin-top: 1rem;
|
481 |
+
}
|
482 |
+
|
483 |
+
.survey-submit:hover {
|
484 |
+
background-color: #2980b9;
|
485 |
+
}
|
486 |
+
"""
|
487 |
+
|
488 |
+
# Custom JavaScript for enhanced functionality
|
489 |
+
custom_js = """
|
490 |
+
function simulateTyping(message, elementId, delay = 30) {
|
491 |
+
const element = document.getElementById(elementId);
|
492 |
+
if (!element) return;
|
493 |
+
|
494 |
+
element.innerHTML = "";
|
495 |
+
let i = 0;
|
496 |
+
|
497 |
+
function type() {
|
498 |
+
if (i < message.length) {
|
499 |
+
element.innerHTML += message.charAt(i);
|
500 |
+
i++;
|
501 |
+
setTimeout(type, delay);
|
502 |
+
}
|
503 |
+
}
|
504 |
+
|
505 |
+
type();
|
506 |
+
}
|
507 |
+
|
508 |
+
// Function to show typing indicator
|
509 |
+
function showTypingIndicator() {
|
510 |
+
const chatbox = document.getElementById('chatbox');
|
511 |
+
if (!chatbox) return;
|
512 |
+
|
513 |
+
const typingIndicator = document.createElement('div');
|
514 |
+
typingIndicator.className = 'typing-indicator';
|
515 |
+
typingIndicator.id = 'typing-indicator';
|
516 |
+
typingIndicator.innerHTML = '<span></span><span></span><span></span>';
|
517 |
+
|
518 |
+
chatbox.appendChild(typingIndicator);
|
519 |
+
chatbox.scrollTop = chatbox.scrollHeight;
|
520 |
+
}
|
521 |
+
|
522 |
+
// Function to hide typing indicator
|
523 |
+
function hideTypingIndicator() {
|
524 |
+
const typingIndicator = document.getElementById('typing-indicator');
|
525 |
+
if (typingIndicator) {
|
526 |
+
typingIndicator.remove();
|
527 |
+
}
|
528 |
+
}
|
529 |
+
|
530 |
+
// Function to connect with a live agent
|
531 |
+
function connectLiveAgent() {
|
532 |
+
alert('Connecting to a live customer service agent. Please wait a moment...');
|
533 |
+
// In a real implementation, this would initiate a connection to a live agent system
|
534 |
+
}
|
535 |
+
|
536 |
+
// Function to show satisfaction survey
|
537 |
+
function showSatisfactionSurvey(surveyHtml) {
|
538 |
+
const chatbox = document.getElementById('chatbox');
|
539 |
+
if (!chatbox) return;
|
540 |
+
|
541 |
+
const surveyDiv = document.createElement('div');
|
542 |
+
surveyDiv.innerHTML = surveyHtml;
|
543 |
+
|
544 |
+
chatbox.appendChild(surveyDiv);
|
545 |
+
chatbox.scrollTop = chatbox.scrollHeight;
|
546 |
+
}
|
547 |
+
|
548 |
+
// Function to submit survey
|
549 |
+
function submitSurvey() {
|
550 |
+
const form = document.getElementById('satisfaction-form');
|
551 |
+
if (!form) return;
|
552 |
+
|
553 |
+
// In a real implementation, this would send the survey data to a server
|
554 |
+
alert('Thank you for your feedback!');
|
555 |
+
form.style.display = 'none';
|
556 |
+
}
|
557 |
+
"""
|
558 |
+
|
559 |
+
# Chat interface with enhanced UI
|
560 |
+
with gr.Blocks(css=custom_css, js=custom_js) as demo:
|
561 |
+
# Store conversation history
|
562 |
+
state = gr.State(value=[])
|
563 |
+
# Store selected language
|
564 |
+
selected_lang = gr.State(value="ar")
|
565 |
+
# Store user name for personalization
|
566 |
+
user_name = gr.State(value=None)
|
567 |
+
|
568 |
+
with gr.Row(elem_classes="header-section"):
|
569 |
+
with gr.Column():
|
570 |
+
gr.Markdown("# Omdurman National Bank | بنك أم درمان الوطني")
|
571 |
+
gr.Markdown("### Virtual Banking Assistant | المساعد المصرفي الافتراضي")
|
572 |
+
|
573 |
+
with gr.Row():
|
574 |
+
with gr.Column(elem_classes="lang-selector"):
|
575 |
+
language_btn = gr.Radio(
|
576 |
+
["العربية", "English"],
|
577 |
+
value="العربية",
|
578 |
+
label="Language | اللغة"
|
579 |
+
)
|
580 |
+
|
581 |
+
with gr.Row(elem_classes="chat-container"):
|
582 |
+
chat_box = gr.HTML(elem_id="chatbox", value="<div style='height: 400px; overflow-y: auto;'></div>")
|
583 |
+
|
584 |
+
# Quick action buttons (will be populated based on language)
|
585 |
+
with gr.Row(elem_classes="quick-actions", visible=True) as quick_actions_container:
|
586 |
+
quick_action_buttons = []
|
587 |
+
for i in range(9): # Create 9 buttons (one for each intent)
|
588 |
+
button = gr.Button("", visible=False, elem_classes="quick-action-button")
|
589 |
+
quick_action_buttons.append(button)
|
590 |
+
|
591 |
+
with gr.Row():
|
592 |
+
with gr.Column(scale=8):
|
593 |
+
text_input = gr.Textbox(
|
594 |
+
placeholder="Type your question here | اكتب سؤالك هنا",
|
595 |
+
label="",
|
596 |
+
elem_id="chat-input"
|
597 |
+
)
|
598 |
+
with gr.Column(scale=1):
|
599 |
+
submit_btn = gr.Button("Send | إرسال", variant="primary")
|
600 |
+
|
601 |
+
with gr.Row():
|
602 |
+
with gr.Column(scale=1):
|
603 |
+
menu_btn = gr.Button("Show Menu | إظهار القائمة", elem_classes="menu-button")
|
604 |
+
with gr.Column(scale=1):
|
605 |
+
live_agent_btn = gr.Button("Connect to Live Agent | الاتصال بوكيل حي", elem_classes="live-agent-button")
|
606 |
+
with gr.Column(scale=1):
|
607 |
+
survey_btn = gr.Button("Feedback | تقييم الخدمة", elem_classes="menu-button")
|
608 |
+
|
609 |
+
with gr.Row(elem_classes="footer-section"):
|
610 |
+
gr.Markdown("© 2025 Omdurman National Bank. All Rights Reserved. | جميع الحقوق محفوظة لبنك أم درمان الوطني ٢٠٢٥ ©")
|
611 |
+
|
612 |
+
# Update language state and quick action buttons when language is changed
|
613 |
+
def update_language_and_buttons(lang):
|
614 |
+
language_code = "ar" if lang == "العربية" else "en"
|
615 |
+
|
616 |
+
# Get the appropriate quick actions based on language
|
617 |
+
quick_actions = QUICK_ACTIONS_AR if language_code == "ar" else QUICK_ACTIONS_EN
|
618 |
+
|
619 |
+
# Update button visibility and text
|
620 |
+
button_updates = []
|
621 |
+
for i, button in enumerate(quick_action_buttons):
|
622 |
+
if i < len(quick_actions):
|
623 |
+
button_updates.append(gr.Button.update(visible=True, value=quick_actions[i]["text"]))
|
624 |
+
else:
|
625 |
+
button_updates.append(gr.Button.update(visible=False))
|
626 |
+
|
627 |
+
return [language_code] + button_updates
|
628 |
+
|
629 |
+
# Connect language button to update function
|
630 |
+
outputs = [selected_lang] + quick_action_buttons
|
631 |
+
language_btn.change(
|
632 |
+
fn=update_language_and_buttons,
|
633 |
+
inputs=language_btn,
|
634 |
+
outputs=outputs
|
635 |
+
)
|
636 |
+
|
637 |
+
# Function to add message to chat
|
638 |
+
def add_message_to_chat(message, is_user, lang):
|
639 |
+
# Create a JavaScript function to add the message to the chat
|
640 |
+
alignment = "right" if (is_user or (not is_user and lang == "ar")) else "left"
|
641 |
+
background = "#e6f7ff" if is_user else "#f0f0f0"
|
642 |
+
|
643 |
+
js_code = f"""
|
644 |
+
(function() {{
|
645 |
+
const chatbox = document.getElementById('chatbox').querySelector('div');
|
646 |
+
const messageDiv = document.createElement('div');
|
647 |
+
messageDiv.style.padding = '1rem';
|
648 |
+
messageDiv.style.borderRadius = '10px';
|
649 |
+
messageDiv.style.marginBottom = '1rem';
|
650 |
+
messageDiv.style.maxWidth = '80%';
|
651 |
+
messageDiv.style.backgroundColor = '{background}';
|
652 |
+
messageDiv.style.marginLeft = '{alignment === "right" ? "auto" : "0"}';
|
653 |
+
messageDiv.style.marginRight = '{alignment === "left" ? "auto" : "0"}';
|
654 |
+
messageDiv.style.textAlign = '{alignment}';
|
655 |
+
messageDiv.innerHTML = `{message}`;
|
656 |
+
chatbox.appendChild(messageDiv);
|
657 |
+
chatbox.scrollTop = chatbox.scrollHeight;
|
658 |
+
}})();
|
659 |
+
"""
|
660 |
+
|
661 |
+
return js_code
|
662 |
+
|
663 |
+
# Handle message submission with typing effect
|
664 |
+
def on_submit(message, chat_history, lang, name):
|
665 |
+
if not message.strip():
|
666 |
+
return "", chat_history, "", name
|
667 |
+
|
668 |
+
# Check if this is a name introduction
|
669 |
+
name_patterns = [
|
670 |
+
r"my name is (\w+)",
|
671 |
+
r"i am (\w+)",
|
672 |
+
r"i'm (\w+)",
|
673 |
+
r"اسمي (\w+)",
|
674 |
+
r"أنا (\w+)"
|
675 |
+
]
|
676 |
+
|
677 |
+
for pattern in name_patterns:
|
678 |
+
match = re.search(pattern, message.lower())
|
679 |
+
if match:
|
680 |
+
name = match.group(1)
|
681 |
+
break
|
682 |
+
|
683 |
+
# Add user message to chat
|
684 |
+
user_js = add_message_to_chat(message, True, lang)
|
685 |
+
|
686 |
+
# Show typing indicator
|
687 |
+
typing_js = "showTypingIndicator();"
|
688 |
+
|
689 |
+
# Get response
|
690 |
+
responses = respond(message)
|
691 |
+
|
692 |
+
# Select response based on language
|
693 |
+
response = responses[lang]
|
694 |
+
|
695 |
+
# Personalize response if name is available
|
696 |
+
if name and CUSTOMER_SERVICE_ENHANCEMENTS_AVAILABLE:
|
697 |
+
if lang == "ar":
|
698 |
+
response = response.replace("مرحبًا", f"مرحبًا {name}")
|
699 |
+
else:
|
700 |
+
response = response.replace("Welcome", f"Welcome {name}")
|
701 |
+
response = response.replace("Hello", f"Hello {name}")
|
702 |
+
|
703 |
+
# Hide typing indicator and add bot response
|
704 |
+
bot_js = f"""
|
705 |
+
setTimeout(function() {{
|
706 |
+
hideTypingIndicator();
|
707 |
+
{add_message_to_chat(response, False, lang)}
|
708 |
+
}}, 1000);
|
709 |
+
"""
|
710 |
+
|
711 |
+
# Combine all JavaScript
|
712 |
+
combined_js = user_js + typing_js + bot_js
|
713 |
+
|
714 |
+
return "", chat_history, combined_js, name
|
715 |
+
|
716 |
+
# Handle menu button click
|
717 |
+
def show_menu(chat_history, lang):
|
718 |
+
menu_responses = {
|
719 |
+
"ar": MENU_AR,
|
720 |
+
"en": MENU_EN
|
721 |
+
}
|
722 |
+
|
723 |
+
# Get menu text
|
724 |
+
menu_text = menu_responses[lang]
|
725 |
+
|
726 |
+
# Add system message showing the menu
|
727 |
+
js_code = add_message_to_chat(menu_text.replace("\n", "<br>"), False, lang)
|
728 |
+
|
729 |
+
return chat_history, js_code
|
730 |
+
|
731 |
+
# Handle quick action button clicks
|
732 |
+
def handle_quick_action(button_index, chat_history, lang, name):
|
733 |
+
# Get the appropriate quick actions based on language
|
734 |
+
quick_actions = QUICK_ACTIONS_AR if lang == "ar" else QUICK_ACTIONS_EN
|
735 |
+
|
736 |
+
if button_index < len(quick_actions):
|
737 |
+
# Get the intent for this button
|
738 |
+
intent = quick_actions[button_index]["intent"]
|
739 |
+
|
740 |
+
# Get the response for this intent
|
741 |
+
if CUSTOMER_SERVICE_ENHANCEMENTS_AVAILABLE:
|
742 |
+
# Use enhanced response if available
|
743 |
+
response_ar = get_enhanced_response(intent, "ar", name)
|
744 |
+
response_en = get_enhanced_response(intent, "en", name)
|
745 |
+
else:
|
746 |
+
# Add a greeting phrase at the beginning
|
747 |
+
greeting_ar = get_random_phrase("greeting", "ar")
|
748 |
+
greeting_en = get_random_phrase("greeting", "en")
|
749 |
+
|
750 |
+
# Add the main response
|
751 |
+
main_response_ar = ONB_GUIDELINES_AR.get(intent, "")
|
752 |
+
main_response_en = ONB_GUIDELINES_EN.get(intent, "")
|
753 |
+
|
754 |
+
# Add a follow-up phrase at the end
|
755 |
+
follow_up_ar = get_random_phrase("follow_up", "ar")
|
756 |
+
follow_up_en = get_random_phrase("follow_up", "en")
|
757 |
+
|
758 |
+
# Combine all parts
|
759 |
+
response_ar = f"{greeting_ar}<br><br>{main_response_ar}<br><br>{follow_up_ar}"
|
760 |
+
response_en = f"{greeting_en}<br><br>{main_response_en}<br><br>{follow_up_en}"
|
761 |
+
|
762 |
+
responses = {
|
763 |
+
"ar": response_ar,
|
764 |
+
"en": response_en
|
765 |
+
}
|
766 |
+
|
767 |
+
# Select response based on language
|
768 |
+
response = responses[lang]
|
769 |
+
|
770 |
+
# Personalize response if name is available
|
771 |
+
if name:
|
772 |
+
if lang == "ar":
|
773 |
+
response = response.replace("مرحبًا", f"مرحبًا {name}")
|
774 |
+
else:
|
775 |
+
response = response.replace("Welcome", f"Welcome {name}")
|
776 |
+
response = response.replace("Hello", f"Hello {name}")
|
777 |
+
|
778 |
+
# Add button text as user message
|
779 |
+
button_text = quick_actions[button_index]["text"]
|
780 |
+
user_js = add_message_to_chat(button_text, True, lang)
|
781 |
+
|
782 |
+
# Show typing indicator
|
783 |
+
typing_js = "showTypingIndicator();"
|
784 |
+
|
785 |
+
# Hide typing indicator and add bot response
|
786 |
+
bot_js = f"""
|
787 |
+
setTimeout(function() {{
|
788 |
+
hideTypingIndicator();
|
789 |
+
{add_message_to_chat(response, False, lang)}
|
790 |
+
}}, 1000);
|
791 |
+
"""
|
792 |
+
|
793 |
+
# Combine all JavaScript
|
794 |
+
combined_js = user_js + typing_js + bot_js
|
795 |
+
|
796 |
+
# Log the interaction
|
797 |
+
log_interaction(button_text, response, intent, lang)
|
798 |
+
|
799 |
+
return chat_history, combined_js
|
800 |
+
|
801 |
+
return chat_history, ""
|
802 |
+
|
803 |
+
# Handle live agent button click
|
804 |
+
def connect_to_live_agent():
|
805 |
+
return "connectLiveAgent();"
|
806 |
+
|
807 |
+
# Handle satisfaction survey button click
|
808 |
+
def show_satisfaction_survey(lang):
|
809 |
+
if CUSTOMER_SERVICE_ENHANCEMENTS_AVAILABLE:
|
810 |
+
survey_html = offer_satisfaction_survey(lang)
|
811 |
+
return f"showSatisfactionSurvey(`{survey_html}`);"
|
812 |
+
else:
|
813 |
+
# Simple survey HTML if enhancements not available
|
814 |
+
title = "استطلاع رضا العملاء" if lang == "ar" else "Customer Satisfaction Survey"
|
815 |
+
intro = "نقدر ملاحظاتك!" if lang == "ar" else "We value your feedback!"
|
816 |
+
submit = "إرسال" if lang == "ar" else "Submit"
|
817 |
+
|
818 |
+
survey_html = f"""
|
819 |
+
<div class="satisfaction-survey" dir="{('rtl' if lang == 'ar' else 'ltr')}">
|
820 |
+
<h3>{title}</h3>
|
821 |
+
<p>{intro}</p>
|
822 |
+
<button onclick="submitSurvey()" class="survey-submit">{submit}</button>
|
823 |
+
</div>
|
824 |
+
"""
|
825 |
+
|
826 |
+
return f"showSatisfactionSurvey(`{survey_html}`);"
|
827 |
+
|
828 |
+
# Link inputs and button to response function
|
829 |
+
submit_btn.click(
|
830 |
+
fn=on_submit,
|
831 |
+
inputs=[text_input, state, selected_lang, user_name],
|
832 |
+
outputs=[text_input, state, chat_box, user_name]
|
833 |
+
)
|
834 |
+
|
835 |
+
# Link menu button to show menu function
|
836 |
+
menu_btn.click(
|
837 |
+
fn=show_menu,
|
838 |
+
inputs=[state, selected_lang],
|
839 |
+
outputs=[state, chat_box]
|
840 |
+
)
|
841 |
+
|
842 |
+
# Link live agent button to connect function
|
843 |
+
live_agent_btn.click(
|
844 |
+
fn=connect_to_live_agent,
|
845 |
+
inputs=[],
|
846 |
+
outputs=[chat_box]
|
847 |
+
)
|
848 |
+
|
849 |
+
# Link survey button to show survey function
|
850 |
+
survey_btn.click(
|
851 |
+
fn=show_satisfaction_survey,
|
852 |
+
inputs=[selected_lang],
|
853 |
+
outputs=[chat_box]
|
854 |
+
)
|
855 |
+
|
856 |
+
# Link quick action buttons to handler function
|
857 |
+
for i, button in enumerate(quick_action_buttons):
|
858 |
+
button.click(
|
859 |
+
fn=lambda idx=i, s=state, l=selected_lang, n=user_name: handle_quick_action(idx, s, l, n),
|
860 |
+
inputs=[state, selected_lang, user_name],
|
861 |
+
outputs=[state, chat_box]
|
862 |
+
)
|
863 |
+
|
864 |
+
# Also trigger on Enter key
|
865 |
+
text_input.submit(
|
866 |
+
fn=on_submit,
|
867 |
+
inputs=[text_input, state, selected_lang, user_name],
|
868 |
+
outputs=[text_input, state, chat_box, user_name]
|
869 |
+
)
|
870 |
+
|
871 |
+
# Initialize the chat with a welcome message
|
872 |
+
def init_chat(lang):
|
873 |
+
# Get welcome message based on language
|
874 |
+
welcome_ar = """
|
875 |
+
<div style='text-align: center; margin-bottom: 20px;'>
|
876 |
+
<img src='https://via.placeholder.com/150?text=ONB+Logo' alt='ONB Logo' style='max-width: 150px;'>
|
877 |
+
<h3>مرحبًا بك في المساعد المصرفي الافتراضي لبنك أم درمان الوطني!</h3>
|
878 |
+
<p>يمكنك طرح أي سؤال حول خدماتنا المصرفية أو استخدام أزرار الإجراءات السريعة أدناه.</p>
|
879 |
+
</div>
|
880 |
+
"""
|
881 |
+
|
882 |
+
welcome_en = """
|
883 |
+
<div style='text-align: center; margin-bottom: 20px;'>
|
884 |
+
<img src='https://via.placeholder.com/150?text=ONB+Logo' alt='ONB Logo' style='max-width: 150px;'>
|
885 |
+
<h3>Welcome to Omdurman National Bank Virtual Banking Assistant!</h3>
|
886 |
+
<p>You can ask any question about our banking services or use the quick action buttons below.</p>
|
887 |
+
</div>
|
888 |
+
"""
|
889 |
+
|
890 |
+
welcome_message = welcome_ar if lang == "ar" else welcome_en
|
891 |
+
|
892 |
+
# Add welcome message to chat
|
893 |
+
js_code = f"""
|
894 |
+
(function() {{
|
895 |
+
const chatbox = document.getElementById('chatbox').querySelector('div');
|
896 |
+
chatbox.innerHTML = `{welcome_message}`;
|
897 |
+
}})();
|
898 |
+
"""
|
899 |
+
|
900 |
+
# Update quick action buttons
|
901 |
+
quick_actions = QUICK_ACTIONS_AR if lang == "ar" else QUICK_ACTIONS_EN
|
902 |
+
button_updates = []
|
903 |
+
for i, button in enumerate(quick_action_buttons):
|
904 |
+
if i < len(quick_actions):
|
905 |
+
button_updates.append(gr.Button.update(visible=True, value=quick_actions[i]["text"]))
|
906 |
+
else:
|
907 |
+
button_updates.append(gr.Button.update(visible=False))
|
908 |
+
|
909 |
+
return [js_code] + button_updates
|
910 |
+
|
911 |
+
# Initialize the chat when the app starts
|
912 |
+
demo.load(
|
913 |
+
fn=lambda: init_chat("ar"),
|
914 |
+
inputs=[],
|
915 |
+
outputs=[chat_box] + quick_action_buttons
|
916 |
+
)
|
917 |
+
|
918 |
+
if __name__ == "__main__":
|
919 |
+
demo.launch(
|
920 |
+
server_name="0.0.0.0",
|
921 |
+
server_port=7860,
|
922 |
+
share=True # Enable public link
|
923 |
+
)
|
improved_chatbot.py
ADDED
@@ -0,0 +1,460 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import re
|
3 |
+
import json
|
4 |
+
import time
|
5 |
+
from datetime import datetime
|
6 |
+
|
7 |
+
# Simple language detection function instead of using transformers
|
8 |
+
def simple_detect_language(text):
|
9 |
+
# Check for Arabic characters
|
10 |
+
arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF]+')
|
11 |
+
if arabic_pattern.search(text):
|
12 |
+
return "ar"
|
13 |
+
return "en"
|
14 |
+
|
15 |
+
# Omdurman National Bank-specific guidelines in Arabic
|
16 |
+
ONB_GUIDELINES_AR = {
|
17 |
+
"balance": "يمكنك التحقق من رصيدك عبر الإنترنت أو عبر تطبيق الهاتف الخاص ببنك أم درمان الوطني. <a href='#' onclick='window.open(\"https://onb.sd/balance\", \"_blank\")'>افحص رصيدك الآن</a>",
|
18 |
+
"lost_card": "في حالة فقدان البطاقة، اتصل بالرقم <a href='tel:249123456789'>249-123-456-789</a> فورًا أو <a href='#' onclick='window.open(\"https://onb.sd/block-card\", \"_blank\")'>أوقف البطاقة عبر الإنترنت</a>.",
|
19 |
+
"loan": "شروط القرض تشمل الحد الأدنى للدخل (5000 جنيه سوداني) وتاريخ ائتماني جيد. <a href='#' onclick='window.open(\"https://onb.sd/loans\", \"_blank\")'>تقدم بطلب قرض الآن</a>",
|
20 |
+
"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>.",
|
21 |
+
"new_account": "لفتح حساب جديد، قم بزيارة أقرب فرع مع جواز سفرك أو هويتك الوطنية. <a href='#' onclick='window.open(\"https://onb.sd/new-account\", \"_blank\")'>احجز موعدًا الآن</a>",
|
22 |
+
"interest_rates": "أسعار الفائدة على الودائع تتراوح بين 5% إلى 10% سنويًا. <a href='#' onclick='window.open(\"https://onb.sd/rates\", \"_blank\")'>اطلع على جميع الأسعار</a>",
|
23 |
+
"branches": "فروعنا موجودة في أم درمان، الخرطوم، وبورتسودان. <a href='#' onclick='window.open(\"https://onb.sd/branches\", \"_blank\")'>اعثر على أقرب فرع</a>",
|
24 |
+
"working_hours": "ساعات العمل من 8 صباحًا إلى 3 مساءً من الأحد إلى الخميس. <a href='#' onclick='window.open(\"https://onb.sd/hours\", \"_blank\")'>تحقق من ساعات العمل الخاصة</a>",
|
25 |
+
"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>"
|
26 |
+
}
|
27 |
+
|
28 |
+
# Omdurman National Bank-specific guidelines in English
|
29 |
+
ONB_GUIDELINES_EN = {
|
30 |
+
"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>",
|
31 |
+
"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>.",
|
32 |
+
"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>",
|
33 |
+
"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>.",
|
34 |
+
"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>",
|
35 |
+
"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>",
|
36 |
+
"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>",
|
37 |
+
"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>",
|
38 |
+
"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>"
|
39 |
+
}
|
40 |
+
|
41 |
+
# Quick action buttons in Arabic
|
42 |
+
QUICK_ACTIONS_AR = [
|
43 |
+
{"text": "تحقق من الرصيد", "intent": "balance"},
|
44 |
+
{"text": "الإبلاغ عن بطاقة مفقودة", "intent": "lost_card"},
|
45 |
+
{"text": "معلومات القرض", "intent": "loan"},
|
46 |
+
{"text": "تحويل الأموال", "intent": "transfer"},
|
47 |
+
{"text": "فتح حساب جديد", "intent": "new_account"},
|
48 |
+
{"text": "أسعار الفائدة", "intent": "interest_rates"},
|
49 |
+
{"text": "مواقع الفروع", "intent": "branches"},
|
50 |
+
{"text": "ساعات العمل", "intent": "working_hours"},
|
51 |
+
{"text": "اتصل بنا", "intent": "contact"}
|
52 |
+
]
|
53 |
+
|
54 |
+
# Quick action buttons in English
|
55 |
+
QUICK_ACTIONS_EN = [
|
56 |
+
{"text": "Check Balance", "intent": "balance"},
|
57 |
+
{"text": "Report Lost Card", "intent": "lost_card"},
|
58 |
+
{"text": "Loan Information", "intent": "loan"},
|
59 |
+
{"text": "Transfer Funds", "intent": "transfer"},
|
60 |
+
{"text": "Open New Account", "intent": "new_account"},
|
61 |
+
{"text": "Interest Rates", "intent": "interest_rates"},
|
62 |
+
{"text": "Branch Locations", "intent": "branches"},
|
63 |
+
{"text": "Working Hours", "intent": "working_hours"},
|
64 |
+
{"text": "Contact Us", "intent": "contact"}
|
65 |
+
]
|
66 |
+
|
67 |
+
# Menu options in both languages
|
68 |
+
MENU_AR = """
|
69 |
+
قائمة الخدمات المصرفية:
|
70 |
+
1. رصيد - استعلام عن رصيد حسابك
|
71 |
+
2. بطاقة - الإبلاغ عن بطاقة مفقودة
|
72 |
+
3. قرض - معلومات عن القروض
|
73 |
+
4. تحويل - تحويل الأموال
|
74 |
+
5. حساب - فتح حساب جديد
|
75 |
+
6. فائدة - أسعار الفائدة
|
76 |
+
7. فرع - مواقع الفروع
|
77 |
+
8. ساعات - ساعات العمل
|
78 |
+
9. اتصال - معلومات الاتصال
|
79 |
+
"""
|
80 |
+
|
81 |
+
MENU_EN = """
|
82 |
+
Banking Services Menu:
|
83 |
+
1. balance - Check your account balance
|
84 |
+
2. card - Report a lost card
|
85 |
+
3. loan - Information about loans
|
86 |
+
4. transfer - Transfer funds
|
87 |
+
5. account - Open a new account
|
88 |
+
6. interest - Interest rates
|
89 |
+
7. branch - Branch locations
|
90 |
+
8. hours - Working hours
|
91 |
+
9. contact - Contact information
|
92 |
+
"""
|
93 |
+
|
94 |
+
# Map intents to keywords (enhanced)
|
95 |
+
INTENT_KEYWORDS = {
|
96 |
+
"balance": ["balance", "check balance", "account balance", "how much", "رصيد", "حساب", "كم المبلغ", "1"],
|
97 |
+
"lost_card": ["lost", "card", "stolen", "missing", "فقدت", "بطاقة", "مسروقة", "ضائعة", "2"],
|
98 |
+
"loan": ["loan", "borrow", "borrowing", "credit", "قرض", "استدانة", "إئتمان", "3"],
|
99 |
+
"transfer": ["transfer", "send money", "payment", "تحويل", "ارسال", "دفع", "4"],
|
100 |
+
"new_account": ["account", "open", "create", "new", "حساب", "فتح", "جديد", "إنشاء", "5"],
|
101 |
+
"interest_rates": ["interest", "rate", "rates", "return", "فائدة", "نسبة", "عائد", "6"],
|
102 |
+
"branches": ["branch", "location", "where", "office", "فرع", "موقع", "أين", "مكتب", "7"],
|
103 |
+
"working_hours": ["hours", "time", "open", "close", "ساعات", "وقت", "مفتوح", "مغلق", "8"],
|
104 |
+
"contact": ["contact", "phone", "email", "call", "اتصال", "هاتف", "بريد", "اتصل", "9"]
|
105 |
+
}
|
106 |
+
|
107 |
+
# Customer service phrases for more human-like responses
|
108 |
+
CUSTOMER_SERVICE_PHRASES_AR = {
|
109 |
+
"greeting": [
|
110 |
+
"مرحبًا! كيف يمكنني مساعدتك اليوم؟",
|
111 |
+
"أهلاً بك في بنك أم درمان الوطني! كيف يمكنني خدمتك؟",
|
112 |
+
"مرحبًا بك! أنا هنا للمساعدة في أي استفسارات مصرفية."
|
113 |
+
],
|
114 |
+
"thanks": [
|
115 |
+
"شكرًا لتواصلك معنا!",
|
116 |
+
"نشكرك على استخدام خدماتنا المصرفية.",
|
117 |
+
"سعداء بخدمتك دائمًا!"
|
118 |
+
],
|
119 |
+
"follow_up": [
|
120 |
+
"هل هناك شيء آخر يمكنني مساعدتك به؟",
|
121 |
+
"هل لديك أي أسئلة أخرى؟",
|
122 |
+
"هل تحتاج إلى مساعدة في أمر آخر؟"
|
123 |
+
]
|
124 |
+
}
|
125 |
+
|
126 |
+
CUSTOMER_SERVICE_PHRASES_EN = {
|
127 |
+
"greeting": [
|
128 |
+
"Hello! How can I assist you today?",
|
129 |
+
"Welcome to Omdurman National Bank! How may I help you?",
|
130 |
+
"Hi there! I'm here to help with any banking inquiries."
|
131 |
+
],
|
132 |
+
"thanks": [
|
133 |
+
"Thank you for contacting us!",
|
134 |
+
"We appreciate you using our banking services.",
|
135 |
+
"Always happy to serve you!"
|
136 |
+
],
|
137 |
+
"follow_up": [
|
138 |
+
"Is there anything else I can help you with?",
|
139 |
+
"Do you have any other questions?",
|
140 |
+
"Do you need assistance with anything else?"
|
141 |
+
]
|
142 |
+
}
|
143 |
+
|
144 |
+
# Function to get a random phrase from the customer service phrases
|
145 |
+
def get_random_phrase(category, language):
|
146 |
+
import random
|
147 |
+
if language == "ar":
|
148 |
+
return random.choice(CUSTOMER_SERVICE_PHRASES_AR[category])
|
149 |
+
else:
|
150 |
+
return random.choice(CUSTOMER_SERVICE_PHRASES_EN[category])
|
151 |
+
|
152 |
+
def classify_intent(message: str):
|
153 |
+
# Check for menu request
|
154 |
+
menu_keywords = ["menu", "options", "help", "قائمة", "خيارات", "مساعدة"]
|
155 |
+
message_lower = message.lower()
|
156 |
+
|
157 |
+
for keyword in menu_keywords:
|
158 |
+
if keyword in message_lower:
|
159 |
+
return "menu"
|
160 |
+
|
161 |
+
# Use keyword matching for intent classification
|
162 |
+
for intent_key, keywords in INTENT_KEYWORDS.items():
|
163 |
+
for keyword in keywords:
|
164 |
+
if keyword.lower() in message_lower:
|
165 |
+
return intent_key
|
166 |
+
|
167 |
+
return "unknown"
|
168 |
+
|
169 |
+
# Function to log customer interactions
|
170 |
+
def log_interaction(user_message, bot_response, intent, language):
|
171 |
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
172 |
+
log_entry = {
|
173 |
+
"timestamp": timestamp,
|
174 |
+
"user_message": user_message,
|
175 |
+
"bot_response": bot_response,
|
176 |
+
"intent": intent,
|
177 |
+
"language": language
|
178 |
+
}
|
179 |
+
|
180 |
+
try:
|
181 |
+
with open("/home/ubuntu/banking_chatbot/interaction_logs.jsonl", "a") as f:
|
182 |
+
f.write(json.dumps(log_entry) + "\n")
|
183 |
+
except Exception as e:
|
184 |
+
print(f"Error logging interaction: {e}")
|
185 |
+
|
186 |
+
def respond(message: str):
|
187 |
+
if not message.strip():
|
188 |
+
return {
|
189 |
+
"ar": "الرجاء كتابة سؤالك.",
|
190 |
+
"en": "Please type your question."
|
191 |
+
}
|
192 |
+
|
193 |
+
# Detect language using simple function
|
194 |
+
language = simple_detect_language(message)
|
195 |
+
|
196 |
+
# Classify the user's intent using keyword matching
|
197 |
+
intent = classify_intent(message)
|
198 |
+
|
199 |
+
# Prepare responses in both languages
|
200 |
+
responses = {
|
201 |
+
"ar": "",
|
202 |
+
"en": ""
|
203 |
+
}
|
204 |
+
|
205 |
+
# Special handling for menu request
|
206 |
+
if intent == "menu":
|
207 |
+
responses["ar"] = MENU_AR
|
208 |
+
responses["en"] = MENU_EN
|
209 |
+
log_interaction(message, responses[language], "menu", language)
|
210 |
+
return responses
|
211 |
+
|
212 |
+
# If intent is recognized, return the corresponding response
|
213 |
+
if intent != "unknown":
|
214 |
+
# Add a greeting phrase at the beginning
|
215 |
+
greeting_ar = get_random_phrase("greeting", "ar")
|
216 |
+
greeting_en = get_random_phrase("greeting", "en")
|
217 |
+
|
218 |
+
# Add the main response
|
219 |
+
main_response_ar = ONB_GUIDELINES_AR.get(intent, "عذرًا، لم يتم التعرف على الخيار المحدد.")
|
220 |
+
main_response_en = ONB_GUIDELINES_EN.get(intent, "Sorry, the selected option was not recognized.")
|
221 |
+
|
222 |
+
# Add a follow-up phrase at the end
|
223 |
+
follow_up_ar = get_random_phrase("follow_up", "ar")
|
224 |
+
follow_up_en = get_random_phrase("follow_up", "en")
|
225 |
+
|
226 |
+
# Combine all parts
|
227 |
+
responses["ar"] = f"{greeting_ar}<br><br>{main_response_ar}<br><br>{follow_up_ar}"
|
228 |
+
responses["en"] = f"{greeting_en}<br><br>{main_response_en}<br><br>{follow_up_en}"
|
229 |
+
else:
|
230 |
+
# Default response if no intent is matched - show menu
|
231 |
+
responses["ar"] = "عذرًا، لم أفهم سؤالك. إليك قائمة بالخدمات المتاحة:" + MENU_AR
|
232 |
+
responses["en"] = "Sorry, I didn't understand your question. Here's a menu of available services:" + MENU_EN
|
233 |
+
|
234 |
+
# Log the interaction
|
235 |
+
log_interaction(message, responses[language], intent, language)
|
236 |
+
|
237 |
+
return responses
|
238 |
+
|
239 |
+
# Custom CSS for better UI
|
240 |
+
custom_css = """
|
241 |
+
.gradio-container {
|
242 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
243 |
+
}
|
244 |
+
|
245 |
+
.chat-message {
|
246 |
+
padding: 1rem;
|
247 |
+
border-radius: 10px;
|
248 |
+
margin-bottom: 1rem;
|
249 |
+
max-width: 80%;
|
250 |
+
}
|
251 |
+
|
252 |
+
.user-message {
|
253 |
+
background-color: #e6f7ff;
|
254 |
+
margin-left: auto;
|
255 |
+
text-align: right;
|
256 |
+
}
|
257 |
+
|
258 |
+
.bot-message {
|
259 |
+
background-color: #f0f0f0;
|
260 |
+
margin-right: auto;
|
261 |
+
text-align: left;
|
262 |
+
}
|
263 |
+
|
264 |
+
.bot-message-ar {
|
265 |
+
background-color: #f0f0f0;
|
266 |
+
margin-left: auto;
|
267 |
+
text-align: right;
|
268 |
+
}
|
269 |
+
|
270 |
+
.header-section {
|
271 |
+
background-color: #1a5276;
|
272 |
+
color: white;
|
273 |
+
padding: 1rem;
|
274 |
+
border-radius: 10px;
|
275 |
+
margin-bottom: 1rem;
|
276 |
+
text-align: center;
|
277 |
+
}
|
278 |
+
|
279 |
+
.footer-section {
|
280 |
+
font-size: 0.8rem;
|
281 |
+
text-align: center;
|
282 |
+
margin-top: 2rem;
|
283 |
+
color: #666;
|
284 |
+
}
|
285 |
+
|
286 |
+
.lang-selector {
|
287 |
+
text-align: right;
|
288 |
+
margin-bottom: 1rem;
|
289 |
+
}
|
290 |
+
|
291 |
+
.menu-button {
|
292 |
+
margin-top: 0.5rem;
|
293 |
+
}
|
294 |
+
|
295 |
+
.quick-actions {
|
296 |
+
display: flex;
|
297 |
+
flex-wrap: wrap;
|
298 |
+
gap: 0.5rem;
|
299 |
+
margin: 1rem 0;
|
300 |
+
}
|
301 |
+
|
302 |
+
.quick-action-button {
|
303 |
+
background-color: #1a5276;
|
304 |
+
color: white;
|
305 |
+
border: none;
|
306 |
+
border-radius: 20px;
|
307 |
+
padding: 0.5rem 1rem;
|
308 |
+
cursor: pointer;
|
309 |
+
font-size: 0.9rem;
|
310 |
+
transition: background-color 0.3s;
|
311 |
+
}
|
312 |
+
|
313 |
+
.quick-action-button:hover {
|
314 |
+
background-color: #2980b9;
|
315 |
+
}
|
316 |
+
|
317 |
+
.chat-container {
|
318 |
+
border: 1px solid #ddd;
|
319 |
+
border-radius: 10px;
|
320 |
+
padding: 1rem;
|
321 |
+
background-color: #f9f9f9;
|
322 |
+
}
|
323 |
+
|
324 |
+
.typing-indicator {
|
325 |
+
display: inline-block;
|
326 |
+
width: 50px;
|
327 |
+
text-align: left;
|
328 |
+
}
|
329 |
+
|
330 |
+
.typing-indicator span {
|
331 |
+
display: inline-block;
|
332 |
+
width: 8px;
|
333 |
+
height: 8px;
|
334 |
+
background-color: #1a5276;
|
335 |
+
border-radius: 50%;
|
336 |
+
margin-right: 5px;
|
337 |
+
animation: typing 1s infinite;
|
338 |
+
}
|
339 |
+
|
340 |
+
.typing-indicator span:nth-child(2) {
|
341 |
+
animation-delay: 0.2s;
|
342 |
+
}
|
343 |
+
|
344 |
+
.typing-indicator span:nth-child(3) {
|
345 |
+
animation-delay: 0.4s;
|
346 |
+
}
|
347 |
+
|
348 |
+
@keyframes typing {
|
349 |
+
0%, 100% {
|
350 |
+
transform: translateY(0);
|
351 |
+
}
|
352 |
+
50% {
|
353 |
+
transform: translateY(-5px);
|
354 |
+
}
|
355 |
+
}
|
356 |
+
|
357 |
+
.live-agent-button {
|
358 |
+
background-color: #27ae60;
|
359 |
+
color: white;
|
360 |
+
border: none;
|
361 |
+
border-radius: 5px;
|
362 |
+
padding: 0.5rem 1rem;
|
363 |
+
cursor: pointer;
|
364 |
+
font-size: 0.9rem;
|
365 |
+
margin-top: 1rem;
|
366 |
+
transition: background-color 0.3s;
|
367 |
+
}
|
368 |
+
|
369 |
+
.live-agent-button:hover {
|
370 |
+
background-color: #2ecc71;
|
371 |
+
}
|
372 |
+
|
373 |
+
/* Add custom styling for links */
|
374 |
+
a {
|
375 |
+
color: #2980b9;
|
376 |
+
text-decoration: none;
|
377 |
+
font-weight: bold;
|
378 |
+
}
|
379 |
+
|
380 |
+
a:hover {
|
381 |
+
text-decoration: underline;
|
382 |
+
}
|
383 |
+
|
384 |
+
/* Add styling for action buttons */
|
385 |
+
.action-button {
|
386 |
+
display: inline-block;
|
387 |
+
background-color: #3498db;
|
388 |
+
color: white;
|
389 |
+
padding: 0.5rem 1rem;
|
390 |
+
border-radius: 5px;
|
391 |
+
margin: 0.5rem 0;
|
392 |
+
text-decoration: none;
|
393 |
+
}
|
394 |
+
|
395 |
+
.action-button:hover {
|
396 |
+
background-color: #2980b9;
|
397 |
+
text-decoration: none;
|
398 |
+
}
|
399 |
+
"""
|
400 |
+
|
401 |
+
# Custom JavaScript for enhanced functionality
|
402 |
+
custom_js = """
|
403 |
+
function simulateTyping(message, elementId, delay = 30) {
|
404 |
+
const element = document.getElementById(elementId);
|
405 |
+
if (!element) return;
|
406 |
+
|
407 |
+
element.innerHTML = "";
|
408 |
+
let i = 0;
|
409 |
+
|
410 |
+
function type() {
|
411 |
+
if (i < message.length) {
|
412 |
+
element.innerHTML += message.charAt(i);
|
413 |
+
i++;
|
414 |
+
setTimeout(type, delay);
|
415 |
+
}
|
416 |
+
}
|
417 |
+
|
418 |
+
type();
|
419 |
+
}
|
420 |
+
|
421 |
+
// Function to show typing indicator
|
422 |
+
function showTypingIndicator() {
|
423 |
+
const chatbox = document.getElementById('chatbox');
|
424 |
+
if (!chatbox) return;
|
425 |
+
|
426 |
+
const typingIndicator = document.createElement('div');
|
427 |
+
typingIndicator.className = 'typing-indicator';
|
428 |
+
typingIndicator.id = 'typing-indicator';
|
429 |
+
typingIndicator.innerHTML = '<span></span><span></span><span></span>';
|
430 |
+
|
431 |
+
chatbox.appendChild(typingIndicator);
|
432 |
+
chatbox.scrollTop = chatbox.scrollHeight;
|
433 |
+
}
|
434 |
+
|
435 |
+
// Function to hide typing indicator
|
436 |
+
function hideTypingIndicator() {
|
437 |
+
const typingIndicator = document.getElementById('typing-indicator');
|
438 |
+
if (typingIndicator) {
|
439 |
+
typingIndicator.remove();
|
440 |
+
}
|
441 |
+
}
|
442 |
+
|
443 |
+
// Function to connect with a live agent
|
444 |
+
function connectLiveAgent() {
|
445 |
+
alert('Connecting to a live customer service agent. Please wait a moment...');
|
446 |
+
// In a real implementation, this would initiate a connection to a live agent system
|
447 |
+
}
|
448 |
+
"""
|
449 |
+
|
450 |
+
# Chat interface with enhanced UI
|
451 |
+
with gr.Blocks(css=custom_css, js=custom_js) as demo:
|
452 |
+
# Store conversation history
|
453 |
+
state = gr.State(value=[])
|
454 |
+
# Store selected language
|
455 |
+
selected_lang = gr.State(value="ar")
|
456 |
+
|
457 |
+
with gr.Row(elem_classes="header-section"):
|
458 |
+
with gr.Column():
|
459 |
+
gr.Markdown("# Omdurman National Bank | بنك أم درمان الوطني")
|
460 |
+
gr.Markdown("### Virtual Banking Assistant | المساعد المصرفي ا<response clipped><NOTE>To save on context only part of this file has been shown to you. You should retry this tool after you have searched inside the file with `grep -n` in order to find the line numbers of what you are looking for.</NOTE>
|
original_chatbot.py
ADDED
@@ -0,0 +1,305 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import re
|
3 |
+
|
4 |
+
# Load language detection model only (smaller model)
|
5 |
+
from transformers import pipeline
|
6 |
+
language_detector = pipeline("text-classification", model="papluca/xlm-roberta-base-language-detection")
|
7 |
+
|
8 |
+
# Omdurman National Bank-specific guidelines in Arabic
|
9 |
+
ONB_GUIDELINES_AR = {
|
10 |
+
"balance": "يمكنك التحقق من رصيدك عبر الإنترنت أو عبر تطبيق الهاتف الخاص ببنك الوطني.",
|
11 |
+
"lost_card": "في حالة فقدان البطاقة، اتصل بالرقم 249-123-456-789 فورًا.",
|
12 |
+
"loan": "شروط القرض تشمل الحد الأدنى للدخل (5000 جنيه سوداني) وتاريخ ائتماني جيد.",
|
13 |
+
"transfer": "لتحويل الأموال، استخدم تطبيق الهاتف أو الخدمة المصرفية عبر الإنترنت.",
|
14 |
+
"new_account": "لفتح حساب جديد، قم بزيارة أقرب فرع مع جواز سفرك أو هويتك الوطنية.",
|
15 |
+
"interest_rates": "أسعار الفائدة على الودائع تتراوح بين 5% إلى 10% سنويًا.",
|
16 |
+
"branches": "فروعنا موجودة في أم درمان، الخرطوم، وبورتسودان. زيارة موقعنا للتفاصيل.",
|
17 |
+
"working_hours": "ساعات العمل من 8 صباحًا إلى 3 مساءً من الأحد إلى الخميس.",
|
18 |
+
"contact": "الاتصال بنا على الرقم 249-123-456-789 أو عبر البريد الإلكتروني [email protected]."
|
19 |
+
}
|
20 |
+
|
21 |
+
# Omdurman National Bank-specific guidelines in English
|
22 |
+
ONB_GUIDELINES_EN = {
|
23 |
+
"balance": "You can check your balance online or via the ONB mobile app.",
|
24 |
+
"lost_card": "In case of a lost card, call 249-123-456-789 immediately.",
|
25 |
+
"loan": "Loan requirements include minimum income (5000 SDG) and good credit history.",
|
26 |
+
"transfer": "To transfer funds, use the mobile app or online banking service.",
|
27 |
+
"new_account": "To open a new account, visit your nearest branch with your passport or national ID.",
|
28 |
+
"interest_rates": "Interest rates on deposits range from 5% to 10% annually.",
|
29 |
+
"branches": "Our branches are located in Omdurman, Khartoum, and Port Sudan. Visit our website for details.",
|
30 |
+
"working_hours": "Working hours are from 8 AM to 3 PM, Sunday to Thursday.",
|
31 |
+
"contact": "Contact us at 249-123-456-789 or via email at [email protected]."
|
32 |
+
}
|
33 |
+
|
34 |
+
# Menu options in both languages
|
35 |
+
MENU_AR = """
|
36 |
+
قائمة الخدمات المصرفية:
|
37 |
+
1. رصيد - استعلام عن رصيد حسابك
|
38 |
+
2. بطاقة - الإبلاغ عن بطاقة مفقودة
|
39 |
+
3. قرض - معلومات عن القروض
|
40 |
+
4. تحويل - تحويل الأموال
|
41 |
+
5. حساب - فتح حساب جديد
|
42 |
+
6. فائدة - أسعار الفائدة
|
43 |
+
7. فرع - مواقع الفروع
|
44 |
+
8. ساعات - ساعات العمل
|
45 |
+
9. اتصال - معلومات الاتصال
|
46 |
+
"""
|
47 |
+
|
48 |
+
MENU_EN = """
|
49 |
+
Banking Services Menu:
|
50 |
+
1. balance - Check your account balance
|
51 |
+
2. card - Report a lost card
|
52 |
+
3. loan - Information about loans
|
53 |
+
4. transfer - Transfer funds
|
54 |
+
5. account - Open a new account
|
55 |
+
6. interest - Interest rates
|
56 |
+
7. branch - Branch locations
|
57 |
+
8. hours - Working hours
|
58 |
+
9. contact - Contact information
|
59 |
+
"""
|
60 |
+
|
61 |
+
# Map intents to keywords (enhanced)
|
62 |
+
INTENT_KEYWORDS = {
|
63 |
+
"balance": ["balance", "check balance", "account balance", "how much", "رصيد", "حساب", "كم المبلغ", "1"],
|
64 |
+
"lost_card": ["lost", "card", "stolen", "missing", "فقدت", "بطاقة", "مسروقة", "ضائعة", "2"],
|
65 |
+
"loan": ["loan", "borrow", "borrowing", "credit", "قرض", "استدانة", "إئتمان", "3"],
|
66 |
+
"transfer": ["transfer", "send money", "payment", "تحويل", "ارسال", "دفع", "4"],
|
67 |
+
"new_account": ["account", "open", "create", "new", "حساب", "فتح", "جديد", "إنشاء", "5"],
|
68 |
+
"interest_rates": ["interest", "rate", "rates", "return", "فائدة", "نسبة", "عائد", "6"],
|
69 |
+
"branches": ["branch", "location", "where", "office", "فرع", "موقع", "أين", "مكتب", "7"],
|
70 |
+
"working_hours": ["hours", "time", "open", "close", "ساعات", "وقت", "مفتوح", "مغلق", "8"],
|
71 |
+
"contact": ["contact", "phone", "email", "call", "اتصال", "هاتف", "بريد", "اتصل", "9"]
|
72 |
+
}
|
73 |
+
|
74 |
+
def detect_language(text):
|
75 |
+
# Use Hugging Face language detection model
|
76 |
+
result = language_detector(text)
|
77 |
+
language = result[0]['label']
|
78 |
+
return language
|
79 |
+
|
80 |
+
def classify_intent(message: str):
|
81 |
+
# Check for menu request
|
82 |
+
menu_keywords = ["menu", "options", "help", "قائمة", "خيارات", "مساعدة"]
|
83 |
+
message_lower = message.lower()
|
84 |
+
|
85 |
+
for keyword in menu_keywords:
|
86 |
+
if keyword in message_lower:
|
87 |
+
return "menu"
|
88 |
+
|
89 |
+
# Use keyword matching for intent classification
|
90 |
+
for intent_key, keywords in INTENT_KEYWORDS.items():
|
91 |
+
for keyword in keywords:
|
92 |
+
if keyword.lower() in message_lower:
|
93 |
+
return intent_key
|
94 |
+
|
95 |
+
return "unknown"
|
96 |
+
|
97 |
+
def respond(message: str):
|
98 |
+
if not message.strip():
|
99 |
+
return {
|
100 |
+
"ar": "الرجاء كتابة سؤالك.",
|
101 |
+
"en": "Please type your question."
|
102 |
+
}
|
103 |
+
|
104 |
+
# Detect language
|
105 |
+
language = detect_language(message)
|
106 |
+
|
107 |
+
# If the language is neither Arabic nor English, default to English
|
108 |
+
if language != "ar" and language != "en":
|
109 |
+
language = "en"
|
110 |
+
|
111 |
+
# Classify the user's intent using keyword matching
|
112 |
+
intent = classify_intent(message)
|
113 |
+
|
114 |
+
# Prepare responses in both languages
|
115 |
+
responses = {
|
116 |
+
"ar": "",
|
117 |
+
"en": ""
|
118 |
+
}
|
119 |
+
|
120 |
+
# Special handling for menu request
|
121 |
+
if intent == "menu":
|
122 |
+
responses["ar"] = MENU_AR
|
123 |
+
responses["en"] = MENU_EN
|
124 |
+
return responses
|
125 |
+
|
126 |
+
# If intent is recognized, return the corresponding response
|
127 |
+
if intent != "unknown":
|
128 |
+
responses["ar"] = ONB_GUIDELINES_AR.get(intent, "عذرًا، لم يتم التعرف على الخيار المحدد.")
|
129 |
+
responses["en"] = ONB_GUIDELINES_EN.get(intent, "Sorry, the selected option was not recognized.")
|
130 |
+
else:
|
131 |
+
# Default response if no intent is matched - show menu
|
132 |
+
responses["ar"] = "عذرًا، لم أفهم سؤالك. إليك قائمة بالخدمات المتاحة:" + MENU_AR
|
133 |
+
responses["en"] = "Sorry, I didn't understand your question. Here's a menu of available services:" + MENU_EN
|
134 |
+
|
135 |
+
return responses
|
136 |
+
|
137 |
+
# Custom CSS for better UI
|
138 |
+
custom_css = """
|
139 |
+
.gradio-container {
|
140 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
141 |
+
}
|
142 |
+
|
143 |
+
.chat-message {
|
144 |
+
padding: 1rem;
|
145 |
+
border-radius: 10px;
|
146 |
+
margin-bottom: 1rem;
|
147 |
+
max-width: 80%;
|
148 |
+
}
|
149 |
+
|
150 |
+
.user-message {
|
151 |
+
background-color: #e6f7ff;
|
152 |
+
margin-left: auto;
|
153 |
+
text-align: right;
|
154 |
+
}
|
155 |
+
|
156 |
+
.bot-message {
|
157 |
+
background-color: #f0f0f0;
|
158 |
+
margin-right: auto;
|
159 |
+
text-align: left;
|
160 |
+
}
|
161 |
+
|
162 |
+
.bot-message-ar {
|
163 |
+
background-color: #f0f0f0;
|
164 |
+
margin-left: auto;
|
165 |
+
text-align: right;
|
166 |
+
}
|
167 |
+
|
168 |
+
.header-section {
|
169 |
+
background-color: #1a5276;
|
170 |
+
color: white;
|
171 |
+
padding: 1rem;
|
172 |
+
border-radius: 10px;
|
173 |
+
margin-bottom: 1rem;
|
174 |
+
text-align: center;
|
175 |
+
}
|
176 |
+
|
177 |
+
.footer-section {
|
178 |
+
font-size: 0.8rem;
|
179 |
+
text-align: center;
|
180 |
+
margin-top: 2rem;
|
181 |
+
color: #666;
|
182 |
+
}
|
183 |
+
|
184 |
+
.lang-selector {
|
185 |
+
text-align: right;
|
186 |
+
margin-bottom: 1rem;
|
187 |
+
}
|
188 |
+
|
189 |
+
.menu-button {
|
190 |
+
margin-top: 0.5rem;
|
191 |
+
}
|
192 |
+
"""
|
193 |
+
|
194 |
+
# Chat interface with enhanced UI
|
195 |
+
with gr.Blocks(css=custom_css) as demo:
|
196 |
+
# Store conversation history
|
197 |
+
state = gr.State(value=[])
|
198 |
+
# Store selected language
|
199 |
+
selected_lang = gr.State(value="ar")
|
200 |
+
|
201 |
+
with gr.Row(elem_classes="header-section"):
|
202 |
+
with gr.Column():
|
203 |
+
gr.Markdown("# Omdurman National Bank | بنك أم درمان الوطني")
|
204 |
+
gr.Markdown("### Virtual Banking Assistant | المساعد المصرفي الافتراضي")
|
205 |
+
|
206 |
+
with gr.Row():
|
207 |
+
with gr.Column(elem_classes="lang-selector"):
|
208 |
+
language_btn = gr.Radio(
|
209 |
+
["العربية", "English"],
|
210 |
+
value="العربية",
|
211 |
+
label="Language | اللغة"
|
212 |
+
)
|
213 |
+
|
214 |
+
with gr.Row():
|
215 |
+
chat_box = gr.Chatbot(elem_id="chatbox", height=400)
|
216 |
+
|
217 |
+
with gr.Row():
|
218 |
+
with gr.Column(scale=8):
|
219 |
+
text_input = gr.Textbox(
|
220 |
+
placeholder="Type your question here | اكتب سؤالك هنا",
|
221 |
+
label="",
|
222 |
+
elem_id="chat-input"
|
223 |
+
)
|
224 |
+
with gr.Column(scale=1):
|
225 |
+
submit_btn = gr.Button("Send | إرسال", variant="primary")
|
226 |
+
|
227 |
+
with gr.Row():
|
228 |
+
with gr.Column(elem_classes="menu-button"):
|
229 |
+
menu_btn = gr.Button("Show Menu | إظهار القائمة")
|
230 |
+
|
231 |
+
with gr.Row(elem_classes="footer-section"):
|
232 |
+
gr.Markdown("© 2025 Omdurman National Bank. All Rights Reserved. | جميع الحقوق محفوظة لبنك أم درمان الوطني ٢٠٢٥ ©")
|
233 |
+
|
234 |
+
# Update language state when language is changed
|
235 |
+
def update_language(lang):
|
236 |
+
if lang == "العربية":
|
237 |
+
return "ar"
|
238 |
+
else:
|
239 |
+
return "en"
|
240 |
+
|
241 |
+
language_btn.change(
|
242 |
+
fn=update_language,
|
243 |
+
inputs=language_btn,
|
244 |
+
outputs=selected_lang
|
245 |
+
)
|
246 |
+
|
247 |
+
# Handle message submission
|
248 |
+
def on_submit(message, chat_history, lang):
|
249 |
+
if not message.strip():
|
250 |
+
return "", chat_history
|
251 |
+
|
252 |
+
# Add user message to chat history
|
253 |
+
chat_history.append([message, None])
|
254 |
+
|
255 |
+
# Get response
|
256 |
+
responses = respond(message)
|
257 |
+
|
258 |
+
# Select response based on language
|
259 |
+
response = responses[lang]
|
260 |
+
|
261 |
+
# Update bot response in chat history
|
262 |
+
chat_history[-1][1] = response
|
263 |
+
|
264 |
+
return "", chat_history
|
265 |
+
|
266 |
+
# Handle menu button click
|
267 |
+
def show_menu(chat_history, lang):
|
268 |
+
menu_responses = {
|
269 |
+
"ar": MENU_AR,
|
270 |
+
"en": MENU_EN
|
271 |
+
}
|
272 |
+
|
273 |
+
# Add system message showing the menu
|
274 |
+
menu_text = menu_responses[lang]
|
275 |
+
chat_history.append([None, menu_text])
|
276 |
+
|
277 |
+
return chat_history
|
278 |
+
|
279 |
+
# Link inputs and button to response function
|
280 |
+
submit_btn.click(
|
281 |
+
fn=on_submit,
|
282 |
+
inputs=[text_input, chat_box, selected_lang],
|
283 |
+
outputs=[text_input, chat_box]
|
284 |
+
)
|
285 |
+
|
286 |
+
# Link menu button to show menu function
|
287 |
+
menu_btn.click(
|
288 |
+
fn=show_menu,
|
289 |
+
inputs=[chat_box, selected_lang],
|
290 |
+
outputs=[chat_box]
|
291 |
+
)
|
292 |
+
|
293 |
+
# Also trigger on Enter key
|
294 |
+
text_input.submit(
|
295 |
+
fn=on_submit,
|
296 |
+
inputs=[text_input, chat_box, selected_lang],
|
297 |
+
outputs=[text_input, chat_box]
|
298 |
+
)
|
299 |
+
|
300 |
+
if __name__ == "__main__":
|
301 |
+
demo.launch(
|
302 |
+
server_name="0.0.0.0",
|
303 |
+
server_port=7860,
|
304 |
+
share=True # Enable public link
|
305 |
+
)
|
requirements.txt
CHANGED
@@ -1,8 +1 @@
|
|
1 |
-
|
2 |
-
gradio>=4.0
|
3 |
-
transformers
|
4 |
-
torch
|
5 |
-
sentencepiece
|
6 |
-
protobuf
|
7 |
-
accelerate
|
8 |
-
optimum
|
|
|
1 |
+
gradio>=3.50.2
|
|
|
|
|
|
|
|
|
|
|
|
|
|