zenaight commited on
Commit ·
a35f267
1
Parent(s): 729c393
Implement image handling in AI chat and enhance WhatsApp messaging
Browse files- Introduced a new `handle_image_request` function to process user requests for property images, allowing the AI to respond with relevant image messages when triggered.
- Updated the `process_message` function to return both the AI response and associated property details, facilitating image requests.
- Enhanced the WhatsApp messaging functionality with a new `send_multiple_messages` function to handle sending both text and image messages, including delays to avoid rate limiting.
- These changes aim to improve user interaction by providing a more comprehensive response to image requests while maintaining clarity in communication.
- ai_chat.py +57 -5
- main.py +20 -5
- whatsapp.py +47 -1
ai_chat.py
CHANGED
|
@@ -58,19 +58,18 @@ def chat_with_session_memory(state):
|
|
| 58 |
f"- {p.get('title')} in {p.get('location')}, {p.get('city')}: "
|
| 59 |
f"{p.get('size_sqm')} sqm, {p.get('price')} ({p.get('price_type')})\n"
|
| 60 |
)
|
| 61 |
-
# Include all available data for the LLM to use
|
| 62 |
if p.get("listing_url"):
|
| 63 |
system_message += f" URL: {p.get('listing_url')}\n"
|
| 64 |
-
if p.get("images"):
|
| 65 |
-
system_message += f" Images: {', '.join(p.get('images', [])[:3])}\n"
|
| 66 |
if p.get("features"):
|
| 67 |
system_message += f" Features: {', '.join(p.get('features', [])[:5])}\n"
|
| 68 |
if p.get("floorplan_pdf"):
|
| 69 |
system_message += f" Floorplan: {p.get('floorplan_pdf')}\n"
|
| 70 |
if p.get("video_url"):
|
| 71 |
system_message += f" Video: {p.get('video_url')}\n"
|
|
|
|
| 72 |
|
| 73 |
-
system_message += "\n\nIMPORTANT: You may only reference listings passed in state['properties']. If the user requests more detail, respond with whatever is in that listing dict (URL, images, features, etc.). If information is not available, respond with 'For this listing, I don't have [specific detail] available right now'."
|
| 74 |
|
| 75 |
# Build messages array with history
|
| 76 |
messages = [{"role": "system", "content": system_message}]
|
|
@@ -425,4 +424,57 @@ async def process_message(user_message: str, user_info: dict = None, session_id:
|
|
| 425 |
await save_message(session_id, wa_id, wamid, "user", user_message)
|
| 426 |
await save_message(session_id, wa_id, f"{wamid}_ai", "assistant", result["response"])
|
| 427 |
|
| 428 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
f"- {p.get('title')} in {p.get('location')}, {p.get('city')}: "
|
| 59 |
f"{p.get('size_sqm')} sqm, {p.get('price')} ({p.get('price_type')})\n"
|
| 60 |
)
|
| 61 |
+
# Include all available data for the LLM to use (but not image URLs in listings)
|
| 62 |
if p.get("listing_url"):
|
| 63 |
system_message += f" URL: {p.get('listing_url')}\n"
|
|
|
|
|
|
|
| 64 |
if p.get("features"):
|
| 65 |
system_message += f" Features: {', '.join(p.get('features', [])[:5])}\n"
|
| 66 |
if p.get("floorplan_pdf"):
|
| 67 |
system_message += f" Floorplan: {p.get('floorplan_pdf')}\n"
|
| 68 |
if p.get("video_url"):
|
| 69 |
system_message += f" Video: {p.get('video_url')}\n"
|
| 70 |
+
# Note: Images are available but not shown in listings - users must ask for them
|
| 71 |
|
| 72 |
+
system_message += "\n\nIMPORTANT: You may only reference listings passed in state['properties']. If the user requests more detail, respond with whatever is in that listing dict (URL, images, features, etc.). If information is not available, respond with 'For this listing, I don't have [specific detail] available right now'. When users ask for images, photos, or pictures, let them know that images are available and will be sent separately."
|
| 73 |
|
| 74 |
# Build messages array with history
|
| 75 |
messages = [{"role": "system", "content": system_message}]
|
|
|
|
| 424 |
await save_message(session_id, wa_id, wamid, "user", user_message)
|
| 425 |
await save_message(session_id, wa_id, f"{wamid}_ai", "assistant", result["response"])
|
| 426 |
|
| 427 |
+
return {
|
| 428 |
+
"response": result["response"],
|
| 429 |
+
"properties": result.get("properties", [])
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
async def handle_image_request(state):
|
| 433 |
+
"""
|
| 434 |
+
Handle requests for property images and return image messages to send.
|
| 435 |
+
"""
|
| 436 |
+
user_message = state["user_message"].lower()
|
| 437 |
+
props = state.get("properties", [])
|
| 438 |
+
|
| 439 |
+
# Check if user is asking for images (more specific triggers)
|
| 440 |
+
image_triggers = [
|
| 441 |
+
"show me the images", "send me the images", "show me photos", "send me photos",
|
| 442 |
+
"show me pictures", "send me pictures", "i want to see", "can i see",
|
| 443 |
+
"image", "images", "photo", "photos", "picture", "pictures"
|
| 444 |
+
]
|
| 445 |
+
|
| 446 |
+
# Check if any image trigger is in the message
|
| 447 |
+
is_image_request = any(trigger in user_message for trigger in image_triggers)
|
| 448 |
+
|
| 449 |
+
# Also check if user is asking about a specific property's images
|
| 450 |
+
if not is_image_request and props:
|
| 451 |
+
property_title = props[0].get("title", "").lower()
|
| 452 |
+
if property_title and any(word in user_message for word in property_title.split()):
|
| 453 |
+
is_image_request = any(trigger in user_message for trigger in ["image", "photo", "picture"])
|
| 454 |
+
|
| 455 |
+
if not is_image_request or not props:
|
| 456 |
+
return None
|
| 457 |
+
|
| 458 |
+
# Get the first property (assuming user is asking about the most recent listing)
|
| 459 |
+
property_data = props[0]
|
| 460 |
+
images = property_data.get("images", [])
|
| 461 |
+
|
| 462 |
+
if not images:
|
| 463 |
+
return ["Sorry, I don't have any images available for this listing."]
|
| 464 |
+
|
| 465 |
+
# Prepare image messages
|
| 466 |
+
image_messages = []
|
| 467 |
+
property_title = property_data.get("title", "This property")
|
| 468 |
+
|
| 469 |
+
# Add a text message first to introduce the images
|
| 470 |
+
image_messages.append(f"Here are the images for {property_title}:")
|
| 471 |
+
|
| 472 |
+
for i, image_url in enumerate(images[:5]): # Limit to 5 images
|
| 473 |
+
caption = f"{property_title} - Image {i+1}" if i > 0 else f"{property_title}"
|
| 474 |
+
image_messages.append({
|
| 475 |
+
"type": "image",
|
| 476 |
+
"url": image_url,
|
| 477 |
+
"caption": caption
|
| 478 |
+
})
|
| 479 |
+
|
| 480 |
+
return image_messages
|
main.py
CHANGED
|
@@ -4,8 +4,8 @@ from fastapi.responses import JSONResponse
|
|
| 4 |
# Import our modular components
|
| 5 |
from config import supabase
|
| 6 |
from database import get_or_create_user, update_user_activity, get_or_create_active_session, get_user_persona, get_or_create_user_intent
|
| 7 |
-
from whatsapp import send_whatsapp_message
|
| 8 |
-
from ai_chat import process_message
|
| 9 |
from api_routes import router
|
| 10 |
|
| 11 |
# --- FastAPI App Setup ---
|
|
@@ -53,7 +53,7 @@ async def receive_message(req: Request):
|
|
| 53 |
intent = await get_or_create_user_intent(session["id"], wa_id)
|
| 54 |
|
| 55 |
# Process with AI including session memory
|
| 56 |
-
|
| 57 |
user_message=user_message,
|
| 58 |
user_info=user_info,
|
| 59 |
session_id=session["id"],
|
|
@@ -63,8 +63,23 @@ async def receive_message(req: Request):
|
|
| 63 |
intent=intent
|
| 64 |
)
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
return JSONResponse({
|
| 70 |
"status": "replied",
|
|
|
|
| 4 |
# Import our modular components
|
| 5 |
from config import supabase
|
| 6 |
from database import get_or_create_user, update_user_activity, get_or_create_active_session, get_user_persona, get_or_create_user_intent
|
| 7 |
+
from whatsapp import send_whatsapp_message, send_multiple_messages
|
| 8 |
+
from ai_chat import process_message, handle_image_request
|
| 9 |
from api_routes import router
|
| 10 |
|
| 11 |
# --- FastAPI App Setup ---
|
|
|
|
| 53 |
intent = await get_or_create_user_intent(session["id"], wa_id)
|
| 54 |
|
| 55 |
# Process with AI including session memory
|
| 56 |
+
ai_result = await process_message(
|
| 57 |
user_message=user_message,
|
| 58 |
user_info=user_info,
|
| 59 |
session_id=session["id"],
|
|
|
|
| 63 |
intent=intent
|
| 64 |
)
|
| 65 |
|
| 66 |
+
ai_response = ai_result["response"]
|
| 67 |
+
properties = ai_result.get("properties", [])
|
| 68 |
+
|
| 69 |
+
# Check if this is an image request
|
| 70 |
+
state = {
|
| 71 |
+
"user_message": user_message,
|
| 72 |
+
"properties": properties
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
image_messages = await handle_image_request(state)
|
| 76 |
+
|
| 77 |
+
if image_messages:
|
| 78 |
+
# Send images
|
| 79 |
+
await send_multiple_messages(wa_id, image_messages)
|
| 80 |
+
else:
|
| 81 |
+
# Send regular text response
|
| 82 |
+
await send_whatsapp_message(wa_id, ai_response)
|
| 83 |
|
| 84 |
return JSONResponse({
|
| 85 |
"status": "replied",
|
whatsapp.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
import httpx
|
|
|
|
| 2 |
from config import PHONE_NUMBER_ID, WHATSAPP_API_TOKEN
|
| 3 |
|
| 4 |
async def send_whatsapp_message(wa_id: str, message: str):
|
|
@@ -21,4 +22,49 @@ async def send_whatsapp_message(wa_id: str, message: str):
|
|
| 21 |
return resp.status_code == 200
|
| 22 |
except Exception as e:
|
| 23 |
print(f"Failed to send message: {e}")
|
| 24 |
-
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import httpx
|
| 2 |
+
import asyncio
|
| 3 |
from config import PHONE_NUMBER_ID, WHATSAPP_API_TOKEN
|
| 4 |
|
| 5 |
async def send_whatsapp_message(wa_id: str, message: str):
|
|
|
|
| 22 |
return resp.status_code == 200
|
| 23 |
except Exception as e:
|
| 24 |
print(f"Failed to send message: {e}")
|
| 25 |
+
return False
|
| 26 |
+
|
| 27 |
+
async def send_whatsapp_image(wa_id: str, image_url: str, caption: str = ""):
|
| 28 |
+
"""Send an image via WhatsApp Business API"""
|
| 29 |
+
url = f"https://graph.facebook.com/v18.0/{PHONE_NUMBER_ID}/messages"
|
| 30 |
+
headers = {
|
| 31 |
+
"Authorization": f"Bearer {WHATSAPP_API_TOKEN}",
|
| 32 |
+
"Content-Type": "application/json"
|
| 33 |
+
}
|
| 34 |
+
payload = {
|
| 35 |
+
"messaging_product": "whatsapp",
|
| 36 |
+
"to": wa_id,
|
| 37 |
+
"type": "image",
|
| 38 |
+
"image": {
|
| 39 |
+
"link": image_url,
|
| 40 |
+
"caption": caption
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
try:
|
| 44 |
+
async with httpx.AsyncClient() as client:
|
| 45 |
+
resp = await client.post(url, headers=headers, json=payload)
|
| 46 |
+
print(f"Sent image → {resp.status_code}: {resp.text}")
|
| 47 |
+
return resp.status_code == 200
|
| 48 |
+
except Exception as e:
|
| 49 |
+
print(f"Failed to send image: {e}")
|
| 50 |
+
return False
|
| 51 |
+
|
| 52 |
+
async def send_multiple_messages(wa_id: str, messages: list):
|
| 53 |
+
"""Send multiple messages with delays to avoid rate limiting"""
|
| 54 |
+
for i, message in enumerate(messages):
|
| 55 |
+
if isinstance(message, dict) and message.get("type") == "image":
|
| 56 |
+
# Send image
|
| 57 |
+
success = await send_whatsapp_image(wa_id, message["url"], message.get("caption", ""))
|
| 58 |
+
else:
|
| 59 |
+
# Send text message
|
| 60 |
+
success = await send_whatsapp_message(wa_id, str(message))
|
| 61 |
+
|
| 62 |
+
if not success:
|
| 63 |
+
print(f"Failed to send message {i+1}")
|
| 64 |
+
return False
|
| 65 |
+
|
| 66 |
+
# Add delay between messages to avoid rate limiting (1 second)
|
| 67 |
+
if i < len(messages) - 1: # Don't delay after the last message
|
| 68 |
+
await asyncio.sleep(1)
|
| 69 |
+
|
| 70 |
+
return True
|