Spaces:
Runtime error
Runtime error
| !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( | |
| """ | |
| <div class="title">🍔 UniBites — Single Restaurant App</div> | |
| <div class="subtitle">Browse • Add to cart • Checkout • Admin • AI menu helper (Groq)</div> | |
| """, | |
| ) | |
| with gr.Tabs(): | |
| # ---------------- CUSTOMER TAB ---------------- | |
| with gr.Tab("🍽️ Customer"): | |
| with gr.Row(): | |
| category = gr.Dropdown(choices=categories(menu0), value="All", label="Choose Category") | |
| refresh_btn = gr.Button("🔄 Refresh Menu", variant="secondary") | |
| gallery = gr.Gallery(label="Menu (tap to vibe)", columns=3, rows=2, height=360) | |
| menu_table = gr.Dataframe(label="Menu Table (for clarity)", interactive=False) | |
| with gr.Row(): | |
| item_choice = gr.Dropdown(choices=dropdown_choices(menu0, "All"), label="Pick Item (for cart)") | |
| qty = gr.Slider(1, 10, value=1, step=1, label="Qty") | |
| with gr.Row(): | |
| preview_img = gr.Image(label="Preview Image", height=220) | |
| preview_txt = gr.Markdown("Pick an item to preview 👀") | |
| with gr.Row(): | |
| add_btn = gr.Button("➕ Add to Cart", variant="primary") | |
| remove_btn = gr.Button("➖ Remove from Cart", variant="secondary") | |
| clear_btn = gr.Button("🧹 Clear Cart", variant="secondary") | |
| cart_msg = gr.Markdown("") | |
| cart_df = gr.Dataframe(label="Cart", interactive=False) | |
| cart_summary = gr.Markdown("") | |
| gr.Markdown("## ✅ Checkout (no stress, just fill basics)") | |
| with gr.Row(): | |
| cust_name = gr.Textbox(label="Your Name", placeholder="e.g., Ali / Ayesha") | |
| phone = gr.Textbox(label="Phone", placeholder="03xx-xxxxxxx") | |
| method = gr.Radio(["Delivery", "Pickup"], value="Delivery", label="Delivery or Pickup?") | |
| address = gr.Textbox(label="Address (required for delivery)", placeholder="Hostel / home address") | |
| notes = gr.Textbox(label="Notes (optional)", placeholder="Less spicy / no onions / etc.") | |
| place_btn = gr.Button("🧾 Place Order", variant="primary") | |
| order_out = gr.Markdown("") | |
| # ---------------- ADMIN TAB ---------------- | |
| with gr.Tab("🧑🍳 Admin"): | |
| gr.Markdown("### Admin Panel (simple, beginner-friendly)") | |
| admin_menu_df = gr.Dataframe(label="Current Menu", interactive=False) | |
| with gr.Accordion("➕ Add New Menu Item", open=False): | |
| with gr.Row(): | |
| a_id = gr.Textbox(label="Item ID", placeholder="e.g., SD010") | |
| a_name = gr.Textbox(label="Name", placeholder="e.g., Garlic Bread") | |
| a_cat = gr.Textbox(label="Category", placeholder="e.g., Sides") | |
| with gr.Row(): | |
| a_price = gr.Number(label="Price (PKR)", value=399) | |
| a_tags = gr.Textbox(label="Tags (comma separated)", placeholder="veg, halal") | |
| a_avail = gr.Checkbox(label="Available", value=True) | |
| a_desc = gr.Textbox(label="Description", placeholder="Short description for customers") | |
| a_img = gr.Textbox(label="Image URL", placeholder="https://... (public image link)") | |
| add_item_btn = gr.Button("✅ Add Item", variant="primary") | |
| admin_msg = gr.Markdown("") | |
| with gr.Accordion("🗑️ Delete Item / 🔁 Toggle Availability", open=False): | |
| with gr.Row(): | |
| del_id = gr.Textbox(label="Item ID to Delete", placeholder="e.g., SD010") | |
| del_btn = gr.Button("🗑️ Delete", variant="secondary") | |
| with gr.Row(): | |
| tog_id = gr.Textbox(label="Item ID to Toggle", placeholder="e.g., SD001") | |
| tog_btn = gr.Button("🔁 Toggle Availability", variant="secondary") | |
| admin_msg2 = gr.Markdown("") | |
| gr.Markdown("### Orders") | |
| orders_df = gr.Dataframe(label="Orders List", interactive=False) | |
| with gr.Row(): | |
| order_id_pick = gr.Dropdown(choices=[], label="Select Order ID") | |
| new_status = gr.Dropdown( | |
| ["Pending", "Preparing", "Ready", "Completed", "Cancelled"], | |
| value="Preparing", | |
| label="Update Status" | |
| ) | |
| with gr.Row(): | |
| view_order_btn = gr.Button("👀 View Order Detail", variant="secondary") | |
| update_status_btn = gr.Button("✅ Update Status", variant="primary") | |
| order_detail = gr.Markdown("Pick an order to see details.") | |
| admin_order_msg = gr.Markdown("") | |
| # ---------------- AI TAB ---------------- | |
| with gr.Tab("🤖 AI Assistant"): | |
| gr.Markdown( | |
| """ | |
| ### Ask me anything about the menu 😌 | |
| Try: | |
| - “I have Rs 800, recommend something spicy” | |
| - “Suggest high-protein items” | |
| - “I’m lactose intolerant, what’s safe?” (I’ll still tell you to confirm with staff) | |
| """ | |
| ) | |
| chatbot = gr.Chatbot(label="Chat", height=360) | |
| user_msg = gr.Textbox(label="Message", placeholder="Type here…") | |
| send_btn = gr.Button("Send 🚀", variant="primary") | |
| # --------- INIT LOAD --------- | |
| def initial_load(menu): | |
| cat = "All" | |
| gal = gallery_items(menu, cat) | |
| choices = dropdown_choices(menu, cat) | |
| return ( | |
| gal, | |
| gr.update(choices=categories(menu), value=cat), | |
| gr.update(choices=choices, value=None), | |
| menu_df(menu), | |
| menu_df(menu), | |
| orders_table(menu, []), | |
| gr.update(choices=[], value=None), | |
| *cart_view(menu, {}) | |
| ) | |
| demo.load( | |
| initial_load, | |
| inputs=[menu_state], | |
| outputs=[gallery, category, item_choice, menu_table, admin_menu_df, orders_df, order_id_pick, cart_df, cart_summary] | |
| ) | |
| # --------- CUSTOMER EVENTS --------- | |
| refresh_btn.click( | |
| customer_refresh, | |
| inputs=[menu_state, category], | |
| outputs=[gallery, item_choice, menu_table] | |
| ) | |
| category.change( | |
| customer_refresh, | |
| inputs=[menu_state, category], | |
| outputs=[gallery, item_choice, menu_table] | |
| ) | |
| item_choice.change( | |
| show_item_preview, | |
| inputs=[menu_state, item_choice], | |
| outputs=[preview_img, preview_txt] | |
| ) | |
| add_btn.click( | |
| add_to_cart, | |
| inputs=[menu_state, cart_state, item_choice, qty], | |
| outputs=[cart_state, cart_msg, cart_df, cart_summary] | |
| ) | |
| remove_btn.click( | |
| remove_from_cart, | |
| inputs=[menu_state, cart_state, item_choice], | |
| outputs=[cart_state, cart_msg, cart_df, cart_summary] | |
| ) | |
| clear_btn.click( | |
| clear_cart, | |
| inputs=[menu_state, cart_state], | |
| outputs=[cart_state, cart_msg, cart_df, cart_summary] | |
| ) | |
| place_btn.click( | |
| checkout, | |
| inputs=[menu_state, cart_state, orders_state, cust_name, phone, method, address, notes], | |
| outputs=[orders_state, order_out] | |
| ).then( | |
| lambda menu, orders: (orders_table(menu, orders), gr.update(choices=[o["order_id"] for o in orders], value=(orders[0]["order_id"] if orders else None))), | |
| inputs=[menu_state, orders_state], | |
| outputs=[orders_df, order_id_pick] | |
| ).then( | |
| lambda menu: (*cart_view(menu, {}), {}), | |
| inputs=[menu_state], | |
| outputs=[cart_df, cart_summary, cart_state] | |
| ) | |
| # --------- ADMIN EVENTS --------- | |
| add_item_btn.click( | |
| admin_add_item, | |
| inputs=[menu_state, a_id, a_name, a_cat, a_price, a_desc, a_tags, a_img, a_avail], | |
| outputs=[menu_state, admin_msg, admin_menu_df] | |
| ).then( | |
| customer_refresh, | |
| inputs=[menu_state, category], | |
| outputs=[gallery, item_choice, menu_table] | |
| ) | |
| del_btn.click( | |
| admin_delete_item, | |
| inputs=[menu_state, del_id], | |
| outputs=[menu_state, admin_msg2, admin_menu_df] | |
| ).then( | |
| customer_refresh, | |
| inputs=[menu_state, category], | |
| outputs=[gallery, item_choice, menu_table] | |
| ) | |
| tog_btn.click( | |
| admin_toggle_availability, | |
| inputs=[menu_state, tog_id], | |
| outputs=[menu_state, admin_msg2, admin_menu_df] | |
| ).then( | |
| customer_refresh, | |
| inputs=[menu_state, category], | |
| outputs=[gallery, item_choice, menu_table] | |
| ) | |
| view_order_btn.click( | |
| order_detail_text, | |
| inputs=[menu_state, orders_state, order_id_pick], | |
| outputs=[order_detail] | |
| ) | |
| update_status_btn.click( | |
| update_order_status, | |
| inputs=[orders_state, order_id_pick, new_status], | |
| outputs=[orders_state, admin_order_msg] | |
| ).then( | |
| lambda menu, orders: (orders_table(menu, orders)), | |
| inputs=[menu_state, orders_state], | |
| outputs=[orders_df] | |
| ).then( | |
| order_detail_text, | |
| inputs=[menu_state, orders_state, order_id_pick], | |
| outputs=[order_detail] | |
| ) | |
| # --------- AI CHAT EVENTS --------- | |
| send_btn.click( | |
| chat_send, | |
| inputs=[chatbot, user_msg, menu_state], | |
| outputs=[chatbot, user_msg] | |
| ) | |
| user_msg.submit( | |
| chat_send, | |
| inputs=[chatbot, user_msg, menu_state], | |
| outputs=[chatbot, user_msg] | |
| ) | |
| demo.launch(share=True, debug=True) |