Fred808 commited on
Commit
0db5605
·
verified ·
1 Parent(s): 7255dd1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +296 -296
app.py CHANGED
@@ -460,331 +460,331 @@ async def process_order_flow(user_id: str, message: str) -> str:
460
  return f"You selected {found_dish}. How many servings would you like?"
461
  else:
462
  return "I couldn't identify the dish. Please type the dish name from our menu."
463
- if state.step == 2:
464
- numbers = re.findall(r'\d+', message)
465
- if not numbers:
466
- return "Please enter a valid number for the quantity (e.g., 1, 2, 3)."
467
- quantity = int(numbers[0])
468
- if quantity <= 0:
469
- return "Please enter a valid quantity (e.g., 1, 2, 3)."
470
- state.data["quantity"] = quantity
471
- state.step = 3
472
- return f"Got it. {quantity} serving(s) of {state.data.get('dish')}. Please provide your phone number and delivery address."
473
-
474
- if state.step == 3:
475
- phone_pattern = r'(\+?\d{10,15})'
476
- phone_match = re.search(phone_pattern, message)
477
- address = None
478
- if phone_match:
479
- phone_number = phone_match.group(1)
480
- address_start = phone_match.end()
481
- address = message[address_start:].strip()
482
- address = re.sub(r'^[,\s]+|[,\s]+$', '', address)
483
- if phone_match and address:
484
- state.data["phone_number"] = phone_number
485
- state.data["address"] = address
486
- asyncio.create_task(update_user_profile(user_id, phone_number, address))
487
- shipping_cost = calculate_shipping_cost(address)
488
- state.data["shipping_cost"] = shipping_cost
489
- state.step = 5
490
- return (f"Thanks! Your phone number is recorded as: {phone_number}.\n"
491
- f"Your delivery address is: {address}.\n"
492
- f"Your delivery cost is N{shipping_cost}. Would you like extras (yes/no)?")
493
- elif phone_match:
494
- state.data["phone_number"] = phone_match.group(1)
495
- asyncio.create_task(update_user_profile(user_id, phone_number))
496
- return "Thank you. Please provide your delivery address."
497
- else:
498
- return ("Please provide both your phone number and delivery address. "
499
- "For example: '09162409591, 1, Iyana Isashi, Isashi, Ojo, Lagos'.")
500
-
501
- # Steps 4, 5, 6, and 7 remain unchanged.
502
- if state.step == 4:
503
- state.data["address"] = message
504
- asyncio.create_task(update_user_profile(user_id, address=message))
505
- shipping_cost = calculate_shipping_cost(message)
506
  state.data["shipping_cost"] = shipping_cost
507
  state.step = 5
508
- return (f"Thanks. Your delivery address is recorded as: {message}.\n"
 
509
  f"Your delivery cost is N{shipping_cost}. Would you like extras (yes/no)?")
510
-
511
- if state.step == 5:
512
- if message.lower() in ["yes", "y"]:
513
- state.step = 6
514
- return "Please list the extras you'd like (e.g., drinks, sides)."
515
- elif message.lower() in ["no", "n"]:
516
- state.data["extras"] = ""
517
- state.step = 7
518
- dish = state.data.get("dish", "")
519
- quantity = state.data.get("quantity", 1)
520
- phone = state.data.get("phone_number", "")
521
- address = state.data.get("address", "")
522
- shipping_cost = state.data.get("shipping_cost", 0)
523
- price_per_serving = 1500
524
- total_price = (quantity * price_per_serving) + shipping_cost
525
- summary = (f"Order Summary:\nDish: {dish}\nQuantity: {quantity}\n"
526
- f"Phone: {phone}\nAddress: {address}\n"
527
- f"Shipping Cost: N{shipping_cost}\n"
528
- f"Total Price: N{total_price}\n"
529
- f"Extras: None\nConfirm order? (yes/no)")
530
- return summary
531
- else:
532
- return "Please respond with 'yes' or 'no' regarding extras."
533
-
534
- if state.step == 6:
535
- state.data["extras"] = message
536
  state.step = 7
537
  dish = state.data.get("dish", "")
538
  quantity = state.data.get("quantity", 1)
539
  phone = state.data.get("phone_number", "")
540
  address = state.data.get("address", "")
541
  shipping_cost = state.data.get("shipping_cost", 0)
