AIVoice11 / app.py
nagasurendra's picture
Update app.py
ace7441 verified
from flask import Flask, render_template_string, request, jsonify
import speech_recognition as sr
from tempfile import NamedTemporaryFile
import os
import ffmpeg
from fuzzywuzzy import process, fuzz
from metaphone import doublemetaphone
import logging
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
# Global variables
cart = {} # Stores items and their quantities in the cart
menu_preferences = "all" # Tracks the current menu preference
default_menu_preferences = "all" # To reset menu preferences
prices = {
"samosa": 9,
"onion pakoda": 10,
"chilli gobi": 12,
"chicken biryani": 14,
"mutton biryani": 16,
"veg biryani": 12,
"paneer butter": 10,
"fish curry": 12,
"chicken manchurian": 14,
"veg manchurian": 12,
"chilli chicken": 14,
"paneer biryani": 13,
"chicken curry": 14
}
menus = {
"all": list(prices.keys()),
"vegetarian": [
"samosa", "onion pakoda", "chilli gobi", "veg biryani", "paneer butter", "veg manchurian", "paneer biryani"
],
"non-vegetarian": [
"chicken biryani", "mutton biryani", "fish curry", "chicken manchurian", "chilli chicken", "chicken curry"
],
"guilt-free": ["samosa", "onion pakoda"]
}
@app.route("/")
def index():
return render_template_string(html_code)
@app.route("/reset-cart", methods=["GET"])
def reset_cart():
global cart, menu_preferences
cart = {}
menu_preferences = default_menu_preferences # Reset menu preferences
return "Cart reset successfully."
@app.route("/process-audio", methods=["POST"])
def process_audio():
try:
# Handle audio input
audio_file = request.files.get("audio")
if not audio_file:
return jsonify({"response": "No audio file provided."}), 400
# Save audio file and convert to WAV format
temp_file = NamedTemporaryFile(delete=False, suffix=".webm")
audio_file.save(temp_file.name)
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)
# Recognize speech
recognizer = sr.Recognizer()
recognizer.dynamic_energy_threshold = True
recognizer.energy_threshold = 100
with sr.AudioFile(converted_file.name) as source:
recognizer.adjust_for_ambient_noise(source, duration=1)
audio_data = recognizer.record(source)
raw_command = recognizer.recognize_google(audio_data).lower()
logging.info(f"Raw recognized command: {raw_command}")
# Preprocess the command
all_menu_items = menus["all"]
command = preprocess_command(raw_command, all_menu_items)
# Process the command and get a response
response = process_command(command)
except sr.UnknownValueError:
response = "Sorry, I couldn't understand. Please try again."
except Exception as e:
response = f"An error occurred: {str(e)}"
finally:
os.unlink(temp_file.name)
os.unlink(converted_file.name)
return jsonify({"response": response})
def preprocess_command(command, menu_items):
"""
Preprocess the user command to improve matching:
- Use fuzzy matching for menu items.
- Use phonetic matching as a fallback.
"""
def phonetic_match(word, options):
word_phonetic = doublemetaphone(word)[0]
for option in options:
if doublemetaphone(option)[0] == word_phonetic:
return option
return None
# First, try fuzzy matching
closest_match = process.extractOne(command, menu_items, scorer=fuzz.token_set_ratio)
if closest_match and closest_match[1] > 60:
return closest_match[0]
# Fallback to phonetic matching
words = command.split()
for word in words:
match = phonetic_match(word, menu_items)
if match:
return match
return command
def process_command(command):
"""
Process the user command based on the current menu and cart.
"""
global cart, menu_preferences
command = command.lower()
# Handle menu preferences
if menu_preferences == "all":
if "non-vegetarian" in command:
menu_preferences = "non-vegetarian"
return "You have chosen the Non-Vegetarian menu. To view the menu, say 'menu'."
elif "vegetarian" in command and "non-vegetarian" not in command:
menu_preferences = "vegetarian"
return "You have chosen the Vegetarian menu. To view the menu, say 'menu'."
elif "guilt-free" in command:
menu_preferences = "guilt-free"
return "You have chosen the Guilt-Free menu. To view the menu, say 'menu'."
elif "all" in command:
menu_preferences = "all"
return "You have chosen the complete menu. To view the menu, say 'menu'."
# Handle commands related to the current menu
menu = menus.get(menu_preferences, menus["all"])
if "menu" in command:
return f"Here is your menu: {', '.join(menu)}. To select an item, say the item name."
elif "price of" in command:
item = command.replace("price of", "").strip()
closest_match = process.extractOne(item, prices.keys(), scorer=fuzz.token_set_ratio)
if closest_match and closest_match[1] > 60:
matched_item = closest_match[0]
return f"The price of {matched_item} is ${prices[matched_item]}."
return "Sorry, I couldn't find that item in the menu."
elif "remove" in command:
item = command.replace("remove", "").strip()
closest_match = process.extractOne(item, list(cart.keys()), scorer=fuzz.token_set_ratio)
if closest_match and closest_match[1] > 60:
matched_item = closest_match[0]
if cart[matched_item] > 1:
cart[matched_item] -= 1
return f"One {matched_item} has been removed from your cart. Current cart: {dict(cart)}."
else:
del cart[matched_item]
return f"{matched_item.capitalize()} has been removed from your cart. Current cart: {dict(cart)}."
return "Sorry, that item is not in your cart."
elif "increase" in command:
item = command.replace("increase", "").strip()
closest_match = process.extractOne(item, list(cart.keys()), scorer=fuzz.token_set_ratio)
if closest_match and closest_match[1] > 60:
matched_item = closest_match[0]
cart[matched_item] += 1
return f"Quantity of {matched_item} increased to {cart[matched_item]}. Current cart: {dict(cart)}."
return "Sorry, that item is not in your cart."
elif "decrease" in command:
item = command.replace("decrease", "").strip()
closest_match = process.extractOne(item, list(cart.keys()), scorer=fuzz.token_set_ratio)
if closest_match and closest_match[1] > 60:
matched_item = closest_match[0]
if cart[matched_item] > 1:
cart[matched_item] -= 1
return f"Quantity of {matched_item} decreased to {cart[matched_item]}. Current cart: {dict(cart)}."
else:
del cart[matched_item]
return f"{matched_item.capitalize()} has been removed from your cart. Current cart: {dict(cart)}."
return "Sorry, that item is not in your cart."
elif any(item in command for item in menu):
closest_match = process.extractOne(command, menu, scorer=fuzz.token_set_ratio)
if closest_match and closest_match[1] > 60:
matched_item = closest_match[0]
cart[matched_item] = cart.get(matched_item, 0) + 1
return f"{matched_item.capitalize()} added to your cart. Current cart: {dict(cart)}. To finalize, say 'final order'."
return "Sorry, I couldn't recognize the item. Could you try again?"
elif "final order" in command:
if cart:
total = sum(prices[item] * count for item, count in cart.items())
response = f"Your final order is: {', '.join(f'{item} x{count}' for item, count in cart.items())}. Your total bill is ${total}. Thank you for ordering! To exit this conversation, say 'goodbye'."
cart.clear()
return response
return "Your cart is empty. Please add items to your cart first."
elif "no" in command or "nothing" in command or "goodbye" in command:
cart.clear()
menu_preferences = default_menu_preferences
return "Goodbye! Thank you for using AI Dining Assistant."
return "Sorry, I couldn't understand that. Please try again."
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 {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
font-family: Arial, sans-serif;
background-color: #f4f4f9;
}
h1 {
color: #333;
}
.mic-button {
font-size: 2rem;
padding: 1rem 2rem;
color: white;
background-color: #007bff;
border: none;
border-radius: 50px;
cursor: pointer;
transition: background-color 0.3s;
}
.mic-button:hover {
background-color: #0056b3;
}
.status, .response {
margin-top: 1rem;
text-align: center;
color: #555;
font-size: 1.2rem;
}
.response {
background-color: #e8e8ff;
padding: 1rem;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
display: none;
}
</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">Response will appear here...</div>
<script>
const micButton = document.getElementById('mic-button');
const status = document.getElementById('status');
const response = document.getElementById('response');
let mediaRecorder;
let audioChunks = [];
let isConversationActive = false;
micButton.addEventListener('click', () => {
if (!isConversationActive) {
isConversationActive = true;
startConversation();
}
});
function startConversation() {
const utterance = new SpeechSynthesisUtterance('Please choose your preference: All, Vegetarian, Non-Vegetarian, or Guilt-Free.');
speechSynthesis.speak(utterance);
utterance.onend = () => {
status.textContent = 'Listening...';
startListening();
};
}
function startListening() {
navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm;codecs=opus' });
mediaRecorder.start();
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 = () => {
console.log("Speech synthesis completed.");
if (data.response.includes("Goodbye")) {
status.textContent = 'Conversation ended. Press the mic button to start again.';
isConversationActive = false;
fetch('/reset-cart'); // Reset the cart dynamically on end
} else if (data.response.includes("Your order is complete")) {
status.textContent = 'Order complete. Thank you for using AI Dining Assistant.';
isConversationActive = false;
fetch('/reset-cart'); // Reset the cart after final order
} else {
status.textContent = 'Listening...';
setTimeout(() => {
startListening();
}, 100);
}
};
utterance.onerror = (e) => {
console.error("Speech synthesis error:", e.error);
status.textContent = 'Error with speech output.';
isConversationActive = false;
};
} catch (error) {
response.textContent = 'Sorry, I could not understand. Please try again.';
response.style.display = 'block';
status.textContent = 'Press the mic button to restart the conversation.';
isConversationActive = false;
}
};
setTimeout(() => mediaRecorder.stop(), 5000);
}).catch(() => {
status.textContent = 'Microphone access denied.';
isConversationActive = false;
});
}
</script>
</body>
</html>
"""
if __name__ == "__main__":
app.run(host="0.0.0.0", port=7860)