Fred808 commited on
Commit
8a10bf4
·
verified ·
1 Parent(s): 40a3c36

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +80 -211
app.py CHANGED
@@ -24,6 +24,7 @@ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
24
  from sqlalchemy.orm import sessionmaker, declarative_base
25
  from sqlalchemy import Column, Integer, String, DateTime, Text, Float
26
 
 
27
  SPOONACULAR_API_KEY = os.getenv("SPOONACULAR_API_KEY", "default_fallback_value")
28
  PAYSTACK_SECRET_KEY = os.getenv("PAYSTACK_SECRET_KEY", "default_fallback_value")
29
  DATABASE_URL = os.getenv("DATABASE_URL", "default_fallback_value")
@@ -35,13 +36,13 @@ WHATSAPP_PHONE_NUMBER_ID = os.getenv("WHATSAPP_PHONE_NUMBER_ID", "default_value"
35
  WHATSAPP_ACCESS_TOKEN = os.getenv("WHATSAPP_ACCESS_TOKEN", "default_value")
36
  MANAGEMENT_WHATSAPP_NUMBER = os.getenv("MANAGEMENT_WHATSAPP_NUMBER", "default_value")
37
 
 
38
  TOWN_SHIPPING_COSTS = {
39
- "lasu gate": 1000,
40
- "ojo": 800,
41
- "ajangbadi": 1200,
42
- "iba": 900,
43
- "okokomaiko": 1500,
44
- "default": 1000
45
  }
46
 
47
  Base = declarative_base()
@@ -99,7 +100,7 @@ class MenuItem(Base):
99
  id = Column(Integer, primary_key=True, index=True)
100
  name = Column(String, unique=True, index=True)
101
  description = Column(Text)
102
- price = Column(Integer) # Price as an integer (in Naira)
103
  nutrition = Column(Text)
104
 
105
  class TownShippingCost(Base):
@@ -108,7 +109,6 @@ class TownShippingCost(Base):
108
  town = Column(String, unique=True, index=True)
109
  cost = Column(Integer)
110
 
111
-
112
  engine = create_async_engine(DATABASE_URL, echo=True)
113
  async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
114
 
@@ -120,65 +120,28 @@ user_state = {}
120
  conversation_context = {}
121
  proactive_timer = {}
122
 
 
123
  menu_items = [
124
- {"name": "Jollof Rice", "description": "A spicy and flavorful rice dish", "price": 1500, "nutrition": "Calories: 300 kcal, Carbs: 50g, Protein: 10g, Fat: 5g"},
125
- {"name": "Fried Rice", "description": "A savory rice dish with vegetables and meat", "price": 1200, "nutrition": "Calories: 350 kcal, Carbs: 55g, Protein: 12g, Fat: 8g"},
126
- {"name": "Chicken Wings", "description": "Crispy fried chicken wings", "price": 2000, "nutrition": "Calories: 400 kcal, Carbs: 20g, Protein: 25g, Fat: 15g"},
127
- {"name": "Egusi Soup", "description": "A rich and hearty soup made with melon seeds", "price": 1000, "nutrition": "Calories: 250 kcal, Carbs: 15g, Protein: 8g, Fat: 10g"},
128
- {"name": "Native Rice", "description": "Traditional native-style rice", "price": 1500, "nutrition": "Calories: 350 kcal, Carbs: 60g, Protein: 10g, Fat: 7g"},
129
- {"name": "Spaghetti Jollof", "description": "Jollof-style spaghetti dish", "price": 1200, "nutrition": "Calories: 400 kcal, Carbs: 65g, Protein: 10g, Fat: 8g"},
130
- {"name": "Macaroni", "description": "Jollof-style macaroni dish", "price": 1200, "nutrition": "Calories: 380 kcal, Carbs: 60g, Protein: 9g, Fat: 7g"},
131
- {"name": "White Rice with Vegetable Sauce", "description": "White rice served with vegetable sauce and chicken", "price": 3700, "nutrition": "Calories: 450 kcal, Carbs: 60g, Protein: 15g, Fat: 10g"},
132
- {"name": "Vegetable Sauce with Chicken", "description": "Vegetable sauce with chicken, no rice", "price": 2500, "nutrition": "Calories: 300 kcal, Carbs: 20g, Protein: 15g, Fat: 10g"},
133
- {"name": "Chicken Drumstick", "description": "Fried or grilled chicken drumstick", "price": 2500, "nutrition": "Calories: 350 kcal, Carbs: 5g, Protein: 30g, Fat: 15g"},
134
- {"name": "Chicken Wings", "description": "Crispy fried chicken wings", "price": 3960, "nutrition": "Calories: 450 kcal, Carbs: 20g, Protein: 30g, Fat: 20g"},
135
- {"name": "Fried Chicken", "description": "Crispy deep-fried chicken", "price": 4140, "nutrition": "Calories: 500 kcal, Carbs: 15g, Protein: 35g, Fat: 25g"},
136
- {"name": "Turkey", "description": "Crispy deep-fried turkey", "price": 4000, "nutrition": "Calories: 480 kcal, Carbs: 10g, Protein: 40g, Fat: 20g"},
137
- {"name": "Gizzard", "description": "Fried or grilled gizzard", "price": 3000, "nutrition": "Calories: 200 kcal, Carbs: 5g, Protein: 20g, Fat: 8g"},
138
- {"name": "Fish", "description": "Fried or grilled fish", "price": 2500, "nutrition": "Calories: 250 kcal, Carbs: 5g, Protein: 25g, Fat: 10g"},
139
- {"name": "Beef", "description": "Spicy fried or grilled beef", "price": 1200, "nutrition": "Calories: 300 kcal, Carbs: 2g, Protein: 28g, Fat: 15g"},
140
- {"name": "Pepper Chicken", "description": "Spicy peppered chicken", "price": 1500, "nutrition": "Calories: 400 kcal, Carbs: 10g, Protein: 35g, Fat: 18g"},
141
- {"name": "Pepper Chicken Big", "description": "Larger portion of spicy peppered chicken", "price": 2500, "nutrition": "Calories: 600 kcal, Carbs: 15g, Protein: 50g, Fat: 25g"},
142
- {"name": "Salad", "description": "Fresh vegetable salad", "price": 800, "nutrition": "Calories: 100 kcal, Carbs: 15g, Protein: 3g, Fat: 5g"},
143
- {"name": "Egg and Yam", "description": "Boiled yam served with eggs", "price": 1000, "nutrition": "Calories: 300 kcal, Carbs: 50g, Protein: 10g, Fat: 5g"},
144
- {"name": "Moi Moi", "description": "Steamed bean pudding", "price": 1000, "nutrition": "Calories: 250 kcal, Carbs: 30g, Protein: 12g, Fat: 8g"},
145
- {"name": "Plantain", "description": "Fried or boiled ripe plantain", "price": 1000, "nutrition": "Calories: 200 kcal, Carbs: 50g, Protein: 2g, Fat: 1g"},
146
- {"name": "Soup with Goat Meat and Semo", "description": "Rich soup with goat meat served with semo", "price": 4600, "nutrition": "Calories: 500 kcal, Carbs: 60g, Protein: 30g, Fat: 15g"},
147
- {"name": "Soup with Chicken and Semo", "description": "Soup served with chicken and semo", "price": 500, "nutrition": "Calories: 450 kcal, Carbs: 55g, Protein: 28g, Fat: 12g"},
148
- {"name": "Extra Semo", "description": "Additional portion of semo", "price": 500, "nutrition": "Calories: 200 kcal, Carbs: 45g, Protein: 5g, Fat: 1g"},
149
- {"name": "Banga Soup", "description": "Traditional dish with catfish", "price": 5000, "nutrition": "Calories: 600 kcal, Carbs: 50g, Protein: 35g, Fat: 15g"}
150
  ]
151
 
152
- nigerian_drinks = [
153
- {"name": "Chapman", "description": "Popular Nigerian cocktail with a fruity, fizzy taste", "price": 1500, "nutrition": "Calories: 180 kcal, Carbs: 40g, Protein: 1g, Fat: 0g"},
154
- {"name": "VitaMilk", "description": "Healthy, protein-rich plant-based milk", "price": 1000, "nutrition": "Calories: 180 kcal, Carbs: 20g, Protein: 8g, Fat: 6g"},
155
- {"name": "Malt Drink", "description": "Non-alcoholic malt-based drink", "price": 1000, "nutrition": "Calories: 200 kcal, Carbs: 50g, Protein: 2g, Fat: 0g"},
156
- {"name": "Soft Drinks", "description": "Carbonated sodas like Coke, Fanta, and Sprite", "price": 800, "nutrition": "Calories: 150 kcal, Carbs: 39g, Protein: 0g, Fat: 0g"},
157
- {"name": "Bottled Water", "description": "Pure, refreshing drinking water", "price": 500, "nutrition": "Calories: 0 kcal, Carbs: 0g, Protein: 0g, Fat: 0g"},
158
- {"name": "Energy Drink", "description": "Boost of energy with caffeine and vitamins", "price": 1500, "nutrition": "Calories: 200 kcal, Carbs: 50g, Protein: 1g, Fat: 0g"}
159
  ]
160
 
161
- menu_items.extend(nigerian_drinks)
162
-
163
-
164
- class ConversationState:
165
- def __init__(self):
166
- self.flow = None
167
- self.step = 0
168
- self.data = {}
169
- self.last_active = datetime.utcnow()
170
-
171
- def update_last_active(self):
172
- self.last_active = datetime.utcnow()
173
-
174
- def is_expired(self):
175
- return datetime.utcnow() - self.last_active > SESSION_TIMEOUT
176
-
177
- def reset(self):
178
- self.flow = None
179
- self.step = 0
180
- self.data = {}
181
- self.last_active = datetime.utcnow()
182
 
183
  SESSION_TIMEOUT = timedelta(minutes=5)
184
 
@@ -212,7 +175,6 @@ async def populate_shipping_costs():
212
  session.add(new_cost)
213
  await session.commit()
214
 
215
-
216
  async def log_chat_to_db(user_id: str, direction: str, message: str):
217
  async with async_session() as session:
218
  entry = ChatHistory(user_id=user_id, direction=direction, message=message)
@@ -255,7 +217,7 @@ def create_paystack_payment_link(email: str, amount: int, reference: str) -> dic
255
  "email": email,
256
  "amount": amount,
257
  "reference": reference,
258
- "callback_url": "https://fred808-botpy-808.hf.space/payment_callback"
259
  }