542
- extras = state.data.get("extras", "")
543
  price_per_serving = 1500
544
  total_price = (quantity * price_per_serving) + shipping_cost
545
  summary = (f"Order Summary:\nDish: {dish}\nQuantity: {quantity}\n"
546
  f"Phone: {phone}\nAddress: {address}\n"
547
  f"Shipping Cost: N{shipping_cost}\n"
548
  f"Total Price: N{total_price}\n"
549
- f"Extras: {extras}\nConfirm order? (yes/no)")
550
  return summary
551
-
552
- if state.step == 7:
553
- if message.lower() in ["yes", "y"]:
554
- order_id = f"ORD-{int(time.time())}"
555
- state.data["order_id"] = order_id
556
- price_per_serving = 1500
557
- quantity = state.data.get("quantity", 1)
558
- shipping_cost = state.data.get("shipping_cost", 0)
559
- total_price = (quantity * price_per_serving) + shipping_cost
560
- state.data["price"] = str(total_price)
561
-
562
- async def save_order():
563
- async with async_session() as session:
564
- order = Order(
565
- order_id=order_id,
566
- user_id=user_id,
567
- dish=state.data["dish"],
568
- quantity=str(quantity),
569
- price=str(total_price),
570
- status="Pending Payment",
571
- delivery_address=state.data.get("address", "")
572
- )
573
- session.add(order)
574
- await session.commit()
575
- asyncio.create_task(save_order())
576
- asyncio.create_task(log_order_tracking(order_id, "Order Placed", "Order placed and awaiting payment."))
577
-
578
- async def notify_management_order(order_details: dict):
579
- message_body = (
580
- f"New Order Received:\n"
581
- f"Order ID: {order_details['order_id']}\n"
582
- f"Dish: {order_details['dish']}\n"
583
- f"Quantity: {order_details['quantity']}\n"
584
- f"Total Price: {order_details['price']}\n"
585
- f"Phone: {state.data.get('phone_number', '')}\n"
586
- f"Delivery Address: {order_details.get('address', 'Not Provided')}\n"
587
- f"Extras: {state.data.get('extras', 'None')}\n"
588
- f"Status: Pending Payment"
589
- )
590
- await asyncio.to_thread(send_whatsapp_message, MANAGEMENT_WHATSAPP_NUMBER, message_body)
591
- order_details = {
592
- "order_id": order_id,
593
- "dish": state.data["dish"],
594
- "quantity": state.data["quantity"],
595
- "price": state.data["price"],
596
- "address": state.data.get("address", "")
597
- }
598
- asyncio.create_task(notify_management_order(order_details))
599
-
600
- email = "customer@example.com"
601
- payment_data = create_paystack_payment_link(email, total_price * 100, order_id)
602
- dish_name = state.data.get("dish", "")
603
- state.reset()
604
- if user_id in user_state:
605
- del user_state[user_id]
606
- if payment_data.get("status"):
607
- payment_link = payment_data["data"]["authorization_url"]
608
- return (f"Thank you for your order of {quantity} serving(s) of {dish_name}! "
609
- f"Your Order ID is {order_id}.\nPlease complete payment here: {payment_link}\n"
610
- "You can track your order status using your Order ID.\n"
611
- "Is there anything else you'd like to order?")
612
- else:
613
- return (f"Your order has been placed with Order ID {order_id}, "
614
- "but we could not initialize payment. Please try again later.")
615
- else:
616
- state.reset()
617
- if user_id in user_state:
618
- del user_state[user_id]
619
- return "Order canceled. Let me know if you'd like to try again."
620
- return ""
621
-
622
-
623
- async def get_or_create_user_profile(user_id: str, phone_number: str = None) -> UserProfile:
624
- async with async_session() as session:
625
- result = await session.execute(
626
- select(UserProfile).where(UserProfile.user_id == user_id)
627
- )
628
- profile = result.scalars().first()
629
- if profile is None:
630
- profile = UserProfile(
631
- user_id=user_id,
632
- phone_number=phone_number,
633
- last_interaction=datetime.utcnow()
634
- )
635
- session.add(profile)
636
- await session.commit()
637
- return profile
638
-
639
- async def update_user_last_interaction(user_id: str):
640
- async with async_session() as session:
641
- result = await session.execute(
642
- select(UserProfile).where(UserProfile.user_id == user_id)
643
- )
644
- profile = result.scalars().first()
645
- if profile:
646
- profile.last_interaction = datetime.utcnow()
647
- await session.commit()
648
 
