Ashwin998/orderingsystem
0
1import gradio as gr2import json3import pandas as pd4from typing import Dict, List, Optional, Tuple5from dataclasses import dataclass6import os7from groq import Groq8import logging9import PyPDF210import docx11import re12from price_parser import Price13 14# Configure logging15logging.basicConfig(level=logging.INFO)16logger = logging.getLogger(__name__)17 18@dataclass19class MenuItem:20 name: str21 price: float22 category: str23 description: str = ""24 dietary_info: List[str] = None25 protein: Optional[float] = None26 calories: Optional[int] = None27 28 def __post_init__(self):29 if self.dietary_info is None:30 self.dietary_info = []31 32@dataclass33class CartItem:34 name: str35 price: float36 quantity: int37 38class Cart:39 def __init__(self):40 self.items: List[CartItem] = []41 self.total: float = 0.042 43 def add_item(self, item: MenuItem, quantity: int = 1):44 """Add an item to the cart."""45 if quantity <= 0:46 raise ValueError("Quantity must be greater than 0.")47 48 # Check if the item already exists in the cart49 for cart_item in self.items:50 if cart_item.name == item.name:51 cart_item.quantity += quantity52 self.total += item.price * quantity53 return54 55 # If the item is not in the cart, add it56 self.items.append(CartItem(name=item.name, price=item.price, quantity=quantity))57 self.total += item.price * quantity58 59 def remove_item(self, item_name: str):60 """Remove an item from the cart."""61 self.items = [item for item in self.items if item.name != item_name]62 self._update_total()63 64 def clear(self):65 """Clear the cart."""66 self.items = []67 self.total = 0.068 69 def _update_total(self):70 """Recalculate the total price of the cart."""71 self.total = sum(item.price * item.quantity for item in self.items)72 73 def get_cart_summary(self) -> str:74 """Return a formatted summary of the cart."""75 if not self.items:76 return "Your cart is empty."77 78 summary = "๐ **Your Cart:**\n"79 for item in self.items:80 summary += f"- {item.name} (${item.price:.2f}) x {item.quantity} = ${item.price * item.quantity:.2f}\n"81 summary += f"**Total:** ${self.total:.2f}"82 return summary83 84class MenuParser:85 @staticmethod86 def extract_price(text: str) -> float:87 """Extract price from text using price_parser library."""88 price = Price.fromstring(text)89 return float(price.amount) if price.amount else 0.090 91 @staticmethod92 def extract_menu_items(text: str) -> List[Dict]:93 """Extract menu items from raw text."""94 menu_items = []95 current_category = "Uncategorized"96 97 # Split text into lines and process98 lines = text.split('\n')99 for line in lines:100 line = line.strip()101 if not line:102 continue103 104 # Check if line is a category header105 if line.isupper() or line.endswith(':'):106 current_category = line.rstrip(':')107 continue108 109 # Try to extract item name and price110 price_match = re.search(r'\$?\d+\.?\d*', line)111 if price_match:112 price_str = price_match.group()113 name = line[:price_match.start()].strip()114 price = MenuParser.extract_price(price_str)115 116 # Extract any description that might follow the price117 description = line[price_match.end():].strip()118 119 # Create menu item120 menu_items.append({121 'name': name,122 'price': price,123 'category': current_category,124 'description': description,125 'dietary_info': MenuParser.extract_dietary_info(line)126 })127 128 return menu_items129 130 @staticmethod131 def extract_dietary_info(text: str) -> List[str]:132 """Extract dietary information from text."""133 dietary_keywords = ['vegan', 'vegetarian', 'gluten-free', 'dairy-free', 134 'nut-free', 'spicy', 'halal', 'kosher']135 found_info = []136 lower_text = text.lower()137 for keyword in dietary_keywords:138 if keyword in lower_text:139 found_info.append(keyword)140 return found_info141 142class MenuState:143 def __init__(self):144 self.menu_items: Dict[str, MenuItem] = {}145 self.is_menu_loaded = False146 147 def load_menu(self, file) -> bool:148 """Load and validate menu data from uploaded file."""149 try:150 file_extension = os.path.splitext(file.name)[1].lower()151 152 if file_extension == '.txt':153 with open(file.name, 'r', encoding='utf-8') as f:154 text_content = f.read()155 menu_data = MenuParser.extract_menu_items(text_content)156 157 elif file_extension == '.pdf':158 text_content = ""159 with open(file.name, 'rb') as f:160 pdf_reader = PyPDF2.PdfReader(f)161 for page in pdf_reader.pages:162 text_content += page.extract_text()163 menu_data = MenuParser.extract_menu_items(text_content)164 165 elif file_extension == '.docx':166 doc = docx.Document(file.name)167 text_content = "\n".join([paragraph.text for paragraph in doc.paragraphs])168 menu_data = MenuParser.extract_menu_items(text_content)169 170 elif file_extension in ['.json', '.csv', '.xlsx']:171 if file_extension == '.json':172 with open(file.name, 'r') as f:173 menu_data = json.load(f)174 if isinstance(menu_data, dict) and 'menu_items' in menu_data:175 menu_data = menu_data['menu_items']176 else:177 df = pd.read_csv(file) if file_extension == '.csv' else pd.read_excel(file)178 menu_data = df.to_dict('records')179 else:180 raise ValueError(f"Unsupported file format: {file_extension}")181 182 # Process extracted menu items183 self.menu_items.clear()184 for item in menu_data:185 if self._validate_menu_item(item):186 menu_item = MenuItem(187 name=item['name'],188 price=float(item['price']),189 category=item.get('category', 'Uncategorized'),190 description=item.get('description', ''),191 dietary_info=item.get('dietary_info', []),192 protein=item.get('protein'),193 calories=item.get('calories')194 )195 self.menu_items[item['name'].lower()] = menu_item196 197 self.is_menu_loaded = bool(self.menu_items)198 return self.is_menu_loaded199 200 except Exception as e:201 logger.error(f"Error loading menu: {str(e)}")202 return False203 204 def _validate_menu_item(self, item: dict) -> bool:205 """Validate required fields in menu item."""206 return 'name' in item and 'price' in item207 208class MenuChatbot:209 def __init__(self):210 self.menu_state = MenuState()211 self.cart = Cart()212 self.groq_client = Groq(api_key=os.getenv("GROQ_API_KEY"))213 214 def get_welcome_message(self) -> str:215 """Return the welcome message for the chatbot."""216 return """๐ Welcome to the Restaurant Menu Chatbot! 217 218I can help you:219โข Find dishes based on your preferences220โข Filter by price range221โข Suggest combinations within your budget222โข Check for dietary restrictions223 224First, please upload a menu file (TXT, PDF, DOCX, JSON, CSV, or Excel), then ask me anything about the menu!"""225 226 def process_menu_upload(self, file) -> str:227 """Handle menu file upload and validation."""228 if file is None:229 return "Please upload a menu file."230 231 success = self.menu_state.load_menu(file)232 if success:233 return f"โ
Menu loaded successfully with {len(self.menu_state.menu_items)} items! You can now ask me questions about the menu."234 return "โ Error loading menu. Please check the file format and try again."235 236 def process_query(self, message: str, history: List[Tuple[str, str]]) -> str:237 """Process user query and generate response."""238 if not self.menu_state.is_menu_loaded:239 return "Please upload a menu file first before asking questions."240 241 try:242 # Prepare context for the LLM243 menu_context = self._prepare_menu_context()244 245 # Construct the prompt for the LLM246 prompt = f"""247 You are a helpful restaurant menu assistant. Below is the menu data:248 249 {menu_context}250 251 The user has asked: "{message}"252 253 Based on the menu items above, provide a detailed and structured response that:254 1. Lists all relevant menu items.255 2. Includes the price, category, and dietary information for each item.256 3. If the user asks for specific filters (e.g., vegetarian, under $10), apply those filters.257 4. Format the response in a clear and user-friendly way.258 259 Example response for "Show me all vegetarian options":260 - Garlic Bread ($5.99) - Starters - Vegetarian261 - Vegetable Stir Fry ($12.99) - Main Course - Vegetarian, Vegan262 - Bruschetta ($7.99) - Appetisers - Vegetarian263 - Chocolate Lava Cake ($8.99) - Desserts - Vegetarian264 - French Fries ($4.99) - Sides - Vegetarian, Vegan265 - Caesar Salad ($6.99) - Sides - Vegetarian266 - Fresh Orange Juice ($4.99) - Beverages - Vegetarian, Vegan267 """268 269 # Get response from Groq270 completion = self.groq_client.chat.completions.create(271 model="llama3-8b-8192",272 messages=[{"role": "user", "content": prompt}],273 temperature=0.1,274 max_tokens=500275 )276 277 response = completion.choices[0].message.content278 return self._validate_and_format_response(response)279 280 except Exception as e:281 logger.error(f"Error processing query: {str(e)}")282 return "I apologize, but I encountered an error processing your request. Please try again."283 284 def _prepare_menu_context(self) -> str:285 """Prepare menu context for the LLM."""286 menu_items = []287 for item in self.menu_state.menu_items.values():288 menu_items.append(289 f"{item.name} (${item.price:.2f}) - {item.category} - "290 f"Dietary: {', '.join(item.dietary_info)}"291 )292 return "\n".join(menu_items)293 294 def _validate_and_format_response(self, response: str) -> str:295 """Validate and format the LLM response."""296 if not response.strip():297 return "I apologize, but I couldn't generate a proper response. Please try rephrasing your question."298 return response299 300 def add_to_cart(self, item_name: str, quantity: int = 1) -> str:301 """Add an item to the cart."""302 if not self.menu_state.is_menu_loaded:303 return "Please upload a menu file first."304 305 item_name_lower = item_name.lower()306 if item_name_lower not in self.menu_state.menu_items:307 return f"โ Item '{item_name}' not found in the menu."308 309 item = self.menu_state.menu_items[item_name_lower]310 self.cart.add_item(item, quantity)311 return f"โ
Added {quantity} x {item.name} to your cart."312 313 def view_cart(self) -> str:314 """Return a summary of the cart."""315 return self.cart.get_cart_summary()316 317def create_gradio_interface():318 """Create and configure the Gradio interface."""319 chatbot = MenuChatbot()320 321 with gr.Blocks() as interface:322 gr.Markdown("# ๐ฝ๏ธ Restaurant Menu Chatbot")323 324 # Welcome message325 gr.Markdown(chatbot.get_welcome_message())326 327 with gr.Row():328 menu_upload = gr.File(329 label="Upload Menu File (TXT, PDF, DOCX, JSON, CSV, or Excel)",330 file_types=[".txt", ".pdf", ".docx", ".json", ".csv", ".xlsx"]331 )332 upload_status = gr.Textbox(label="Upload Status", interactive=False)333 334 chatbot_interface = gr.Chatbot(335 label="Chat History",336 height=400337 )338 339 with gr.Row():340 msg = gr.Textbox(341 label="Type your question here...",342 placeholder="e.g., 'What vegetarian options do you have under $15?'",343 scale=4344 )345 submit_btn = gr.Button("Submit", scale=1)346 347 # Display menu items as buttons348 menu_items_display = gr.Column()349 350 # Quantity input and add to cart button351 with gr.Row():352 quantity_input = gr.Number(353 label="Quantity",354 value=1,355 minimum=1,356 scale=1357 )358 add_to_cart_btn = gr.Button("Add to Cart", scale=1)359 360 # Cart management buttons361 with gr.Row():362 view_cart_btn = gr.Button("View Cart")363 clear_cart_btn = gr.Button("Clear Cart")364 365 clear_chat = gr.Button("Clear Chat History")366 367 def user_query(message, history):368 if message.strip() == "":369 return "", history370 response = chatbot.process_query(message, history)371 history.append((message, response))372 return "", history373 374 def display_menu_items(file):375 """Display menu items as buttons after uploading the menu."""376 if file is None:377 return []378 379 chatbot.process_menu_upload(file)380 menu_items = list(chatbot.menu_state.menu_items.values())381 buttons = [382 gr.Button(383 f"{item.name} (${item.price:.2f})",384 variant="secondary"385 )386 for item in menu_items387 ]388 return buttons389 390 def add_item_to_cart(item_name, quantity):391 """Add an item to the cart."""392 return chatbot.add_to_cart(item_name, quantity)393 394 def view_cart():395 """View the cart summary."""396 return chatbot.view_cart()397 398 def clear_cart():399 """Clear the cart."""400 chatbot.cart.clear()401 return "๐ Cart cleared."402 403 # Event handlers404 menu_upload.change(405 fn=display_menu_items,406 inputs=[menu_upload],407 outputs=[menu_items_display]408 )409 410 msg.submit(411 fn=user_query,412 inputs=[msg, chatbot_interface],413 outputs=[msg, chatbot_interface]414 )415 416 submit_btn.click(417 fn=user_query,418 inputs=[msg, chatbot_interface],419 outputs=[msg, chatbot_interface]420 )421 422 # Add to cart functionality423 for item in chatbot.menu_state.menu_items.values():424 add_to_cart_btn.click(425 fn=add_item_to_cart,426 inputs=[gr.Textbox(value=item.name, visible=False), quantity_input],427 outputs=[upload_status]428 )429 430 view_cart_btn.click(431 fn=view_cart,432 inputs=None,433 outputs=[upload_status]434 )435 436 clear_cart_btn.click(437 fn=clear_cart,438 inputs=None,439 outputs=[upload_status]440 )441 442 clear_chat.click(443 fn=lambda: None,444 inputs=None,445 outputs=chatbot_interface,446 queue=False447 )448 449 return interface450 451if __name__ == "__main__":452 interface = create_gradio_interface()453 interface.launch(share=True)