Spaces:
Sleeping
Sleeping
from simple_salesforce import Salesforce | |
import logging | |
from flask import Flask, render_template_string, request, jsonify | |
import speech_recognition as sr | |
from tempfile import NamedTemporaryFile | |
import os | |
import ffmpeg | |
from werkzeug.exceptions import BadRequest | |
import re | |
import requests | |
import datetime | |
app = Flask(__name__) | |
logging.basicConfig(level=logging.INFO) | |
SALESFORCE_INSTANCE = "https://biryanihub-dev-ed.develop.my.salesforce.com" | |
API_VERSION = "v59.0" | |
# Salesforce connection setup | |
sf = Salesforce( | |
username='diggavalli98@gmail.com', | |
password='Sati@1020', | |
security_token='sSSjyhInIsUohKpG8sHzty2q', | |
consumer_key='3MVG9WVXk15qiz1JbtW1tT9a7Wnkos2RuGamw6p1lC5uPescT5NB2nPygpo6rQ87K1T.zBEn.wR.A6JdgHnIU', | |
consumer_secret='A75C6B7801D5D20BED0E46631CF58C4F7FF28E4DAF442FE667553D29C35C0451' | |
) | |
AUTH_HEADERS = { | |
"Authorization": f"Bearer {sf.session_id}", | |
"Content-Type": "application/json" | |
} | |
# Global variables | |
cart = [] # To store items, quantities, and prices | |
MENU = {} | |
current_category = None | |
current_item = None | |
awaiting_quantity = False | |
# Initialize customer details to None to avoid errors if not filled yet | |
customer_name = None | |
customer_email = None | |
customer_mobile = None | |
# Fetching menu items dynamically from Salesforce | |
def get_menu_items(): | |
menu_items = {} | |
query = "SELECT Name, Category__c, Price__c, Ingredients__c FROM Menu_Item__c" | |
results = sf.query_all(query) | |
# Loop through each record in the query result | |
for record in results['records']: | |
category = record['Category__c'] # 'category' should be defined inside the loop | |
item_name = record['Name'] | |
price = record['Price__c'] | |
# Ensure no None categories are processed | |
if category is not None: # Check if category is not None before processing | |
if category not in menu_items: | |
menu_items[category] = {} # Initialize category if it's not already in the dictionary | |
menu_items[category][item_name] = price # Add item to category | |
return menu_items | |
# Update global MENU variable | |
MENU = get_menu_items() | |
def find_customer_by_email(email): | |
"""Finds a customer by email in Salesforce.""" | |
query = f"SELECT Id FROM Customer_Login__c WHERE Email__c = '{email}'" | |
results = sf.query(query) | |
if results["records"]: | |
return results["records"][0]["Id"] # Return customer ID | |
return None | |
def extract_email(command): | |
"""Extracts an email address from a speech command.""" | |
# Replace ' at ' with '@' for common speech-to-text variations | |
command = command.replace(" at ", "@") | |
# Use regex to find a valid email address | |
match = re.search(r'[\w\.-]+@[\w\.-]+\.\w+', command) | |
return match.group(0) if match else None | |
def create_customer(email, name, phone): | |
"""Creates a new customer in Salesforce.""" | |
customer_data = { | |
"Email__c": email, | |
"Name": name, | |
"Phone_Number__c": phone | |
} | |
try: | |
result = sf.Customer_Login__c.create(customer_data) | |
return result["id"] # Return created Customer ID | |
except Exception as e: | |
print(f"Error creating customer: {e}") | |
return None | |
def create_order(email, total_amount): | |
"""Creates an order linked to the customer email in Salesforce with additional details.""" | |
customer_id = find_customer_by_email(email) # Find customer by email | |
if not customer_id: | |
print(f"Customer with email {email} not found. Cannot create order.") | |
return None | |
# Format order details from the cart | |
order_details = "; ".join([f"{item} x{quantity} (${price * quantity})" for item, price, quantity in cart]) | |
# Define order status (default to 'Pending') | |
order_status = "Pending" | |
# Get current date | |
order_date = datetime.datetime.now().isoformat() # ISO format (YYYY-MM-DD) | |
# Prepare the order data dictionary | |
order_data = { | |
"Customer__c": customer_id, # Link the order to the customer using Customer ID | |
"Total_Amount__c": total_amount, # Total price of the order | |
"Order_Date__c": order_date, # Date of the order | |
"Order_Status__c": order_status, # Status of the order | |
"Order_Details__c": order_details, # Itemized order details | |
"Customer_Email__c": customer_email | |
} | |
try: | |
result = sf.Order__c.create(order_data) | |
print("Order successfully created!") | |
return result["id"] # Return created order ID | |
except Exception as e: | |
print(f"Error creating order: {e}") | |
return None | |
def process_order(email, name, phone, total_amount): | |
"""Main function to process an order.""" | |
customer_id = find_customer_by_email(email) | |
if not customer_id: | |
print("Email not found. Creating new customer...") | |
customer_id = create_customer(email, name, phone) | |
if customer_id: | |
# Correct the call to create_order by passing only email and total_amount | |
create_order(email, total_amount) | |
else: | |
print("Failed to process order. Could not create/find customer.") | |
# HTML Template for Frontend | |
html_code = """ | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>AI Dining Assistant</title> | |
<style> | |
body { | |
font-family: Arial, sans-serif; | |
text-align: center; | |
background-color: #f4f4f9; | |
} | |
h1 { | |
color: #333; | |
} | |
.mic-button { | |
width: 80px; | |
height: 80px; | |
border-radius: 50%; | |
background-color: #007bff; | |
color: white; | |
font-size: 24px; | |
border: none; | |
cursor: pointer; | |
} | |
.status, .response { | |
margin-top: 20px; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>AI Dining Assistant</h1> | |
<button class="mic-button" id="mic-button">🎤</button> | |
<div class="status" id="status">Press the mic button to start...</div> | |
<div class="response" id="response" style="display: none;">Response will appear here...</div> | |
<script> | |
const micButton = document.getElementById('mic-button'); | |
const status = document.getElementById('status'); | |
const response = document.getElementById('response'); | |
let isListening = false; | |
micButton.addEventListener('click', () => { | |
if (!isListening) { | |
isListening = true; | |
greetUser(); | |
} | |
}); | |
function greetUser() { | |
const utterance = new SpeechSynthesisUtterance("Hi. Welcome to Biryani Hub. Can I show you the menu?"); | |
speechSynthesis.speak(utterance); | |
utterance.onend = () => { | |
status.textContent = "Listening..."; | |
startListening(); | |
}; | |
} | |
async function startListening() { | |
const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); | |
const mediaRecorder = new MediaRecorder(stream, { mimeType: "audio/webm;codecs=opus" }); | |
const audioChunks = []; | |
mediaRecorder.ondataavailable = (event) => audioChunks.push(event.data); | |
mediaRecorder.onstop = async () => { | |
const audioBlob = new Blob(audioChunks, { type: "audio/webm" }); | |
const formData = new FormData(); | |
formData.append("audio", audioBlob); | |
status.textContent = "Processing..."; | |
try { | |
const result = await fetch("/process-audio", { method: "POST", body: formData }); | |
const data = await result.json(); | |
response.textContent = data.response; | |
response.style.display = "block"; | |
const utterance = new SpeechSynthesisUtterance(data.response); | |
speechSynthesis.speak(utterance); | |
utterance.onend = () => { | |
if (!data.response.includes("Goodbye") && !data.response.includes("final order")) { | |
startListening(); // Continue listening | |
} else { | |
status.textContent = "Conversation ended."; | |
isListening = false; | |
} | |
}; | |
} catch (error) { | |
response.textContent = "Error processing your request. Please try again."; | |
status.textContent = "Press the mic button to restart."; | |
isListening = false; | |
} | |
}; | |
mediaRecorder.start(); | |
setTimeout(() => mediaRecorder.stop(), 5000); // Stop recording after 5 seconds | |
} | |
</script> | |
</body> | |
</html> | |
""" | |
def index(): | |
return render_template_string(html_code) | |
def display_menu(): | |
"""Returns the available menu categories to the user.""" | |
global MENU | |
menu_text = "Here are the available categories: " | |
# Ensure no None categories are included | |
categories = [category for category in MENU.keys() if category is not None] | |
if categories: | |
menu_text += ", ".join(categories) | |
else: | |
menu_text = "Sorry, the menu is unavailable at the moment." | |
return menu_text | |
def process_audio(): | |
global current_category, current_item, awaiting_quantity, customer_name, customer_email, customer_mobile | |
try: | |
audio_file = request.files.get("audio") | |
if not audio_file: | |
raise BadRequest("No audio file provided.") | |
temp_file = NamedTemporaryFile(delete=False, suffix=".webm") | |
audio_file.save(temp_file.name) | |
if os.path.getsize(temp_file.name) == 0: | |
raise BadRequest("Uploaded audio file is empty.") | |
converted_file = NamedTemporaryFile(delete=False, suffix=".wav") | |
ffmpeg.input(temp_file.name).output( | |
converted_file.name, acodec="pcm_s16le", ac=1, ar="16000" | |
).run(overwrite_output=True) | |
recognizer = sr.Recognizer() | |
with sr.AudioFile(converted_file.name) as source: | |
audio_data = recognizer.record(source) | |
try: | |
command = recognizer.recognize_google(audio_data) | |
logging.info(f"Recognized command: {command}") | |
response = process_command(command) | |
except sr.UnknownValueError: | |
response = "Sorry, I couldn't understand your command. Could you please repeat?" | |
except sr.RequestError as e: | |
response = f"Error with the speech recognition service: {e}" | |
return jsonify({"response": response}) | |
except BadRequest as br: | |
return jsonify({"response": f"Bad Request: {str(br)}"}), 400 | |
except Exception as e: | |
return jsonify({"response": f"An error occurred: {str(e)}"}), 500 | |
finally: | |
if os.path.exists(temp_file.name): | |
os.unlink(temp_file.name) | |
if os.path.exists(converted_file.name): | |
os.unlink(converted_file.name) | |
# Global function to store the order in Salesforce | |
def store_order_in_salesforce(cart, total_amount, name, email, phone): | |
"""Stores an order in Salesforce along with the line items.""" | |
customer_id = find_customer_by_email(email) | |
if not customer_id: | |
customer_id = create_customer(email, name, phone) | |
if not customer_id: | |
return "Error: Unable to create customer in Salesforce." | |
# Define order_details | |
order_details = "; ".join([f"{item} x{quantity} (₹{price * quantity})" for item, price, quantity in cart]) | |
# Create the order | |
order_id = create_order(email, total_amount) | |
if order_id: | |
# After creating the order, now add line items for each cart item | |
for item, price, quantity in cart: | |
# Create an Order Line Item | |
order_line_data = { | |
"Order__c": order_id, # Link the order | |
"Customer_Name__c": name, # Use name instead of customer_name | |
"Customer_Email__c":customer_email, | |
"Order_Details__c": order_details, # Order_details (fixed) | |
"Quantity__c": quantity, # Quantity ordered | |
"Unit_Price__c": price, # Price per unit | |
"Total_Amount__c": price * quantity, # Total price for this item | |
"Total_Bill__c": total_amount, | |
"Order_Status__c": "Pending", # Fixed order_status | |
"Customer2__c": customer_id # Ensure this is valid, maybe replace with customer_id | |
} | |
try: | |
sf.Order_Line_Item__c.create(order_line_data) | |
logging.info(f"Order Line Item for {item} created successfully!") | |
except Exception as e: | |
logging.error(f"Error creating order line item for {item}: {e}") | |
return "Order placed successfully with line items!" | |
else: | |
return "There was an error processing your order." | |
# Function to extract mobile number from the command | |
def extract_mobile_number(command): | |
"""Extracts mobile number using regex.""" | |
match = re.search(r'\b\d{10}\b', command) # Looks for a 10-digit number | |
return match.group(0) if match else None | |
def extract_quantity(command): | |
match = re.search(r'\b\d+\b', command) # Look for numbers in the command | |
return int(match.group(0)) if match else None # Return an integer or None | |
# Extract quantity from command (e.g., "two" -> 2, "three" -> 3, etc.) | |
def extract_quantity(command): | |
number_words = { | |
"one": 1, "two": 2, "three": 3, "four": 4, "five": 5, | |
"six": 6, "seven": 7, "eight": 8, "nine": 9, "ten": 10, | |
"1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "10": 10 | |
} | |
command_words = command.split() | |
for word in command_words: | |
if word in number_words: | |
return number_words[word] | |
return None | |
# Main logic to process the command | |
# This is the function that processes the commands, make sure the logic is inside it | |
def process_command(command): | |
global cart, MENU, current_category, current_item, awaiting_quantity, customer_name, customer_email, customer_mobile | |
command = command.lower().strip() | |
logging.debug(f"Command received: {command}") | |
# Handle name input | |
if "my name is" in command: | |
customer_name = command.replace("my name is", "").strip() | |
if not customer_email: | |
return f"Thanks, {customer_name}. Can you please provide your email address?" | |
elif not customer_mobile: | |
return f"Got it! Can you provide your mobile number?" | |
return f"Thanks, {customer_name}. How can I assist you further?" | |
# Handle email input | |
if "my email is" in command: | |
email = extract_email(command) | |
if email: | |
customer_email = email | |
if not customer_name: | |
return "Thanks! Can you also provide your name?" | |
elif not customer_mobile: | |
return "Got it! Can you provide your mobile number?" | |
return f"Thanks! Your email is {customer_email}. How can I assist you further?" | |
return "Sorry, I couldn't detect a valid email. Please try again." | |
# Handle mobile number input | |
if "my mobile number is" in command or customer_mobile is None: | |
mobile_number = extract_mobile_number(command) | |
if mobile_number: | |
customer_mobile = mobile_number | |
if not customer_name: | |
return "Thanks! Can you also provide your name?" | |
elif not customer_email: | |
return "Got it! Can you provide your email address?" | |
return f"Got your mobile number: {customer_mobile}. How can I assist you further?" | |
elif "my mobile number is" in command: | |
return "Sorry, I couldn't detect a valid mobile number. Please try again." | |
# Handle menu request | |
if "menu" in command or "yes" in command or "yeah" in command: | |
return display_menu() | |
# Handle category selection | |
for category, items in MENU.items(): | |
if category.lower() in command: | |
current_category = category | |
item_list = ", ".join([f"{item} (${price})" for item, price in items.items()]) | |
return f"You're in the {category} category. We have: {item_list}. What would you like to order?" | |
# Handle item selection after category selection | |
if current_category: | |
for item in MENU[current_category].keys(): | |
if item.lower() in command: | |
current_item = item | |
awaiting_quantity = True | |
return f"How many quantities of {current_item} would you like?" | |
# Handle quantity input | |
if awaiting_quantity: | |
quantity = extract_quantity(command) # Extract quantity from command | |
if quantity is not None: | |
cart.append((current_item, MENU[current_category][current_item], quantity)) | |
awaiting_quantity = False | |
total = sum(i[1] * i[2] for i in cart) | |
cart_summary = ", ".join([f"{i[0]} x{i[2]} (${i[1] * i[2]})" for i in cart]) | |
return f"Added {quantity} x {current_item} to your cart. Your current cart: {cart_summary}. Total: ${total}. Would you like to add more items?" | |
else: | |
return "Sorry, I couldn't understand the quantity. Please provide a valid quantity." | |
# Handle final order | |
if "final order" in command or "submit" in command or "proceed" in command or "place the order" in command: | |
if not customer_name: | |
return "Can you please provide your name? You can say, 'My name is [your name]'" | |
if not customer_email: | |
return "Can you please provide your email? You can say, 'My email is [your email]'" | |
if not customer_mobile: | |
return "Can you please provide your mobile number? You can say, 'My mobile is [your number]'" | |
if cart: | |
total = sum(item[1] * item[2] for item in cart) | |
store_response = store_order_in_salesforce(cart, total, customer_name, customer_email, customer_mobile) | |
cart.clear() # Ensure cart is cleared after final order | |
return f"Your final order has been placed successfully! Total price: ${total}. {store_response}" | |
else: | |
return "Your cart is empty. Please add items before placing the final order." | |
return "Sorry, I didn't understand that. Please try again." | |
if __name__ == "__main__": | |
app.run(host="0.0.0.0", port=7860) |