649
- async def send_proactive_greeting(user_id: str):
650
- greeting = "Hi again! We miss you. Would you like to see our new menu items or get personalized recommendations?"
651
- await log_chat_to_db(user_id, "outbound", greeting)
652
- return greeting
 
 
 
 
 
 
 
 
 
 
 
 
 
653
 
654
- app = FastAPI()
 
 
 
 
 
 
 
 
655
 
656
- @app.on_event("startup")
657
- async def on_startup():
658
- await init_db()
 
 
 
 
 
 
 
 
 
 
 
 
659
 
660
- @app.post("/chatbot")
661
- async def chatbot_response(request: Request, background_tasks: BackgroundTasks):
662
- data = await request.json()
663
- user_id = data.get("user_id")
664
- user_message = data.get("message", "").strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
665
 
666
- if user_id not in conversation_context:
667
- conversation_context[user_id] = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
668
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
669
  conversation_context[user_id].append({
670
  "timestamp": datetime.utcnow().isoformat(),
671
- "role": "user",
672
- "message": user_message
673
  })
674
-
675
- background_tasks.add_task(log_chat_to_db, user_id, "inbound", user_message)
676
-
677
- sentiment_score = analyze_sentiment(user_message)
678
- background_tasks.add_task(log_sentiment, user_id, user_message, sentiment_score)
679
- sentiment_modifier = "Great to hear from you! " if sentiment_score > 0.3 else ""
680
-
681
- if user_message.strip() == "1" or "menu" in user_message.lower():
682
- if user_id in user_state:
683
- del user_state[user_id]
684
- menu_with_images = []
685
- for index, item in enumerate(menu_items, start=1):
686
- image_url = google_image_scrape(item["name"])
687
- menu_with_images.append({
688
- "number": index,
689
- "name": item["name"],
690
- "description": item["description"],
691
- "price": item["price"],
692
- "image_url": image_url
693
- })
694
- response_payload = {
695
- "response": sentiment_modifier + "Here’s our delicious menu:",
696
- "menu": menu_with_images,
697
- "follow_up": (
698
- "To order, type the *number* or *name* of the dish you'd like. "
699
- "For example, type '1' or 'Jollof Rice' to order Jollof Rice.\n\n"
700
- "You can also ask for nutritional facts by typing, for example, 'Nutritional facts for Jollof Rice'."
701
- )
702
- }
703
- background_tasks.add_task(log_chat_to_db, user_id, "outbound", str(response_payload))
704
  conversation_context[user_id].append({
705
  "timestamp": datetime.utcnow().isoformat(),
706
  "role": "bot",
707
- "message": response_payload["response"]
708
  })
