vvinayakkkkk's picture
Create app.py
d1b4064 verified
import streamlit as st
import pandas as pd
import numpy as np
import faiss
import json
import time
import google.generativeai as genai
from datetime import datetime
import os
from sentence_transformers import SentenceTransformer
import uuid
# Configuration
API_KEYS = [
os.getenv("GEMINI_API_KEY_1"),
os.getenv("GEMINI_API_KEY_2"),
os.getenv("GEMINI_API_KEY_3"),
os.getenv("GEMINI_API_KEY_4"),
os.getenv("GEMINI_API_KEY_5"),
os.getenv("GEMINI_API_KEY_6"),
os.getenv("GEMINI_API_KEY_7"),
os.getenv("GEMINI_API_KEY_8"),
os.getenv("GEMINI_API_KEY_9"),
os.getenv("GEMINI_API_KEY_10"),
os.getenv("GEMINI_API_KEY_11"),
os.getenv("GEMINI_API_KEY_12"),
os.getenv("GEMINI_API_KEY_13"),
os.getenv("GEMINI_API_KEY_14")
]
# Function to get a valid API key
def get_valid_api_key():
for api_key in API_KEYS:
if api_key:
return api_key
raise ValueError("No valid API keys available")
# Set up Gemini
genai.configure(api_key=get_valid_api_key())
model = genai.GenerativeModel('gemini-1.5-pro')
# Initialize the embedding model
@st.cache_resource
def load_embedding_model():
return SentenceTransformer('all-MiniLM-L6-v2')
embedding_model = load_embedding_model()
# Sample menu data (in a real app, this would come from a database)
@st.cache_data
def load_menu_data():
return pd.DataFrame({
'id': range(1, 21),
'name': [
'Margherita Pizza', 'Veggie Supreme Pizza', 'BBQ Chicken Pizza',
'Caesar Salad', 'Greek Salad', 'Garden Salad',
'Spaghetti Bolognese', 'Vegetable Pasta', 'Seafood Pasta',
'Chocolate Cake', 'Tiramisu', 'Cheesecake',
'Espresso', 'Cappuccino', 'Iced Coffee',
'Garlic Bread', 'Bruschetta', 'Mozzarella Sticks',
'Coca-Cola', 'Sprite'
],
'category': [
'Pizza', 'Pizza', 'Pizza',
'Salad', 'Salad', 'Salad',
'Pasta', 'Pasta', 'Pasta',
'Dessert', 'Dessert', 'Dessert',
'Beverage', 'Beverage', 'Beverage',
'Appetizer', 'Appetizer', 'Appetizer',
'Beverage', 'Beverage'
],
'price': [
12.99, 14.99, 15.99,
8.99, 9.99, 7.99,
13.99, 12.99, 16.99,
6.99, 7.99, 7.99,
3.99, 4.99, 4.99,
5.99, 6.99, 7.99,
2.99, 2.99
],
'description': [
'Classic pizza with tomato sauce, mozzarella, and basil',
'Pizza topped with bell peppers, onions, mushrooms, olives, and tomatoes',
'Pizza with BBQ chicken, red onions, and cilantro',
'Romaine lettuce, croutons, parmesan, and Caesar dressing',
'Mixed greens, feta, olives, cucumbers, and Greek dressing',
'Fresh mixed greens with seasonal vegetables and dressing',
'Spaghetti with rich meat sauce and parmesan',
'Pasta with mixed vegetables in marinara sauce',
'Pasta with shrimp, mussels, and calamari in garlic sauce',
'Rich chocolate cake with ganache frosting',
'Classic Italian dessert with coffee-soaked ladyfingers and mascarpone',
'Creamy New York style cheesecake',
'Strong Italian espresso shot',
'Espresso with steamed milk and foam',
'Chilled coffee served over ice',
'Toasted bread with garlic butter and herbs',
'Toasted bread topped with diced tomatoes, basil, and olive oil',
'Fried mozzarella sticks with marinara sauce',
'Classic cola soft drink',
'Lemon-lime soft drink'
],
'dietary_info': [
'vegetarian',
'vegetarian',
'contains meat',
'vegetarian',
'vegetarian',
'vegetarian, vegan',
'contains meat',
'vegetarian',
'contains seafood',
'vegetarian, contains gluten',
'vegetarian, contains gluten',
'vegetarian, contains gluten',
'vegetarian, vegan',
'vegetarian',
'vegetarian',
'vegetarian, contains gluten',
'vegetarian, contains gluten',
'vegetarian, contains gluten',
'vegetarian, vegan',
'vegetarian, vegan'
],
'preparation_time': [
15, 18, 18,
5, 5, 5,
12, 12, 15,
5, 5, 5,
2, 3, 3,
8, 10, 10,
1, 1
],
'popular': [
True, True, True,
True, False, False,
True, False, True,
True, True, True,
False, True, False,
True, False, True,
True, True
]
})
menu_df = load_menu_data()
# Build FAISS index for menu items
@st.cache_resource
def build_faiss_index(menu_df):
# Create item descriptions for embedding
item_texts = []
for _, row in menu_df.iterrows():
text = f"{row['name']} - {row['description']} - {row['category']} - {row['dietary_info']}"
item_texts.append(text)
# Generate embeddings
embeddings = embedding_model.encode(item_texts)
# Convert to float32 (required by FAISS)
embeddings = np.array([embedding for embedding in embeddings]).astype('float32')
# Build FAISS index
dimension = embeddings.shape[1]
index = faiss.IndexFlatL2(dimension)
index.add(embeddings)
return index, embeddings
index, embeddings = build_faiss_index(menu_df)
# Load discount data
@st.cache_data
def load_discount_data():
return [
{
'id': 1,
'name': 'Happy Hour',
'description': '20% off all beverages between 3PM-5PM',
'discount_type': 'percentage',
'discount_value': 20,
'min_order_value': 0,
'eligible_categories': ['Beverage'],
'days_valid': ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
'start_time': '15:00',
'end_time': '17:00',
'code': 'HAPPY20'
},
{
'id': 2,
'name': 'Family Feast',
'description': '$10 off orders over $50',
'discount_type': 'amount',
'discount_value': 10,
'min_order_value': 50,
'eligible_categories': ['All'],
'days_valid': ['All'],
'start_time': '00:00',
'end_time': '23:59',
'code': 'FAMILY10'
},
{
'id': 3,
'name': 'Pizza Wednesday',
'description': 'Buy one pizza, get second at half price',
'discount_type': 'special',
'discount_value': 50,
'min_order_value': 0,
'eligible_categories': ['Pizza'],
'days_valid': ['Wednesday'],
'start_time': '00:00',
'end_time': '23:59',
'code': 'PIZZA50'
},
{
'id': 4,
'name': 'Dessert Delight',
'description': '15% off all desserts',
'discount_type': 'percentage',
'discount_value': 15,
'min_order_value': 0,
'eligible_categories': ['Dessert'],
'days_valid': ['Monday', 'Tuesday', 'Sunday'],
'start_time': '00:00',
'end_time': '23:59',
'code': 'SWEET15'
},
{
'id': 5,
'name': 'First-Time Customer',
'description': '10% off your first order',
'discount_type': 'percentage',
'discount_value': 10,
'min_order_value': 0,
'eligible_categories': ['All'],
'days_valid': ['All'],
'start_time': '00:00',
'end_time': '23:59',
'code': 'WELCOME10'
}
]
discounts = load_discount_data()
# Load daily specials
@st.cache_data
def load_daily_specials():
return {
'Monday': {
'name': 'Meatless Monday',
'description': 'All vegetarian dishes at 15% discount',
'featured_item': 'Veggie Supreme Pizza'
},
'Tuesday': {
'name': 'Pasta Tuesday',
'description': 'All pasta dishes come with free garlic bread',
'featured_item': 'Spaghetti Bolognese'
},
'Wednesday': {
'name': 'Pizza Wednesday',
'description': 'Buy one pizza, get second at half price',
'featured_item': 'BBQ Chicken Pizza'
},
'Thursday': {
'name': 'Thirsty Thursday',
'description': 'All beverages are buy one get one free',
'featured_item': 'Iced Coffee'
},
'Friday': {
'name': 'Family Friday',
'description': 'Free dessert with orders over $40',
'featured_item': 'Chocolate Cake'
},
'Saturday': {
'name': 'Sampler Saturday',
'description': '20% off all appetizers',
'featured_item': 'Mozzarella Sticks'
},
'Sunday': {
'name': 'Sweet Sunday',
'description': '25% off all desserts',
'featured_item': 'Tiramisu'
}
}
daily_specials = load_daily_specials()
# Initialize session state
if 'chat_history' not in st.session_state:
st.session_state.chat_history = []
if 'current_order' not in st.session_state:
st.session_state.current_order = []
if 'order_id' not in st.session_state:
st.session_state.order_id = str(uuid.uuid4())[:8]
if 'previous_orders' not in st.session_state:
st.session_state.previous_orders = []
if 'applied_discount' not in st.session_state:
st.session_state.applied_discount = None
if 'is_first_time_customer' not in st.session_state:
st.session_state.is_first_time_customer = True
# Search menu items based on query
def search_menu_items(query, top_k=5):
# Convert query to embedding
query_embedding = embedding_model.encode([query]).astype('float32')
# Search in FAISS index
distances, indices = index.search(query_embedding, top_k)
# Get matching items
results = []
for i in indices[0]:
if i < len(menu_df):
results.append(menu_df.iloc[i].to_dict())
return results
# Get applicable discounts for the current order
def get_applicable_discounts(order_items):
# If no items in order, no discounts apply
if not order_items:
return []
applicable = []
total_value = sum(item['price'] * item['quantity'] for item in order_items)
categories_in_order = set(menu_df.loc[menu_df['id'] == item['id'], 'category'].iloc[0] for item in order_items)
current_day = datetime.now().strftime("%A")
current_time = datetime.now().strftime("%H:%M")
for discount in discounts:
# Check minimum order value
if total_value < discount['min_order_value']:
continue
# Check day validity
if 'All' not in discount['days_valid'] and current_day not in discount['days_valid']:
continue
# Check time validity
if current_time < discount['start_time'] or current_time > discount['end_time']:
continue
# Check category eligibility
if 'All' not in discount['eligible_categories']:
if not any(category in discount['eligible_categories'] for category in categories_in_order):
continue
# Special handling for first-time customer discount
if discount['code'] == 'WELCOME10' and not st.session_state.is_first_time_customer:
continue
# Special handling for "buy one get one" type discounts
if discount['code'] == 'PIZZA50':
pizza_count = sum(item['quantity'] for item in order_items
if menu_df.loc[menu_df['id'] == item['id'], 'category'].iloc[0] == 'Pizza')
if pizza_count < 2:
continue
applicable.append(discount)
return applicable
# Apply discount to order
def apply_discount(order, discount):
total = order['total']
discount_amount = 0
if discount['discount_type'] == 'percentage':
discount_amount = total * (discount['discount_value'] / 100)
elif discount['discount_type'] == 'amount':
discount_amount = discount['discount_value']
elif discount['discount_type'] == 'special' and discount['code'] == 'PIZZA50':
# Handle buy one get one half off for pizzas
pizza_items = [item for item in order['items']
if menu_df.loc[menu_df['id'] == item['id'], 'category'].iloc[0] == 'Pizza']
if len(pizza_items) >= 2:
# Sort by price to discount the cheaper one
pizza_items.sort(key=lambda x: x['price'])
discount_amount = pizza_items[0]['price'] * 0.5
# Cap the discount at the total order value
discount_amount = min(discount_amount, total)
# Update order with discount
order['discount'] = {
'name': discount['name'],
'code': discount['code'],
'amount': discount_amount
}
order['discounted_total'] = total - discount_amount
return order
# Process user query with Gemini
def process_query(user_query, chat_history):
# Format chat history
formatted_history = "\n".join([f"{'User' if msg['role'] == 'user' else 'Assistant'}: {msg['content']}" for msg in chat_history[-5:] if chat_history])
# Search for relevant menu items
menu_items = search_menu_items(user_query)
menu_context = json.dumps([{k: v for k, v in item.items() if k != 'id'} for item in menu_items])
# Current order context
order_context = []
if st.session_state.current_order:
order_context = [f"{item['quantity']}x {item['name']} (${item['price']} each)" for item in st.session_state.current_order]
order_context_str = "Current order: " + ", ".join(order_context) if order_context else "Current order: Empty"
# Get applicable discounts
applicable_discounts = get_applicable_discounts(st.session_state.current_order)
discount_context = "No applicable discounts available." if not applicable_discounts else "Available discounts:\n" + "\n".join([
f"• {d['name']}: {d['description']} (Code: {d['code']})" for d in applicable_discounts
])
# Get today's special
today = datetime.now().strftime("%A")
today_special = daily_specials.get(today, {'name': 'No special today', 'description': ''})
special_context = f"Today's Special ({today}):\n• {today_special['name']} - {today_special['description']}"
# Create prompt for Gemini
prompt = f"""You are an AI assistant for a restaurant ordering system. Help the customer with their order.
Recent conversation:
{formatted_history}
Current user query: {user_query}
{order_context_str}
{special_context}
{discount_context}
Most relevant menu items:
{menu_context}
You can handle:
1. Questions about menu items, ingredients, preparation time, and dietary restrictions
2. Recommendations based on customer preferences
3. Adding items to the order (respond with ADD_TO_ORDER: [item_name], [quantity])
4. Removing items from the order (respond with REMOVE_FROM_ORDER: [item_name])
5. Clearing the order (respond with CLEAR_ORDER)
6. Completing the order (respond with COMPLETE_ORDER)
7. Applying a discount code (respond with APPLY_DISCOUNT: [discount_code])
Format your responses using bullet points (•) when listing multiple items, options, or steps.
When recommending items, explain why they might be good choices.
For any daily specials or promotions, highlight them clearly in your response.
For specific actions, use the special commands listed above followed by your natural response.
"""
# Get response from Gemini
try:
response = model.generate_content(prompt)
except Exception as e:
# Rotate API key and retry
API_KEYS.pop(0)
if API_KEYS:
genai.configure(api_key=get_valid_api_key())
model = genai.GenerativeModel('gemini-1.5-pro')
response = model.generate_content(prompt)
else:
raise e
# Process any special commands in the response
commands = []
response_text = response.text
if "ADD_TO_ORDER:" in response_text:
parts = response_text.split("ADD_TO_ORDER:")
command_part = parts[1].split("\n")[0].strip()
item_name, quantity = command_part.rsplit(",", 1)
item_name = item_name.strip()
quantity = int(quantity.strip())
commands.append({"type": "add", "item": item_name, "quantity": quantity})
# Remove the command from the response
response_text = response_text.replace(f"ADD_TO_ORDER: {command_part}", "")
if "REMOVE_FROM_ORDER:" in response_text:
parts = response_text.split("REMOVE_FROM_ORDER:")
item_name = parts[1].split("\n")[0].strip()
commands.append({"type": "remove", "item": item_name})
# Remove the command from the response
response_text = response_text.replace(f"REMOVE_FROM_ORDER: {item_name}", "")
if "CLEAR_ORDER" in response_text:
commands.append({"type": "clear"})
# Remove the command from the response
response_text = response_text.replace("CLEAR_ORDER", "")
if "COMPLETE_ORDER" in response_text:
commands.append({"type": "complete"})
# Remove the command from the response
response_text = response_text.replace("COMPLETE_ORDER", "")
if "APPLY_DISCOUNT:" in response_text:
parts = response_text.split("APPLY_DISCOUNT:")
discount_code = parts[1].split("\n")[0].strip()
commands.append({"type": "discount", "code": discount_code})
# Remove the command from the response
response_text = response_text.replace(f"APPLY_DISCOUNT: {discount_code}", "")
# Format response text with Markdown
response_text = response_text.strip().replace("\n", "\n\n")
return response_text, commands
# Add item to order
def add_to_order(item_name, quantity):
# Search for the item in the menu
matching_items = menu_df[menu_df['name'].str.lower() == item_name.lower()]
if not matching_items.empty:
item = matching_items.iloc[0]
# Check if item is already in order
for order_item in st.session_state.current_order:
if order_item['name'].lower() == item_name.lower():
order_item['quantity'] += quantity
return True
# Add new item to order
st.session_state.current_order.append({
'id': item['id'],
'name': item['name'],
'price': item['price'],
'quantity': quantity,
'preparation_time': item['preparation_time']
})
return True
else:
# Try fuzzy matching
similar_items = search_menu_items(item_name, top_k=1)
if similar_items:
item = similar_items[0]
# Check if item is already in order
for order_item in st.session_state.current_order:
if order_item['name'].lower() == item['name'].lower():
order_item['quantity'] += quantity
return True
# Add new item to order
st.session_state.current_order.append({
'id': item['id'],
'name': item['name'],
'price': item['price'],
'quantity': quantity,
'preparation_time': item['preparation_time']
})
return True
return False
# Remove item from order
def remove_from_order(item_name):
for i, item in enumerate(st.session_state.current_order):
if item['name'].lower() == item_name.lower():
st.session_state.current_order.pop(i)
return True
return False
# Clear the order
def clear_order():
st.session_state.current_order = []
st.session_state.order_id = str(uuid.uuid4())[:8]
# Complete the order
def complete_order():
if st.session_state.current_order:
# Calculate order details
items = st.session_state.current_order
total = sum(item['price'] * item['quantity'] for item in items)
max_prep_time = max(item['preparation_time'] for item in items)
# Calculate estimated ready time
preparation_end_time = datetime.now().timestamp() + (max_prep_time * 60)
estimated_ready_time = datetime.fromtimestamp(preparation_end_time).strftime("%H:%M:%S")
# Create order summary
order = {
'order_id': st.session_state.order_id,
'items': items.copy(),
'total': total,
'preparation_time': max_prep_time,
'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
'status': 'Confirmed',
'estimated_ready_time': estimated_ready_time # Add this key
}
# Apply discount if any
if st.session_state.applied_discount:
order = apply_discount(order, st.session_state.applied_discount)
st.session_state.applied_discount = None
# Add to previous orders
st.session_state.previous_orders.append(order)
# User is no longer a first-time customer
st.session_state.is_first_time_customer = False
# Clear current order
st.session_state.current_order = []
st.session_state.order_id = str(uuid.uuid4())[:8]
return order
return None
# Generate order report
def generate_order_report(order):
preparation_end_time = datetime.now().timestamp() + (order['preparation_time'] * 60)
estimated_ready_time = datetime.fromtimestamp(preparation_end_time).strftime("%H:%M:%S")
report = f"""
## Order Confirmation - #{order['order_id']}
**Order Time:** {order['timestamp']}
**Estimated Ready Time:** {estimated_ready_time} (approximately {order['preparation_time']} minutes)
### Items Ordered:
"""
for item in order['items']:
report += f"• {item['quantity']}x {item['name']} - ${item['price'] * item['quantity']:.2f}\n"
report += f"""
**Subtotal:** ${order['total']:.2f}
"""
if 'discount' in order:
report += f"""
**Discount Applied:** {order['discount']['name']} (Code: {order['discount']['code']})
**Discount Amount:** -${order['discount']['amount']:.2f}
**Total After Discount:** ${order['discounted_total']:.2f}
"""
else:
report += f"**Total Amount:** ${order['total']:.2f}\n"
report += f"""
Thank you for your order! You can check the status of your order using your order ID: {order['order_id']}
"""
return report
# Main Streamlit UI
def main():
st.title("🍽️ Smart Restaurant Ordering System")
# Get today's special for display
today = datetime.now().strftime("%A")
today_special = daily_specials.get(today, {'name': 'No special today', 'description': ''})
# Display today's special
st.info(f"**{today}'s Special:** {today_special['name']} - {today_special['description']}")
# Display available items for today
st.header("Available Items for Today")
available_items = menu_df[menu_df['popular'] == True]
for _, item in available_items.iterrows():
st.write(f"• {item['name']} - ${item['price']:.2f} ({item['category']})")
st.write("Only these items are available for today.")
# Sidebar for order details and discount options
with st.sidebar:
st.header("Your Current Order")
if st.session_state.current_order:
total = 0
for item in st.session_state.current_order:
st.write(f"• {item['quantity']}x {item['name']} - ${item['price'] * item['quantity']:.2f}")
total += item['price'] * item['quantity']
st.write("---")
st.write(f"**Subtotal:** ${total:.2f}")
# Display applicable discounts
applicable_discounts = get_applicable_discounts(st.session_state.current_order)
if applicable_discounts:
st.write("**Available Discounts:**")
for discount in applicable_discounts:
if st.button(f"Apply {discount['code']}: {discount['description']}", key=f"disc_{discount['code']}"):
st.session_state.applied_discount = discount
if st.session_state.applied_discount:
temp_order = {'total': total, 'items': st.session_state.current_order}
discounted_order = apply_discount(temp_order, st.session_state.applied_discount)
st.success(f"**Discount Applied:** {st.session_state.applied_discount['name']}")
st.write(f"**Discount Amount:** -${discounted_order['discount']['amount']:.2f}")
st.write(f"**Total After Discount:** ${discounted_order['discounted_total']:.2f}")
if st.button("Confirm Order"):
order = complete_order()
if order:
report = generate_order_report(order)
st.session_state.chat_history.append({
'role': 'assistant',
'content': report
})
else:
st.write("Your order is empty. Add items by chatting with our assistant!")
st.write("---")
# Display discount information
st.header("Current Promotions")
st.write(f"**{today}'s Special:** {today_special['name']} - {today_special['description']}")
with st.expander("View All Discounts"):
for discount in discounts:
st.write(f"• **{discount['name']}** ({discount['code']})")
st.write(f" {discount['description']}")
valid_days = "All days" if 'All' in discount['days_valid'] else ", ".join(discount['days_valid'])
st.write(f" Valid: {valid_days} from {discount['start_time']} to {discount['end_time']}")
st.write("")
# Display previous orders
if st.session_state.previous_orders:
st.header("Previous Orders")
for order in st.session_state.previous_orders[-3:]: # Show last 3 orders
st.write(f"**Order #{order['order_id']}** - {order['timestamp']}")
st.write(f"Total: ${order.get('discounted_total', order['total']):.2f}")
if st.button(f"View Details", key=f"view_{order['order_id']}"):
report = generate_order_report(order)
st.info(report)
# Display chat history
for message in st.session_state.chat_history:
if message['role'] == 'user':
st.chat_message('user').write(message['content'])
else:
st.chat_message('assistant').markdown(message['content']) # Use markdown for assistant messages
# Chat input
if prompt := st.chat_input("How can I help with your order today?"):
# Display user message
st.chat_message("user").write(prompt)
# Add to chat history
st.session_state.chat_history.append({
'role': 'user',
'content': prompt
})
# Get AI response
with st.spinner("Processing your request..."):
response_text, commands = process_query(prompt, st.session_state.chat_history)
# Process any commands
command_messages = []
for command in commands:
if command['type'] == 'add':
success = add_to_order(command['item'], command['quantity'])
if success:
command_messages.append(f"Added {command['quantity']}x {command['item']} to your order.")
else:
command_messages.append(f"Could not find {command['item']} in our menu.")
elif command['type'] == 'remove':
success = remove_from_order(command['item'])
if success:
command_messages.append(f"Removed {command['item']} from your order.")
else:
command_messages.append(f"Could not find {command['item']} in your order.")
elif command['type'] == 'clear':
clear_order()
command_messages.append("Your order has been cleared.")
elif command['type'] == 'complete':
order = complete_order()
if order:
report = generate_order_report(order)
st.session_state.chat_history.append({
'role': 'assistant',
'content': report
})
elif command['type'] == 'discount':
discount_code = command['code']
discount = next((d for d in discounts if d['code'] == discount_code), None)
if discount:
st.session_state.applied_discount = discount
command_messages.append(f"Applied discount: {discount['name']}")
else:
command_messages.append(f"Invalid discount code: {discount_code}")
# Combine response with command messages
if command_messages:
response_text += "\n\n" + "\n".join(command_messages)
# Display AI response
st.chat_message("assistant").markdown(response_text) # Use markdown for assistant messages
# Add to chat history
st.session_state.chat_history.append({
'role': 'assistant',
'content': response_text
})
# Run the app
if __name__ == "__main__":
main()