Spaces:
Running
Running
Commit ·
1306e79
1
Parent(s): 9b90b36
fyp
Browse files- app/ai/agent/brain.py +15 -1
- app/ai/agent/nodes/listing_collect.py +31 -0
- app/ai/tools/listing_tool.py +14 -12
app/ai/agent/brain.py
CHANGED
|
@@ -46,7 +46,21 @@ AVAILABLE_TOOLS = """
|
|
| 46 |
|
| 47 |
1. update_listing(fields: dict)
|
| 48 |
- Use this when user provides property details (price, bedrooms, location, etc.)
|
| 49 |
-
- Fields: title, description, price, bedrooms, bathrooms, location, amenities (list), requirements (list),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
2. search_properties(query: str, location: str, min_price: int, max_price: int, beds: int)
|
| 52 |
- Use this when user wants to FIND/SEARCH for properties
|
|
|
|
| 46 |
|
| 47 |
1. update_listing(fields: dict)
|
| 48 |
- Use this when user provides property details (price, bedrooms, location, etc.)
|
| 49 |
+
- Fields: title, description, price, bedrooms, bathrooms, location, amenities (list), requirements (list), listing_type
|
| 50 |
+
|
| 51 |
+
LISTING TYPES (4 total):
|
| 52 |
+
- "rent" = Regular long-term rental (monthly/yearly payment)
|
| 53 |
+
- "sale" = Property for sale (one-time payment)
|
| 54 |
+
- "short-stay" = Short-term like Airbnb (daily/weekly/nightly)
|
| 55 |
+
- "roommate" = Someone ALREADY living in a place wants to find a person to SHARE the space with
|
| 56 |
+
|
| 57 |
+
ROOMMATE EXPLAINED: If user says "I want to share my apartment", "looking for someone to split rent",
|
| 58 |
+
"need a roommate", "want to find someone to live with" → listing_type = "roommate"
|
| 59 |
+
|
| 60 |
+
ROLE-BASED PERMISSIONS:
|
| 61 |
+
- Landlord: Can list ALL types (rent, sale, short-stay, roommate)
|
| 62 |
+
- Renter: Can ONLY list "roommate" type. If renter tries to list rent/sale/short-stay,
|
| 63 |
+
politely explain they can only list roommate listings.
|
| 64 |
|
| 65 |
2. search_properties(query: str, location: str, min_price: int, max_price: int, beds: int)
|
| 66 |
- Use this when user wants to FIND/SEARCH for properties
|
app/ai/agent/nodes/listing_collect.py
CHANGED
|
@@ -689,6 +689,37 @@ Just return the message, no quotes."""
|
|
| 689 |
state.temp_data["response_text"] = smart_response.get("response_text")
|
| 690 |
state.temp_data["action"] = action
|
| 691 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 692 |
logger.info("Continuing collection",
|
| 693 |
action=action,
|
| 694 |
missing_count=len(missing_required))
|
|
|
|
| 689 |
state.temp_data["response_text"] = smart_response.get("response_text")
|
| 690 |
state.temp_data["action"] = action
|
| 691 |
|
| 692 |
+
# ✅ NEW: Generate draft_ui for NEW listings if we have enough data to display
|
| 693 |
+
# This ensures users see a visual card, not just text, when providing images/fields
|
| 694 |
+
has_images = len(state.provided_fields.get("images", [])) > 0
|
| 695 |
+
has_key_fields = any([
|
| 696 |
+
state.provided_fields.get("location"),
|
| 697 |
+
state.provided_fields.get("bedrooms"),
|
| 698 |
+
state.provided_fields.get("price"),
|
| 699 |
+
])
|
| 700 |
+
|
| 701 |
+
if has_images or has_key_fields:
|
| 702 |
+
from app.ai.agent.nodes.listing_validate import build_draft_ui_from_dict
|
| 703 |
+
draft_data = {
|
| 704 |
+
"title": state.provided_fields.get("title", "New Listing"),
|
| 705 |
+
"description": state.provided_fields.get("description", ""),
|
| 706 |
+
"location": state.provided_fields.get("location", ""),
|
| 707 |
+
"address": state.provided_fields.get("address", ""),
|
| 708 |
+
"bedrooms": state.provided_fields.get("bedrooms", 0),
|
| 709 |
+
"bathrooms": state.provided_fields.get("bathrooms", 0),
|
| 710 |
+
"price": state.provided_fields.get("price", 0),
|
| 711 |
+
"currency": state.provided_fields.get("currency", ""),
|
| 712 |
+
"price_type": state.provided_fields.get("price_type", "monthly"),
|
| 713 |
+
"listing_type": state.provided_fields.get("listing_type", "rent"),
|
| 714 |
+
"amenities": state.provided_fields.get("amenities", []),
|
| 715 |
+
"images": state.provided_fields.get("images", []),
|
| 716 |
+
"requirements": state.provided_fields.get("requirements", []),
|
| 717 |
+
}
|
| 718 |
+
draft_ui = build_draft_ui_from_dict(draft_data)
|
| 719 |
+
draft_ui["status"] = "draft" # Mark as draft (not editing existing)
|
| 720 |
+
state.temp_data["draft_ui"] = draft_ui
|
| 721 |
+
logger.info("Draft UI generated for new listing", has_images=has_images, image_count=len(draft_data.get("images", [])))
|
| 722 |
+
|
| 723 |
logger.info("Continuing collection",
|
| 724 |
action=action,
|
| 725 |
missing_count=len(missing_required))
|
app/ai/tools/listing_tool.py
CHANGED
|
@@ -415,15 +415,12 @@ async def auto_detect_listing_type(price_type: str, user_role: str, user_message
|
|
| 415 |
if user_role in ["renter", "roommate"]:
|
| 416 |
return "roommate"
|
| 417 |
|
| 418 |
-
# If LLM explicitly extracted a valid type, trust it
|
| 419 |
if extracted_type:
|
| 420 |
valid_types = ["rent", "sale", "short-stay", "roommate"]
|
| 421 |
if extracted_type in valid_types:
|
| 422 |
-
# Landlords can
|
| 423 |
-
|
| 424 |
-
pass # Allow or block? Let's strictly block for now
|
| 425 |
-
else:
|
| 426 |
-
return extracted_type
|
| 427 |
|
| 428 |
# LANDLORD: Detect from price_type as backup
|
| 429 |
price_type_lower = (price_type or "").lower().strip()
|
|
@@ -470,21 +467,26 @@ def validate_listing_type_for_role(listing_type: str, user_role: str) -> tuple[b
|
|
| 470 |
"""
|
| 471 |
Validate if user can create this listing type.
|
| 472 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 473 |
Returns:
|
| 474 |
(is_valid, error_message)
|
| 475 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 476 |
|
| 477 |
if user_role == "renter":
|
| 478 |
if listing_type != "roommate":
|
| 479 |
-
return False, "As a renter, you can only create roommate listings. Would you like to list a room for a roommate instead?"
|
| 480 |
return True, ""
|
| 481 |
|
|
|
|
| 482 |
if user_role == "landlord":
|
| 483 |
-
|
| 484 |
-
return False, "As a landlord, you cannot create roommate listings. You can list properties for rent, short-stay, or sale."
|
| 485 |
-
if listing_type in ["rent", "short-stay", "sale"]:
|
| 486 |
-
return True, ""
|
| 487 |
-
return False, f"Invalid listing type: {listing_type}. Please choose rent, short-stay, or sale."
|
| 488 |
|
| 489 |
# Default: allow
|
| 490 |
return True, ""
|
|
|
|
| 415 |
if user_role in ["renter", "roommate"]:
|
| 416 |
return "roommate"
|
| 417 |
|
| 418 |
+
# If LLM explicitly extracted a valid type, trust it
|
| 419 |
if extracted_type:
|
| 420 |
valid_types = ["rent", "sale", "short-stay", "roommate"]
|
| 421 |
if extracted_type in valid_types:
|
| 422 |
+
# Landlords can list ALL types including roommate
|
| 423 |
+
return extracted_type
|
|
|
|
|
|
|
|
|
|
| 424 |
|
| 425 |
# LANDLORD: Detect from price_type as backup
|
| 426 |
price_type_lower = (price_type or "").lower().strip()
|
|
|
|
| 467 |
"""
|
| 468 |
Validate if user can create this listing type.
|
| 469 |
|
| 470 |
+
Permissions:
|
| 471 |
+
- Landlord: Can list ALL types (rent, sale, short-stay, roommate)
|
| 472 |
+
- Renter: Can ONLY list roommate
|
| 473 |
+
|
| 474 |
Returns:
|
| 475 |
(is_valid, error_message)
|
| 476 |
"""
|
| 477 |
+
valid_types = ["rent", "sale", "short-stay", "roommate"]
|
| 478 |
+
|
| 479 |
+
if listing_type not in valid_types:
|
| 480 |
+
return False, f"Invalid listing type: {listing_type}. Please choose rent, sale, short-stay, or roommate."
|
| 481 |
|
| 482 |
if user_role == "renter":
|
| 483 |
if listing_type != "roommate":
|
| 484 |
+
return False, "As a renter, you can only create roommate listings. Roommate listings are for finding someone to share your living space with. Would you like to list a room for a roommate instead?"
|
| 485 |
return True, ""
|
| 486 |
|
| 487 |
+
# Landlord can list ANY type including roommate
|
| 488 |
if user_role == "landlord":
|
| 489 |
+
return True, ""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 490 |
|
| 491 |
# Default: allow
|
| 492 |
return True, ""
|