709
- return JSONResponse(content=response_payload)
710
-
711
- if is_order_intent(user_message) or (user_id in user_state and user_state[user_id].flow == "order"):
712
- order_response = await process_order_flow(user_id, user_message)
713
- if order_response:
714
- background_tasks.add_task(log_chat_to_db, user_id, "outbound", order_response)
715
- conversation_context[user_id].append({
716
- "timestamp": datetime.utcnow().isoformat(),
717
- "role": "bot",
718
- "message": order_response
719
- })
720
- return JSONResponse(content={"response": sentiment_modifier + order_response})
721
-
722
- # Instead of calling the LLM fallback, use a default response:
723
- default_response = "I'm sorry, I didn't understand that. Please type 'menu' to see our options or 'order' to place an order."
724
- background_tasks.add_task(log_chat_to_db, user_id, "outbound", default_response)
725
- conversation_context[user_id].append({
726
- "timestamp": datetime.utcnow().isoformat(),
727
- "role": "bot",
728
- "message": default_response
729
- })
730
- return JSONResponse(content={"response": sentiment_modifier + default_response})
731
-
732
-
733
- @app.get("/chat_history/{user_id}")
734
- async def get_chat_history(user_id: str):
735
- async with async_session() as session:
736
- result = await session.execute(
737
- ChatHistory.__table__.select().where(ChatHistory.user_id == user_id)
738
- )
739
- history = result.fetchall()
740
- return [dict(row) for row in history]
741
-
742
- @app.get("/order/{order_id}")
743
- async def get_order(order_id: str):
744
- async with async_session() as session:
745
- result = await session.execute(
746
- Order.__table__.select().where(Order.order_id == order_id)
747
- )
748
- order = result.fetchone()
749
- if order:
750
- return dict(order)
751
- else:
752
- raise HTTPException(status_code=404, detail="Order not found.")
753
-
754
- @app.get("/user_profile/{user_id}")
755
- async def get_user_profile(user_id: str):
756
- profile = await get_or_create_user_profile(user_id)
757
- return {
758
- "user_id": profile.user_id,
759
- "phone_number": profile.phone_number,
760
- "name": profile.name,
761
- "email": profile.email,
762
- "preferences": profile.preferences,
763
- "last_interaction": profile.last_interaction.isoformat(),
764
- "order_ids": profile.order_ids
765
- }
766
-
767
- @app.get("/analytics")
768
- async def get_analytics():
769
- async with async_session() as session:
770
- msg_result = await session.execute(ChatHistory.__table__.count())
771
- total_messages = msg_result.scalar() or 0
772
- order_result = await session.execute(Order.__table__.count())
773
- total_orders = order_result.scalar() or 0
774
- sentiment_result = await session.execute("SELECT AVG(sentiment_score) FROM sentiment_logs")
775
- avg_sentiment = sentiment_result.scalar() or 0
776
- return {
777
- "total_messages": total_messages,
778
- "total_orders": total_orders,
779
- "average_sentiment": avg_sentiment
780
- }
781
-
782
- HUGGING_FACE_API_TOKEN = os.getenv("HUGGING_FACE_API_TOKEN")
783
- if not HUGGING_FACE_API_TOKEN:
784
- raise ValueError("Hugging Face API token not found in environment variables.")
785
-
786
- WHISPER_API_URL = "https://router.huggingface.co/fal-ai"
787
- WHISPER_API_HEADERS = {"Authorization": f"Bearer {HUGGING_FACE_API_TOKEN}"}
788
 
789
  class TranscriptionResponse(BaseModel):
790
  transcription: str
 
460
  return f"You selected {found_dish}. How many servings would you like?"
461
  else:
462
  return "I couldn't identify the dish. Please type the dish name from our menu."
463
+ if state.step == 2:
464
+ numbers = re.findall(r'\d+', message)
465
+ if not numbers:
466
+ return "Please enter a valid number for the quantity (e.g., 1, 2, 3)."
467
+ quantity = int(numbers[0])
468
+ if quantity <= 0:
469
+ return "Please enter a valid quantity (e.g., 1, 2, 3)."
470
+ state.data["quantity"] = quantity
471
+ state.step = 3
472
+ return f"Got it. {quantity} serving(s) of {state.data.get('dish')}. Please provide your phone number and delivery address."
473
+
474
+ if state.step == 3:
475
+ phone_pattern = r'(\+?\d{10,15})'
476
+ phone_match = re.search(phone_pattern, message)
477
+ address = None
478
+ if phone_match:
479
+ phone_number = phone_match.group(1)
480
+ address_start = phone_match.end()
481
+ address = message[address_start:].strip()
482
+ address = re.sub(r'^[,\s]+|[,\s]+$', '', address)
483
+ if phone_match and address:
484
+ state.data["phone_number"] = phone_number
485
+ state.data["address"] = address
486
+ asyncio.create_task(update_user_profile(user_id, phone_number, address))
487
+ shipping_cost = calculate_shipping_cost(address)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
488
  state.data["shipping_cost"] = shipping_cost
489
  state.step = 5
490
+ return (f"Thanks! Your phone number is recorded as: {phone_number}.\n"
491
+ f"Your delivery address is: {address}.\n"
492
  f"Your delivery cost is N{shipping_cost}. Would you like extras (yes/no)?")