260
  try:
261
  response = requests.post(url, json=data, headers=headers, timeout=10)
@@ -346,8 +308,8 @@ def calculate_shipping_cost(address: str) -> int:
346
  def calculate_eta(destination: str) -> str:
347
  if not GOOGLE_MAPS_API_KEY:
348
  return "ETA unavailable (Google Maps API key missing)."
349
-
350
- origin = "Plot 13 Isashi Road, Iyana Isashi, Off Lagos - Badagry Expy, Lagos"
351
  url = f"https://maps.googleapis.com/maps/api/directions/json?origin={origin}&destination={destination}&key={GOOGLE_MAPS_API_KEY}"
352
 
353
  try:
@@ -398,7 +360,6 @@ async def get_menu_items():
398
  for item in items
399
  ]
400
 
401
-
402
  async def track_order(user_id: str, order_id: str) -> str:
403
  async with async_session() as session:
404
  order_result = await session.execute(
@@ -475,24 +436,18 @@ def match_dishes(user_input: str, threshold: int = 70) -> list:
475
  Then, try an exact whole-word match using a regex with word boundaries.
476
  If no exact match is found, fall back to fuzzy matching.
477
  """
478
- # Normalize input: lowercase, strip, and collapse spaces
479
  normalized_input = re.sub(r'\s+', ' ', user_input.lower().strip())
480
  matched_dishes = []
481
 
482
- # Try exact matching with word boundaries.
483
  for item in menu_items:
484
  dish_name = item["name"]
485
- # Create a pattern with word boundaries; e.g., for "fried rice":
486
- # pattern becomes: r'\bfried rice\b'
487
  pattern = r'\b' + re.escape(dish_name.lower()) + r'\b'
488
  if re.search(pattern, normalized_input):
489
  matched_dishes.append(dish_name)
490
 
491
- # If we found any exact matches, return them.
492
  if matched_dishes:
493
  return list(set(matched_dishes))
494
 
495
- # If no exact match, fall back to fuzzy matching.
496
  for item in menu_items:
497
  dish_name = item["name"]
498
  score = fuzz.token_sort_ratio(normalized_input, dish_name.lower())
@@ -501,61 +456,48 @@ def match_dishes(user_input: str, threshold: int = 70) -> list:
501
 
502
  return list(set(matched_dishes))
503
 
504
- # def get_dish_price(dish: str) -> int:
505
- # for item in menu_items:
506
- # if item["name"].lower() == dish.lower():
507
- # return item["price"]
508
- # return 0 # or raise an error if dish not found
509
-
510
- # Fetch dish price from the MenuItem table
511
  async def get_dish_price(dish: str) -> int:
512
  async with async_session() as session:
513
- # Using ilike() for case-insensitive matching
514
  result = await session.execute(select(MenuItem).where(MenuItem.name.ilike(dish)))
515
  menu_item = result.scalars().first()
516
  return menu_item.price if menu_item else 0
517
 
518
- # Fetch shipping cost from the TownShippingCost table based on the address.
519
  async def get_shipping_cost(address: str) -> int:
520
  async with async_session() as session:
521
  result = await session.execute(select(TownShippingCost))
522
  costs = result.scalars().all()
523
  address_lower = address.lower()
524
- # Look for a matching town in the address.
525
  for cost in costs:
526
  if cost.town in address_lower:
527
  return cost.cost
528
- # Fallback: return the default cost.
529
  default_cost = next((c for c in costs if c.town == "default"), None)
530
- return default_cost.cost if default_cost else 1000
531
-
532
 
533
  def send_email_notification(order_details):
534
- # Construct the email payload. Adjust values as needed.
535
  payload = {
536
- "from": "yungdml31@gmail.com",
537
- "to": "samyung05@gmail.com, angelofoodcourt@gmail.com", # You can also use a comma-separated list for multiple recipients.
538
  "subject": f"New Order Received: {order_details['order_id']}",
539
  "body": (
540
  f"New Order Received:\n"
541
  f"Order ID: {order_details['order_id']}\n"
542
  f"Dish: {order_details['dish']}\n"
543
  f"Quantity: {order_details['quantity']}\n"
544
- f"Total Price: N{order_details['price']}\n"
545
  f"Phone: {order_details.get('phone_number', 'Not Provided')}\n"
546
  f"Delivery Address: {order_details.get('address', 'Not Provided')}\n"
547
  f"Extras: {order_details.get('extras', 'None')}\n"
548
  f"Status: Pending Payment"
549
  ),
550
- # Even though your endpoint is set up to receive these fields, if they're fixed you can omit them or keep them here:
551
- "smtpHost": "smtp.gmail.com", # or your SMTP host
552
- "smtpPort": 587, # or your SMTP port
553
- "smtpSecure": "false", # boolean as string if needed
554
- "smtpUser": "yungdml31@gmail.com",
555
- "smtpPassword": "uddvxabxotlvfewk",
556
  }
557
 
558
- # Replace the URL with the URL of your SMTP API endpoint.
559
  url = "https://smtp-server-ten.vercel.app/smtp"
560
  try:
561
  response = requests.post(url, json=payload, timeout=10)
@@ -565,29 +507,6 @@ def send_email_notification(order_details):
565
  print(f"Error sending email: {e}")
566
  return None
567
 
568
-
569
-
570
- def match_dishes(user_input: str, threshold: int = 70) -> list:
571
- matched_dishes = []
572
- user_input_lower = user_input.lower()
573
- for item in menu_items:
574
- dish_name = item["name"]
575
- # Direct substring check
576
- if dish_name.lower() in user_input_lower:
577
- matched_dishes.append(dish_name)
578
- else:
579
- score = fuzz.token_sort_ratio(user_input_lower, dish_name.lower())
580
- if score >= threshold:
581
- matched_dishes.append(dish_name)
582
- return list(set(matched_dishes))
583
-
584
- def get_dish_price(dish: str) -> int:
585
- for item in menu_items:
586
- if item["name"].lower() == dish.lower():
587
- return item["price"]
588
- return 0
589
-
590
-
591
  async def process_order_flow(user_id: str, message: str) -> str:
592
  """
593
  Processes order flow allowing an unlimited number of dishes.
@@ -598,16 +517,14 @@ async def process_order_flow(user_id: str, message: str) -> str:
598
  del user_state[user_id]
599
  state = None
600
 
601
- # 1) If user says "order" or "menu", initialize the order flow.
602
  if message.lower() in ["order", "menu"]:
603
  state = ConversationState()
604
  state.flow = "order"
605
  state.step = 1
606
  state.update_last_active()
607
  user_state[user_id] = state
608
- if message.lower() == "order":
609
- return "Sure! What dish would you like to order?"
610
- return ""
611
 
612
  if not state and "order" in message.lower():
613
  state = ConversationState()
@@ -615,14 +532,13 @@ async def process_order_flow(user_id: str, message: str) -> str:
615
  state.step = 1
616
  state.update_last_active()
617
  user_state[user_id] = state
618
- return "Sure! What dish would you like to order?"
619
 
620
- # 2) If we aren't in the order flow yet, try to detect dish(es)
621
  if not state or state.flow != "order":
622
  matched = match_dishes(message)
623
  if matched:
624
  if len(matched) == 1:
625
- # Single dish order
626
  found_dish = matched[0]
627
  state = ConversationState()
628
  state.flow = "order"
@@ -648,13 +564,12 @@ async def process_order_flow(user_id: str, message: str) -> str:
648
  state.step = 5
649
  return (f"Thanks! Your phone number is recorded as: {state.data['phone_number']}.\n"
650
  f"Your delivery address is: {state.data['address']}.\n"
651
- f"Your delivery cost is N{shipping_cost}. Would you like extras (yes/no)?")
652
  elif state.data.get("phone_number") and not state.data.get("address"):
653
- return "Thank you. Please provide your delivery address."
654
  else:
655
- return "Please provide your phone number and address."
656
  else:
657
- # Multiple dishes detected – store all in candidate_dishes.
658
  state = ConversationState()
659
  state.flow = "order"
660
  state.update_last_active()
@@ -667,7 +582,6 @@ async def process_order_flow(user_id: str, message: str) -> str:
667
  else:
668
  return "I couldn't identify the dish. Please type the dish name from our menu."
669
 
670
- # --- Candidate Dishes Clarification Branch ---
671
  if state and state.flow == "order" and "candidate_dishes" in state.data:
672
  normalized = message.strip().lower()
673
  if normalized in ["both", "all"]:
@@ -676,7 +590,7 @@ async def process_order_flow(user_id: str, message: str) -> str:
676
  state.step = 2
677
  dishes_str = ", ".join(state.data["dishes"])
678
  return (f"You have selected: {dishes_str}. How many servings of each would you like? "
679
- "(For example, '2 for Jollof Rice, 3 for Chicken Wings, 1 for Fish')")
680
  else:
681
  for dish in state.data["candidate_dishes"]:
682
  if dish.lower() in normalized:
@@ -688,16 +602,13 @@ async def process_order_flow(user_id: str, message: str) -> str:
688
  return (f"Please specify which one you'd like to order from: {dish_options} "
689
  "(or type 'both' if you'd like to order all).")
690
 
691
-
692
-
693
- # 3) If state exists and we're at step 2: parse quantity details.
694
  if state and state.flow == "order" and state.step == 2:
695
- # For multi-dish orders, expect input like "2 for Jollof Rice, 3 for Chicken Wings, 1 for Fish"
696
  if "dishes" in state.data and len(state.data["dishes"]) > 1:
697
  pairs = re.findall(r'(\d+)\s*for\s*([a-zA-Z\s]+)', message, flags=re.IGNORECASE)
698
  if not pairs:
699
  return ("I'm sorry, I didn't understand the quantity details. "
700
- "Please specify like '2 for Jollof Rice, 3 for Chicken Wings, 1 for Fish'.")
701
  order_quantities = {}
702
  for quantity, dish_text in pairs:
703
  dish_text = dish_text.strip().lower()
@@ -709,11 +620,10 @@ async def process_order_flow(user_id: str, message: str) -> str:
709
  state.step = 3
710
  summary = "\n".join([f"{q} serving(s) of {d}" for d, q in order_quantities.items()])
711
  return (f"Got it. You have ordered:\n{summary}\n"
712
- "Please provide your phone number and delivery address.")
713
  else:
714
  return ("I'm sorry, I couldn't match those dishes. "
715
- "Please try something like '2 for Jollof Rice, 3 for Chicken Wings, 1 for Fish'.")
716
- # For single-dish order at step 2: user types just a quantity.
717
  numbers = re.findall(r'\d+', message)
718
  if not numbers:
719
  return "Please enter a valid number for the quantity (e.g., 1, 2, 3)."
@@ -724,10 +634,9 @@ async def process_order_flow(user_id: str, message: str) -> str:
724
  state.step = 3
725
  dish = state.data.get("dish", "")
726
  return (f"Got it. {quantity} serving(s) of {dish}.\n"
727
- "Please provide your phone number and delivery address.")
728
 
729
- # Step 4: Parse phone & address (for single or multi-dish orders), then skip extras.
730
- # --- Step 4: Parse phone & address, compute shipping cost, and jump to confirmation ---
731
  if state and state.flow == "order" and state.step == 3:
732
  phone_pattern = r'(\+?\d{10,15})'
733
  phone_match = re.search(phone_pattern, message)
@@ -744,67 +653,57 @@ async def process_order_flow(user_id: str, message: str) -> str:
744
  state.data["address"] = message.strip()
745
  else:
746
  return ("Please provide both your phone number and address. "
747
- "For example: '09162409591, 1, Iyana Isashi, Isashi, Lagos'.")
748
  if not state.data.get("address"):
749
  return "Thank you. Please provide your delivery address."
750
- # Get shipping cost from the database
751
  shipping_cost = await get_shipping_cost(state.data["address"])
752
  state.data["shipping_cost"] = shipping_cost
753
-
754
- # Automatically set extras to empty (since we're not using extras)
755
  state.data["extras"] = ""
756
-
757
- # Jump directly to order confirmation (Step 7)
758
  state.step = 7
759
-
760
- # For order summary, determine dish summary and quantity:
761
  if "orders" in state.data:
762
  dish_summary = ", ".join(state.data["orders"].keys())
763
  quantity_total = sum(state.data["orders"].values())
764
  total_price = 0
765
  for dish, qty in state.data["orders"].items():
766
- total_price += (get_dish_price(dish)) * qty
 
767
  total_price += shipping_cost
768
  else:
769
  dish_summary = state.data.get("dish", "")
770
  quantity_total = state.data.get("quantity", 1)
771
- dish_price = get_dish_price(dish_summary)
772
  total_price = (quantity_total * dish_price) + shipping_cost
773
-
774
  return (f"Order Summary:\n"
775
  f"Dish(es): {dish_summary}\n"
776
  f"Quantity: {quantity_total}\n"
777
  f"Phone: {state.data.get('phone_number', '')}\n"
778
  f"Address: {state.data.get('address', '')}\n"
779
- f"Shipping Cost: N{shipping_cost}\n"
780
- f"Total Price: N{total_price}\n"
781
  "Confirm order? (yes/no)")
782
 
783
-
784
-
785
-
786
  # 7) Step 7: Order Confirmation and Payment Link Generation
787
  if state and state.flow == "order" and state.step == 7:
788
  if message.lower() in ["yes", "y"]:
789
  order_id = f"ORD-{int(time.time())}"
790
  state.data["order_id"] = order_id
791
-
792
- # Calculate total price:
793
  if "orders" in state.data:
794
  total_price = 0
795
  for dish, qty in state.data["orders"].items():
796
- total_price += (get_dish_price(dish)) * qty
 
797
  total_price += state.data.get("shipping_cost", 0)
798
  dish_summary = ", ".join(state.data["orders"].keys())
799
  quantity_total = sum(state.data["orders"].values())
800
  else:
801
  dish_summary = state.data.get("dish", "")
802
  quantity_total = state.data.get("quantity", 1)
803
- dish_price = get_dish_price(dish_summary)
804
  total_price = (quantity_total * dish_price) + state.data.get("shipping_cost", 0)
805
 
806
  state.data["price"] = str(total_price)
807
-
808
  async def save_order():
809
  async with async_session() as session:
810
  order = Order(
@@ -820,8 +719,7 @@ async def process_order_flow(user_id: str, message: str) -> str:
820
  await session.commit()
821
  asyncio.create_task(save_order())
822
  asyncio.create_task(log_order_tracking(order_id, "Order Placed", "Order placed and awaiting payment."))
823
-
824
- # Prepare order details for email notification.
825
  order_details = {
826
  "order_id": order_id,
827
  "dish": dish_summary,
@@ -831,48 +729,27 @@ async def process_order_flow(user_id: str, message: str) -> str:
831
  "address": state.data.get("address", "Not Provided"),
832
  "extras": state.data.get("extras", "None")
833
  }
834
- admin_emails = os.getenv("ADMIN_EMAILS", "admin@example.com").split(",")
835
- email_subject = f"New Order Received: {order_id}"
836
- email_body = (
837
- f"New Order Received:\n"
838
- f"Order ID: {order_id}\n"
839
- f"Dish(es): {dish_summary}\n"
840
- f"Quantity: {quantity_total}\n"
841
- f"Total Price: N{total_price}\n"
842
- f"Phone: {state.data.get('phone_number', '')}\n"
843
- f"Delivery Address: {state.data.get('address', 'Not Provided')}\n"
844
- f"Extras: {state.data.get('extras', 'None')}\n"
845
- f"Status: Pending Payment"
846
- )
847
  email_response = send_email_notification(order_details)
848
  if email_response:
849
  print("Email notification sent successfully.")
850
  else:
851
  print("Failed to send email notification.")
852
-
853
- email_for_paystack = "customer@example.com" # Replace with user's email if available
854
  payment_data = create_paystack_payment_link(email_for_paystack, total_price * 100, order_id)
855
  state.reset()
856
  if user_id in user_state:
857
  del user_state[user_id]
858
  if payment_data.get("status"):
859
  payment_link = payment_data["data"]["authorization_url"]
860
- return (f"Thank you for your order of {quantity_total} serving(s) of {dish_summary}! "
861
  f"Your Order ID is {order_id}.\n\n"
862
- "Please complete your payment using one of the following options:\n"
863
- f"1. Pay online via our Paystack link: {payment_link}\n"
864
- "2. Alternatively, you can make a bank transfer to the following account:\n"
865
- " Account Number: 1433042821\n"
866
- " Bank: Access Bank\n"
867
- " Account Name: Angelo Food Court 2\n\n"
868
- "If you choose the bank transfer option, please send a screenshot of your payment confirmation to this chatbot.\n\n"
869
- "You can track your order status using your Order ID.\n"
870
- "Is there anything else you'd like to order?")
871
  else:
872
  return (f"Your order has been placed with Order ID {order_id}, "
873
- "but we could not initialize online payment. Please try again later, or "
874
- "you may opt to pay via bank transfer to Account Number 1433042821, Access Bank, Angelo Food Court 2 "
875
- "and send your payment screenshot to this chatbot.")
876
  elif message.lower() in ["no", "n"]:
877
  state.reset()
878
  if user_id in user_state:
@@ -882,19 +759,16 @@ async def process_order_flow(user_id: str, message: str) -> str:
882
  return "I didn't understand that. Please type 'yes' to confirm your order or 'no' to cancel it."
883
 
884
  return ""
885
-
886
 
887
  def _parse_single_dish_line(message: str, dish_name: str) -> dict:
888
-
889
  result = {"quantity": None, "phone": None, "address": None}
890
 
891
- # 1) Parse quantity
892
  numbers = re.findall(r'\d+', message)
893
  if numbers:
894
- # We'll assume the first numeric is quantity if no "for" pattern is used
895
  result["quantity"] = int(numbers[0])
896
 
897
- # 2) Parse phone
898
  phone_pattern = r'(\+?\d{10,15})'
899
  phone_match = re.search(phone_pattern, message)
900
  address = None
@@ -937,7 +811,7 @@ async def update_user_last_interaction(user_id: str):
937
  await session.commit()
938
 
939
  async def send_proactive_greeting(user_id: str):
940
- greeting = "Hi again! We miss you. Would you like to see our new menu items or get personalized recommendations?"
941
  await log_chat_to_db(user_id, "outbound", greeting)
942
  return greeting
943
 
@@ -984,12 +858,12 @@ async def chatbot_response(request: Request, background_tasks: BackgroundTasks):
984
  "image_url": image_url
985
  })
