"""
PropBazaar — AI Real Estate Assistant
HuggingFace Spaces entry point (Gradio)
"""
import os
import sys
import gradio as gr
# Make src importable
sys.path.insert(0, os.path.dirname(__file__))
from src.database.queries import init_db
from src.rag.retriever import load_vector_store
from src.rag.chatbot import chat
from src.rag.searcher import (
get_all_properties, get_leases_expiring, get_leases_vacant_or_pending
)
# ── Startup ──────────────────────────────────────────────────────
print("Initialising PropBazaar...")
init_db()
load_vector_store()
print("PropBazaar ready ✅")
ADMIN_USERNAME = os.environ.get("ADMIN_USERNAME", "manager")
ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD", "propbazaar2025")
WHATSAPP_URL = "https://wa.me/919800000000"
# ── Helpers ──────────────────────────────────────────────────────
def _fmt_price(price_inr):
if price_inr >= 10000000:
return f"₹{price_inr/10000000:.2f} Cr"
return f"₹{price_inr/100000:.1f} L"
# ── Chat handler ─────────────────────────────────────────────────
def customer_chat(message, history, role_state):
if not message.strip():
return history, history, ""
role = role_state or "customer"
history_fmt = [{"role": h[0], "content": h[1]} for h in history] if history else []
result = chat(message, role=role, history=history_fmt)
reply = result["reply"]
history = history or []
history.append(("user", message))
history.append(("assistant", reply))
# Convert to Gradio chatbot format
gradio_history = [[u, a] for u, a in zip(
[h[1] for h in history if h[0] == "user"],
[h[1] for h in history if h[0] == "assistant"]
)]
return gradio_history, history, ""
def admin_login(username, password):
if username == ADMIN_USERNAME and password == ADMIN_PASSWORD:
return (
gr.update(visible=False),
gr.update(visible=True),
"✅ Logged in as Manager"
)
return (
gr.update(visible=True),
gr.update(visible=False),
"❌ Invalid credentials"
)
def get_dashboard_data():
"""Return all properties as a dataframe for the manager dashboard."""
import pandas as pd
props = get_all_properties()
if not props:
return pd.DataFrame()
df = pd.DataFrame(props)
df["price_display"] = df["price_inr"].apply(_fmt_price)
cols = ["property_id", "title", "bhk", "property_type", "area_sqft",
"price_display", "location", "furnishing", "condition_grade", "available"]
return df[[c for c in cols if c in df.columns]]
def get_expiring_leases(days):
import pandas as pd
records = get_leases_expiring(int(days))
if not records:
return pd.DataFrame(columns=["property_id", "title", "location",
"monthly_rent", "lease_status", "lease_end",
"tenant_name", "followup_person"])
df = pd.DataFrame(records)
return df[["property_id", "title", "location", "monthly_rent",
"lease_status", "lease_end", "tenant_name", "followup_person"]]
def get_vacant_pending():
import pandas as pd
records = get_leases_vacant_or_pending()
if not records:
return pd.DataFrame(columns=["property_id", "title", "location",
"lease_status", "lease_end",
"followup_person", "notes"])
df = pd.DataFrame(records)
return df[["property_id", "title", "location", "lease_status",
"lease_end", "followup_person", "notes"]]
def manager_chat_fn(message, history, chat_history_state):
if not message.strip():
return history, chat_history_state, ""
history_fmt = [{"role": h[0], "content": h[1]}
for h in chat_history_state] if chat_history_state else []
result = chat(message, role="manager", history=history_fmt)
reply = result["reply"]
chat_history_state = chat_history_state or []
chat_history_state.append(("user", message))
chat_history_state.append(("assistant", reply))
gradio_history = [[u, a] for u, a in zip(
[h[1] for h in chat_history_state if h[0] == "user"],
[h[1] for h in chat_history_state if h[0] == "assistant"]
)]
return gradio_history, chat_history_state, ""
# ── UI ───────────────────────────────────────────────────────────
CSS = """
#header { background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
padding: 24px 32px; border-radius: 12px; margin-bottom: 16px; }
#header h1 { color: #e94560; margin: 0; font-size: 2rem; }
#header p { color: #a8b2d8; margin: 4px 0 0; font-size: 0.95rem; }
.chatbot { border-radius: 10px; }
.send-btn { background: #e94560 !important; border: none !important; color: white !important; }
.tab-nav button { font-weight: 600; }
"""
with gr.Blocks(css=CSS, title="PropBazaar — AI Real Estate Assistant") as demo:
# Header
gr.HTML("""
""")
role_state = gr.State("customer")
chat_history_state = gr.State([])
with gr.Tabs():
# ── Tab 1: Customer Chatbot ──────────────────────────────
with gr.Tab("🏡 Find Properties"):
gr.Markdown("""
**Ask me anything!** Examples:
- *Show me 2BHK flats in Andheri under ₹1 crore*
- *3BHK fully furnished in Bandra between 1.5 and 2 crore*
- *Villas in Thane below 3 crore with parking*
- *What is the stamp duty in Mumbai?*
- *Do you help with home loans?*
""")
chatbot = gr.Chatbot(
label="PropBazaar Assistant",
elem_id="chatbot",
height=420,
show_label=False,
)
with gr.Row():
msg_input = gr.Textbox(
placeholder="Type your query here... (e.g. '2BHK under 90 lakh in Malad')",
show_label=False,
scale=5,
lines=1,
)
send_btn = gr.Button("Send 🚀", elem_classes="send-btn", scale=1)
with gr.Row():
clear_btn = gr.Button("🗑️ Clear Chat", size="sm")
wa_btn = gr.Button("📱 WhatsApp Us", size="sm", variant="secondary")
gr.Markdown("*Powered by Groq LLaMA 3.3 · Data from PropBazaar inventory*")
# Quick prompts
with gr.Accordion("💡 Quick Search Examples", open=False):
with gr.Row():
gr.Button("2BHK in Andheri under 1 Cr").click(
lambda: "Show me 2BHK flats in Andheri under 1 crore",
outputs=msg_input
)
gr.Button("3BHK fully furnished Bandra").click(
lambda: "3BHK fully furnished flat in Bandra",
outputs=msg_input
)
gr.Button("Stamp duty info").click(
lambda: "What is the stamp duty in Mumbai?",
outputs=msg_input
)
with gr.Row():
gr.Button("Villa in Thane").click(
lambda: "Show me villas in Thane",
outputs=msg_input
)
gr.Button("Home loan process").click(
lambda: "How do I get a home loan for buying a flat?",
outputs=msg_input
)
gr.Button("Property registration docs").click(
lambda: "What documents are needed for property registration?",
outputs=msg_input
)
def send_message(message, history, chat_hist_state):
return customer_chat(message, chat_hist_state, "customer")
send_btn.click(
send_message,
inputs=[msg_input, chatbot, chat_history_state],
outputs=[chatbot, chat_history_state, msg_input]
)
msg_input.submit(
send_message,
inputs=[msg_input, chatbot, chat_history_state],
outputs=[chatbot, chat_history_state, msg_input]
)
clear_btn.click(
lambda: ([], [], ""),
outputs=[chatbot, chat_history_state, msg_input]
)
wa_btn.click(lambda: None, js=f"() => window.open('{WHATSAPP_URL}', '_blank')")
# ── Tab 2: Manager Dashboard ─────────────────────────────
with gr.Tab("🔐 Manager Dashboard"):
login_section = gr.Group(visible=True)
dashboard_section = gr.Group(visible=False)
login_status = gr.Markdown("")
with login_section:
gr.Markdown("### 🔒 Manager Login")
with gr.Row():
username_input = gr.Textbox(label="Username", placeholder="manager")
password_input = gr.Textbox(label="Password", type="password")
login_btn = gr.Button("Login", variant="primary")
with dashboard_section:
gr.Markdown("### 📊 Manager Dashboard")
with gr.Tabs():
with gr.Tab("🏠 All Properties"):
refresh_props_btn = gr.Button("🔄 Refresh", size="sm")
props_table = gr.Dataframe(
label="Property Inventory",
interactive=False,
wrap=True,
)
refresh_props_btn.click(get_dashboard_data, outputs=props_table)
demo.load(get_dashboard_data, outputs=props_table)
with gr.Tab("📅 Leases Expiring Soon"):
with gr.Row():
days_slider = gr.Slider(
minimum=7, maximum=90, value=30, step=7,
label="Show leases expiring within (days)"
)
refresh_lease_btn = gr.Button("🔄 Refresh", size="sm")
leases_table = gr.Dataframe(
label="Expiring Leases",
interactive=False,
wrap=True,
)
refresh_lease_btn.click(
get_expiring_leases,
inputs=days_slider,
outputs=leases_table
)
days_slider.change(
get_expiring_leases,
inputs=days_slider,
outputs=leases_table
)
with gr.Tab("🚨 Vacant / Pending"):
refresh_vacant_btn = gr.Button("🔄 Refresh", size="sm")
vacant_table = gr.Dataframe(
label="Vacant & Pending Properties",
interactive=False,
wrap=True,
)
refresh_vacant_btn.click(get_vacant_pending, outputs=vacant_table)
with gr.Tab("💬 Manager Chat"):
gr.Markdown("Ask about leases, inventory, or get AI-powered insights.")
mgr_chatbot = gr.Chatbot(height=350, show_label=False)
mgr_chat_state = gr.State([])
with gr.Row():
mgr_input = gr.Textbox(
placeholder="e.g. 'Show leases expiring this month' or 'List vacant properties'",
show_label=False, scale=5
)
mgr_send_btn = gr.Button("Send", scale=1, variant="primary")
mgr_clear_btn = gr.Button("🗑️ Clear", size="sm")
mgr_send_btn.click(
manager_chat_fn,
inputs=[mgr_input, mgr_chatbot, mgr_chat_state],
outputs=[mgr_chatbot, mgr_chat_state, mgr_input]
)
mgr_input.submit(
manager_chat_fn,
inputs=[mgr_input, mgr_chatbot, mgr_chat_state],
outputs=[mgr_chatbot, mgr_chat_state, mgr_input]
)
mgr_clear_btn.click(
lambda: ([], [], ""),
outputs=[mgr_chatbot, mgr_chat_state, mgr_input]
)
login_btn.click(
admin_login,
inputs=[username_input, password_input],
outputs=[login_section, dashboard_section, login_status]
)
# ── Tab 3: About ─────────────────────────────────────────
with gr.Tab("ℹ️ About"):
gr.Markdown("""
## 🏠 PropBazaar — AI Real Estate Assistant
PropBazaar is an intelligent RAG-based chatbot for a Mumbai resale real estate business.
### Features
- **🔍 Property Search** — Find flats, villas, studios by budget, BHK, location, furnishing
- **💬 FAQ Chatbot** — Answers on home loans, stamp duty, registration, RERA, documents
- **📊 Manager Dashboard** — Track lease expirations, vacant properties, portfolio
- **🔐 Secure Login** — Manager-only access to business data
### How to Set Up
1. Clone this Space
2. Add your `GROQ_API_KEY` in Space Settings → Secrets (free at console.groq.com)
3. Optionally add `GEMINI_API_KEY` for semantic FAQ search
4. Set `ADMIN_USERNAME` and `ADMIN_PASSWORD` for the manager dashboard
### Tech Stack
- **Frontend**: Gradio (HuggingFace Spaces)
- **AI**: Groq LLaMA 3.3 70B (fast, free tier available)
- **Database**: SQLite (property & lease data)
- **Search**: FAISS vector search + keyword fallback
- **Data**: CSV → SQLite on startup
---
*Built with ❤️ for Indian Real Estate businesses*
""")
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)