Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -20,7 +20,7 @@ DEFAULT_TWILIO_AUTH_TOKEN_FALLBACK = "" # Fallback if secret "TWILIO_TOKEN" is n
|
|
20 |
DEFAULT_GROQ_API_KEY_FALLBACK = "" # Fallback if secret "GROQ_API_KEY" is not found
|
21 |
|
22 |
DEFAULT_TWILIO_CONVERSATION_SERVICE_SID = ""
|
23 |
-
DEFAULT_TWILIO_BOT_WHATSAPP_IDENTITY = st.secrets.get("TWILIO_PHONE_NUMBER"
|
24 |
DEFAULT_EMBEDDING_MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
|
25 |
DEFAULT_POLLING_INTERVAL_S = 30
|
26 |
DOCS_FOLDER = "docs/"
|
@@ -66,7 +66,7 @@ def load_pdf_data(file_path):
|
|
66 |
for page_num in range(len(reader.pages)):
|
67 |
page = reader.pages[page_num]
|
68 |
text_pages.append(page.extract_text() or "")
|
69 |
-
|
70 |
except FileNotFoundError:
|
71 |
st.error(f"Error: PDF file not found at {file_path}")
|
72 |
return []
|
@@ -87,7 +87,7 @@ def chunk_text(text_pages, chunk_size=1000, chunk_overlap=200):
|
|
87 |
if end >= len(full_text):
|
88 |
break
|
89 |
start += (chunk_size - chunk_overlap)
|
90 |
-
if start >= len(full_text):
|
91 |
break
|
92 |
return [chunk for chunk in chunks if chunk.strip()]
|
93 |
|
@@ -113,7 +113,7 @@ def create_faiss_index(_text_chunks, _embedding_model):
|
|
113 |
st.warning("No valid text chunks to embed for FAISS index.")
|
114 |
return None, []
|
115 |
embeddings = _embedding_model.encode(valid_chunks, convert_to_tensor=False)
|
116 |
-
if embeddings.ndim == 1:
|
117 |
embeddings = embeddings.reshape(1, -1)
|
118 |
if embeddings.shape[0] == 0:
|
119 |
st.warning("No embeddings were generated for FAISS index.")
|
@@ -132,8 +132,8 @@ def search_faiss_index(index, query_text, embedding_model, indexed_chunks, k=3):
|
|
132 |
return []
|
133 |
try:
|
134 |
query_embedding = embedding_model.encode([query_text], convert_to_tensor=False)
|
135 |
-
if query_embedding.ndim == 1:
|
136 |
-
|
137 |
distances, indices = index.search(np.array(query_embedding, dtype=np.float32), k)
|
138 |
results = []
|
139 |
for i in range(len(indices[0])):
|
@@ -150,37 +150,66 @@ def get_order_details(order_id, customer_orders_data):
|
|
150 |
if not customer_orders_data:
|
151 |
return "Customer order data is not loaded."
|
152 |
for order in customer_orders_data:
|
153 |
-
if order.get("order_id") == order_id:
|
154 |
return json.dumps(order, indent=2)
|
155 |
return f"No order found with ID: {order_id}."
|
156 |
|
157 |
def get_product_info(query, products_data):
|
158 |
-
"""Retrieves product information based on a query.
|
|
|
|
|
159 |
if not products_data:
|
|
|
160 |
return "Product data is not loaded."
|
|
|
161 |
query_lower = query.lower()
|
162 |
found_products = []
|
|
|
163 |
for product in products_data:
|
164 |
-
#
|
165 |
-
|
166 |
-
|
167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
168 |
found_products.append(product)
|
|
|
169 |
if found_products:
|
170 |
return json.dumps(found_products, indent=2)
|
171 |
return f"No product information found matching your query: '{query}'."
|
172 |
|
173 |
# --- LLM Operations ---
|
174 |
-
@st.cache_data(show_spinner="Generating response with LLaMA3...")
|
175 |
def generate_response_groq(_groq_client, query, context, model="llama3-8b-8192"):
|
176 |
"""Generates a response using GROQ LLaMA3 API."""
|
177 |
if not _groq_client:
|
178 |
return "GROQ client not initialized. Please check API key."
|
179 |
if not query:
|
180 |
return "Query is empty."
|
|
|
|
|
181 |
prompt = f"""You are a helpful customer support assistant.
|
182 |
Use the following context to answer the user's question.
|
183 |
-
If the context doesn't contain the answer, state that you don't have enough information.
|
184 |
Do not make up information. Be concise and polite.
|
185 |
|
186 |
Context:
|
@@ -206,7 +235,7 @@ Assistant Answer:
|
|
206 |
|
207 |
def initialize_groq_client(api_key_val):
|
208 |
"""Initializes the GROQ client."""
|
209 |
-
if not api_key_val:
|
210 |
st.warning("GROQ API Key is missing.")
|
211 |
return None
|
212 |
try:
|
@@ -217,7 +246,7 @@ def initialize_groq_client(api_key_val):
|
|
217 |
return None
|
218 |
|
219 |
# --- Twilio Operations ---
|
220 |
-
def initialize_twilio_client(acc_sid, auth_tkn):
|
221 |
"""Initializes the Twilio client."""
|
222 |
if not acc_sid or not auth_tkn:
|
223 |
st.warning("Twilio Account SID or Auth Token is missing.")
|
@@ -229,8 +258,8 @@ def initialize_twilio_client(acc_sid, auth_tkn): # Changed parameter names
|
|
229 |
st.error(f"Failed to initialize Twilio client: {e}")
|
230 |
return None
|
231 |
|
232 |
-
def get_new_whatsapp_messages(twilio_client, conversation_service_sid_val, bot_start_time_utc,
|
233 |
-
|
234 |
"""Fetches new, unanswered WhatsApp messages from Twilio Conversations."""
|
235 |
if not twilio_client:
|
236 |
st.warning("Twilio client not initialized.")
|
@@ -238,25 +267,34 @@ def get_new_whatsapp_messages(twilio_client, conversation_service_sid_val, bot_s
|
|
238 |
if not conversation_service_sid_val:
|
239 |
st.warning("Twilio Conversation Service SID not provided.")
|
240 |
return []
|
|
|
|
|
|
|
|
|
241 |
|
242 |
new_messages_to_process = []
|
243 |
try:
|
|
|
244 |
conversations = twilio_client.conversations.v1 \
|
245 |
.services(conversation_service_sid_val) \
|
246 |
.conversations \
|
247 |
-
.list(limit=50)
|
248 |
|
249 |
for conv in conversations:
|
|
|
250 |
if conv.date_updated and conv.date_updated > bot_start_time_utc:
|
|
|
251 |
messages = twilio_client.conversations.v1 \
|
252 |
.services(conversation_service_sid_val) \
|
253 |
.conversations(conv.sid) \
|
254 |
.messages \
|
255 |
-
.list(order='desc', limit=10)
|
256 |
|
257 |
for msg in messages:
|
258 |
if msg.sid in processed_message_sids:
|
259 |
-
continue
|
|
|
|
|
260 |
if msg.author and msg.author.lower() != bot_whatsapp_identity_val.lower() and \
|
261 |
msg.date_created and msg.date_created > bot_start_time_utc:
|
262 |
new_messages_to_process.append({
|
@@ -264,12 +302,15 @@ def get_new_whatsapp_messages(twilio_client, conversation_service_sid_val, bot_s
|
|
264 |
"author_identity": msg.author, "message_body": msg.body,
|
265 |
"timestamp_utc": msg.date_created
|
266 |
})
|
267 |
-
|
|
|
|
|
|
|
268 |
except Exception as e:
|
269 |
st.error(f"Error fetching Twilio messages: {e}")
|
270 |
-
return sorted(new_messages_to_process, key=lambda m: m['timestamp_utc'])
|
271 |
|
272 |
-
def send_whatsapp_message(twilio_client, conversation_service_sid_val, conversation_sid, message_body, bot_identity_val):
|
273 |
"""Sends a message to a Twilio Conversation from the bot's identity."""
|
274 |
if not twilio_client:
|
275 |
st.error("Twilio client not initialized for sending message.")
|
@@ -304,14 +345,14 @@ if APP_TWILIO_ACCOUNT_SID:
|
|
304 |
st.sidebar.text_input("Twilio Account SID (from Secrets)", value="********" + APP_TWILIO_ACCOUNT_SID[-4:] if len(APP_TWILIO_ACCOUNT_SID) > 4 else "********", disabled=True)
|
305 |
twilio_account_sid_to_use = APP_TWILIO_ACCOUNT_SID
|
306 |
else:
|
307 |
-
st.sidebar.warning("Secret '
|
308 |
twilio_account_sid_to_use = st.sidebar.text_input("Twilio Account SID (Enter Manually)", value=DEFAULT_TWILIO_ACCOUNT_SID_FALLBACK, type="password")
|
309 |
|
310 |
if APP_TWILIO_AUTH_TOKEN:
|
311 |
st.sidebar.text_input("Twilio Auth Token (from Secrets)", value="********", disabled=True)
|
312 |
twilio_auth_token_to_use = APP_TWILIO_AUTH_TOKEN
|
313 |
else:
|
314 |
-
st.sidebar.warning("Secret '
|
315 |
twilio_auth_token_to_use = st.sidebar.text_input("Twilio Auth Token (Enter Manually)", value=DEFAULT_TWILIO_AUTH_TOKEN_FALLBACK, type="password")
|
316 |
|
317 |
if APP_GROQ_API_KEY:
|
@@ -325,7 +366,7 @@ else:
|
|
325 |
twilio_conversation_service_sid_to_use = st.sidebar.text_input(
|
326 |
"Twilio Conversation Service SID (IS...)",
|
327 |
value=APP_TWILIO_CONVERSATION_SERVICE_SID_SECRET or DEFAULT_TWILIO_CONVERSATION_SERVICE_SID,
|
328 |
-
type="password",
|
329 |
help="The SID of your Twilio Conversations Service. Can be set by 'TWILIO_CONVERSATION_SERVICE_SID' secret."
|
330 |
)
|
331 |
twilio_bot_whatsapp_identity_to_use = st.sidebar.text_input(
|
@@ -333,11 +374,11 @@ twilio_bot_whatsapp_identity_to_use = st.sidebar.text_input(
|
|
333 |
value=APP_TWILIO_BOT_WHATSAPP_IDENTITY_SECRET or DEFAULT_TWILIO_BOT_WHATSAPP_IDENTITY,
|
334 |
help="e.g., 'whatsapp:+1234567890'. Can be set by 'TWILIO_BOT_WHATSAPP_IDENTITY' secret."
|
335 |
)
|
336 |
-
embedding_model_name_to_use = st.sidebar.text_input(
|
337 |
"Embedding Model Name",
|
338 |
value=DEFAULT_EMBEDDING_MODEL_NAME
|
339 |
)
|
340 |
-
polling_interval_to_use = st.sidebar.number_input(
|
341 |
"Twilio Polling Interval (seconds)",
|
342 |
min_value=10, max_value=300,
|
343 |
value=DEFAULT_POLLING_INTERVAL_S,
|
@@ -356,27 +397,40 @@ if "manual_chat_history" not in st.session_state: st.session_state.manual_chat_h
|
|
356 |
# --- Helper: Simple Intent Classifier ---
|
357 |
def simple_intent_classifier(query):
|
358 |
query_lower = query.lower()
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
370 |
|
371 |
# --- Main Application Controls ---
|
372 |
col1, col2, col3, col4 = st.columns(4)
|
373 |
with col1:
|
374 |
if st.button("🚀 Start App", disabled=st.session_state.app_started, use_container_width=True):
|
375 |
-
if not groq_api_key_to_use:
|
376 |
st.error("GROQ API Key is required.")
|
377 |
else:
|
378 |
with st.spinner("Initializing RAG pipeline..."):
|
379 |
-
st.session_state.embedding_model = initialize_embedding_model(embedding_model_name_to_use)
|
380 |
st.session_state.customer_orders_data = load_json_data(CUSTOMER_ORDERS_FILE)
|
381 |
st.session_state.products_data = load_json_data(PRODUCTS_FILE)
|
382 |
policy_pdf_pages = load_pdf_data(POLICY_PDF_FILE)
|
@@ -389,18 +443,28 @@ with col1:
|
|
389 |
create_faiss_index(st.session_state.pdf_text_chunks_raw, st.session_state.embedding_model)
|
390 |
else:
|
391 |
st.session_state.faiss_index_pdfs, st.session_state.indexed_pdf_chunks = None, []
|
392 |
-
st.warning("FAISS index for PDFs could not be created.")
|
393 |
|
394 |
-
st.session_state.groq_client = initialize_groq_client(groq_api_key_to_use)
|
395 |
-
|
396 |
-
|
397 |
-
|
|
|
|
|
|
|
|
|
398 |
st.session_state.rag_pipeline_ready = True
|
399 |
st.session_state.app_started = True
|
400 |
st.success("RAG Application Started!")
|
401 |
st.rerun()
|
402 |
else:
|
403 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
404 |
st.session_state.app_started = False
|
405 |
with col2:
|
406 |
if st.button("🛑 Stop App", disabled=not st.session_state.app_started, use_container_width=True):
|
@@ -410,52 +474,61 @@ with col2:
|
|
410 |
"bot_start_time_utc", "processed_message_sids", "manual_chat_history"]
|
411 |
for key in keys_to_reset:
|
412 |
if key in st.session_state: del st.session_state[key]
|
|
|
413 |
st.session_state.app_started = False
|
414 |
st.session_state.bot_started = False
|
415 |
st.session_state.rag_pipeline_ready = False
|
416 |
st.session_state.processed_message_sids = set()
|
417 |
st.session_state.manual_chat_history = []
|
418 |
st.success("Application Stopped.")
|
|
|
|
|
|
|
|
|
419 |
st.rerun()
|
420 |
with col3:
|
421 |
if st.button("💬 Start WhatsApp Bot", disabled=not st.session_state.app_started or st.session_state.bot_started, use_container_width=True):
|
422 |
-
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]):
|
423 |
-
st.error("Twilio
|
424 |
else:
|
425 |
-
st.session_state.twilio_client = initialize_twilio_client(twilio_account_sid_to_use, twilio_auth_token_to_use)
|
426 |
if st.session_state.twilio_client:
|
427 |
st.session_state.bot_started = True
|
428 |
st.session_state.bot_start_time_utc = datetime.now(timezone.utc)
|
429 |
-
st.session_state.processed_message_sids = set()
|
430 |
-
st.session_state.last_twilio_poll_time = time.time() - polling_interval_to_use -1 #
|
431 |
st.success("WhatsApp Bot Started!")
|
432 |
st.rerun()
|
433 |
else:
|
434 |
-
st.error("Failed to initialize Twilio client.")
|
435 |
with col4:
|
436 |
if st.button("🔕 Stop WhatsApp Bot", disabled=not st.session_state.bot_started, use_container_width=True):
|
437 |
st.session_state.bot_started = False
|
438 |
st.info("WhatsApp Bot Stopped.")
|
|
|
|
|
439 |
st.rerun()
|
440 |
st.divider()
|
441 |
|
442 |
# --- Manual Query Interface ---
|
443 |
if st.session_state.get("app_started") and st.session_state.get("rag_pipeline_ready"):
|
444 |
st.subheader("💬 Manual Query")
|
|
|
445 |
for chat_entry in st.session_state.manual_chat_history:
|
446 |
with st.chat_message(chat_entry["role"]):
|
447 |
st.markdown(chat_entry["content"])
|
448 |
-
if "context" in chat_entry and chat_entry["context"]:
|
449 |
with st.expander("Retrieved Context"):
|
450 |
try:
|
451 |
# Attempt to parse as JSON only if it looks like a JSON string
|
452 |
-
if isinstance(chat_entry["context"], str) and
|
|
|
453 |
st.json(json.loads(chat_entry["context"]))
|
454 |
-
|
455 |
-
|
|
|
456 |
st.text(str(chat_entry["context"]))
|
457 |
-
except (json.JSONDecodeError, TypeError):
|
458 |
-
# Fallback for any other parsing errors
|
459 |
st.text(str(chat_entry["context"]))
|
460 |
|
461 |
user_query_manual = st.chat_input("Ask a question:")
|
@@ -464,121 +537,159 @@ if st.session_state.get("app_started") and st.session_state.get("rag_pipeline_re
|
|
464 |
with st.chat_message("user"): st.markdown(user_query_manual)
|
465 |
|
466 |
with st.spinner("Thinking..."):
|
467 |
-
intent_result = simple_intent_classifier(user_query_manual)
|
468 |
intent = intent_result[0]
|
469 |
-
potential_oid_from_intent = intent_result[1] # This is the extracted ID if any
|
470 |
|
471 |
-
context_for_llm, raw_context_data = "No specific context.", None
|
472 |
|
473 |
if intent == "ORDER_STATUS":
|
474 |
order_id_to_check = None
|
475 |
-
if potential_oid_from_intent:
|
476 |
order_id_to_check = potential_oid_from_intent
|
477 |
-
else:
|
478 |
-
|
479 |
-
|
480 |
-
|
481 |
-
|
482 |
-
if possible_match:
|
483 |
-
order_id_to_check = possible_match
|
484 |
-
|
485 |
-
|
486 |
if order_id_to_check:
|
487 |
-
raw_context_data = get_order_details(order_id_to_check
|
488 |
-
context_for_llm = f"Order Details: {raw_context_data}"
|
489 |
else:
|
490 |
-
context_for_llm = "
|
491 |
-
raw_context_data = {"message": "Order ID needed."}
|
|
|
492 |
elif intent == "PRODUCT_INFO":
|
493 |
raw_context_data = get_product_info(user_query_manual, st.session_state.products_data)
|
494 |
-
context_for_llm = f"Product Information: {raw_context_data}"
|
495 |
-
|
496 |
-
|
497 |
-
if st.session_state.faiss_index_pdfs and st.session_state.embedding_model:
|
498 |
-
k_val =
|
499 |
retrieved_chunks = search_faiss_index(st.session_state.faiss_index_pdfs, user_query_manual,
|
500 |
st.session_state.embedding_model, st.session_state.indexed_pdf_chunks, k=k_val)
|
501 |
if retrieved_chunks:
|
502 |
-
context_for_llm = "\n\n".join(retrieved_chunks)
|
503 |
-
raw_context_data = retrieved_chunks
|
504 |
else:
|
505 |
-
context_for_llm = "
|
506 |
raw_context_data = {"message": "No relevant PDF chunks found."}
|
507 |
else:
|
508 |
-
context_for_llm = "
|
509 |
-
raw_context_data = {"message": "PDF index not ready."}
|
510 |
|
511 |
llm_response = generate_response_groq(st.session_state.groq_client, user_query_manual, context_for_llm)
|
|
|
512 |
with st.chat_message("assistant"):
|
513 |
st.markdown(llm_response)
|
514 |
-
if raw_context_data:
|
515 |
-
with st.expander("Retrieved Context"):
|
516 |
try:
|
517 |
-
if isinstance(raw_context_data, str) and
|
|
|
518 |
st.json(json.loads(raw_context_data))
|
|
|
|
|
519 |
else:
|
520 |
st.text(str(raw_context_data))
|
521 |
except (json.JSONDecodeError, TypeError):
|
522 |
st.text(str(raw_context_data))
|
523 |
st.session_state.manual_chat_history.append({"role": "assistant", "content": llm_response, "context": raw_context_data})
|
|
|
524 |
|
525 |
# --- Twilio Bot Polling Logic ---
|
526 |
if st.session_state.get("bot_started") and st.session_state.get("rag_pipeline_ready"):
|
527 |
current_time = time.time()
|
528 |
-
|
|
|
|
|
|
|
|
|
529 |
st.session_state.last_twilio_poll_time = current_time
|
530 |
-
|
531 |
-
|
532 |
-
|
533 |
-
|
534 |
-
|
535 |
-
|
536 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
537 |
if new_messages:
|
538 |
-
st.info(f"Found {len(new_messages)} new WhatsApp message(s).")
|
539 |
for msg_data in new_messages:
|
540 |
-
user_query_whatsapp, conv_sid, msg_sid, author_id =
|
541 |
-
|
|
|
|
|
|
|
542 |
|
543 |
-
|
|
|
544 |
intent_whatsapp = intent_result_whatsapp[0]
|
545 |
-
potential_oid_whatsapp = intent_result_whatsapp[1]
|
546 |
|
547 |
-
|
|
|
548 |
if intent_whatsapp == "ORDER_STATUS":
|
549 |
order_id_to_check_whatsapp = None
|
550 |
if potential_oid_whatsapp:
|
551 |
order_id_to_check_whatsapp = potential_oid_whatsapp
|
552 |
else:
|
553 |
-
|
554 |
-
|
555 |
-
|
556 |
-
order_id_to_check_whatsapp = possible_match_whatsapp
|
557 |
|
558 |
if order_id_to_check_whatsapp:
|
559 |
-
|
|
|
560 |
else:
|
561 |
-
|
|
|
562 |
elif intent_whatsapp == "PRODUCT_INFO":
|
563 |
-
|
|
|
|
|
564 |
elif intent_whatsapp == "GENERAL_POLICY_FAQ" or intent_whatsapp == "UNKNOWN":
|
565 |
-
if st.session_state.faiss_index_pdfs and st.session_state.embedding_model:
|
566 |
-
|
567 |
-
|
568 |
-
|
569 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
570 |
|
571 |
-
|
572 |
-
|
573 |
st.session_state.processed_message_sids.add(msg_sid)
|
574 |
-
st.success(f"
|
575 |
-
else:
|
576 |
-
|
|
|
|
|
|
|
|
|
577 |
|
578 |
# --- Footer & Status ---
|
579 |
st.sidebar.markdown("---")
|
580 |
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.")
|
581 |
if st.session_state.get("app_started"):
|
582 |
-
|
|
|
|
|
|
|
|
|
583 |
else:
|
584 |
-
st.sidebar.warning("App is STOPPED.")
|
|
|
|
20 |
DEFAULT_GROQ_API_KEY_FALLBACK = "" # Fallback if secret "GROQ_API_KEY" is not found
|
21 |
|
22 |
DEFAULT_TWILIO_CONVERSATION_SERVICE_SID = ""
|
23 |
+
DEFAULT_TWILIO_BOT_WHATSAPP_IDENTITY = st.secrets.get("TWILIO_PHONE_NUMBER", "whatsapp:+14155238886") # Twilio Sandbox default
|
24 |
DEFAULT_EMBEDDING_MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
|
25 |
DEFAULT_POLLING_INTERVAL_S = 30
|
26 |
DOCS_FOLDER = "docs/"
|
|
|
66 |
for page_num in range(len(reader.pages)):
|
67 |
page = reader.pages[page_num]
|
68 |
text_pages.append(page.extract_text() or "")
|
69 |
+
return text_pages
|
70 |
except FileNotFoundError:
|
71 |
st.error(f"Error: PDF file not found at {file_path}")
|
72 |
return []
|
|
|
87 |
if end >= len(full_text):
|
88 |
break
|
89 |
start += (chunk_size - chunk_overlap)
|
90 |
+
if start >= len(full_text): # Should be `start > len(full_text) - chunk_overlap` or similar to avoid empty last chunk
|
91 |
break
|
92 |
return [chunk for chunk in chunks if chunk.strip()]
|
93 |
|
|
|
113 |
st.warning("No valid text chunks to embed for FAISS index.")
|
114 |
return None, []
|
115 |
embeddings = _embedding_model.encode(valid_chunks, convert_to_tensor=False)
|
116 |
+
if embeddings.ndim == 1: # Handle single chunk case
|
117 |
embeddings = embeddings.reshape(1, -1)
|
118 |
if embeddings.shape[0] == 0:
|
119 |
st.warning("No embeddings were generated for FAISS index.")
|
|
|
132 |
return []
|
133 |
try:
|
134 |
query_embedding = embedding_model.encode([query_text], convert_to_tensor=False)
|
135 |
+
if query_embedding.ndim == 1: # Handle single query embedding
|
136 |
+
query_embedding = query_embedding.reshape(1, -1)
|
137 |
distances, indices = index.search(np.array(query_embedding, dtype=np.float32), k)
|
138 |
results = []
|
139 |
for i in range(len(indices[0])):
|
|
|
150 |
if not customer_orders_data:
|
151 |
return "Customer order data is not loaded."
|
152 |
for order in customer_orders_data:
|
153 |
+
if order.get("order_id") == order_id: # Assuming order_id is stored as uppercase in JSON or matches case
|
154 |
return json.dumps(order, indent=2)
|
155 |
return f"No order found with ID: {order_id}."
|
156 |
|
157 |
def get_product_info(query, products_data):
|
158 |
+
"""Retrieves product information based on a query.
|
159 |
+
This function is updated to correctly find products by ID, name, or type within the query.
|
160 |
+
"""
|
161 |
if not products_data:
|
162 |
+
st.warning("Product data is not loaded or is empty in get_product_info.")
|
163 |
return "Product data is not loaded."
|
164 |
+
|
165 |
query_lower = query.lower()
|
166 |
found_products = []
|
167 |
+
|
168 |
for product in products_data:
|
169 |
+
if not isinstance(product, dict): # Skip if product entry is not a dictionary
|
170 |
+
continue
|
171 |
+
|
172 |
+
product_id_lower = str(product.get("Product_ID", "")).lower()
|
173 |
+
product_name_lower = str(product.get("Product_Name", "")).lower()
|
174 |
+
product_type_lower = str(product.get("Product_Type", "")).lower()
|
175 |
+
|
176 |
+
match = False
|
177 |
+
# 1. Check if the Product ID is mentioned in the query
|
178 |
+
if product_id_lower and product_id_lower in query_lower:
|
179 |
+
match = True
|
180 |
+
|
181 |
+
# 2. If no match by ID, check for Product Name
|
182 |
+
# - If the query (e.g., "rattle") is a substring of the product name (e.g., "soft rattle set")
|
183 |
+
# - OR if the product name (e.g., "soft rattle set") is a substring of the query (e.g., "info on soft rattle set")
|
184 |
+
if not match and product_name_lower:
|
185 |
+
if query_lower in product_name_lower or product_name_lower in query_lower:
|
186 |
+
match = True
|
187 |
+
|
188 |
+
# 3. If no match yet, check for Product Type similarly
|
189 |
+
if not match and product_type_lower:
|
190 |
+
if query_lower in product_type_lower or product_type_lower in query_lower:
|
191 |
+
match = True
|
192 |
+
|
193 |
+
if match:
|
194 |
found_products.append(product)
|
195 |
+
|
196 |
if found_products:
|
197 |
return json.dumps(found_products, indent=2)
|
198 |
return f"No product information found matching your query: '{query}'."
|
199 |
|
200 |
# --- LLM Operations ---
|
201 |
+
@st.cache_data(show_spinner="Generating response with LLaMA3...") # Consider disabling caching if context changes frequently or add more granular cache invalidation
|
202 |
def generate_response_groq(_groq_client, query, context, model="llama3-8b-8192"):
|
203 |
"""Generates a response using GROQ LLaMA3 API."""
|
204 |
if not _groq_client:
|
205 |
return "GROQ client not initialized. Please check API key."
|
206 |
if not query:
|
207 |
return "Query is empty."
|
208 |
+
|
209 |
+
# Basic prompt, can be enhanced
|
210 |
prompt = f"""You are a helpful customer support assistant.
|
211 |
Use the following context to answer the user's question.
|
212 |
+
If the context doesn't contain the answer, state that you don't have enough information or ask clarifying questions.
|
213 |
Do not make up information. Be concise and polite.
|
214 |
|
215 |
Context:
|
|
|
235 |
|
236 |
def initialize_groq_client(api_key_val):
|
237 |
"""Initializes the GROQ client."""
|
238 |
+
if not api_key_val:
|
239 |
st.warning("GROQ API Key is missing.")
|
240 |
return None
|
241 |
try:
|
|
|
246 |
return None
|
247 |
|
248 |
# --- Twilio Operations ---
|
249 |
+
def initialize_twilio_client(acc_sid, auth_tkn):
|
250 |
"""Initializes the Twilio client."""
|
251 |
if not acc_sid or not auth_tkn:
|
252 |
st.warning("Twilio Account SID or Auth Token is missing.")
|
|
|
258 |
st.error(f"Failed to initialize Twilio client: {e}")
|
259 |
return None
|
260 |
|
261 |
+
def get_new_whatsapp_messages(twilio_client, conversation_service_sid_val, bot_start_time_utc,
|
262 |
+
processed_message_sids, bot_whatsapp_identity_val):
|
263 |
"""Fetches new, unanswered WhatsApp messages from Twilio Conversations."""
|
264 |
if not twilio_client:
|
265 |
st.warning("Twilio client not initialized.")
|
|
|
267 |
if not conversation_service_sid_val:
|
268 |
st.warning("Twilio Conversation Service SID not provided.")
|
269 |
return []
|
270 |
+
if not bot_whatsapp_identity_val: # Added check
|
271 |
+
st.warning("Twilio Bot WhatsApp Identity not provided.")
|
272 |
+
return []
|
273 |
+
|
274 |
|
275 |
new_messages_to_process = []
|
276 |
try:
|
277 |
+
# Fetch conversations updated since the bot started or a reasonable window
|
278 |
conversations = twilio_client.conversations.v1 \
|
279 |
.services(conversation_service_sid_val) \
|
280 |
.conversations \
|
281 |
+
.list(limit=50) # Consider filtering by date_updated if API supports
|
282 |
|
283 |
for conv in conversations:
|
284 |
+
# Check if conversation was updated after bot start time
|
285 |
if conv.date_updated and conv.date_updated > bot_start_time_utc:
|
286 |
+
# Fetch recent messages from this conversation
|
287 |
messages = twilio_client.conversations.v1 \
|
288 |
.services(conversation_service_sid_val) \
|
289 |
.conversations(conv.sid) \
|
290 |
.messages \
|
291 |
+
.list(order='desc', limit=10) # Get latest messages first
|
292 |
|
293 |
for msg in messages:
|
294 |
if msg.sid in processed_message_sids:
|
295 |
+
continue # Skip already processed messages
|
296 |
+
|
297 |
+
# Check if message is from a user (not the bot) and is new
|
298 |
if msg.author and msg.author.lower() != bot_whatsapp_identity_val.lower() and \
|
299 |
msg.date_created and msg.date_created > bot_start_time_utc:
|
300 |
new_messages_to_process.append({
|
|
|
302 |
"author_identity": msg.author, "message_body": msg.body,
|
303 |
"timestamp_utc": msg.date_created
|
304 |
})
|
305 |
+
# Assuming we only process the latest unread message per conversation polling cycle
|
306 |
+
# If multiple new messages from the same user in one convo need processing, this break might be too early.
|
307 |
+
# For simplicity, processing one latest unread message per conversation poll.
|
308 |
+
break
|
309 |
except Exception as e:
|
310 |
st.error(f"Error fetching Twilio messages: {e}")
|
311 |
+
return sorted(new_messages_to_process, key=lambda m: m['timestamp_utc']) # Process in chronological order
|
312 |
|
313 |
+
def send_whatsapp_message(twilio_client, conversation_service_sid_val, conversation_sid, message_body, bot_identity_val):
|
314 |
"""Sends a message to a Twilio Conversation from the bot's identity."""
|
315 |
if not twilio_client:
|
316 |
st.error("Twilio client not initialized for sending message.")
|
|
|
345 |
st.sidebar.text_input("Twilio Account SID (from Secrets)", value="********" + APP_TWILIO_ACCOUNT_SID[-4:] if len(APP_TWILIO_ACCOUNT_SID) > 4 else "********", disabled=True)
|
346 |
twilio_account_sid_to_use = APP_TWILIO_ACCOUNT_SID
|
347 |
else:
|
348 |
+
st.sidebar.warning("Secret 'TWILIO_ACCOUNT_SID' not found.") # Corrected secret name from TWILIO_SID
|
349 |
twilio_account_sid_to_use = st.sidebar.text_input("Twilio Account SID (Enter Manually)", value=DEFAULT_TWILIO_ACCOUNT_SID_FALLBACK, type="password")
|
350 |
|
351 |
if APP_TWILIO_AUTH_TOKEN:
|
352 |
st.sidebar.text_input("Twilio Auth Token (from Secrets)", value="********", disabled=True)
|
353 |
twilio_auth_token_to_use = APP_TWILIO_AUTH_TOKEN
|
354 |
else:
|
355 |
+
st.sidebar.warning("Secret 'TWILIO_AUTH_TOKEN' not found.") # Corrected secret name from TWILIO_TOKEN
|
356 |
twilio_auth_token_to_use = st.sidebar.text_input("Twilio Auth Token (Enter Manually)", value=DEFAULT_TWILIO_AUTH_TOKEN_FALLBACK, type="password")
|
357 |
|
358 |
if APP_GROQ_API_KEY:
|
|
|
366 |
twilio_conversation_service_sid_to_use = st.sidebar.text_input(
|
367 |
"Twilio Conversation Service SID (IS...)",
|
368 |
value=APP_TWILIO_CONVERSATION_SERVICE_SID_SECRET or DEFAULT_TWILIO_CONVERSATION_SERVICE_SID,
|
369 |
+
type="password", # Keep as password if sensitive
|
370 |
help="The SID of your Twilio Conversations Service. Can be set by 'TWILIO_CONVERSATION_SERVICE_SID' secret."
|
371 |
)
|
372 |
twilio_bot_whatsapp_identity_to_use = st.sidebar.text_input(
|
|
|
374 |
value=APP_TWILIO_BOT_WHATSAPP_IDENTITY_SECRET or DEFAULT_TWILIO_BOT_WHATSAPP_IDENTITY,
|
375 |
help="e.g., 'whatsapp:+1234567890'. Can be set by 'TWILIO_BOT_WHATSAPP_IDENTITY' secret."
|
376 |
)
|
377 |
+
embedding_model_name_to_use = st.sidebar.text_input(
|
378 |
"Embedding Model Name",
|
379 |
value=DEFAULT_EMBEDDING_MODEL_NAME
|
380 |
)
|
381 |
+
polling_interval_to_use = st.sidebar.number_input(
|
382 |
"Twilio Polling Interval (seconds)",
|
383 |
min_value=10, max_value=300,
|
384 |
value=DEFAULT_POLLING_INTERVAL_S,
|
|
|
397 |
# --- Helper: Simple Intent Classifier ---
|
398 |
def simple_intent_classifier(query):
|
399 |
query_lower = query.lower()
|
400 |
+
# Order status: look for 'order', 'status', 'track', 'delivery' AND an order ID pattern
|
401 |
+
order_keywords = ["order", "status", "track", "delivery"]
|
402 |
+
# Regex for 'ORD' followed by 3 or more digits (case insensitive for 'ord')
|
403 |
+
order_id_match = re.search(r'\b(ord\d{3,})\b', query_lower, re.IGNORECASE)
|
404 |
+
|
405 |
+
if any(k in query_lower for k in order_keywords):
|
406 |
+
if order_id_match:
|
407 |
+
return "ORDER_STATUS", order_id_match.group(1).upper() # Return intent and extracted ID
|
408 |
+
return "ORDER_STATUS", None # Order-related query but no specific ID found yet
|
409 |
+
|
410 |
+
# Product info: look for 'product', 'item', 'buy', 'price', 'feature', 'stock' OR a product ID pattern
|
411 |
+
product_keywords = ["product", "item", "buy", "price", "feature", "stock"]
|
412 |
+
# Regex for 'PRD' followed by 3 or more digits (case insensitive for 'prd')
|
413 |
+
# This is an example, adjust if your product IDs have a different format
|
414 |
+
product_id_match = re.search(r'\b(prd\d{3,})\b', query_lower, re.IGNORECASE)
|
415 |
+
if any(k in query_lower for k in product_keywords) or product_id_match:
|
416 |
+
# If a PRD ID is explicitly found, we can pass it, though get_product_info also searches the query
|
417 |
+
# For simplicity, product_info intent doesn't pass an ID here, get_product_info handles it.
|
418 |
+
return "PRODUCT_INFO", None
|
419 |
+
|
420 |
+
if any(k in query_lower for k in ["return", "policy", "refund", "exchange", "faq", "question", "how to", "support"]):
|
421 |
+
return "GENERAL_POLICY_FAQ", None
|
422 |
+
|
423 |
+
return "UNKNOWN", None
|
424 |
|
425 |
# --- Main Application Controls ---
|
426 |
col1, col2, col3, col4 = st.columns(4)
|
427 |
with col1:
|
428 |
if st.button("🚀 Start App", disabled=st.session_state.app_started, use_container_width=True):
|
429 |
+
if not groq_api_key_to_use:
|
430 |
st.error("GROQ API Key is required.")
|
431 |
else:
|
432 |
with st.spinner("Initializing RAG pipeline..."):
|
433 |
+
st.session_state.embedding_model = initialize_embedding_model(embedding_model_name_to_use)
|
434 |
st.session_state.customer_orders_data = load_json_data(CUSTOMER_ORDERS_FILE)
|
435 |
st.session_state.products_data = load_json_data(PRODUCTS_FILE)
|
436 |
policy_pdf_pages = load_pdf_data(POLICY_PDF_FILE)
|
|
|
443 |
create_faiss_index(st.session_state.pdf_text_chunks_raw, st.session_state.embedding_model)
|
444 |
else:
|
445 |
st.session_state.faiss_index_pdfs, st.session_state.indexed_pdf_chunks = None, []
|
446 |
+
st.warning("FAISS index for PDFs could not be created (model or chunks missing).")
|
447 |
|
448 |
+
st.session_state.groq_client = initialize_groq_client(groq_api_key_to_use)
|
449 |
+
|
450 |
+
# Check all critical components for RAG readiness
|
451 |
+
if st.session_state.embedding_model and \
|
452 |
+
st.session_state.groq_client and \
|
453 |
+
st.session_state.customer_orders_data is not None and \
|
454 |
+
st.session_state.products_data is not None and \
|
455 |
+
(st.session_state.faiss_index_pdfs is not None or not all_pdf_text_pages): # Index needed if PDFs exist
|
456 |
st.session_state.rag_pipeline_ready = True
|
457 |
st.session_state.app_started = True
|
458 |
st.success("RAG Application Started!")
|
459 |
st.rerun()
|
460 |
else:
|
461 |
+
error_messages = []
|
462 |
+
if not st.session_state.embedding_model: error_messages.append("Embedding model failed to initialize.")
|
463 |
+
if not st.session_state.groq_client: error_messages.append("GROQ client failed to initialize.")
|
464 |
+
if st.session_state.customer_orders_data is None: error_messages.append(f"CustomerOrders.json ({CUSTOMER_ORDERS_FILE}) failed to load.")
|
465 |
+
if st.session_state.products_data is None: error_messages.append(f"Products.json ({PRODUCTS_FILE}) failed to load.")
|
466 |
+
if all_pdf_text_pages and st.session_state.faiss_index_pdfs is None: error_messages.append("PDF FAISS index failed to create.")
|
467 |
+
st.error("Failed to initialize RAG pipeline. Issues:\n- " + "\n- ".join(error_messages) + "\nCheck configurations and ensure all data files are present in 'docs/'.")
|
468 |
st.session_state.app_started = False
|
469 |
with col2:
|
470 |
if st.button("🛑 Stop App", disabled=not st.session_state.app_started, use_container_width=True):
|
|
|
474 |
"bot_start_time_utc", "processed_message_sids", "manual_chat_history"]
|
475 |
for key in keys_to_reset:
|
476 |
if key in st.session_state: del st.session_state[key]
|
477 |
+
# Explicitly reset to default states
|
478 |
st.session_state.app_started = False
|
479 |
st.session_state.bot_started = False
|
480 |
st.session_state.rag_pipeline_ready = False
|
481 |
st.session_state.processed_message_sids = set()
|
482 |
st.session_state.manual_chat_history = []
|
483 |
st.success("Application Stopped.")
|
484 |
+
# Clear cached resources if desired (or let Streamlit manage them)
|
485 |
+
# initialize_embedding_model.clear()
|
486 |
+
# create_faiss_index.clear()
|
487 |
+
# generate_response_groq.clear()
|
488 |
st.rerun()
|
489 |
with col3:
|
490 |
if st.button("💬 Start WhatsApp Bot", disabled=not st.session_state.app_started or st.session_state.bot_started, use_container_width=True):
|
491 |
+
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]):
|
492 |
+
st.error("Twilio Account SID, Auth Token, Conversation Service SID, and Bot WhatsApp Identity are all required.")
|
493 |
else:
|
494 |
+
st.session_state.twilio_client = initialize_twilio_client(twilio_account_sid_to_use, twilio_auth_token_to_use)
|
495 |
if st.session_state.twilio_client:
|
496 |
st.session_state.bot_started = True
|
497 |
st.session_state.bot_start_time_utc = datetime.now(timezone.utc)
|
498 |
+
st.session_state.processed_message_sids = set() # Reset processed messages on bot start
|
499 |
+
st.session_state.last_twilio_poll_time = time.time() - polling_interval_to_use - 1 # Ensure immediate first poll
|
500 |
st.success("WhatsApp Bot Started!")
|
501 |
st.rerun()
|
502 |
else:
|
503 |
+
st.error("Failed to initialize Twilio client. WhatsApp Bot not started.")
|
504 |
with col4:
|
505 |
if st.button("🔕 Stop WhatsApp Bot", disabled=not st.session_state.bot_started, use_container_width=True):
|
506 |
st.session_state.bot_started = False
|
507 |
st.info("WhatsApp Bot Stopped.")
|
508 |
+
# Optionally clear twilio_client from session state if desired
|
509 |
+
# if "twilio_client" in st.session_state: del st.session_state.twilio_client
|
510 |
st.rerun()
|
511 |
st.divider()
|
512 |
|
513 |
# --- Manual Query Interface ---
|
514 |
if st.session_state.get("app_started") and st.session_state.get("rag_pipeline_ready"):
|
515 |
st.subheader("💬 Manual Query")
|
516 |
+
# Display chat history
|
517 |
for chat_entry in st.session_state.manual_chat_history:
|
518 |
with st.chat_message(chat_entry["role"]):
|
519 |
st.markdown(chat_entry["content"])
|
520 |
+
if "context" in chat_entry and chat_entry["context"]: # Check if context exists and is not None/empty
|
521 |
with st.expander("Retrieved Context"):
|
522 |
try:
|
523 |
# Attempt to parse as JSON only if it looks like a JSON string
|
524 |
+
if isinstance(chat_entry["context"], str) and \
|
525 |
+
(chat_entry["context"].strip().startswith('{') or chat_entry["context"].strip().startswith('[')):
|
526 |
st.json(json.loads(chat_entry["context"]))
|
527 |
+
elif isinstance(chat_entry["context"], list): # Handle if context is already a list (e.g. PDF chunks)
|
528 |
+
st.json(chat_entry["context"]) # Or st.text for list of strings
|
529 |
+
else: # Otherwise, display as plain text
|
530 |
st.text(str(chat_entry["context"]))
|
531 |
+
except (json.JSONDecodeError, TypeError): # Fallback for any other parsing errors
|
|
|
532 |
st.text(str(chat_entry["context"]))
|
533 |
|
534 |
user_query_manual = st.chat_input("Ask a question:")
|
|
|
537 |
with st.chat_message("user"): st.markdown(user_query_manual)
|
538 |
|
539 |
with st.spinner("Thinking..."):
|
540 |
+
intent_result = simple_intent_classifier(user_query_manual)
|
541 |
intent = intent_result[0]
|
542 |
+
potential_oid_from_intent = intent_result[1] # This is the extracted Order ID if any
|
543 |
|
544 |
+
context_for_llm, raw_context_data = "No specific context could be retrieved.", None # Default
|
545 |
|
546 |
if intent == "ORDER_STATUS":
|
547 |
order_id_to_check = None
|
548 |
+
if potential_oid_from_intent: # ID from intent classifier (preferred)
|
549 |
order_id_to_check = potential_oid_from_intent
|
550 |
+
else: # Fallback: try to find any 'ORDxxx' in the query if intent classifier missed it (less likely with current regex)
|
551 |
+
match_manual = re.search(r'\b(ord\d{3,})\b', user_query_manual.lower(), re.IGNORECASE)
|
552 |
+
if match_manual:
|
553 |
+
order_id_to_check = match_manual.group(1).upper()
|
554 |
+
|
|
|
|
|
|
|
|
|
555 |
if order_id_to_check:
|
556 |
+
raw_context_data = get_order_details(order_id_to_check, st.session_state.customer_orders_data)
|
557 |
+
context_for_llm = f"Order Details for {order_id_to_check}: {raw_context_data}"
|
558 |
else:
|
559 |
+
context_for_llm = "To check an order status, please provide a valid Order ID (e.g., ORD123)."
|
560 |
+
raw_context_data = {"message": "Order ID needed or not found in query."}
|
561 |
+
|
562 |
elif intent == "PRODUCT_INFO":
|
563 |
raw_context_data = get_product_info(user_query_manual, st.session_state.products_data)
|
564 |
+
context_for_llm = f"Product Information related to '{user_query_manual}': {raw_context_data}"
|
565 |
+
|
566 |
+
elif intent == "GENERAL_POLICY_FAQ" or intent == "UNKNOWN": # Consolidate for PDF search
|
567 |
+
if st.session_state.faiss_index_pdfs and st.session_state.embedding_model and st.session_state.indexed_pdf_chunks:
|
568 |
+
k_val = 3 if intent == "GENERAL_POLICY_FAQ" else 2 # Retrieve more for specific FAQ, less for UNKNOWN
|
569 |
retrieved_chunks = search_faiss_index(st.session_state.faiss_index_pdfs, user_query_manual,
|
570 |
st.session_state.embedding_model, st.session_state.indexed_pdf_chunks, k=k_val)
|
571 |
if retrieved_chunks:
|
572 |
+
context_for_llm = "Relevant information from documents:\n\n" + "\n\n---\n\n".join(retrieved_chunks)
|
573 |
+
raw_context_data = retrieved_chunks # Store the list of chunks
|
574 |
else:
|
575 |
+
context_for_llm = "I couldn't find specific information in our policy or FAQ documents regarding your query."
|
576 |
raw_context_data = {"message": "No relevant PDF chunks found."}
|
577 |
else:
|
578 |
+
context_for_llm = "Our policy and FAQ documents are currently unavailable for search."
|
579 |
+
raw_context_data = {"message": "PDF index or embedding model not ready."}
|
580 |
|
581 |
llm_response = generate_response_groq(st.session_state.groq_client, user_query_manual, context_for_llm)
|
582 |
+
|
583 |
with st.chat_message("assistant"):
|
584 |
st.markdown(llm_response)
|
585 |
+
if raw_context_data: # Display context if it was retrieved
|
586 |
+
with st.expander("Retrieved Context For Assistant"): # Changed label for clarity
|
587 |
try:
|
588 |
+
if isinstance(raw_context_data, str) and \
|
589 |
+
(raw_context_data.strip().startswith('{') or raw_context_data.strip().startswith('[')):
|
590 |
st.json(json.loads(raw_context_data))
|
591 |
+
elif isinstance(raw_context_data, list):
|
592 |
+
st.json(raw_context_data) # Display list of strings (chunks) as JSON array
|
593 |
else:
|
594 |
st.text(str(raw_context_data))
|
595 |
except (json.JSONDecodeError, TypeError):
|
596 |
st.text(str(raw_context_data))
|
597 |
st.session_state.manual_chat_history.append({"role": "assistant", "content": llm_response, "context": raw_context_data})
|
598 |
+
st.rerun() # Rerun to update chat display immediately
|
599 |
|
600 |
# --- Twilio Bot Polling Logic ---
|
601 |
if st.session_state.get("bot_started") and st.session_state.get("rag_pipeline_ready"):
|
602 |
current_time = time.time()
|
603 |
+
# Ensure last_twilio_poll_time is initialized
|
604 |
+
if "last_twilio_poll_time" not in st.session_state:
|
605 |
+
st.session_state.last_twilio_poll_time = current_time - polling_interval_to_use - 1
|
606 |
+
|
607 |
+
if (current_time - st.session_state.last_twilio_poll_time) > polling_interval_to_use:
|
608 |
st.session_state.last_twilio_poll_time = current_time
|
609 |
+
|
610 |
+
# Check if Twilio client and necessary configs are available
|
611 |
+
if not st.session_state.get("twilio_client") or \
|
612 |
+
not twilio_conversation_service_sid_to_use or \
|
613 |
+
not twilio_bot_whatsapp_identity_to_use or \
|
614 |
+
not st.session_state.get("bot_start_time_utc"):
|
615 |
+
st.warning("Twilio client/config missing for polling. Ensure bot is started and SIDs are set.")
|
616 |
+
else:
|
617 |
+
with st.spinner(f"Checking WhatsApp messages (last poll: {datetime.fromtimestamp(st.session_state.last_twilio_poll_time).strftime('%H:%M:%S')})..."):
|
618 |
+
new_messages = get_new_whatsapp_messages(st.session_state.twilio_client,
|
619 |
+
twilio_conversation_service_sid_to_use,
|
620 |
+
st.session_state.bot_start_time_utc,
|
621 |
+
st.session_state.processed_message_sids,
|
622 |
+
twilio_bot_whatsapp_identity_to_use)
|
623 |
if new_messages:
|
624 |
+
st.info(f"Found {len(new_messages)} new WhatsApp message(s) to process.")
|
625 |
for msg_data in new_messages:
|
626 |
+
user_query_whatsapp, conv_sid, msg_sid, author_id = \
|
627 |
+
msg_data["message_body"], msg_data["conversation_sid"], \
|
628 |
+
msg_data["message_sid"], msg_data["author_identity"]
|
629 |
+
|
630 |
+
st.write(f"Processing WhatsApp message from {author_id} in conversation {conv_sid}: '{user_query_whatsapp}' (SID: {msg_sid})")
|
631 |
|
632 |
+
# --- (Identical RAG logic as manual query, adapted for WhatsApp context) ---
|
633 |
+
intent_result_whatsapp = simple_intent_classifier(user_query_whatsapp)
|
634 |
intent_whatsapp = intent_result_whatsapp[0]
|
635 |
+
potential_oid_whatsapp = intent_result_whatsapp[1]
|
636 |
|
637 |
+
context_for_llm_whatsapp = "No specific context could be retrieved."
|
638 |
+
|
639 |
if intent_whatsapp == "ORDER_STATUS":
|
640 |
order_id_to_check_whatsapp = None
|
641 |
if potential_oid_whatsapp:
|
642 |
order_id_to_check_whatsapp = potential_oid_whatsapp
|
643 |
else:
|
644 |
+
match_whatsapp = re.search(r'\b(ord\d{3,})\b', user_query_whatsapp.lower(), re.IGNORECASE)
|
645 |
+
if match_whatsapp:
|
646 |
+
order_id_to_check_whatsapp = match_whatsapp.group(1).upper()
|
|
|
647 |
|
648 |
if order_id_to_check_whatsapp:
|
649 |
+
order_details_whatsapp = get_order_details(order_id_to_check_whatsapp, st.session_state.customer_orders_data)
|
650 |
+
context_for_llm_whatsapp = f"Order Details for {order_id_to_check_whatsapp}: {order_details_whatsapp}"
|
651 |
else:
|
652 |
+
context_for_llm_whatsapp = "To check an order status, please provide a valid Order ID (e.g., ORD123)."
|
653 |
+
|
654 |
elif intent_whatsapp == "PRODUCT_INFO":
|
655 |
+
product_info_whatsapp = get_product_info(user_query_whatsapp, st.session_state.products_data)
|
656 |
+
context_for_llm_whatsapp = f"Product Information related to '{user_query_whatsapp}': {product_info_whatsapp}"
|
657 |
+
|
658 |
elif intent_whatsapp == "GENERAL_POLICY_FAQ" or intent_whatsapp == "UNKNOWN":
|
659 |
+
if st.session_state.faiss_index_pdfs and st.session_state.embedding_model and st.session_state.indexed_pdf_chunks:
|
660 |
+
k_val_whatsapp = 3 if intent_whatsapp == "GENERAL_POLICY_FAQ" else 2
|
661 |
+
chunks_whatsapp = search_faiss_index(st.session_state.faiss_index_pdfs, user_query_whatsapp,
|
662 |
+
st.session_state.embedding_model, st.session_state.indexed_pdf_chunks, k=k_val_whatsapp)
|
663 |
+
if chunks_whatsapp:
|
664 |
+
context_for_llm_whatsapp = "Relevant information from documents:\n\n" + "\n\n---\n\n".join(chunks_whatsapp)
|
665 |
+
else:
|
666 |
+
context_for_llm_whatsapp = "I couldn't find specific information in our policy or FAQ documents regarding your query."
|
667 |
+
else:
|
668 |
+
context_for_llm_whatsapp = "Our policy and FAQ documents are currently unavailable for search."
|
669 |
+
# --- End of RAG logic for WhatsApp ---
|
670 |
+
|
671 |
+
response_whatsapp = generate_response_groq(st.session_state.groq_client, user_query_whatsapp, context_for_llm_whatsapp)
|
672 |
|
673 |
+
if send_whatsapp_message(st.session_state.twilio_client, twilio_conversation_service_sid_to_use,
|
674 |
+
conv_sid, response_whatsapp, twilio_bot_whatsapp_identity_to_use):
|
675 |
st.session_state.processed_message_sids.add(msg_sid)
|
676 |
+
st.success(f"Successfully responded to WhatsApp message SID {msg_sid} from {author_id}.")
|
677 |
+
else:
|
678 |
+
st.error(f"Failed to send WhatsApp response for message SID {msg_sid} from {author_id}.")
|
679 |
+
st.experimental_rerun() # Rerun to clear spinner and update UI if messages were processed
|
680 |
+
# else:
|
681 |
+
# st.write(f"No new WhatsApp messages since last poll at {datetime.fromtimestamp(st.session_state.last_twilio_poll_time).strftime('%H:%M:%S')}.")
|
682 |
+
|
683 |
|
684 |
# --- Footer & Status ---
|
685 |
st.sidebar.markdown("---")
|
686 |
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.")
|
687 |
if st.session_state.get("app_started"):
|
688 |
+
status_color = "green" if st.session_state.get("rag_pipeline_ready") else "orange"
|
689 |
+
app_status_text = "App RUNNING" if st.session_state.get("rag_pipeline_ready") else "App Initializing/Error"
|
690 |
+
bot_status_text = "WhatsApp Bot RUNNING" if st.session_state.get("bot_started") else "WhatsApp Bot STOPPED"
|
691 |
+
st.sidebar.markdown(f"<span style='color:{status_color};'>{app_status_text}</span>. {bot_status_text}.", unsafe_allow_html=True)
|
692 |
+
|
693 |
else:
|
694 |
+
st.sidebar.warning("App is STOPPED.")
|
695 |
+
|