986
  response_payload = {
987
- "response": sentiment_modifier + "Here’s our delicious menu:",
988
  "menu": menu_with_images,
989
  "follow_up": (
990
  "To order, type the *number* or *name* of the dish you'd like. "
991
- "For example, type '1' or 'Jollof Rice' to order Jollof Rice.\n\n"
992
- "You can also ask for nutritional facts by typing, for example, 'Nutritional facts for Jollof Rice'."
993
  )
994
  }
995
  background_tasks.add_task(log_chat_to_db, user_id, "outbound", str(response_payload))
@@ -1011,8 +885,7 @@ async def chatbot_response(request: Request, background_tasks: BackgroundTasks):
1011
  })
1012
  return JSONResponse(content={"response": sentiment_modifier + order_response})
1013
 
1014
- # Instead of calling the LLM fallback, use a default response:
1015
- default_response = "I'm sorry, I didn't understand that. Please type 'menu' to see our options or 'order' to place an order."
1016
  background_tasks.add_task(log_chat_to_db, user_id, "outbound", default_response)
1017
  conversation_context[user_id].append({
1018
  "timestamp": datetime.utcnow().isoformat(),
@@ -1021,7 +894,6 @@ async def chatbot_response(request: Request, background_tasks: BackgroundTasks):
1021
  })