493
+ elif phone_match:
494
+ state.data["phone_number"] = phone_match.group(1)
495
+ asyncio.create_task(update_user_profile(user_id, phone_number))
496
+ return "Thank you. Please provide your delivery address."
497
+ else:
498
+ return ("Please provide both your phone number and delivery address. "
499
+ "For example: '09162409591, 1, Iyana Isashi, Isashi, Ojo, Lagos'.")
500
+
501
+ # Steps 4, 5, 6, and 7 remain unchanged.
502
+ if state.step == 4:
503
+ state.data["address"] = message
504
+ asyncio.create_task(update_user_profile(user_id, address=message))
505
+ shipping_cost = calculate_shipping_cost(message)
506
+ state.data["shipping_cost"] = shipping_cost
507
+ state.step = 5
508
+ return (f"Thanks. Your delivery address is recorded as: {message}.\n"
509
+ f"Your delivery cost is N{shipping_cost}. Would you like extras (yes/no)?")
510
+
511
+ if state.step == 5:
512
+ if message.lower() in ["yes", "y"]:
513
+ state.step = 6
514
+ return "Please list the extras you'd like (e.g., drinks, sides)."
515
+ elif message.lower() in ["no", "n"]:
516
+ state.data["extras"] = ""
 
 
517
  state.step = 7
518
  dish = state.data.get("dish", "")
519
  quantity = state.data.get("quantity", 1)
520
  phone = state.data.get("phone_number", "")
521
  address = state.data.get("address", "")
522
  shipping_cost = state.data.get("shipping_cost", 0)
 
523
  price_per_serving = 1500
524
  total_price = (quantity * price_per_serving) + shipping_cost
525
  summary = (f"Order Summary:\nDish: {dish}\nQuantity: {quantity}\n"
526
  f"Phone: {phone}\nAddress: {address}\n"
527
  f"Shipping Cost: N{shipping_cost}\n"
528
  f"Total Price: N{total_price}\n"
529
+ f"Extras: None\nConfirm order? (yes/no)")
530
  return summary
531
+ else:
532
+ return "Please respond with 'yes' or 'no' regarding extras."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
533
 
534
+ if state.step == 6:
535
+ state.data["extras"] = message
536
+ state.step = 7
537
+ dish = state.data.get("dish", "")
538
+ quantity = state.data.get("quantity", 1)
539
+ phone = state.data.get("phone_number", "")
540
+ address = state.data.get("address", "")
541
+ shipping_cost = state.data.get("shipping_cost", 0)
542
+ extras = state.data.get("extras", "")
543
+ price_per_serving = 1500
544
+ total_price = (quantity * price_per_serving) + shipping_cost
545
+ summary = (f"Order Summary:\nDish: {dish}\nQuantity: {quantity}\n"
546
+ f"Phone: {phone}\nAddress: {address}\n"
547
+ f"Shipping Cost: N{shipping_cost}\n"
548
+ f"Total Price: N{total_price}\n"
549
+ f"Extras: {extras}\nConfirm order? (yes/no)")
550
+ return summary
551
 
552
+ if state.step == 7:
553
+ if message.lower() in ["yes", "y"]:
554
+ order_id = f"ORD-{int(time.time())}"
555
+ state.data["order_id"] = order_id
556
+ price_per_serving = 1500
557
+ quantity = state.data.get("quantity", 1)
558
+ shipping_cost = state.data.get("shipping_cost", 0)
559
+ total_price = (quantity * price_per_serving) + shipping_cost
560
+ state.data["price"] = str(total_price)
561
 
562
+ async def save_order():
563
+ async with async_session() as session:
564
+ order = Order(
565
+ order_id=order_id,
566
+ user_id=user_id,
567
+ dish=state.data["dish"],
568
+ quantity=str(quantity),
569
+ price=str(total_price),
570
+ status="Pending Payment",
571
+ delivery_address=state.data.get("address", "")
572
+ )
573
+ session.add(order)
574
+ await session.commit()
575
+ asyncio.create_task(save_order())
576
+ asyncio.create_task(log_order_tracking(order_id, "Order Placed", "Order placed and awaiting payment."))
577
 
578
+ async def notify_management_order(order_details: dict):
579
+ message_body = (
580
+ f"New Order Received:\n"
581
+ f"Order ID: {order_details['order_id']}\n"
582
+ f"Dish: {order_details['dish']}\n"
583
+ f"Quantity: {order_details['quantity']}\n"
584
+ f"Total Price: {order_details['price']}\n"
585
+ f"Phone: {state.data.get('phone_number', '')}\n"
586
+ f"Delivery Address: {order_details.get('address', 'Not Provided')}\n"
587
+ f"Extras: {state.data.get('extras', 'None')}\n"
588
+ f"Status: Pending Payment"
589
+ )
590
+ await asyncio.to_thread(send_whatsapp_message, MANAGEMENT_WHATSAPP_NUMBER, message_body)
591
+ order_details = {
592
+ "order_id": order_id,
593
+ "dish": state.data["dish"],
594
+ "quantity": state.data["quantity"],
595
+ "price": state.data["price"],
596
+ "address": state.data.get("address", "")
597
+ }
598
+ asyncio.create_task(notify_management_order(order_details))
599
 
