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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|