1022
  return JSONResponse(content={"response": sentiment_modifier + default_response})
1023
 
1024
-
1025
  @app.get("/chat_history/{user_id}")
1026
  async def get_chat_history(user_id: str):
1027
  async with async_session() as session:
@@ -1134,7 +1006,7 @@ async def payment_callback(request: Request):
1134
  )
1135
  except Exception as e:
1136
  print(f"WhatsApp message sending failed: {e}")
1137
- redirect_url = "https://wa.link/es8qdg"
1138
  return RedirectResponse(url=redirect_url)
1139
  else:
1140
  data = await request.json()
@@ -1163,10 +1035,8 @@ async def payment_callback(request: Request):
1163
  else:
1164
  raise HTTPException(status_code=404, detail="Order not found.")
1165
 
1166
-
1167
  @app.get("/track_order/{order_id}")
1168
  async def track_order(order_id: str):
1169
-
1170
  async with async_session() as session:
1171
  result = await session.execute(
1172
  select(OrderTracking)
@@ -1185,4 +1055,3 @@ async def track_order(order_id: str):
1185
  return JSONResponse(content=response)
1186
  else:
1187
  raise HTTPException(status_code=404, detail="No tracking information found for this order.")
1188
-
 
24
  from sqlalchemy.orm import sessionmaker, declarative_base
25
  from sqlalchemy import Column, Integer, String, DateTime, Text, Float
26
 
27
+ # Environment keys remain, but you can adjust them if needed.
28
  SPOONACULAR_API_KEY = os.getenv("SPOONACULAR_API_KEY", "default_fallback_value")
29
  PAYSTACK_SECRET_KEY = os.getenv("PAYSTACK_SECRET_KEY", "default_fallback_value")
30
  DATABASE_URL = os.getenv("DATABASE_URL", "default_fallback_value")
 
36
  WHATSAPP_ACCESS_TOKEN = os.getenv("WHATSAPP_ACCESS_TOKEN", "default_value")
37
  MANAGEMENT_WHATSAPP_NUMBER = os.getenv("MANAGEMENT_WHATSAPP_NUMBER", "default_value")
38
 
39
+ # Updated shipping costs for popular Los Angeles neighborhoods (in dollars)
40
  TOWN_SHIPPING_COSTS = {
41
+ "downtown los angeles": 5,
42
+ "hollywood": 6,
43
+ "santa monica": 7,
44
+ "pasadena": 5,
45
+ "default": 8
 
46
  }
47
 
48
  Base = declarative_base()
 
100
  id = Column(Integer, primary_key=True, index=True)
101
  name = Column(String, unique=True, index=True)
102
  description = Column(Text)
103
+ price = Column(Integer) # Price in dollars
104
  nutrition = Column(Text)
105
 
106
  class TownShippingCost(Base):
 
109
  town = Column(String, unique=True, index=True)
110
  cost = Column(Integer)
111
 
 
112
  engine = create_async_engine(DATABASE_URL, echo=True)
113
  async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
114
 
 
120
  conversation_context = {}
121
  proactive_timer = {}
122
 
123
+ # --- Updated Menu for Creole Kings ---
124
  menu_items = [
125
+ {"name": "Jambalaya", "description": "A classic Creole dish with rice, chicken, sausage, and shrimp", "price": 15, "nutrition": "Approximately 500 kcal"},
126
+ {"name": "Gumbo", "description": "A hearty stew with chicken, sausage, okra, and spices", "price": 18, "nutrition": "Approximately 600 kcal"},
127
+ {"name": "Crawfish Étouffée", "description": "A rich, flavorful dish with crawfish simmered in a spiced roux", "price": 20, "nutrition": "Approximately 550 kcal"},
128
+ {"name": "Red Beans & Rice", "description": "Slow-cooked red beans served over rice with Creole spices", "price": 12, "nutrition": "Approximately 450 kcal"},
129
+ {"name": "Shrimp Po' Boy", "description": "Crispy fried shrimp served on a French roll with lettuce and tomato", "price": 16, "nutrition": "Approximately 500 kcal"},
130
+ {"name": "Muffuletta Sandwich", "description": "A hearty sandwich with layered meats, cheeses, and olive salad", "price": 14, "nutrition": "Approximately 650 kcal"},
131
+ {"name": "Blackened Fish", "description": "Fish fillet seasoned with Cajun spices and seared", "price": 17, "nutrition": "Approximately 400 kcal"},
132
+ {"name": "Creole Salad", "description": "Fresh mixed greens with a Creole vinaigrette", "price": 10, "nutrition": "Approximately 300 kcal"},
133
+ {"name": "Beignets", "description": "Light and fluffy deep-fried pastries dusted with powdered sugar", "price": 8, "nutrition": "Approximately 350 kcal"},
134
+ {"name": "Bananas Foster", "description": "Sautéed bananas in a rich butter and brown sugar sauce, flambéed with rum", "price": 9, "nutrition": "Approximately 400 kcal"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  ]
136
 
137
+ creole_drinks = [
138
+ {"name": "Hurricane Cocktail", "description": "A sweet and tangy cocktail with rum and passion fruit", "price": 12, "nutrition": "Approximately 250 kcal"},
139
+ {"name": "Cajun Spice Margarita", "description": "A margarita with a hint of Cajun spice", "price": 11, "nutrition": "Approximately 200 kcal"},
140
+ {"name": "Iced Tea", "description": "Refreshing iced tea, unsweetened or sweetened", "price": 5, "nutrition": "Approximately 100 kcal"},
141
+ {"name": "Lemonade", "description": "Freshly squeezed lemonade", "price": 6, "nutrition": "Approximately 120 kcal"}
 
 
142
  ]
143
 
144
+ menu_items.extend(creole_drinks)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
  SESSION_TIMEOUT = timedelta(minutes=5)
147
 
 
175
  session.add(new_cost)
176
  await session.commit()
177
 
 
178
  async def log_chat_to_db(user_id: str, direction: str, message: str):
179
  async with async_session() as session:
180
  entry = ChatHistory(user_id=user_id, direction=direction, message=message)
 
217
  "email": email,
218
  "amount": amount,
219
  "reference": reference,
220
+ "callback_url": "https://creolekings.com/payment_callback" # Updated callback URL example
221
  }
222
  try:
223
  response = requests.post(url, json=data, headers=headers, timeout=10)
 
308
  def calculate_eta(destination: str) -> str:
309
  if not GOOGLE_MAPS_API_KEY:
310
  return "ETA unavailable (Google Maps API key missing)."
311
+ # Updated origin address for Creole Kings in Los Angeles
312
+ origin = "123 Main Street, Los Angeles, CA"
313
  url = f"https://maps.googleapis.com/maps/api/directions/json?origin={origin}&destination={destination}&key={GOOGLE_MAPS_API_KEY}"
314
 
315
  try:
 
360
  for item in items
361
  ]
