""" 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)