CoolFace
Apppublic

Ashwin998/orderingsystem

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
app.py453 linesDownload Raw Back to root
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)