362
 
 
363
  async def track_order(user_id: str, order_id: str) -> str:
364
  async with async_session() as session:
365
  order_result = await session.execute(
 
436
  Then, try an exact whole-word match using a regex with word boundaries.
437
  If no exact match is found, fall back to fuzzy matching.
438
  """
 
439
  normalized_input = re.sub(r'\s+', ' ', user_input.lower().strip())
440
  matched_dishes = []
441
 
 
442
  for item in menu_items:
443
  dish_name = item["name"]
 
 
444
  pattern = r'\b' + re.escape(dish_name.lower()) + r'\b'
445
  if re.search(pattern, normalized_input):
446
  matched_dishes.append(dish_name)
447
 
 
448
  if matched_dishes:
449
  return list(set(matched_dishes))
450
 
 
451
  for item in menu_items:
452
  dish_name = item["name"]
453
  score = fuzz.token_sort_ratio(normalized_input, dish_name.lower())
 
456
 
457
  return list(set(matched_dishes))
458
 
459
+ # Use the asynchronous get_dish_price from the database
 
 
 
 
 
 
460
  async def get_dish_price(dish: str) -> int:
461
  async with async_session() as session:
 
462
  result = await session.execute(select(MenuItem).where(MenuItem.name.ilike(dish)))
463
  menu_item = result.scalars().first()
464
  return menu_item.price if menu_item else 0
465
 
 
466
  async def get_shipping_cost(address: str) -> int:
467
  async with async_session() as session:
468
  result = await session.execute(select(TownShippingCost))
469
  costs = result.scalars().all()
470
  address_lower = address.lower()
 
471
  for cost in costs:
472
  if cost.town in address_lower:
473
  return cost.cost
 
474
  default_cost = next((c for c in costs if c.town == "default"), None)
475
+ return default_cost.cost if default_cost else 8
 
476
 
477
  def send_email_notification(order_details):
478
+ # Updated email notification for Creole Kings
479
  payload = {
480
+ "from": "orders@creolekings.com",
481
+ "to": "manager@creolekings.com",
482
  "subject": f"New Order Received: {order_details['order_id']}",
483
  "body": (
484
  f"New Order Received:\n"
485
  f"Order ID: {order_details['order_id']}\n"
486
  f"Dish: {order_details['dish']}\n"
487
  f"Quantity: {order_details['quantity']}\n"
488
+ f"Total Price: ${order_details['price']}\n"
489
  f"Phone: {order_details.get('phone_number', 'Not Provided')}\n"
490
  f"Delivery Address: {order_details.get('address', 'Not Provided')}\n"
491
  f"Extras: {order_details.get('extras', 'None')}\n"
492
  f"Status: Pending Payment"
493
  ),
494
+ "smtpHost": "smtp.gmail.com",
495
+ "smtpPort": 587,
496
+ "smtpSecure": "false",
497
+ "smtpUser": "orders@creolekings.com",
498
+ "smtpPassword": "creolekings_smtp_password",
 
499
  }
500
 
 
501
  url = "https://smtp-server-ten.vercel.app/smtp"
502
  try:
503
  response = requests.post(url, json=payload, timeout=10)
 
507
  print(f"Error sending email: {e}")
508
  return None
509
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
510
  async def process_order_flow(user_id: str, message: str) -> str:
511
  """
512
  Processes order flow allowing an unlimited number of dishes.
 
517
  del user_state[user_id]
518
  state = None
519
 
520
+ # 1) Initialize order flow with a welcome message for Creole Kings.
521
  if message.lower() in ["order", "menu"]:
522
  state = ConversationState()
523
  state.flow = "order"
524
  state.step = 1
525
  state.update_last_active()
526
  user_state[user_id] = state
527
+ return "Welcome to Creole Kings! What dish would you like to order today?"
 
 
528
 
529
  if not state and "order" in message.lower():
530
  state = ConversationState()
 
532
  state.step = 1
533
  state.update_last_active()
534
  user_state[user_id] = state
535
+ return "Welcome to Creole Kings! What dish would you like to order today?"
536
 
537
+ # 2) Detect dish(es)
538
  if not state or state.flow != "order":
539
  matched = match_dishes(message)
540
  if matched:
541
  if len(matched) == 1:
 
542
  found_dish = matched[0]
543
  state = ConversationState()
544
  state.flow = "order"
 
564
  state.step = 5
565
  return (f"Thanks! Your phone number is recorded as: {state.data['phone_number']}.\n"
566
  f"Your delivery address is: {state.data['address']}.\n"
567
+ f"Delivery fee: ${shipping_cost}. Would you like extras (yes/no)?")
568
  elif state.data.get("phone_number") and not state.data.get("address"):
569
+ return "Thank you. Please provide your delivery address (e.g., '3105551234, 123 Main St, Los Angeles, CA')."
570
  else:
571
+ return "Please provide your phone number and address (e.g., '3105551234, 123 Main St, Los Angeles, CA')."
572
  else:
 
573
  state = ConversationState()
574
  state.flow = "order"
575
  state.update_last_active()
 
582
  else:
583
  return "I couldn't identify the dish. Please type the dish name from our menu."
584
 
 
585
  if state and state.flow == "order" and "candidate_dishes" in state.data:
586
  normalized = message.strip().lower()
587
  if normalized in ["both", "all"]:
 
590
  state.step = 2
591
  dishes_str = ", ".join(state.data["dishes"])
592
  return (f"You have selected: {dishes_str}. How many servings of each would you like? "
593
+ "(For example, '2 for Jambalaya, 3 for Gumbo')")
594
  else:
595
  for dish in state.data["candidate_dishes"]:
596
  if dish.lower() in normalized:
 
602
  return (f"Please specify which one you'd like to order from: {dish_options} "
603
  "(or type 'both' if you'd like to order all).")
604
 
605
+ # 3) Step 2: Parse quantity details.
 
 
606
  if state and state.flow == "order" and state.step == 2:
 
607
  if "dishes" in state.data and len(state.data["dishes"]) > 1:
608
  pairs = re.findall(r'(\d+)\s*for\s*([a-zA-Z\s]+)', message, flags=re.IGNORECASE)
609
  if not pairs:
610
  return ("I'm sorry, I didn't understand the quantity details. "
611
+ "Please specify like '2 for Jambalaya, 3 for Gumbo'.")
612
  order_quantities = {}
613
  for quantity, dish_text in pairs:
614
  dish_text = dish_text.strip().lower()
 
620
  state.step = 3
621
  summary = "\n".join([f"{q} serving(s) of {d}" for d, q in order_quantities.items()])
622
  return (f"Got it. You have ordered:\n{summary}\n"
623
+ "Please provide your phone number and delivery address (e.g., '3105551234, 123 Main St, Los Angeles, CA').")
624
  else:
625
  return ("I'm sorry, I couldn't match those dishes. "
626
+ "Please try something like '2 for Jambalaya, 3 for Gumbo'.")
 
627
  numbers = re.findall(r'\d+', message)
628
  if not numbers:
629
  return "Please enter a valid number for the quantity (e.g., 1, 2, 3)."
 
634
  state.step = 3
635
  dish = state.data.get("dish", "")
636
  return (f"Got it. {quantity} serving(s) of {dish}.\n"
637
+ "Please provide your phone number and delivery address (e.g., '3105551234, 123 Main St, Los Angeles, CA').")
638
 
639
+ # 4) Step 3: Parse phone & address.
 
640
  if state and state.flow == "order" and state.step == 3:
641
  phone_pattern = r'(\+?\d{10,15})'
642
  phone_match = re.search(phone_pattern, message)
 
653
  state.data["address"] = message.strip()
654
  else:
655
  return ("Please provide both your phone number and address. "
656
+ "For example: '3105551234, 123 Main St, Los Angeles, CA'.")
657
  if not state.data.get("address"):
658
  return "Thank you. Please provide your delivery address."
 
659
  shipping_cost = await get_shipping_cost(state.data["address"])
660
  state.data["shipping_cost"] = shipping_cost
 
 
661
  state.data["extras"] = ""
 
 
662
  state.step = 7
 
 
663
  if "orders" in state.data:
664
  dish_summary = ", ".join(state.data["orders"].keys())
665
  quantity_total = sum(state.data["orders"].values())
666
  total_price = 0
667
  for dish, qty in state.data["orders"].items():
668
+ dish_price = await get_dish_price(dish)
669
+ total_price += dish_price * qty
670
  total_price += shipping_cost
671
  else:
672
  dish_summary = state.data.get("dish", "")
673
  quantity_total = state.data.get("quantity", 1)
674
+ dish_price = await get_dish_price(dish_summary)
675
  total_price = (quantity_total * dish_price) + shipping_cost
 
676
  return (f"Order Summary:\n"
677
  f"Dish(es): {dish_summary}\n"
678
  f"Quantity: {quantity_total}\n"
679
  f"Phone: {state.data.get('phone_number', '')}\n"
680
  f"Address: {state.data.get('address', '')}\n"
681
+ f"Delivery Fee: ${shipping_cost}\n"
682
+ f"Total Price: ${total_price}\n"
683
  "Confirm order? (yes/no)")
684
 
 
 
 
685
  # 7) Step 7: Order Confirmation and Payment Link Generation
686
  if state and state.flow == "order" and state.step == 7:
687
  if message.lower() in ["yes", "y"]:
688
  order_id = f"ORD-{int(time.time())}"
689
  state.data["order_id"] = order_id
690
+
 
691
  if "orders" in state.data:
692
  total_price = 0
693
  for dish, qty in state.data["orders"].items():
694
+ dish_price = await get_dish_price(dish)
695
+ total_price += dish_price * qty
696
  total_price += state.data.get("shipping_cost", 0)
697
  dish_summary = ", ".join(state.data["orders"].keys())
698
  quantity_total = sum(state.data["orders"].values())
699
  else:
700
  dish_summary = state.data.get("dish", "")
701
  quantity_total = state.data.get("quantity", 1)
702
+ dish_price = await get_dish_price(dish_summary)
703
  total_price = (quantity_total * dish_price) + state.data.get("shipping_cost", 0)
704
 
705
  state.data["price"] = str(total_price)
706
+
707
  async def save_order():
708
  async with async_session() as session:
709
  order = Order(
 
719
  await session.commit()
720
  asyncio.create_task(save_order())
721
  asyncio.create_task(log_order_tracking(order_id, "Order Placed", "Order placed and awaiting payment."))
722
+
 
723
  order_details = {
724
  "order_id": order_id,
725
  "dish": dish_summary,
 
729
  "address": state.data.get("address", "Not Provided"),
730
  "extras": state.data.get("extras", "None")
731
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
732
  email_response = send_email_notification(order_details)
733
  if email_response:
734
  print("Email notification sent successfully.")
735
  else:
736
  print("Failed to send email notification.")
737
+
738
+ email_for_paystack = "customer@example.com" # Replace with customer's email if available
739
  payment_data = create_paystack_payment_link(email_for_paystack, total_price * 100, order_id)
740
  state.reset()
741
  if user_id in user_state:
742
  del user_state[user_id]
743
  if payment_data.get("status"):
744
  payment_link = payment_data["data"]["authorization_url"]
745
+ return (f"Thank you for your order of {quantity_total} serving(s) of {dish_summary} from Creole Kings! "
746
  f"Your Order ID is {order_id}.\n\n"
747
+ "Please complete your payment online using the link below:\n"
748
+ f"{payment_link}\n\n"
749
+ "You can track your order status using your Order ID.\nWould you like to place another order?")
 
 
 
 
 
 
750
  else:
751
  return (f"Your order has been placed with Order ID {order_id}, "
752
+ "but we could not initialize online payment. Please try again later.")
 
 
753
  elif message.lower() in ["no", "n"]:
754
  state.reset()
755
  if user_id in user_state:
 
759
  return "I didn't understand that. Please type 'yes' to confirm your order or 'no' to cancel it."
760
 
761
  return ""
 
762
 
763
  def _parse_single_dish_line(message: str, dish_name: str) -> dict:
 
764
  result = {"quantity": None, "phone": None, "address": None}
765
 
766
+ # Parse quantity
767
  numbers = re.findall(r'\d+', message)
768
  if numbers:
 
769
  result["quantity"] = int(numbers[0])
770
 
771
+ # Parse phone
772
  phone_pattern = r'(\+?\d{10,15})'
773
  phone_match = re.search(phone_pattern, message)
774
  address = None
 
811
  await session.commit()
812
 
813
  async def send_proactive_greeting(user_id: str):
814
+ greeting = "Hi again! We miss you at Creole Kings. Would you like to see our new menu items or get personalized recommendations?"
815
  await log_chat_to_db(user_id, "outbound", greeting)
816
  return greeting
817
 
 
858
  "image_url": image_url
859
  })
860
  response_payload = {
861
+ "response": sentiment_modifier + "Here’s our mouthwatering Creole menu:",
862
  "menu": menu_with_images,
863
  "follow_up": (
864
  "To order, type the *number* or *name* of the dish you'd like. "
865
+ "For example, type '1' or 'Jambalaya' to order.\n\n"
866
+ "You can also ask for nutritional facts by typing, for example, 'Nutritional facts for Gumbo'."
867
  )
868
  }
869
  background_tasks.add_task(log_chat_to_db, user_id, "outbound", str(response_payload))
 
885
  })
