import streamlit as st import re import random import string from datetime import datetime, timedelta # --------------------------------------------------------- # Simple Pizza Support Chatbot (Streamlit) # - Rule-based NLU (keyword & pattern matching) # - Menu lookup, offers, order placement, order tracking, FAQs # - No external APIs, perfect for a live workshop demo # --------------------------------------------------------- st.set_page_config(page_title="Pizza Order Chatbot Demo", page_icon="🍕", layout="centered") # ---------------------------- # Demo Data # ---------------------------- MENU = { "Classic": [ {"name": "Margherita", "desc": "Cheese, tomato, basil", "prices": {"small": 199, "medium": 299, "large": 399}}, {"name": "Farmhouse", "desc": "Onion, capsicum, corn, olives", "prices": {"small": 249, "medium": 349, "large": 449}}, ], "Meat": [ {"name": "Pepperoni", "desc": "Pepperoni & extra cheese", "prices": {"small": 279, "medium": 379, "large": 479}}, {"name": "BBQ Chicken", "desc": "Smoky chicken, onion, BBQ sauce", "prices": {"small": 299, "medium": 399, "large": 499}}, ], "Special": [ {"name": "Paneer Tikka", "desc": "Spiced paneer, onion, capsicum", "prices": {"small": 269, "medium": 369, "large": 469}}, {"name": "Veggie Overload", "desc": "Mushroom, corn, olives, jalapeño", "prices": {"small": 289, "medium": 389, "large": 489}}, ], } OFFERS = [ "Use code RIDHI to get 5% off on all orders!", "Buy 2 Large pizzas and get 1 Garlic Bread free.", "Free delivery for orders above ₹599.", ] FAQ = { "timings": "We deliver daily from 10 AM to 11 PM.", "payment": "We accept UPI, cards, netbanking, and cash on delivery.", "delivery time": "Average delivery time is 30-40 minutes depending on your location.", "refund": "If there is an issue, please contact support and refunds will be processed within 3-5 working days.", } SIZES = ["small", "medium", "large"] # ---------------------------- # Utilities # ---------------------------- def _rand_order_id(prefix="OD"): return prefix + "-" + ''.join(random.choices(string.ascii_uppercase + string.digits, k=6)) def format_menu() -> str: lines = ["Here is our pizza menu:\n"] for category, items in MENU.items(): lines.append(f"**{category}**") for it in items: price_line = ", ".join([f"{sz.title()}: ₹{amt}" for sz, amt in it["prices"].items()]) lines.append(f"- **{it['name']}** – {it['desc']} ({price_line})") lines.append("") return "\n".join(lines) def list_offers() -> str: return "\n".join([f"- {o}" for o in OFFERS]) def menu_lookup(pizza_name: str): pizza_name_l = pizza_name.lower() for _, items in MENU.items(): for it in items: if it["name"].lower() == pizza_name_l: return it # fuzzy contains for _, items in MENU.items(): for it in items: if pizza_name_l in it["name"].lower(): return it return None def extract_size(text: str): for s in SIZES: if re.search(rf"\b{s}\b", text, flags=re.I): return s return None def extract_pizza(text: str): # search exact names first for _, items in MENU.items(): for it in items: if re.search(rf"\b{re.escape(it['name'])}\b", text, flags=re.I): return it['name'] # fallback: partial tokens tokens = re.findall(r"[A-Za-z]+", text) for t in tokens: p = menu_lookup(t) if p: return p['name'] return None def estimate_eta(minutes=35): return (datetime.now() + timedelta(minutes=minutes)).strftime("%I:%M %p") # ---------------------------- # Intent Detection (very simple & explainable for workshop) # ---------------------------- INTENT_PATTERNS = { "greet": [r"\bhi\b", r"\bhello\b", r"\bhey\b"], "menu": [r"menu", r"what.*pizzas", r"list.*pizza", r"show.*pizza"], "offers": [r"offer", r"discount", r"coupon", r"promo"], "order": [r"order", r"i want", r"i'd like", r"buy", r"add to cart"], "track": [r"track", r"where.*order", r"status", r"deliver"], "help": [r"help", r"support", r"issue"], "thanks": [r"thanks", r"thank you"], } def detect_intent(text: str) -> str: t = text.lower().strip() for intent, patterns in INTENT_PATTERNS.items(): for pat in patterns: if re.search(pat, t): return intent # FAQs for key in FAQ.keys(): if key in t: return "faq" return "unknown" # ---------------------------- # Order & State Management # ---------------------------- if "chat" not in st.session_state: st.session_state.chat = [] # list of dicts: {role, content} if "orders" not in st.session_state: st.session_state.orders = {} # order_id -> {pizza, size, price, status, eta} if "last_order_id" not in st.session_state: st.session_state.last_order_id = None def add_bot(msg: str): st.session_state.chat.append({"role": "assistant", "content": msg}) def add_user(msg: str): st.session_state.chat.append({"role": "user", "content": msg}) def place_order(pizza_name: str, size: str): item = menu_lookup(pizza_name) if not item: return None, "Sorry, I couldn't find that pizza. Use 'menu' to see options." if size not in item["prices"]: return None, f"Please choose a valid size for {item['name']}: small / medium / large." order_id = _rand_order_id() price = item["prices"][size] eta = estimate_eta(random.choice([25, 30, 35, 40])) st.session_state.orders[order_id] = { "pizza": item["name"], "size": size, "price": price, "status": "Confirmed", "eta": eta, } st.session_state.last_order_id = order_id return order_id, ( f"Order placed!\n\n" f"**Order ID:** {order_id}\n" f"**Item:** {item['name']} ({size.title()})\n" f"**Amount:** ₹{price}\n" f"**Estimated Delivery:** {eta}\n\n" f"Use `track {order_id}` anytime to check status." ) def track_order(order_id: str): data = st.session_state.orders.get(order_id) if not data: return "I couldn't find that order ID. Please check and try again." # Simulate simple status transitions for demo transitions = ["Confirmed", "Being Prepared", "Out for Delivery", "Delivered"] cur_status = data["status"] try: idx = transitions.index(cur_status) if idx < len(transitions) - 1 and random.random() < 0.5: data["status"] = transitions[idx + 1] st.session_state.orders[order_id] = data except ValueError: pass return ( f"**Order ID:** {order_id}\n" f"**Item:** {data['pizza']} ({data['size'].title()})\n" f"**Status:** {data['status']}\n" f"**ETA:** {data['eta']}" ) # ---------------------------- # Bot Brain # ---------------------------- def bot_reply(user_text: str) -> str: intent = detect_intent(user_text) if intent == "greet": return ( "Hello! I am your Pizza Order Assistant. You can say things like:\n" "- 'menu' to see pizzas\n- 'any offers?'\n- 'order a medium Margherita'\n- 'track OD-ABC123'" ) if intent == "menu": return format_menu() if intent == "offers": return "Here are the current offers:\n\n" + list_offers() if intent == "faq": # find which FAQ t = user_text.lower() for k, v in FAQ.items(): if k in t: return f"**{k.title()}**: {v}" return "You can ask about timings, payment, delivery time, or refund." if intent == "order": pizza = extract_pizza(user_text) or "Margherita" size = extract_size(user_text) or "medium" order_id, msg = place_order(pizza, size) return msg if intent == "track": # allow 'track ' or fallback to last order m = re.search(r"(od-[a-z0-9]{6})", user_text.lower()) if m: oid = m.group(1).upper() else: oid = st.session_state.last_order_id if not oid: return "Please provide an order ID, e.g., 'track OD-ABC123'. If you just ordered, try again in a moment." return track_order(oid) if intent == "help": return ( "I can help you with:\n" "- Viewing the menu (type 'menu')\n" "- Placing an order (e.g., 'order a large Pepperoni')\n" "- Tracking orders (e.g., 'track OD-XXXXXX')\n" "- Checking offers (type 'offers')\n" "- FAQs: timings, payment, delivery time, refund" ) if intent == "thanks": return "You're welcome! Anything else I can do for you?" # Unknown → assistively guide return ( "I didn't quite get that. Try:\n" "- 'menu' to see pizzas\n- 'offers' to see discounts\n- 'order a medium Margherita'\n- 'track OD-XXXXXX' to track your order" ) # ---------------------------- # UI # ---------------------------- with st.sidebar: st.title("🍕 Pizza Bot – Demo") st.caption("A simple, explainable chatbot for workshops") if st.button("Reset Conversation"): st.session_state.chat = [] st.session_state.last_order_id = None st.experimental_rerun() st.subheader("Quick Actions") if st.button("Show Menu"): add_user("menu") add_bot(format_menu()) if st.button("View Offers"): add_user("offers") add_bot("Here are the current offers:\n\n" + list_offers()) st.markdown(""" # 🍕 Pizza Order Chatbot (Workshop Demo) Welcome! Type a message below. Try: **menu**, **offers**, **order a medium Margherita**, **track OD-XXXXXX** """) for msg in st.session_state.chat: with st.chat_message(msg["role"]): st.markdown(msg["content"]) user_text = st.chat_input("Type here…") if user_text: add_user(user_text) reply = bot_reply(user_text) add_bot(reply) with st.chat_message("assistant"): st.markdown(reply) # Footer note for presenter st.caption( "Presenter tip: Explain how intents are detected via regex and how state (orders, last order) is kept in session_state." )