waleedmohd commited on
Commit
209b414
·
verified ·
1 Parent(s): 330cbd9

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +682 -64
app.py CHANGED
@@ -1,36 +1,116 @@
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
  قائمة الخدمات المصرفية:
@@ -71,11 +151,13 @@ INTENT_KEYWORDS = {
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
@@ -94,6 +176,23 @@ def classify_intent(message: str):
94
 
95
  return "unknown"
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  def respond(message: str):
98
  if not message.strip():
99
  return {
@@ -101,12 +200,8 @@ def respond(message: str):
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)
@@ -121,17 +216,77 @@ def respond(message: str):
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
@@ -189,14 +344,226 @@ custom_css = """
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():
@@ -211,8 +578,15 @@ with gr.Blocks(css=custom_css) as demo:
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):
@@ -225,32 +599,92 @@ with gr.Blocks(css=custom_css) as demo:
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)
@@ -258,10 +692,26 @@ with gr.Blocks(css=custom_css) as demo:
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):
@@ -270,31 +720,199 @@ with gr.Blocks(css=custom_css) as demo:
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__":
@@ -302,4 +920,4 @@ if __name__ == "__main__":
302
  server_name="0.0.0.0",
303
  server_port=7860,
304
  share=True # Enable public link
305
- )
 
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]'>info@onb.sd</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]'>info@onb.sd</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
  قائمة الخدمات المصرفية:
 
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
 
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("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 {
 
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)
 
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
 
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():
 
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):
 
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)
 
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):
 
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__":
 
920
  server_name="0.0.0.0",
921
  server_port=7860,
922
  share=True # Enable public link
923
+ )