886
  return JSONResponse(content={"response": sentiment_modifier + order_response})
887
 
888
+ default_response = "I'm sorry, I didn't understand that. Please type 'menu' to see our options or 'order' to place an order at Creole Kings."
 
889
  background_tasks.add_task(log_chat_to_db, user_id, "outbound", default_response)
890
  conversation_context[user_id].append({
891
  "timestamp": datetime.utcnow().isoformat(),
 
894
  })
895
  return JSONResponse(content={"response": sentiment_modifier + default_response})
896
 
 
897
  @app.get("/chat_history/{user_id}")
898
  async def get_chat_history(user_id: str):
899
  async with async_session() as session:
 
1006
  )
1007
  except Exception as e:
1008
  print(f"WhatsApp message sending failed: {e}")
1009
+ redirect_url = "https://creolekings.com/thankyou"
1010
  return RedirectResponse(url=redirect_url)
1011
  else:
1012
  data = await request.json()
 
1035
  else:
1036
  raise HTTPException(status_code=404, detail="Order not found.")
1037
 
 
1038
  @app.get("/track_order/{order_id}")
1039
  async def track_order(order_id: str):
 
1040
  async with async_session() as session:
1041
  result = await session.execute(
1042
  select(OrderTracking)
 
1055
  return JSONResponse(content=response)
1056
  else:
1057
  raise HTTPException(status_code=404, detail="No tracking information found for this order.")