Spaces:
Sleeping
Sleeping
| """ | |
| 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(""" | |
| <div id="header"> | |
| <h1>π PropBazaar</h1> | |
| <p>AI-Powered Real Estate Assistant β Mumbai & MMR</p> | |
| </div> | |
| """) | |
| 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) | |