Spaces:
Sleeping
Sleeping
| 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 | |
| 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) | |
| 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 | |
| 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 | |
| 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 | |
| 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() |