Spaces:
Sleeping
Sleeping
import streamlit as st | |
import os | |
import time | |
from datetime import datetime, timezone | |
import json | |
import PyPDF2 | |
from sentence_transformers import SentenceTransformer | |
import faiss | |
import numpy as np | |
from twilio.rest import Client | |
from groq import Groq | |
import re # Import re module | |
# --- Page Configuration --- | |
st.set_page_config(page_title="RAG Customer Support Chatbot", layout="wide") | |
# --- Default Configurations & File Paths --- | |
DEFAULT_TWILIO_ACCOUNT_SID_FALLBACK = "" # Fallback if secret "TWILIO_SID" is not found | |
DEFAULT_TWILIO_AUTH_TOKEN_FALLBACK = "" # Fallback if secret "TWILIO_TOKEN" is not found | |
DEFAULT_GROQ_API_KEY_FALLBACK = "" # Fallback if secret "GROQ_API_KEY" is not found | |
DEFAULT_TWILIO_CONVERSATION_SERVICE_SID = "" | |
DEFAULT_TWILIO_BOT_WHATSAPP_IDENTITY = st.secrets.get("TWILIO_PHONE_NUMBER")#"whatsapp:+14155238886" # Twilio Sandbox default | |
DEFAULT_EMBEDDING_MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2" | |
DEFAULT_POLLING_INTERVAL_S = 30 | |
DOCS_FOLDER = "docs/" | |
CUSTOMER_ORDERS_FILE = os.path.join(DOCS_FOLDER, "CustomerOrders.json") | |
PRODUCTS_FILE = os.path.join(DOCS_FOLDER, "Products.json") | |
POLICY_PDF_FILE = os.path.join(DOCS_FOLDER, "ProductReturnPolicy.pdf") | |
FAQ_PDF_FILE = os.path.join(DOCS_FOLDER, "FAQ.pdf") | |
# --- Application Secrets Configuration --- | |
# These are the primary keys fetched from st.secrets as per user request | |
APP_TWILIO_ACCOUNT_SID = st.secrets.get("TWILIO_ACCOUNT_SID") | |
APP_TWILIO_AUTH_TOKEN = st.secrets.get("TWILIO_AUTH_TOKEN") | |
APP_GROQ_API_KEY = st.secrets.get("GROQ_API_KEY") | |
# Other secrets with fallback to defaults/sidebar input (if secrets not found) | |
APP_TWILIO_CONVERSATION_SERVICE_SID_SECRET = st.secrets.get("TWILIO_CONVERSATION_SERVICE_SID") | |
APP_TWILIO_BOT_WHATSAPP_IDENTITY_SECRET = st.secrets.get("TWILIO_BOT_WHATSAPP_IDENTITY") | |
# --- RAG Processing Utilities --- | |
def load_json_data(file_path): | |
"""Loads data from a JSON file.""" | |
try: | |
with open(file_path, 'r', encoding='utf-8') as f: | |
data = json.load(f) | |
return data | |
except FileNotFoundError: | |
st.error(f"Error: JSON file not found at {file_path}") | |
return None | |
except json.JSONDecodeError: | |
st.error(f"Error: Could not decode JSON from {file_path}") | |
return None | |
except Exception as e: | |
st.error(f"An unexpected error occurred while loading {file_path}: {e}") | |
return None | |
def load_pdf_data(file_path): | |
"""Extracts text from a PDF file, page by page.""" | |
try: | |
with open(file_path, 'rb') as f: | |
reader = PyPDF2.PdfReader(f) | |
text_pages = [] | |
for page_num in range(len(reader.pages)): | |
page = reader.pages[page_num] | |
text_pages.append(page.extract_text() or "") | |
return text_pages | |
except FileNotFoundError: | |
st.error(f"Error: PDF file not found at {file_path}") | |
return [] | |
except Exception as e: | |
st.error(f"An error occurred while processing PDF {file_path}: {e}") | |
return [] | |
def chunk_text(text_pages, chunk_size=1000, chunk_overlap=200): | |
"""Chunks text from PDF pages into smaller, overlapping pieces.""" | |
full_text = "\n".join(text_pages) | |
if not full_text.strip(): | |
return [] | |
chunks = [] | |
start = 0 | |
while start < len(full_text): | |
end = start + chunk_size | |
chunks.append(full_text[start:end]) | |
if end >= len(full_text): | |
break | |
start += (chunk_size - chunk_overlap) | |
if start >= len(full_text): | |
break | |
return [chunk for chunk in chunks if chunk.strip()] | |
def initialize_embedding_model(model_name=DEFAULT_EMBEDDING_MODEL_NAME): | |
"""Initializes and returns a SentenceTransformer model.""" | |
try: | |
model = SentenceTransformer(model_name) | |
return model | |
except Exception as e: | |
st.error(f"Error initializing embedding model '{model_name}': {e}") | |
return None | |
def create_faiss_index(_text_chunks, _embedding_model): | |
"""Creates a FAISS index from text chunks and an embedding model.""" | |
if not _text_chunks or _embedding_model is None: | |
st.warning("Cannot create FAISS index: No text chunks or embedding model available.") | |
return None, [] | |
try: | |
valid_chunks = [str(chunk) for chunk in _text_chunks if chunk and isinstance(chunk, str) and chunk.strip()] | |
if not valid_chunks: | |
st.warning("No valid text chunks to embed for FAISS index.") | |
return None, [] | |
embeddings = _embedding_model.encode(valid_chunks, convert_to_tensor=False) | |
if embeddings.ndim == 1: | |
embeddings = embeddings.reshape(1, -1) | |
if embeddings.shape[0] == 0: | |
st.warning("No embeddings were generated for FAISS index.") | |
return None, [] | |
dimension = embeddings.shape[1] | |
index = faiss.IndexFlatL2(dimension) | |
index.add(np.array(embeddings, dtype=np.float32)) | |
return index, valid_chunks | |
except Exception as e: | |
st.error(f"Error creating FAISS index: {e}") | |
return None, [] | |
def search_faiss_index(index, query_text, embedding_model, indexed_chunks, k=3): | |
"""Searches the FAISS index and returns top_k relevant chunk texts.""" | |
if index is None or embedding_model is None or not query_text: | |
return [] | |
try: | |
query_embedding = embedding_model.encode([query_text], convert_to_tensor=False) | |
if query_embedding.ndim == 1: | |
query_embedding = query_embedding.reshape(1, -1) | |
distances, indices = index.search(np.array(query_embedding, dtype=np.float32), k) | |
results = [] | |
for i in range(len(indices[0])): | |
idx = indices[0][i] | |
if 0 <= idx < len(indexed_chunks): | |
results.append(indexed_chunks[idx]) | |
return results | |
except Exception as e: | |
st.error(f"Error searching FAISS index: {e}") | |
return [] | |
def get_order_details(order_id, customer_orders_data): | |
"""Retrieves order details for a given order_id.""" | |
if not customer_orders_data: | |
return "Customer order data is not loaded." | |
for order in customer_orders_data: | |
if order.get("order_id") == order_id: | |
return json.dumps(order, indent=2) | |
return f"No order found with ID: {order_id}." | |
def get_product_info(query, products_data): | |
"""Retrieves product information based on a query.""" | |
if not products_data: | |
return "Product data is not loaded." | |
query_lower = query.lower() | |
found_products = [] | |
for product in products_data: | |
if query_lower in (product.get("name", "").lower()) or \ | |
query_lower in (product.get("description", "").lower()) or \ | |
query_lower == (product.get("product_id", "").lower()): | |
found_products.append(product) | |
if found_products: | |
return json.dumps(found_products, indent=2) | |
return f"No product information found matching your query: '{query}'." | |
# --- LLM Operations --- | |
def generate_response_groq(_groq_client, query, context, model="llama3-8b-8192"): | |
"""Generates a response using GROQ LLaMA3 API.""" | |
if not _groq_client: | |
return "GROQ client not initialized. Please check API key." | |
if not query: | |
return "Query is empty." | |
prompt = f"""You are a helpful customer support assistant. | |
Use the following context to answer the user's question. | |
If the context doesn't contain the answer, state that you don't have enough information. | |
Do not make up information. Be concise and polite. | |
Context: | |
{context} | |
User Question: {query} | |
Assistant Answer: | |
""" | |
try: | |
chat_completion = _groq_client.chat.completions.create( | |
messages=[ | |
{"role": "system", "content": "You are a helpful customer support assistant."}, | |
{"role": "user", "content": prompt} | |
], | |
model=model, temperature=0.7, max_tokens=1024, top_p=1 | |
) | |
response = chat_completion.choices[0].message.content | |
return response | |
except Exception as e: | |
st.error(f"Error calling GROQ API: {e}") | |
return "Sorry, I encountered an error while trying to generate a response." | |
def initialize_groq_client(api_key_val): | |
"""Initializes the GROQ client.""" | |
if not api_key_val: # Changed parameter name to avoid conflict | |
st.warning("GROQ API Key is missing.") | |
return None | |
try: | |
client = Groq(api_key=api_key_val) | |
return client | |
except Exception as e: | |
st.error(f"Failed to initialize GROQ client: {e}") | |
return None | |
# --- Twilio Operations --- | |
def initialize_twilio_client(acc_sid, auth_tkn): # Changed parameter names | |
"""Initializes the Twilio client.""" | |
if not acc_sid or not auth_tkn: | |
st.warning("Twilio Account SID or Auth Token is missing.") | |
return None | |
try: | |
client = Client(acc_sid, auth_tkn) | |
return client | |
except Exception as e: | |
st.error(f"Failed to initialize Twilio client: {e}") | |
return None | |
def get_new_whatsapp_messages(twilio_client, conversation_service_sid_val, bot_start_time_utc, # Renamed | |
processed_message_sids, bot_whatsapp_identity_val): # Renamed | |
"""Fetches new, unanswered WhatsApp messages from Twilio Conversations.""" | |
if not twilio_client: | |
st.warning("Twilio client not initialized.") | |
return [] | |
if not conversation_service_sid_val: | |
st.warning("Twilio Conversation Service SID not provided.") | |
return [] | |
new_messages_to_process = [] | |
try: | |
conversations = twilio_client.conversations.v1 \ | |
.services(conversation_service_sid_val) \ | |
.conversations \ | |
.list(limit=50) | |
for conv in conversations: | |
if conv.date_updated and conv.date_updated > bot_start_time_utc: | |
messages = twilio_client.conversations.v1 \ | |
.services(conversation_service_sid_val) \ | |
.conversations(conv.sid) \ | |
.messages \ | |
.list(order='desc', limit=10) | |
for msg in messages: | |
if msg.sid in processed_message_sids: | |
continue | |
if msg.author and msg.author.lower() != bot_whatsapp_identity_val.lower() and \ | |
msg.date_created and msg.date_created > bot_start_time_utc: | |
new_messages_to_process.append({ | |
"conversation_sid": conv.sid, "message_sid": msg.sid, | |
"author_identity": msg.author, "message_body": msg.body, | |
"timestamp_utc": msg.date_created | |
}) | |
break | |
except Exception as e: | |
st.error(f"Error fetching Twilio messages: {e}") | |
return sorted(new_messages_to_process, key=lambda m: m['timestamp_utc']) | |
def send_whatsapp_message(twilio_client, conversation_service_sid_val, conversation_sid, message_body, bot_identity_val): # Renamed | |
"""Sends a message to a Twilio Conversation from the bot's identity.""" | |
if not twilio_client: | |
st.error("Twilio client not initialized for sending message.") | |
return False | |
if not conversation_service_sid_val: | |
st.error("Twilio Conversation Service SID not provided for sending message.") | |
return False | |
if not bot_identity_val: | |
st.error("Bot identity not provided for sending message.") | |
return False | |
try: | |
twilio_client.conversations.v1 \ | |
.services(conversation_service_sid_val) \ | |
.conversations(conversation_sid) \ | |
.messages \ | |
.create(author=bot_identity_val, body=message_body) | |
st.success(f"Sent reply to conversation {conversation_sid}") | |
return True | |
except Exception as e: | |
st.error(f"Error sending Twilio message to {conversation_sid}: {e}") | |
return False | |
# --- Main Application Logic & UI --- | |
st.title("🤖 RAG-Based Customer Support Chatbot") | |
st.markdown("Powered by Streamlit, Twilio, GROQ LLaMA3, and FAISS.") | |
# --- Sidebar for Configurations --- | |
st.sidebar.title("⚙️ Configurations") | |
# Use APP_ prefixed variables for values from secrets, then allow manual input if not found | |
if APP_TWILIO_ACCOUNT_SID: | |
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) | |
twilio_account_sid_to_use = APP_TWILIO_ACCOUNT_SID | |
else: | |
st.sidebar.warning("Secret 'TWILIO_SID' not found.") | |
twilio_account_sid_to_use = st.sidebar.text_input("Twilio Account SID (Enter Manually)", value=DEFAULT_TWILIO_ACCOUNT_SID_FALLBACK, type="password") | |
if APP_TWILIO_AUTH_TOKEN: | |
st.sidebar.text_input("Twilio Auth Token (from Secrets)", value="********", disabled=True) | |
twilio_auth_token_to_use = APP_TWILIO_AUTH_TOKEN | |
else: | |
st.sidebar.warning("Secret 'TWILIO_TOKEN' not found.") | |
twilio_auth_token_to_use = st.sidebar.text_input("Twilio Auth Token (Enter Manually)", value=DEFAULT_TWILIO_AUTH_TOKEN_FALLBACK, type="password") | |
if APP_GROQ_API_KEY: | |
st.sidebar.text_input("GROQ API Key (from Secrets)", value="gsk_********" + APP_GROQ_API_KEY[-4:] if len(APP_GROQ_API_KEY) > 8 else "********", disabled=True) | |
groq_api_key_to_use = APP_GROQ_API_KEY | |
else: | |
st.sidebar.warning("Secret 'GROQ_API_KEY' not found.") | |
groq_api_key_to_use = st.sidebar.text_input("GROQ API Key (Enter Manually)", value=DEFAULT_GROQ_API_KEY_FALLBACK, type="password") | |
# For other configurations that can be overridden if secrets not found or for user preference | |
twilio_conversation_service_sid_to_use = st.sidebar.text_input( | |
"Twilio Conversation Service SID (IS...)", | |
value=APP_TWILIO_CONVERSATION_SERVICE_SID_SECRET or DEFAULT_TWILIO_CONVERSATION_SERVICE_SID, | |
type="password", | |
help="The SID of your Twilio Conversations Service. Can be set by 'TWILIO_CONVERSATION_SERVICE_SID' secret." | |
) | |
twilio_bot_whatsapp_identity_to_use = st.sidebar.text_input( | |
"Twilio Bot WhatsApp Identity", | |
value=APP_TWILIO_BOT_WHATSAPP_IDENTITY_SECRET or DEFAULT_TWILIO_BOT_WHATSAPP_IDENTITY, | |
help="e.g., 'whatsapp:+1234567890'. Can be set by 'TWILIO_BOT_WHATSAPP_IDENTITY' secret." | |
) | |
embedding_model_name_to_use = st.sidebar.text_input( # Renamed | |
"Embedding Model Name", | |
value=DEFAULT_EMBEDDING_MODEL_NAME | |
) | |
polling_interval_to_use = st.sidebar.number_input( # Renamed | |
"Twilio Polling Interval (seconds)", | |
min_value=10, max_value=300, | |
value=DEFAULT_POLLING_INTERVAL_S, | |
step=5 | |
) | |
# --- Initialize Session State --- | |
if "app_started" not in st.session_state: st.session_state.app_started = False | |
if "bot_started" not in st.session_state: st.session_state.bot_started = False | |
if "rag_pipeline_ready" not in st.session_state: st.session_state.rag_pipeline_ready = False | |
if "last_twilio_poll_time" not in st.session_state: st.session_state.last_twilio_poll_time = time.time() | |
if "bot_start_time_utc" not in st.session_state: st.session_state.bot_start_time_utc = None | |
if "processed_message_sids" not in st.session_state: st.session_state.processed_message_sids = set() | |
if "manual_chat_history" not in st.session_state: st.session_state.manual_chat_history = [] | |
# --- Helper: Simple Intent Classifier --- | |
def simple_intent_classifier(query): | |
query_lower = query.lower() | |
if any(k in query_lower for k in ["order", "status", "track", "delivery"]): | |
# More specific regex to find 'ORD' followed by digits (assuming order IDs are like ORD1001) | |
match = re.search(r'\b(ord\d{3,})\b', query_lower) # Matches 'ord' followed by at least 3 digits, as a whole word | |
if match: | |
return "ORDER_STATUS", match.group(1).upper() # Return intent and extracted ID | |
# Fallback if specific order ID not found but still an order-related query | |
return "ORDER_STATUS", None # Indicate order status intent but no specific ID found yet | |
if any(k in query_lower for k in ["product", "item", "buy", "price", "feature", "stock"]): return "PRODUCT_INFO", None | |
if any(k in query_lower for k in ["return", "policy", "refund", "exchange", "faq", "question", "how to", "support"]): return "GENERAL_POLICY_FAQ", None | |
return "UNKNOWN", None # Return intent and None for ID if unknown | |
# --- Main Application Controls --- | |
col1, col2, col3, col4 = st.columns(4) | |
with col1: | |
if st.button("🚀 Start App", disabled=st.session_state.app_started, use_container_width=True): | |
if not groq_api_key_to_use: # Use the correct variable | |
st.error("GROQ API Key is required.") | |
else: | |
with st.spinner("Initializing RAG pipeline..."): | |
st.session_state.embedding_model = initialize_embedding_model(embedding_model_name_to_use) # Use correct var | |
st.session_state.customer_orders_data = load_json_data(CUSTOMER_ORDERS_FILE) | |
st.session_state.products_data = load_json_data(PRODUCTS_FILE) | |
policy_pdf_pages = load_pdf_data(POLICY_PDF_FILE) | |
faq_pdf_pages = load_pdf_data(FAQ_PDF_FILE) | |
all_pdf_text_pages = policy_pdf_pages + faq_pdf_pages | |
st.session_state.pdf_text_chunks_raw = chunk_text(all_pdf_text_pages) | |
if st.session_state.embedding_model and st.session_state.pdf_text_chunks_raw: | |
st.session_state.faiss_index_pdfs, st.session_state.indexed_pdf_chunks = \ | |
create_faiss_index(st.session_state.pdf_text_chunks_raw, st.session_state.embedding_model) | |
else: | |
st.session_state.faiss_index_pdfs, st.session_state.indexed_pdf_chunks = None, [] | |
st.warning("FAISS index for PDFs could not be created.") | |
st.session_state.groq_client = initialize_groq_client(groq_api_key_to_use) # Use correct var | |
if st.session_state.embedding_model and st.session_state.groq_client and \ | |
st.session_state.customer_orders_data and st.session_state.products_data: | |
st.session_state.rag_pipeline_ready = True | |
st.session_state.app_started = True | |
st.success("RAG Application Started!") | |
st.rerun() | |
else: | |
st.error("Failed to initialize RAG pipeline. Check configurations and ensure all data files are present in 'docs/'.") | |
st.session_state.app_started = False | |
with col2: | |
if st.button("🛑 Stop App", disabled=not st.session_state.app_started, use_container_width=True): | |
keys_to_reset = ["app_started", "bot_started", "rag_pipeline_ready", "embedding_model", | |
"customer_orders_data", "products_data", "pdf_text_chunks_raw", | |
"faiss_index_pdfs", "indexed_pdf_chunks", "groq_client", "twilio_client", | |
"bot_start_time_utc", "processed_message_sids", "manual_chat_history"] | |
for key in keys_to_reset: | |
if key in st.session_state: del st.session_state[key] | |
st.session_state.app_started = False | |
st.session_state.bot_started = False | |
st.session_state.rag_pipeline_ready = False | |
st.session_state.processed_message_sids = set() | |
st.session_state.manual_chat_history = [] | |
st.success("Application Stopped.") | |
st.rerun() | |
with col3: | |
if st.button("💬 Start WhatsApp Bot", disabled=not st.session_state.app_started or st.session_state.bot_started, use_container_width=True): | |
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]): # Use correct vars | |
st.error("Twilio credentials, Service SID, and Bot Identity are required.") | |
else: | |
st.session_state.twilio_client = initialize_twilio_client(twilio_account_sid_to_use, twilio_auth_token_to_use) # Use correct vars | |
if st.session_state.twilio_client: | |
st.session_state.bot_started = True | |
st.session_state.bot_start_time_utc = datetime.now(timezone.utc) | |
st.session_state.processed_message_sids = set() | |
st.session_state.last_twilio_poll_time = time.time() - polling_interval_to_use -1 # Use correct var | |
st.success("WhatsApp Bot Started!") | |
st.rerun() | |
else: | |
st.error("Failed to initialize Twilio client.") | |
with col4: | |
if st.button("🔕 Stop WhatsApp Bot", disabled=not st.session_state.bot_started, use_container_width=True): | |
st.session_state.bot_started = False | |
st.info("WhatsApp Bot Stopped.") | |
st.rerun() | |
st.divider() | |
# --- Manual Query Interface --- | |
if st.session_state.get("app_started") and st.session_state.get("rag_pipeline_ready"): | |
st.subheader("💬 Manual Query") | |
for chat_entry in st.session_state.manual_chat_history: | |
with st.chat_message(chat_entry["role"]): | |
st.markdown(chat_entry["content"]) | |
if "context" in chat_entry and chat_entry["context"]: | |
with st.expander("Retrieved Context"): | |
try: | |
# Attempt to parse as JSON only if it looks like a JSON string | |
if isinstance(chat_entry["context"], str) and (chat_entry["context"].strip().startswith('{') or chat_entry["context"].strip().startswith('[')): | |
st.json(json.loads(chat_entry["context"])) | |
else: | |
# Otherwise, display as plain text | |
st.text(str(chat_entry["context"])) | |
except (json.JSONDecodeError, TypeError): | |
# Fallback for any other parsing errors | |
st.text(str(chat_entry["context"])) | |
user_query_manual = st.chat_input("Ask a question:") | |
if user_query_manual: | |
st.session_state.manual_chat_history.append({"role": "user", "content": user_query_manual}) | |
with st.chat_message("user"): st.markdown(user_query_manual) | |
with st.spinner("Thinking..."): | |
intent_result = simple_intent_classifier(user_query_manual) # Get both intent and potential_id | |
intent = intent_result[0] | |
potential_oid_from_intent = intent_result[1] # This is the extracted ID if any | |
context_for_llm, raw_context_data = "No specific context.", None | |
if intent == "ORDER_STATUS": | |
order_id_to_check = None | |
if potential_oid_from_intent: | |
order_id_to_check = potential_oid_from_intent | |
else: | |
# Fallback for edge cases, though the regex should catch most | |
words = user_query_manual.upper().split() | |
# This regex specifically looks for 'ORD' followed by digits | |
possible_match = next((w for w in words if re.match(r'ORD\d+', w)), None) | |
if possible_match: | |
order_id_to_check = possible_match | |
if order_id_to_check: | |
raw_context_data = get_order_details(order_id_to_check.upper(), st.session_state.customer_orders_data) | |
context_for_llm = f"Order Details: {raw_context_data}" | |
else: | |
context_for_llm = "Please provide a valid Order ID (e.g., ORD1234)." | |
raw_context_data = {"message": "Order ID needed."} | |
elif intent == "PRODUCT_INFO": | |
raw_context_data = get_product_info(user_query_manual, st.session_state.products_data) | |
context_for_llm = f"Product Information: {raw_context_data}" | |
elif intent == "GENERAL_POLICY_FAQ" or intent == "UNKNOWN": | |
# ... (rest of your existing logic for these intents) ... | |
if st.session_state.faiss_index_pdfs and st.session_state.embedding_model: | |
k_val = 2 if intent == "GENERAL_POLICY_FAQ" else 1 | |
retrieved_chunks = search_faiss_index(st.session_state.faiss_index_pdfs, user_query_manual, | |
st.session_state.embedding_model, st.session_state.indexed_pdf_chunks, k=k_val) | |
if retrieved_chunks: | |
context_for_llm = "\n\n".join(retrieved_chunks) | |
raw_context_data = retrieved_chunks | |
else: | |
context_for_llm = "No specific policy/FAQ info found." if intent == "GENERAL_POLICY_FAQ" else "Could not find relevant info." | |
raw_context_data = {"message": "No relevant PDF chunks found."} | |
else: | |
context_for_llm = "Policy/FAQ documents unavailable." | |
raw_context_data = {"message": "PDF index not ready."} | |
llm_response = generate_response_groq(st.session_state.groq_client, user_query_manual, context_for_llm) | |
with st.chat_message("assistant"): | |
st.markdown(llm_response) | |
if raw_context_data: | |
with st.expander("Retrieved Context"): | |
try: | |
if isinstance(raw_context_data, str) and (raw_context_data.strip().startswith('{') or raw_context_data.strip().startswith('[')): | |
st.json(json.loads(raw_context_data)) | |
else: | |
st.text(str(raw_context_data)) | |
except (json.JSONDecodeError, TypeError): | |
st.text(str(raw_context_data)) | |
st.session_state.manual_chat_history.append({"role": "assistant", "content": llm_response, "context": raw_context_data}) | |
# --- Twilio Bot Polling Logic --- | |
if st.session_state.get("bot_started") and st.session_state.get("rag_pipeline_ready"): | |
current_time = time.time() | |
if (current_time - st.session_state.get("last_twilio_poll_time", 0)) > polling_interval_to_use: # Use correct var | |
st.session_state.last_twilio_poll_time = current_time | |
with st.spinner("Checking WhatsApp messages..."): | |
if not st.session_state.get("twilio_client") or not twilio_conversation_service_sid_to_use or not twilio_bot_whatsapp_identity_to_use: # Use correct vars | |
st.warning("Twilio client/config missing for polling.") | |
else: | |
new_messages = get_new_whatsapp_messages(st.session_state.twilio_client, twilio_conversation_service_sid_to_use, | |
st.session_state.bot_start_time_utc, st.session_state.processed_message_sids, | |
twilio_bot_whatsapp_identity_to_use) # Use correct vars | |
if new_messages: | |
st.info(f"Found {len(new_messages)} new WhatsApp message(s).") | |
for msg_data in new_messages: | |
user_query_whatsapp, conv_sid, msg_sid, author_id = msg_data["message_body"], msg_data["conversation_sid"], msg_data["message_sid"], msg_data["author_identity"] | |
st.write(f"Processing from {author_id} in {conv_sid}: '{user_query_whatsapp}'") | |
intent_result_whatsapp = simple_intent_classifier(user_query_whatsapp) # Use the updated classifier | |
intent_whatsapp = intent_result_whatsapp[0] | |
potential_oid_whatsapp = intent_result_whatsapp[1] # Extracted ID from intent classifier | |
context_whatsapp = "No specific context." | |
if intent_whatsapp == "ORDER_STATUS": | |
order_id_to_check_whatsapp = None | |
if potential_oid_whatsapp: | |
order_id_to_check_whatsapp = potential_oid_whatsapp | |
else: | |
words_whatsapp = user_query_whatsapp.upper().split() | |
possible_match_whatsapp = next((w for w in words_whatsapp if re.match(r'ORD\d+', w)), None) | |
if possible_match_whatsapp: | |
order_id_to_check_whatsapp = possible_match_whatsapp | |
if order_id_to_check_whatsapp: | |
context_whatsapp = f"Order Details: {get_order_details(order_id_to_check_whatsapp.upper(), st.session_state.customer_orders_data)}" | |
else: | |
context_whatsapp = "Please provide a valid Order ID." | |
elif intent_whatsapp == "PRODUCT_INFO": | |
context_whatsapp = f"Product Info: {get_product_info(user_query_whatsapp, st.session_state.products_data)}" | |
elif intent_whatsapp == "GENERAL_POLICY_FAQ" or intent_whatsapp == "UNKNOWN": | |
if st.session_state.faiss_index_pdfs and st.session_state.embedding_model: | |
k_val = 2 if intent_whatsapp == "GENERAL_POLICY_FAQ" else 1 | |
chunks = search_faiss_index(st.session_state.faiss_index_pdfs, user_query_whatsapp, st.session_state.embedding_model, st.session_state.indexed_pdf_chunks, k=k_val) | |
context_whatsapp = "\n\n".join(chunks) if chunks else ("No policy/FAQ info." if intent_whatsapp == "GENERAL_POLICY_FAQ" else "No relevant info.") | |
else: context_whatsapp = "Policy/FAQ docs unavailable." | |
response_whatsapp = generate_response_groq(st.session_state.groq_client, user_query_whatsapp, context_whatsapp) | |
if send_whatsapp_message(st.session_state.twilio_client, twilio_conversation_service_sid_to_use, conv_sid, response_whatsapp, twilio_bot_whatsapp_identity_to_use): # Use correct vars | |
st.session_state.processed_message_sids.add(msg_sid) | |
st.success(f"Responded to {msg_sid} from {author_id}") | |
else: st.error(f"Failed to send response for {msg_sid}") | |
st.experimental_rerun() | |
# --- Footer & Status --- | |
st.sidebar.markdown("---") | |
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.") | |
if st.session_state.get("app_started"): | |
st.sidebar.success(f"App RUNNING. WhatsApp Bot {'RUNNING' if st.session_state.get('bot_started') else 'STOPPED'}.") | |
else: | |
st.sidebar.warning("App is STOPPED.") |