600
+ email = "customer@example.com"
601
+ payment_data = create_paystack_payment_link(email, total_price * 100, order_id)
602
+ dish_name = state.data.get("dish", "")
603
+ state.reset()
604
+ if user_id in user_state:
605
+ del user_state[user_id]
606
+ if payment_data.get("status"):
607
+ payment_link = payment_data["data"]["authorization_url"]
608
+ return (f"Thank you for your order of {quantity} serving(s) of {dish_name}! "
609
+ f"Your Order ID is {order_id}.\nPlease complete payment here: {payment_link}\n"
610
+ "You can track your order status using your Order ID.\n"
611
+ "Is there anything else you'd like to order?")
612
+ else:
613
+ return (f"Your order has been placed with Order ID {order_id}, "
614
+ "but we could not initialize payment. Please try again later.")
615
+ else:
616
+ state.reset()
617
+ if user_id in user_state:
618
+ del user_state[user_id]
619
+ return "Order canceled. Let me know if you'd like to try again."
620
+ return ""
621
+
622
 
623
+ async def get_or_create_user_profile(user_id: str, phone_number: str = None) -> UserProfile:
624
+ async with async_session() as session:
625
+ result = await session.execute(
626
+ select(UserProfile).where(UserProfile.user_id == user_id)
627
+ )
628
+ profile = result.scalars().first()
629
+ if profile is None:
630
+ profile = UserProfile(
631
+ user_id=user_id,
632
+ phone_number=phone_number,
633
+ last_interaction=datetime.utcnow()
634
+ )
635
+ session.add(profile)
636
+ await session.commit()
637
+ return profile
638
+
639
+ async def update_user_last_interaction(user_id: str):
640
+ async with async_session() as session:
641
+ result = await session.execute(
642
+ select(UserProfile).where(UserProfile.user_id == user_id)
643
+ )
644
+ profile = result.scalars().first()
645
+ if profile:
646
+ profile.last_interaction = datetime.utcnow()
647
+ await session.commit()
648
+
649
+ async def send_proactive_greeting(user_id: str):
650
+ greeting = "Hi again! We miss you. Would you like to see our new menu items or get personalized recommendations?"
651
+ await log_chat_to_db(user_id, "outbound", greeting)
652
+ return greeting
653
+
654
+ app = FastAPI()
655
+
656
+ @app.on_event("startup")
657
+ async def on_startup():
658
+ await init_db()
659
+
660
+ @app.post("/chatbot")
661
+ async def chatbot_response(request: Request, background_tasks: BackgroundTasks):
662
+ data = await request.json()
663
+ user_id = data.get("user_id")
664
+ user_message = data.get("message", "").strip()
665
+
666
+ if user_id not in conversation_context:
667
+ conversation_context[user_id] = []
668
+
669
+ conversation_context[user_id].append({
670
+ "timestamp": datetime.utcnow().isoformat(),
671
+ "role": "user",
672
+ "message": user_message
673
+ })
674
+
675
+ background_tasks.add_task(log_chat_to_db, user_id, "inbound", user_message)
676
+
677
+ sentiment_score = analyze_sentiment(user_message)
678
+ background_tasks.add_task(log_sentiment, user_id, user_message, sentiment_score)
679
+ sentiment_modifier = "Great to hear from you! " if sentiment_score > 0.3 else ""
680
+
681
+ if user_message.strip() == "1" or "menu" in user_message.lower():
682
+ if user_id in user_state:
683
+ del user_state[user_id]
684
+ menu_with_images = []
685
+ for index, item in enumerate(menu_items, start=1):
686
+ image_url = google_image_scrape(item["name"])
687
+ menu_with_images.append({
688
+ "number": index,
689
+ "name": item["name"],
690
+ "description": item["description"],
691
+ "price": item["price"],
692
+ "image_url": image_url
693
+ })
694
+ response_payload = {
695
+ "response": sentiment_modifier + "Here’s our delicious menu:",
696
+ "menu": menu_with_images,
697
+ "follow_up": (
698
+ "To order, type the *number* or *name* of the dish you'd like. "
699
+ "For example, type '1' or 'Jollof Rice' to order Jollof Rice.\n\n"
700
+ "You can also ask for nutritional facts by typing, for example, 'Nutritional facts for Jollof Rice'."
701
+ )
702
+ }
703
+ background_tasks.add_task(log_chat_to_db, user_id, "outbound", str(response_payload))
704
  conversation_context[user_id].append({
705
  "timestamp": datetime.utcnow().isoformat(),
706
+ "role": "bot",
707
+ "message": response_payload["response"]
708
  })
