File size: 10,337 Bytes
28ba0c9 206963c 28ba0c9 206963c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
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 <order_id>' 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."
)
|