masadonline commited on
Commit
9342de7
·
verified ·
1 Parent(s): 17a2a58

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +57 -147
app.py CHANGED
@@ -18,8 +18,6 @@ st.set_page_config(page_title="RAG Customer Support Chatbot", layout="wide")
18
  DEFAULT_TWILIO_ACCOUNT_SID_FALLBACK = ""
19
  DEFAULT_TWILIO_AUTH_TOKEN_FALLBACK = ""
20
  DEFAULT_GROQ_API_KEY_FALLBACK = ""
21
-
22
- #DEFAULT_TWILIO_CONVERSATION_SERVICE_SID = ""
23
  DEFAULT_TWILIO_BOT_WHATSAPP_IDENTITY = st.secrets.get("TWILIO_PHONE_NUMBER", "whatsapp:+14155238886")
24
  DEFAULT_EMBEDDING_MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
25
  DEFAULT_POLLING_INTERVAL_S = 30
@@ -33,11 +31,8 @@ FAQ_PDF_FILE = os.path.join(DOCS_FOLDER, "FAQ.pdf")
33
  APP_TWILIO_ACCOUNT_SID = st.secrets.get("TWILIO_ACCOUNT_SID")
34
  APP_TWILIO_AUTH_TOKEN = st.secrets.get("TWILIO_AUTH_TOKEN")
35
  APP_GROQ_API_KEY = st.secrets.get("GROQ_API_KEY")
36
-
37
- #APP_TWILIO_CONVERSATION_SERVICE_SID_SECRET = st.secrets.get("TWILIO_CONVERSATION_SERVICE_SID")
38
  APP_TWILIO_BOT_WHATSAPP_IDENTITY_SECRET = st.secrets.get("TWILIO_BOT_WHATSAPP_IDENTITY")
39
 
40
-
41
  # --- RAG Processing Utilities ---
42
  def load_json_data(file_path):
43
  try:
@@ -202,7 +197,6 @@ def generate_response_groq(_groq_client, query, context, model="llama3-8b-8192",
202
 
203
  item_description = item_name if item_name else "the ordered item(s)"
204
 
205
- # Construct the core information string that the LLM needs to build upon
206
  core_info_parts = [
207
  f"your order {order_id}",
208
  f"for {item_description}",
@@ -220,10 +214,9 @@ def generate_response_groq(_groq_client, query, context, model="llama3-8b-8192",
220
  core_info_parts.append("(delivery date not specified)")
221
 
222
  core_information_to_include = ", ".join(core_info_parts[:-1]) + (f" {core_info_parts[-1]}" if len(core_info_parts) > 1 else "")
223
- if not order_status.lower() == "delivered" and len(core_info_parts) > 1 : # for non-delivered, avoid 'and' before status
224
  core_information_to_include = f"your order {order_id} for {item_description} has a status of '{order_status}'"
225
 
226
-
227
  user_prompt = (
228
  f"Customer: {customer_name}\n"
229
  f"Order ID: {order_id}\n"
@@ -244,10 +237,8 @@ def generate_response_groq(_groq_client, query, context, model="llama3-8b-8192",
244
  f"Ensure the sentence flows naturally and uses the details you've been given.\n"
245
  f"Respond now with ONLY that single sentence."
246
  )
247
- # For LLM's deeper reference, though the primary instruction is above:
248
- # user_prompt += f"\n\nFull database context for your reference if needed: {context}"
249
 
250
- else: # Default prompt structure for other intents or if details are missing
251
  system_message = "You are a helpful customer support assistant."
252
  user_prompt = f"""Use the following context to answer the user's question.
253
  If the context doesn't contain the answer, state that you don't have enough information or ask clarifying questions.
@@ -267,17 +258,16 @@ Assistant Answer:
267
  {"role": "user", "content": user_prompt}
268
  ],
269
  model=model,
270
- temperature=0.5, # Slightly lower temperature might help with stricter adherence
271
  max_tokens=1024,
272
  top_p=1
273
  )
274
- response = chat_completion.choices[0].message.content.strip() # Added strip()
275
  return response
276
  except Exception as e:
277
  st.error(f"Error calling GROQ API: {e}")
278
  return "Sorry, I encountered an error while trying to generate a response."
279
 
280
-
281
  def initialize_groq_client(api_key_val):
282
  if not api_key_val:
283
  st.warning("GROQ API Key is missing.")
@@ -301,62 +291,53 @@ def initialize_twilio_client(acc_sid, auth_tkn):
301
  st.error(f"Failed to initialize Twilio client: {e}")
302
  return None
303
 
304
- def get_new_whatsapp_messages(twilio_client, conversation_service_sid_val, bot_start_time_utc,
305
- processed_message_sids, bot_whatsapp_identity_val):
306
  if not twilio_client:
307
  st.warning("Twilio client not initialized.")
308
  return []
309
- if not conversation_service_sid_val:
310
- st.warning("Twilio Conversation Service SID not provided.")
311
- return []
312
  if not bot_whatsapp_identity_val:
313
  st.warning("Twilio Bot WhatsApp Identity not provided.")
314
  return []
315
 
316
  new_messages_to_process = []
317
  try:
318
- conversations = twilio_client.conversations.v1 \
319
- .services(conversation_service_sid_val) \
320
- .conversations \
321
- .list(limit=50)
322
 
323
  for conv in conversations:
324
  if conv.date_updated and conv.date_updated > bot_start_time_utc:
325
  messages = twilio_client.conversations.v1 \
326
- .services(conversation_service_sid_val) \
327
  .conversations(conv.sid) \
328
  .messages \
329
- .list(order='desc', limit=10)
330
 
331
  for msg in messages:
332
  if msg.sid in processed_message_sids:
333
- continue
334
 
 
335
  if msg.author and msg.author.lower() != bot_whatsapp_identity_val.lower() and \
336
- msg.date_created and msg.date_created > bot_start_time_utc:
 
337
  new_messages_to_process.append({
338
  "conversation_sid": conv.sid, "message_sid": msg.sid,
339
  "author_identity": msg.author, "message_body": msg.body,
340
  "timestamp_utc": msg.date_created
341
  })
342
- break
343
  except Exception as e:
344
  st.error(f"Error fetching Twilio messages: {e}")
345
- return sorted(new_messages_to_process, key=lambda m: m['timestamp_utc'])
346
 
347
- def send_whatsapp_message(twilio_client, conversation_service_sid_val, conversation_sid, message_body, bot_identity_val):
348
  if not twilio_client:
349
  st.error("Twilio client not initialized for sending message.")
350
  return False
351
- if not conversation_service_sid_val:
352
- st.error("Twilio Conversation Service SID not provided for sending message.")
353
- return False
354
  if not bot_identity_val:
355
  st.error("Bot identity not provided for sending message.")
356
  return False
357
  try:
358
  twilio_client.conversations.v1 \
359
- .services(conversation_service_sid_val) \
360
  .conversations(conversation_sid) \
361
  .messages \
362
  .create(author=bot_identity_val, body=message_body)
@@ -366,6 +347,27 @@ def send_whatsapp_message(twilio_client, conversation_service_sid_val, conversat
366
  st.error(f"Error sending Twilio message to {conversation_sid}: {e}")
367
  return False
368
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  # --- Main Application Logic & UI ---
370
  st.title("🤖 RAG-Based Customer Support Chatbot")
371
  st.markdown("Powered by Streamlit, Twilio, GROQ LLaMA3, and FAISS.")
@@ -394,16 +396,10 @@ else:
394
  st.sidebar.warning("Secret 'GROQ_API_KEY' not found.")
395
  groq_api_key_to_use = st.sidebar.text_input("GROQ API Key (Enter Manually)", value=DEFAULT_GROQ_API_KEY_FALLBACK, type="password")
396
 
397
- # twilio_conversation_service_sid_to_use = st.sidebar.text_input(
398
- # "Twilio Conversation Service SID (IS...)",
399
- # value=APP_TWILIO_CONVERSATION_SERVICE_SID_SECRET or DEFAULT_TWILIO_CONVERSATION_SERVICE_SID,
400
- # type="password",
401
- # help="The SID of your Twilio Conversations Service. Can be set by 'TWILIO_CONVERSATION_SERVICE_SID' secret."
402
- # )
403
  twilio_bot_whatsapp_identity_to_use = st.sidebar.text_input(
404
  "Twilio Bot WhatsApp Identity",
405
  value=APP_TWILIO_BOT_WHATSAPP_IDENTITY_SECRET or DEFAULT_TWILIO_BOT_WHATSAPP_IDENTITY,
406
- help="e.g., 'whatsapp:+1234567890'. Can be set by 'TWILIO_BOT_WHATSAPP_IDENTITY' secret."
407
  )
408
  embedding_model_name_to_use = st.sidebar.text_input(
409
  "Embedding Model Name",
@@ -425,27 +421,6 @@ if "bot_start_time_utc" not in st.session_state: st.session_state.bot_start_time
425
  if "processed_message_sids" not in st.session_state: st.session_state.processed_message_sids = set()
426
  if "manual_chat_history" not in st.session_state: st.session_state.manual_chat_history = []
427
 
428
- # --- Helper: Simple Intent Classifier ---
429
- def simple_intent_classifier(query):
430
- query_lower = query.lower()
431
- order_keywords = ["order", "status", "track", "delivery"]
432
- order_id_match = re.search(r'\b(ord\d{3,})\b', query_lower, re.IGNORECASE)
433
-
434
- if any(k in query_lower for k in order_keywords):
435
- if order_id_match:
436
- return "ORDER_STATUS", order_id_match.group(1).upper()
437
- return "ORDER_STATUS", None
438
-
439
- product_keywords = ["product", "item", "buy", "price", "feature", "stock"]
440
- product_id_match = re.search(r'\b(prd\d{3,})\b', query_lower, re.IGNORECASE)
441
- if any(k in query_lower for k in product_keywords) or product_id_match:
442
- return "PRODUCT_INFO", None
443
-
444
- if any(k in query_lower for k in ["return", "policy", "refund", "exchange", "faq", "question", "how to", "support"]):
445
- return "GENERAL_POLICY_FAQ", None
446
-
447
- return "UNKNOWN", None
448
-
449
  # --- Main Application Controls ---
450
  col1, col2, col3, col4 = st.columns(4)
451
  with col1:
@@ -506,8 +481,8 @@ with col2:
506
  st.rerun()
507
  with col3:
508
  if st.button("💬 Start WhatsApp Bot", disabled=not st.session_state.app_started or st.session_state.bot_started, use_container_width=True):
509
- if not all([twilio_account_sid_to_use, twilio_auth_token_to_use, twilio_conversation_service_sid_to_use, twilio_bot_whatsapp_identity_to_use]):
510
- st.error("Twilio Account SID, Auth Token, Conversation Service SID, and Bot WhatsApp Identity are all required.")
511
  else:
512
  st.session_state.twilio_client = initialize_twilio_client(twilio_account_sid_to_use, twilio_auth_token_to_use)
513
  if st.session_state.twilio_client:
@@ -560,7 +535,6 @@ if st.session_state.get("app_started") and st.session_state.get("rag_pipeline_re
560
  extracted_customer_name, extracted_item_name, extracted_shipping_address, \
561
  extracted_delivery_date, extracted_order_id, extracted_order_status = [None] * 6
562
 
563
-
564
  if intent == "ORDER_STATUS":
565
  order_id_to_check = None
566
  if potential_oid_from_intent:
@@ -572,9 +546,6 @@ if st.session_state.get("app_started") and st.session_state.get("rag_pipeline_re
572
 
573
  if order_id_to_check:
574
  raw_context_data = get_order_details(order_id_to_check, st.session_state.customer_orders_data)
575
- # context_for_llm will be used as the 'context' parameter in generate_response_groq
576
- # For ORDER_STATUS, this raw_context_data (JSON string) is still useful for LLM's reference,
577
- # even though specific fields are extracted for the specialized prompt.
578
  context_for_llm = raw_context_data
579
 
580
  if isinstance(raw_context_data, str) and not raw_context_data.startswith("No order found") and not raw_context_data.startswith("Customer order data is not loaded"):
@@ -585,23 +556,23 @@ if st.session_state.get("app_started") and st.session_state.get("rag_pipeline_re
585
  if items and len(items) > 0 and isinstance(items[0], dict):
586
  extracted_item_name = items[0].get("name", "your item(s)")
587
  else:
588
- extracted_item_name = "your item(s)" # Fallback
589
  extracted_shipping_address = order_data_dict.get("shipping_address")
590
  extracted_delivery_date = order_data_dict.get("delivered_on")
591
  extracted_order_status = order_data_dict.get("status")
592
- extracted_order_id = order_data_dict.get("order_id") # Should be same as order_id_to_check
593
  except json.JSONDecodeError:
594
  st.warning(f"Could not parse order details JSON for {order_id_to_check} for personalization.")
595
  context_for_llm = f"Error parsing order details for {order_id_to_check}. Raw data: {raw_context_data}"
596
- elif isinstance(raw_context_data, str): # Handle "No order found" or "data not loaded"
597
- context_for_llm = raw_context_data # LLM will state this
598
  else:
599
  context_for_llm = "To check an order status, please provide a valid Order ID (e.g., ORD123)."
600
  raw_context_data = {"message": "Order ID needed or not found in query."}
601
 
602
  elif intent == "PRODUCT_INFO":
603
  raw_context_data = get_product_info(user_query_manual, st.session_state.products_data)
604
- context_for_llm = raw_context_data # Product info is directly used as context
605
 
606
  elif intent == "GENERAL_POLICY_FAQ" or intent == "UNKNOWN":
607
  if st.session_state.faiss_index_pdfs and st.session_state.embedding_model and st.session_state.indexed_pdf_chunks:
@@ -627,7 +598,7 @@ if st.session_state.get("app_started") and st.session_state.get("rag_pipeline_re
627
  item_name=extracted_item_name,
628
  shipping_address=extracted_shipping_address,
629
  delivery_date=extracted_delivery_date,
630
- order_id=extracted_order_id, # This will be the specific order ID from user query
631
  order_status=extracted_order_status
632
  )
633
 
@@ -646,7 +617,7 @@ if st.session_state.get("app_started") and st.session_state.get("rag_pipeline_re
646
  except (json.JSONDecodeError, TypeError):
647
  st.text(str(raw_context_data))
648
  st.session_state.manual_chat_history.append({"role": "assistant", "content": llm_response, "context": raw_context_data})
649
- st.rerun()
650
 
651
  # --- Twilio Bot Polling Logic ---
652
  if st.session_state.get("bot_started") and st.session_state.get("rag_pipeline_ready"):
@@ -657,18 +628,17 @@ if st.session_state.get("bot_started") and st.session_state.get("rag_pipeline_re
657
  if (current_time - st.session_state.last_twilio_poll_time) > polling_interval_to_use:
658
  st.session_state.last_twilio_poll_time = current_time
659
 
660
- if not st.session_state.get("twilio_client") or \
661
- not twilio_conversation_service_sid_to_use or \
662
- not twilio_bot_whatsapp_identity_to_use or \
663
- not st.session_state.get("bot_start_time_utc"):
664
- st.warning("Twilio client/config missing for polling. Ensure bot is started and SIDs are set.")
665
  else:
666
  with st.spinner(f"Checking WhatsApp messages (last poll: {datetime.fromtimestamp(st.session_state.last_twilio_poll_time).strftime('%H:%M:%S')})..."):
667
- new_messages = get_new_whatsapp_messages(st.session_state.twilio_client,
668
- twilio_conversation_service_sid_to_use,
669
- st.session_state.bot_start_time_utc,
670
- st.session_state.processed_message_sids,
671
- twilio_bot_whatsapp_identity_to_use)
 
 
672
  if new_messages:
673
  st.info(f"Found {len(new_messages)} new WhatsApp message(s) to process.")
674
  for msg_data in new_messages:
@@ -688,7 +658,6 @@ if st.session_state.get("bot_started") and st.session_state.get("rag_pipeline_re
688
  wa_customer_name, wa_item_name, wa_shipping_address, \
689
  wa_delivery_date, wa_order_id, wa_order_status = [None] * 6
690
 
691
-
692
  if intent_whatsapp == "ORDER_STATUS":
693
  order_id_to_check_whatsapp = None
694
  if potential_oid_whatsapp:
@@ -700,7 +669,7 @@ if st.session_state.get("bot_started") and st.session_state.get("rag_pipeline_re
700
 
701
  if order_id_to_check_whatsapp:
702
  raw_context_data_whatsapp = get_order_details(order_id_to_check_whatsapp, st.session_state.customer_orders_data)
703
- context_for_llm_whatsapp = raw_context_data_whatsapp # Full JSON string as context
704
 
705
  if isinstance(raw_context_data_whatsapp, str) and not raw_context_data_whatsapp.startswith("No order found") and not raw_context_data_whatsapp.startswith("Customer order data is not loaded"):
706
  try:
@@ -718,63 +687,4 @@ if st.session_state.get("bot_started") and st.session_state.get("rag_pipeline_re
718
  except json.JSONDecodeError:
719
  st.warning(f"Could not parse order details JSON for {order_id_to_check_whatsapp} (WhatsApp) for personalization.")
720
  context_for_llm_whatsapp = f"Error parsing order details for {order_id_to_check_whatsapp}. Raw data: {raw_context_data_whatsapp}"
721
- elif isinstance(raw_context_data_whatsapp, str):
722
- context_for_llm_whatsapp = raw_context_data_whatsapp
723
- else:
724
- context_for_llm_whatsapp = "To check an order status, please provide a valid Order ID (e.g., ORD123)."
725
- raw_context_data_whatsapp = {"message": "Order ID needed or not found in query."}
726
-
727
-
728
- elif intent_whatsapp == "PRODUCT_INFO":
729
- raw_context_data_whatsapp = get_product_info(user_query_whatsapp, st.session_state.products_data)
730
- context_for_llm_whatsapp = raw_context_data_whatsapp
731
-
732
- elif intent_whatsapp == "GENERAL_POLICY_FAQ" or intent_whatsapp == "UNKNOWN":
733
- if st.session_state.faiss_index_pdfs and st.session_state.embedding_model and st.session_state.indexed_pdf_chunks:
734
- k_val_whatsapp = 3 if intent_whatsapp == "GENERAL_POLICY_FAQ" else 2
735
- chunks_whatsapp = search_faiss_index(st.session_state.faiss_index_pdfs, user_query_whatsapp,
736
- st.session_state.embedding_model, st.session_state.indexed_pdf_chunks, k=k_val_whatsapp)
737
- if chunks_whatsapp:
738
- context_for_llm_whatsapp = "Relevant information from documents:\n\n" + "\n\n---\n\n".join(chunks_whatsapp)
739
- raw_context_data_whatsapp = chunks_whatsapp
740
- else:
741
- context_for_llm_whatsapp = "I couldn't find specific information in our policy or FAQ documents regarding your query."
742
- raw_context_data_whatsapp = {"message": "No relevant PDF chunks found."}
743
- else:
744
- context_for_llm_whatsapp = "Our policy and FAQ documents are currently unavailable for search."
745
- raw_context_data_whatsapp = {"message": "PDF index or embedding model not ready."}
746
-
747
- response_whatsapp = generate_response_groq(
748
- _groq_client=st.session_state.groq_client,
749
- query=user_query_whatsapp,
750
- context=context_for_llm_whatsapp,
751
- intent=intent_whatsapp,
752
- customer_name=wa_customer_name,
753
- item_name=wa_item_name,
754
- shipping_address=wa_shipping_address,
755
- delivery_date=wa_delivery_date,
756
- order_id=wa_order_id,
757
- order_status=wa_order_status
758
- )
759
-
760
- if send_whatsapp_message(st.session_state.twilio_client, twilio_conversation_service_sid_to_use,
761
- conv_sid, response_whatsapp, twilio_bot_whatsapp_identity_to_use):
762
- st.session_state.processed_message_sids.add(msg_sid)
763
- st.success(f"Successfully responded to WhatsApp message SID {msg_sid} from {author_id}.")
764
- else:
765
- st.error(f"Failed to send WhatsApp response for message SID {msg_sid} from {author_id}.")
766
- st.experimental_rerun()
767
-
768
-
769
- # --- Footer & Status ---
770
- st.sidebar.markdown("---")
771
- st.sidebar.info("Ensure all keys and SIDs are correctly configured. Primary API keys (Twilio SID/Token, GROQ Key) are loaded from secrets if available.")
772
- if st.session_state.get("app_started"):
773
- status_color = "green" if st.session_state.get("rag_pipeline_ready") else "orange"
774
- app_status_text = "App RUNNING" if st.session_state.get("rag_pipeline_ready") else "App Initializing/Error"
775
- bot_status_text = "WhatsApp Bot RUNNING" if st.session_state.get("bot_started") else "WhatsApp Bot STOPPED"
776
- st.sidebar.markdown(f"<span style='color:{status_color};'>{app_status_text}</span>. {bot_status_text}.", unsafe_allow_html=True)
777
-
778
- else:
779
- st.sidebar.warning("App is STOPPED.")
780
-
 
18
  DEFAULT_TWILIO_ACCOUNT_SID_FALLBACK = ""
19
  DEFAULT_TWILIO_AUTH_TOKEN_FALLBACK = ""
20
  DEFAULT_GROQ_API_KEY_FALLBACK = ""
 
 
21
  DEFAULT_TWILIO_BOT_WHATSAPP_IDENTITY = st.secrets.get("TWILIO_PHONE_NUMBER", "whatsapp:+14155238886")
22
  DEFAULT_EMBEDDING_MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
23
  DEFAULT_POLLING_INTERVAL_S = 30
 
31
  APP_TWILIO_ACCOUNT_SID = st.secrets.get("TWILIO_ACCOUNT_SID")
32
  APP_TWILIO_AUTH_TOKEN = st.secrets.get("TWILIO_AUTH_TOKEN")
33
  APP_GROQ_API_KEY = st.secrets.get("GROQ_API_KEY")
 
 
34
  APP_TWILIO_BOT_WHATSAPP_IDENTITY_SECRET = st.secrets.get("TWILIO_BOT_WHATSAPP_IDENTITY")
35
 
 
36
  # --- RAG Processing Utilities ---
37
  def load_json_data(file_path):
38
  try:
 
197
 
198
  item_description = item_name if item_name else "the ordered item(s)"
199
 
 
200
  core_info_parts = [
201
  f"your order {order_id}",
202
  f"for {item_description}",
 
214
  core_info_parts.append("(delivery date not specified)")
215
 
216
  core_information_to_include = ", ".join(core_info_parts[:-1]) + (f" {core_info_parts[-1]}" if len(core_info_parts) > 1 else "")
217
+ if not order_status.lower() == "delivered" and len(core_info_parts) > 1:
218
  core_information_to_include = f"your order {order_id} for {item_description} has a status of '{order_status}'"
219
 
 
220
  user_prompt = (
221
  f"Customer: {customer_name}\n"
222
  f"Order ID: {order_id}\n"
 
237
  f"Ensure the sentence flows naturally and uses the details you've been given.\n"
238
  f"Respond now with ONLY that single sentence."
239
  )
 
 
240
 
241
+ else:
242
  system_message = "You are a helpful customer support assistant."
243
  user_prompt = f"""Use the following context to answer the user's question.
244
  If the context doesn't contain the answer, state that you don't have enough information or ask clarifying questions.
 
258
  {"role": "user", "content": user_prompt}
259
  ],
260
  model=model,
261
+ temperature=0.5,
262
  max_tokens=1024,
263
  top_p=1
264
  )
265
+ response = chat_completion.choices[0].message.content.strip()
266
  return response
267
  except Exception as e:
268
  st.error(f"Error calling GROQ API: {e}")
269
  return "Sorry, I encountered an error while trying to generate a response."
270
 
 
271
  def initialize_groq_client(api_key_val):
272
  if not api_key_val:
273
  st.warning("GROQ API Key is missing.")
 
291
  st.error(f"Failed to initialize Twilio client: {e}")
292
  return None
293
 
294
+ def get_new_whatsapp_messages(twilio_client, bot_start_time_utc, processed_message_sids, bot_whatsapp_identity_val):
 
295
  if not twilio_client:
296
  st.warning("Twilio client not initialized.")
297
  return []
 
 
 
298
  if not bot_whatsapp_identity_val:
299
  st.warning("Twilio Bot WhatsApp Identity not provided.")
300
  return []
301
 
302
  new_messages_to_process = []
303
  try:
304
+ # Get all conversations (not limited to a specific service)
305
+ conversations = twilio_client.conversations.v1.conversations.list(limit=50)
 
 
306
 
307
  for conv in conversations:
308
  if conv.date_updated and conv.date_updated > bot_start_time_utc:
309
  messages = twilio_client.conversations.v1 \
 
310
  .conversations(conv.sid) \
311
  .messages \
312
+ .list(order='desc', limit=10)
313
 
314
  for msg in messages:
315
  if msg.sid in processed_message_sids:
316
+ continue
317
 
318
+ # Check if message is from WhatsApp and not from the bot
319
  if msg.author and msg.author.lower() != bot_whatsapp_identity_val.lower() and \
320
+ msg.date_created and msg.date_created > bot_start_time_utc and \
321
+ msg.author.startswith('whatsapp:'):
322
  new_messages_to_process.append({
323
  "conversation_sid": conv.sid, "message_sid": msg.sid,
324
  "author_identity": msg.author, "message_body": msg.body,
325
  "timestamp_utc": msg.date_created
326
  })
327
+ break
328
  except Exception as e:
329
  st.error(f"Error fetching Twilio messages: {e}")
330
+ return sorted(new_messages_to_process, key=lambda m: m['timestamp_utc'])
331
 
332
+ def send_whatsapp_message(twilio_client, conversation_sid, message_body, bot_identity_val):
333
  if not twilio_client:
334
  st.error("Twilio client not initialized for sending message.")
335
  return False
 
 
 
336
  if not bot_identity_val:
337
  st.error("Bot identity not provided for sending message.")
338
  return False
339
  try:
340
  twilio_client.conversations.v1 \
 
341
  .conversations(conversation_sid) \
342
  .messages \
343
  .create(author=bot_identity_val, body=message_body)
 
347
  st.error(f"Error sending Twilio message to {conversation_sid}: {e}")
348
  return False
349
 
350
+ # --- Simple Intent Classifier ---
351
+ def simple_intent_classifier(query):
352
+ query_lower = query.lower()
353
+ order_keywords = ["order", "status", "track", "delivery"]
354
+ order_id_match = re.search(r'\b(ord\d{3,})\b', query_lower, re.IGNORECASE)
355
+
356
+ if any(k in query_lower for k in order_keywords):
357
+ if order_id_match:
358
+ return "ORDER_STATUS", order_id_match.group(1).upper()
359
+ return "ORDER_STATUS", None
360
+
361
+ product_keywords = ["product", "item", "buy", "price", "feature", "stock"]
362
+ product_id_match = re.search(r'\b(prd\d{3,})\b', query_lower, re.IGNORECASE)
363
+ if any(k in query_lower for k in product_keywords) or product_id_match:
364
+ return "PRODUCT_INFO", None
365
+
366
+ if any(k in query_lower for k in ["return", "policy", "refund", "exchange", "faq", "question", "how to", "support"]):
367
+ return "GENERAL_POLICY_FAQ", None
368
+
369
+ return "UNKNOWN", None
370
+
371
  # --- Main Application Logic & UI ---
372
  st.title("🤖 RAG-Based Customer Support Chatbot")
373
  st.markdown("Powered by Streamlit, Twilio, GROQ LLaMA3, and FAISS.")
 
396
  st.sidebar.warning("Secret 'GROQ_API_KEY' not found.")
397
  groq_api_key_to_use = st.sidebar.text_input("GROQ API Key (Enter Manually)", value=DEFAULT_GROQ_API_KEY_FALLBACK, type="password")
398
 
 
 
 
 
 
 
399
  twilio_bot_whatsapp_identity_to_use = st.sidebar.text_input(
400
  "Twilio Bot WhatsApp Identity",
401
  value=APP_TWILIO_BOT_WHATSAPP_IDENTITY_SECRET or DEFAULT_TWILIO_BOT_WHATSAPP_IDENTITY,
402
+ help="e.g., 'whatsapp:+1234567890'"
403
  )
404
  embedding_model_name_to_use = st.sidebar.text_input(
405
  "Embedding Model Name",
 
421
  if "processed_message_sids" not in st.session_state: st.session_state.processed_message_sids = set()
422
  if "manual_chat_history" not in st.session_state: st.session_state.manual_chat_history = []
423
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
  # --- Main Application Controls ---
425
  col1, col2, col3, col4 = st.columns(4)
426
  with col1:
 
481
  st.rerun()
482
  with col3:
483
  if st.button("💬 Start WhatsApp Bot", disabled=not st.session_state.app_started or st.session_state.bot_started, use_container_width=True):
484
+ if not all([twilio_account_sid_to_use, twilio_auth_token_to_use, twilio_bot_whatsapp_identity_to_use]):
485
+ st.error("Twilio Account SID, Auth Token, and Bot WhatsApp Identity are all required.")
486
  else:
487
  st.session_state.twilio_client = initialize_twilio_client(twilio_account_sid_to_use, twilio_auth_token_to_use)
488
  if st.session_state.twilio_client:
 
535
  extracted_customer_name, extracted_item_name, extracted_shipping_address, \
536
  extracted_delivery_date, extracted_order_id, extracted_order_status = [None] * 6
537
 
 
538
  if intent == "ORDER_STATUS":
539
  order_id_to_check = None
540
  if potential_oid_from_intent:
 
546
 
547
  if order_id_to_check:
548
  raw_context_data = get_order_details(order_id_to_check, st.session_state.customer_orders_data)
 
 
 
549
  context_for_llm = raw_context_data
550
 
551
  if isinstance(raw_context_data, str) and not raw_context_data.startswith("No order found") and not raw_context_data.startswith("Customer order data is not loaded"):
 
556
  if items and len(items) > 0 and isinstance(items[0], dict):
557
  extracted_item_name = items[0].get("name", "your item(s)")
558
  else:
559
+ extracted_item_name = "your item(s)"
560
  extracted_shipping_address = order_data_dict.get("shipping_address")
561
  extracted_delivery_date = order_data_dict.get("delivered_on")
562
  extracted_order_status = order_data_dict.get("status")
563
+ extracted_order_id = order_data_dict.get("order_id")
564
  except json.JSONDecodeError:
565
  st.warning(f"Could not parse order details JSON for {order_id_to_check} for personalization.")
566
  context_for_llm = f"Error parsing order details for {order_id_to_check}. Raw data: {raw_context_data}"
567
+ elif isinstance(raw_context_data, str):
568
+ context_for_llm = raw_context_data
569
  else:
570
  context_for_llm = "To check an order status, please provide a valid Order ID (e.g., ORD123)."
571
  raw_context_data = {"message": "Order ID needed or not found in query."}
572
 
573
  elif intent == "PRODUCT_INFO":
574
  raw_context_data = get_product_info(user_query_manual, st.session_state.products_data)
575
+ context_for_llm = raw_context_data
576
 
577
  elif intent == "GENERAL_POLICY_FAQ" or intent == "UNKNOWN":
578
  if st.session_state.faiss_index_pdfs and st.session_state.embedding_model and st.session_state.indexed_pdf_chunks:
 
598
  item_name=extracted_item_name,
599
  shipping_address=extracted_shipping_address,
600
  delivery_date=extracted_delivery_date,
601
+ order_id=extracted_order_id,
602
  order_status=extracted_order_status
603
  )
604
 
 
617
  except (json.JSONDecodeError, TypeError):
618
  st.text(str(raw_context_data))
619
  st.session_state.manual_chat_history.append({"role": "assistant", "content": llm_response, "context": raw_context_data})
620
+ st.rerun()
621
 
622
  # --- Twilio Bot Polling Logic ---
623
  if st.session_state.get("bot_started") and st.session_state.get("rag_pipeline_ready"):
 
628
  if (current_time - st.session_state.last_twilio_poll_time) > polling_interval_to_use:
629
  st.session_state.last_twilio_poll_time = current_time
630
 
631
+ if not st.session_state.get("twilio_client") or not twilio_bot_whatsapp_identity_to_use or not st.session_state.get("bot_start_time_utc"):
632
+ st.warning("Twilio client/config missing for polling. Ensure bot is started and WhatsApp identity is set.")
 
 
 
633
  else:
634
  with st.spinner(f"Checking WhatsApp messages (last poll: {datetime.fromtimestamp(st.session_state.last_twilio_poll_time).strftime('%H:%M:%S')})..."):
635
+ new_messages = get_new_whatsapp_messages(
636
+ st.session_state.twilio_client,
637
+ st.session_state.bot_start_time_utc,
638
+ st.session_state.processed_message_sids,
639
+ twilio_bot_whatsapp_identity_to_use
640
+ )
641
+
642
  if new_messages:
643
  st.info(f"Found {len(new_messages)} new WhatsApp message(s) to process.")
644
  for msg_data in new_messages:
 
658
  wa_customer_name, wa_item_name, wa_shipping_address, \
659
  wa_delivery_date, wa_order_id, wa_order_status = [None] * 6
660
 
 
661
  if intent_whatsapp == "ORDER_STATUS":
662
  order_id_to_check_whatsapp = None
663
  if potential_oid_whatsapp:
 
669
 
670
  if order_id_to_check_whatsapp:
671
  raw_context_data_whatsapp = get_order_details(order_id_to_check_whatsapp, st.session_state.customer_orders_data)
672
+ context_for_llm_whatsapp = raw_context_data_whatsapp
673
 
674
  if isinstance(raw_context_data_whatsapp, str) and not raw_context_data_whatsapp.startswith("No order found") and not raw_context_data_whatsapp.startswith("Customer order data is not loaded"):
675
  try:
 
687
  except json.JSONDecodeError:
688
  st.warning(f"Could not parse order details JSON for {order_id_to_check_whatsapp} (WhatsApp) for personalization.")
689
  context_for_llm_whatsapp = f"Error parsing order details for {order_id_to_check_whatsapp}. Raw data: {raw_context_data_whatsapp}"
690
+ elif isinstance(raw_context_data_whats