709
+ return JSONResponse(content=response_payload)
710
+
711
+ if is_order_intent(user_message) or (user_id in user_state and user_state[user_id].flow == "order"):
712
+ order_response = await process_order_flow(user_id, user_message)
713
+ if order_response:
714
+ background_tasks.add_task(log_chat_to_db, user_id, "outbound", order_response)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
715
  conversation_context[user_id].append({
716
  "timestamp": datetime.utcnow().isoformat(),
717
  "role": "bot",
718
+ "message": order_response
719
  })
720
+ return JSONResponse(content={"response": sentiment_modifier + order_response})
721
+
722
+ # Instead of calling the LLM fallback, use a default response:
723
+ default_response = "I'm sorry, I didn't understand that. Please type 'menu' to see our options or 'order' to place an order."
724
+ background_tasks.add_task(log_chat_to_db, user_id, "outbound", default_response)
725
+ conversation_context[user_id].append({
726
+ "timestamp": datetime.utcnow().isoformat(),
727
+ "role": "bot",
728
+ "message": default_response
729
+ })
730
+ return JSONResponse(content={"response": sentiment_modifier + default_response})
731
+
732
+
733
+ @app.get("/chat_history/{user_id}")
734
+ async def get_chat_history(user_id: str):
735
+ async with async_session() as session:
736
+ result = await session.execute(
737
+ ChatHistory.__table__.select().where(ChatHistory.user_id == user_id)
738
+ )
739
+ history = result.fetchall()
740
+ return [dict(row) for row in history]
741
+
742
+ @app.get("/order/{order_id}")
743
+ async def get_order(order_id: str):
744
+ async with async_session() as session:
745
+ result = await session.execute(
746
+ Order.__table__.select().where(Order.order_id == order_id)
747
+ )
748
+ order = result.fetchone()
749
+ if order:
750
+ return dict(order)
751
+ else:
752
+ raise HTTPException(status_code=404, detail="Order not found.")
753
+
754
+ @app.get("/user_profile/{user_id}")
755
+ async def get_user_profile(user_id: str):
756
+ profile = await get_or_create_user_profile(user_id)
757
+ return {
758
+ "user_id": profile.user_id,
759
+ "phone_number": profile.phone_number,
760
+ "name": profile.name,
761
+ "email": profile.email,
762
+ "preferences": profile.preferences,
763
+ "last_interaction": profile.last_interaction.isoformat(),
764
+ "order_ids": profile.order_ids
765
+ }
766
+
767
+ @app.get("/analytics")
768
+ async def get_analytics():
769
+ async with async_session() as session:
770
+ msg_result = await session.execute(ChatHistory.__table__.count())
771
+ total_messages = msg_result.scalar() or 0
772
+ order_result = await session.execute(Order.__table__.count())
773
+ total_orders = order_result.scalar() or 0
774
+ sentiment_result = await session.execute("SELECT AVG(sentiment_score) FROM sentiment_logs")
775
+ avg_sentiment = sentiment_result.scalar() or 0
776
+ return {
777
+ "total_messages": total_messages,
778
+ "total_orders": total_orders,
779
+ "average_sentiment": avg_sentiment
780
+ }
781
+
782
+ HUGGING_FACE_API_TOKEN = os.getenv("HUGGING_FACE_API_TOKEN")
783
+ if not HUGGING_FACE_API_TOKEN:
784
+ raise ValueError("Hugging Face API token not found in environment variables.")
785
+
786
+ WHISPER_API_URL = "https://router.huggingface.co/fal-ai"
787
+ WHISPER_API_HEADERS = {"Authorization": f"Bearer {HUGGING_FACE_API_TOKEN}"}
 
 
 
 
 
 
 
 
 
 
 
788
 
789
  class TranscriptionResponse(BaseModel):
790
  transcription: str