!pip -q install gradio groq pandas import os # Paste your key inside quotes: os.environ["GROQ_API_KEY"] = "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" import os import uuid import time import pandas as pd import gradio as gr from groq import Groq # ----------------------------- # 1) SAMPLE MENU (with images) # ----------------------------- def starter_menu(): # NOTE: These are public image URLs for demo. You can replace with your own. return [ { "id": "PZ001", "name": "Pepperoni Pizza", "category": "Pizza", "price": 1299, "desc": "Cheesy, crispy, and a lil’ spicy.", "tags": ["spicy", "halal"], "image": "https://images.unsplash.com/photo-1601924638867-3ec4d4baf4e1?auto=format&fit=crop&w=800&q=60", "available": True, }, { "id": "PZ002", "name": "Margherita Pizza", "category": "Pizza", "price": 1099, "desc": "Classic tomato + basil vibes.", "tags": ["veg", "halal"], "image": "https://images.unsplash.com/photo-1604068549290-dea0e4a305ca?auto=format&fit=crop&w=800&q=60", "available": True, }, { "id": "BBQ001", "name": "BBQ Chicken Burger", "category": "Burgers", "price": 699, "desc": "Smoky BBQ sauce + crunchy bite.", "tags": ["halal"], "image": "https://images.unsplash.com/photo-1568901346375-23c9450c58cd?auto=format&fit=crop&w=800&q=60", "available": True, }, { "id": "BBQ002", "name": "Zinger Burger", "category": "Burgers", "price": 749, "desc": "Spicy fried chicken — main character energy.", "tags": ["spicy", "halal"], "image": "https://images.unsplash.com/photo-1550547660-d9450f859349?auto=format&fit=crop&w=800&q=60", "available": True, }, { "id": "PA001", "name": "Alfredo Pasta", "category": "Pasta", "price": 999, "desc": "Creamy comfort with extra sauce points.", "tags": ["halal"], "image": "https://images.unsplash.com/photo-1523986371872-9d3ba2e2f5a8?auto=format&fit=crop&w=800&q=60", "available": True, }, { "id": "PA002", "name": "Arrabbiata Pasta", "category": "Pasta", "price": 949, "desc": "Spicy tomato pasta for heat lovers.", "tags": ["spicy", "veg", "halal"], "image": "https://images.unsplash.com/photo-1611270629569-8b357cb88da9?auto=format&fit=crop&w=800&q=60", "available": True, }, { "id": "BBQ003", "name": "BBQ Platter", "category": "BBQ", "price": 1799, "desc": "Mixed grill — shareable, but you won’t.", "tags": ["halal"], "image": "https://images.unsplash.com/photo-1544025162-d76694265947?auto=format&fit=crop&w=800&q=60", "available": True, }, { "id": "SD001", "name": "Loaded Fries", "category": "Sides", "price": 499, "desc": "Cheese + sauce + chaos (the good kind).", "tags": ["veg", "halal"], "image": "https://images.unsplash.com/photo-1541592106381-b31e9677c0e5?auto=format&fit=crop&w=800&q=60", "available": True, }, { "id": "SD002", "name": "Nuggets (6 pcs)", "category": "Sides", "price": 399, "desc": "Crispy bites for quick wins.", "tags": ["halal"], "image": "https://images.unsplash.com/photo-1606755962773-d324e0a13086?auto=format&fit=crop&w=800&q=60", "available": True, }, { "id": "DR001", "name": "Mint Lemonade", "category": "Drinks", "price": 249, "desc": "Fresh, chill, and super refreshing.", "tags": ["veg"], "image": "https://images.unsplash.com/photo-1551024709-8f23befc6f87?auto=format&fit=crop&w=800&q=60", "available": True, }, { "id": "DR002", "name": "Iced Coffee", "category": "Drinks", "price": 349, "desc": "For assignments + deadlines (we get it).", "tags": ["veg"], "image": "https://images.unsplash.com/photo-1517701604599-bb29b565090c?auto=format&fit=crop&w=800&q=60", "available": True, }, { "id": "DS001", "name": "Chocolate Brownie", "category": "Desserts", "price": 399, "desc": "Fudgy, gooey, happiness.", "tags": ["veg"], "image": "https://images.unsplash.com/photo-1606313564200-e75d5e30476c?auto=format&fit=crop&w=800&q=60", "available": True, }, { "id": "DS002", "name": "Cheesecake Slice", "category": "Desserts", "price": 499, "desc": "Creamy slice of peace.", "tags": ["veg"], "image": "https://images.unsplash.com/photo-1542826438-5b0b65e02c76?auto=format&fit=crop&w=800&q=60", "available": True, }, { "id": "VL001", "name": "Veggie Bowl", "category": "Healthy", "price": 699, "desc": "Light, filling, and gym-friendly.", "tags": ["veg", "high-protein"], "image": "https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=800&q=60", "available": True, }, { "id": "VL002", "name": "Grilled Chicken Bowl", "category": "Healthy", "price": 799, "desc": "Protein + flavor = W.", "tags": ["high-protein", "halal"], "image": "https://images.unsplash.com/photo-1546069901-ba9599a7e63c?auto=format&fit=crop&w=800&q=60", "available": True, }, ] # ----------------------------- # 2) HELPERS (HCI-friendly) # ----------------------------- def money(pkr: int) -> str: return f"Rs {pkr:,}" def menu_df(menu): rows = [] for m in menu: if m.get("available", True): rows.append({ "ID": m["id"], "Item": m["name"], "Category": m["category"], "Price (PKR)": m["price"], "Tags": ", ".join(m.get("tags", [])), "Available": "Yes" if m.get("available", True) else "No", }) df = pd.DataFrame(rows) if not df.empty: df = df.sort_values(["Category", "Item"]).reset_index(drop=True) return df def categories(menu): cats = sorted(set([m["category"] for m in menu])) return ["All"] + cats def items_by_category(menu, cat): items = [] for m in menu: if not m.get("available", True): continue if cat == "All" or m["category"] == cat: items.append(m) return items def gallery_items(menu, cat): items = items_by_category(menu, cat) gal = [] for m in items: caption = f"{m['name']} • {money(m['price'])} • {m['category']} • {', '.join(m.get('tags', []))}" gal.append((m["image"], caption)) return gal def dropdown_choices(menu, cat): items = items_by_category(menu, cat) # show name + price for clarity return [f"{m['name']} ({money(m['price'])}) [{m['id']}]" for m in items] def find_item(menu, item_id): for m in menu: if m["id"] == item_id: return m return None def parse_id_from_choice(choice): # "Name (Rs x) [ID]" if not choice: return None if "[" in choice and "]" in choice: return choice.split("[")[-1].split("]")[0].strip() return None def cart_to_df(cart, menu): rows = [] subtotal = 0 for item_id, qty in cart.items(): it = find_item(menu, item_id) if not it: continue line = it["price"] * qty subtotal += line rows.append({ "Item": it["name"], "Qty": qty, "Unit": it["price"], "Line Total": line, }) df = pd.DataFrame(rows) if df.empty: df = pd.DataFrame([{"Item":"(Cart is empty)", "Qty":"-", "Unit":"-", "Line Total":"-"}]) return df, subtotal def calc_totals(subtotal, service_pct=5, tax_pct=0): service = int(round(subtotal * (service_pct / 100))) tax = int(round(subtotal * (tax_pct / 100))) total = subtotal + service + tax return service, tax, total # ----------------------------- # 3) GROQ LLM (Required style) # ----------------------------- def groq_reply(user_text: str, menu: list) -> str: # Required style import os from groq import Groq if not os.environ.get("GROQ_API_KEY"): return "⚠️ Groq key missing. Please set `os.environ['GROQ_API_KEY']` in Cell 2." client = Groq( api_key=os.environ.get("GROQ_API_KEY"), ) # Provide menu context to LLM menu_summary = [] for m in menu: if m.get("available", True): menu_summary.append({ "id": m["id"], "name": m["name"], "category": m["category"], "price_pkr": m["price"], "tags": m.get("tags", []), "desc": m.get("desc", ""), }) system_msg = ( "You are a friendly restaurant assistant for undergraduate students. " "Use short, clear answers. Recommend items based on budget/taste. " "If user mentions allergies, advise to confirm with restaurant staff (disclaimer). " "Return 3-5 suggestions max with prices in PKR. Keep it GenZ but respectful." ) chat_completion = client.chat.completions.create( messages=[ {"role": "system", "content": system_msg}, {"role": "system", "content": f"MENU_JSON: {menu_summary}"}, {"role": "user", "content": user_text}, ], model="llama-3.3-70b-versatile", ) return chat_completion.choices[0].message.content # ----------------------------- # 4) GRADIO APP STATE # ----------------------------- def init_states(): menu = starter_menu() cart = {} # {item_id: qty} orders = [] # list of orders return menu, cart, orders # ----------------------------- # 5) CUSTOMER ACTIONS # ----------------------------- def customer_refresh(menu, cat): gal = gallery_items(menu, cat) choices = dropdown_choices(menu, cat) df = menu_df(menu) return gal, gr.update(choices=choices, value=None), df def show_item_preview(menu, item_choice): item_id = parse_id_from_choice(item_choice) it = find_item(menu, item_id) if item_id else None if not it: return None, "Pick an item to preview 👀" return it["image"], f"**{it['name']}**\n\n{it['desc']}\n\n**Price:** {money(it['price'])}\n\n**Tags:** {', '.join(it.get('tags', []))}" def add_to_cart(menu, cart, item_choice, qty): item_id = parse_id_from_choice(item_choice) if not item_id: return cart, "⚠️ Please select an item first.", *cart_view(menu, cart) if qty is None or qty < 1: return cart, "⚠️ Quantity must be at least 1.", *cart_view(menu, cart) it = find_item(menu, item_id) if not it or not it.get("available", True): return cart, "⚠️ That item is not available right now.", *cart_view(menu, cart) cart = dict(cart) cart[item_id] = cart.get(item_id, 0) + int(qty) msg = f"✅ Added **{it['name']}** x{qty} to cart. Let’s gooo 🛒✨" return cart, msg, *cart_view(menu, cart) def remove_from_cart(menu, cart, item_choice): item_id = parse_id_from_choice(item_choice) if not item_id: return cart, "⚠️ Select an item to remove.", *cart_view(menu, cart) cart = dict(cart) if item_id in cart: del cart[item_id] it = find_item(menu, item_id) name = it["name"] if it else item_id return cart, f"🗑️ Removed **{name}** from cart.", *cart_view(menu, cart) return cart, "ℹ️ Item not found in cart.", *cart_view(menu, cart) def clear_cart(menu, cart): cart = {} return cart, "🧹 Cart cleared. Fresh start.", *cart_view(menu, cart) def cart_view(menu, cart): df, subtotal = cart_to_df(cart, menu) service, tax, total = calc_totals(subtotal, service_pct=5, tax_pct=0) summary = ( f"**Subtotal:** {money(subtotal)} \n" f"**Service (5%):** {money(service)} \n" f"**Tax (0%):** {money(tax)} \n" f"### **Total:** {money(total)}" ) return df, summary def checkout(menu, cart, orders, cust_name, phone, method, address, notes): # HCI: Validate required inputs if not cart: return orders, "⚠️ Your cart is empty. Add something tasty first." if not cust_name or len(cust_name.strip()) < 2: return orders, "⚠️ Please enter your name." if not phone or len(phone.strip()) < 7: return orders, "⚠️ Please enter a valid phone number." if method == "Delivery" and (not address or len(address.strip()) < 5): return orders, "⚠️ Delivery needs an address." # build order df, subtotal = cart_to_df(cart, menu) service, tax, total = calc_totals(subtotal, 5, 0) order_id = "ORD-" + uuid.uuid4().hex[:8].upper() order = { "order_id": order_id, "time": time.strftime("%Y-%m-%d %H:%M:%S"), "name": cust_name.strip(), "phone": phone.strip(), "method": method, "address": (address.strip() if address else ""), "notes": (notes.strip() if notes else ""), "items": dict(cart), "subtotal": subtotal, "service": service, "tax": tax, "total": total, "status": "Pending", } orders = list(orders) orders.insert(0, order) # newest on top # create a readable slip slip_lines = [f"🧾 **Order Slip — {order_id}**", f"**Time:** {order['time']}", ""] for item_id, qty in cart.items(): it = find_item(menu, item_id) if it: slip_lines.append(f"- {it['name']} x{qty} ({money(it['price'])} each)") slip_lines += [ "", f"**Subtotal:** {money(subtotal)}", f"**Service (5%):** {money(service)}", f"**Total:** **{money(total)}**", "", f"**Method:** {method}", f"**Customer:** {order['name']} ({order['phone']})", ] if method == "Delivery": slip_lines.append(f"**Address:** {order['address']}") if order["notes"]: slip_lines.append(f"**Notes:** {order['notes']}") slip = "\n".join(slip_lines) return orders, f"✅ Order placed! {order_id} is in progress. You ate that 😌🔥\n\n{slip}" # ----------------------------- # 6) ADMIN ACTIONS # ----------------------------- def orders_table(menu, orders): rows = [] for o in orders: rows.append({ "Order ID": o["order_id"], "Time": o["time"], "Customer": o["name"], "Method": o["method"], "Total": o["total"], "Status": o["status"], }) df = pd.DataFrame(rows) if df.empty: df = pd.DataFrame([{"Order ID":"(No orders yet)", "Time":"-", "Customer":"-", "Method":"-", "Total":"-", "Status":"-"}]) return df def order_detail_text(menu, orders, order_id): if not order_id: return "Pick an Order ID to view details." o = next((x for x in orders if x["order_id"] == order_id), None) if not o: return "Order not found." lines = [ f"## {o['order_id']}", f"**Time:** {o['time']}", f"**Customer:** {o['name']} — {o['phone']}", f"**Method:** {o['method']}", ] if o["method"] == "Delivery": lines.append(f"**Address:** {o['address']}") if o.get("notes"): lines.append(f"**Notes:** {o['notes']}") lines.append("") lines.append("### Items") for item_id, qty in o["items"].items(): it = find_item(menu, item_id) if it: lines.append(f"- {it['name']} x{qty} ({money(it['price'])} each)") lines.append("") lines.append(f"**Subtotal:** {money(o['subtotal'])}") lines.append(f"**Service:** {money(o['service'])}") lines.append(f"**Total:** **{money(o['total'])}**") lines.append(f"**Status:** {o['status']}") return "\n".join(lines) def update_order_status(orders, order_id, new_status): if not order_id: return orders, "⚠️ Select an Order ID first." orders = list(orders) for o in orders: if o["order_id"] == order_id: o["status"] = new_status return orders, f"✅ Status updated: {order_id} → **{new_status}**" return orders, "⚠️ Order not found." def admin_add_item(menu, item_id, name, category, price, desc, tags_csv, image_url, available): # Validation if not item_id or len(item_id.strip()) < 3: return menu, "⚠️ Item ID is required (e.g., BB001).", menu_df(menu) if not name or len(name.strip()) < 2: return menu, "⚠️ Item name is required.", menu_df(menu) if not category or len(category.strip()) < 2: return menu, "⚠️ Category is required.", menu_df(menu) try: price = int(price) if price <= 0: raise ValueError() except: return menu, "⚠️ Price must be a positive number.", menu_df(menu) if not image_url or not image_url.startswith("http"): return menu, "⚠️ Image URL must be a valid http(s) link.", menu_df(menu) tags = [t.strip() for t in (tags_csv or "").split(",") if t.strip()] menu = list(menu) # prevent duplicates if any(m["id"] == item_id.strip() for m in menu): return menu, "⚠️ Item ID already exists. Use Edit flow (or change ID).", menu_df(menu) menu.append({ "id": item_id.strip(), "name": name.strip(), "category": category.strip(), "price": price, "desc": (desc or "").strip(), "tags": tags, "image": image_url.strip(), "available": bool(available), }) return menu, f"✅ Added **{name.strip()}** to menu.", menu_df(menu) def admin_delete_item(menu, delete_id): if not delete_id: return menu, "⚠️ Provide an Item ID to delete.", menu_df(menu) menu = [m for m in menu if m["id"] != delete_id.strip()] return menu, f"🗑️ Deleted item **{delete_id.strip()}** (if it existed).", menu_df(menu) def admin_toggle_availability(menu, toggle_id): if not toggle_id: return menu, "⚠️ Provide an Item ID to toggle.", menu_df(menu) menu = list(menu) for m in menu: if m["id"] == toggle_id.strip(): m["available"] = not m.get("available", True) state = "Available ✅" if m["available"] else "Unavailable 🚫" return menu, f"🔁 {m['name']} is now: **{state}**", menu_df(menu) return menu, "⚠️ Item not found.", menu_df(menu) # ----------------------------- # 7) AI CHAT # ----------------------------- def chat_send(chat_history, user_msg, menu): user_msg = (user_msg or "").strip() if not user_msg: return chat_history, "" chat_history = list(chat_history) chat_history.append(("You", user_msg)) try: bot = groq_reply(user_msg, menu) except Exception as e: bot = f"⚠️ LLM error: {e}" chat_history.append(("Assistant", bot)) return chat_history, "" # ----------------------------- # 8) BUILD UI (GenZ + HCI) # ----------------------------- menu0, cart0, orders0 = init_states() with gr.Blocks( title="UniBites — Restaurant Web App (Python + Gradio + Groq)", theme=gr.themes.Soft(), css=""" .title {font-size: 26px; font-weight: 800;} .subtitle {opacity: 0.85;} .card {border-radius: 16px;} """ ) as demo: menu_state = gr.State(menu0) cart_state = gr.State(cart0) orders_state = gr.State(orders0) gr.Markdown( """