#!/usr/bin/env python3 # SMOLAGENTS 1.19 FIX - Must be imported before anything else from final_fix import apply_final_fix from browser_agent_fix import validate_listing_url_for_nyc # NEW: Import fixed address extraction (prioritizes mapaddress and structured data) from fixed_address_extraction import apply_fixed_extraction # Apply all fixes at startup apply_final_fix() apply_fixed_extraction() import gradio as gr import json import pandas as pd import re from datetime import datetime, timezone from typing import Dict, List, Any, Optional from agent_setup import initialize_caseworker_agent from tools import final_answer import ast # Import our new utilities and constants from utils import log_tool_action, current_timestamp, parse_observation_data from constants import StageEvent, RiskLevel, Borough, VoucherType from browser_agent import BrowserAgent from violation_checker_agent import ViolationCheckerAgent # Import V0's enhanced email handling from email_handler import EmailTemplateHandler, enhanced_classify_message, enhanced_handle_email_request # Import shortlist utilities from shortlist_utils import ( add_to_shortlist, remove_from_shortlist, get_shortlist, is_shortlisted, get_shortlist_summary, get_shortlisted_ids ) # --- Internationalization Setup --- i18n_dict = { "en": { "app_title": "🏠 NYC Voucher Housing Navigator", "app_subtitle": "Your personal AI Caseworker for finding voucher-friendly housing with building safety insights.", "language_selector": "Language / Idioma / 语言 / ভাষা", "conversation_label": "Conversation with VoucherBot", "message_label": "Your Message", "message_placeholder": "Start by telling me your voucher type, required bedrooms, and max rent...", "preferences_title": "🎛️ Search Preferences", "strict_mode_label": "Strict Mode (Only show buildings with 0 violations)", "borough_label": "Preferred Borough", "max_rent_label": "Maximum Rent", "listings_label": "Matching Listings", "status_label": "Status", "status_ready": "Ready to search...", "no_listings": "I don't have any listings to show you right now. Please search for apartments first!", "no_listings_title": "📋 No Current Listings", "invalid_listing": "I only have {count} listings available. Please ask for a listing between 1 and {count}.", "invalid_listing_title": "❌ Invalid Listing Number", "showing_listings": "Showing {count} listings", "strict_applied": "🔒 Strict mode applied: {count} listings with 0 violations", "strict_applied_title": "🔒 Filtering Applied", "results_found": "✅ Found {count} voucher-friendly listings with safety information!", "results_title": "✅ Results Ready", "no_safe_listings": "No listings meet your safety criteria. Try disabling strict mode to see all available options.", "no_safe_title": "⚠️ No Safe Listings", "search_error": "❌ Search error: {error}", "search_error_title": "❌ Search Error", "error_occurred": "I apologize, but I encountered an error: {error}", "error_title": "❌ Error", "general_response_title": "💬 General Response", "conversation_mode": "Conversation mode", "no_criteria": "No listings meet criteria", "what_if_analysis": "What-if analysis", "what_if_error_title": "❌ What-If Error", "error_what_if": "I encountered an error processing your what-if scenario: {error}", "error_listings_available": "Error - {count} listings available", "error_what_if_processing": "Error in what-if processing", "error_conversation": "Error in conversation", "col_address": "Address", "col_price": "Price", "col_risk_level": "Risk Level", "col_violations": "Violations", "col_last_inspection": "Last Inspection", "col_link": "Link", "col_summary": "Summary", "col_shortlist": "Shortlist", "link_not_available": "No link available", "shortlist_save": "➕", "shortlist_saved": "✅", "shortlist_empty": "Your shortlist is empty. Save some listings to get started!", "shortlist_title": "Your Shortlist", "shortlist_added": "Added to shortlist", "shortlist_removed": "Removed from shortlist", "shortlist_cleared": "Shortlist cleared", "intro_greeting": """👋 **Hi there! I'm Navi, your personal NYC Housing Navigator!** I'm here to help you find safe, affordable, and voucher-friendly housing in New York City. I understand that finding the right home can feel overwhelming, but you don't have to do this alone - I'm here to guide you every step of the way! 😊 **To get started, just tell me:** • What type of voucher do you have? (Section 8, CityFHEPS, HASA, etc.) • How many bedrooms do you need? 🛏️ • What's your maximum rent budget? 💰 • Do you have a preferred borough? 🗽""" }, "es": { "app_title": "🏠 Navegador de Vivienda con Voucher de NYC", "app_subtitle": "Tu trabajador social personal de IA para encontrar vivienda que acepta vouchers con información de seguridad del edificio.", "language_selector": "Idioma / Language / 语言 / ভাষা", "conversation_label": "Conversación con VoucherBot", "message_label": "Tu Mensaje", "message_placeholder": "Comienza diciéndome tu tipo de voucher, habitaciones requeridas y renta máxima...", "preferences_title": "🎛️ Preferencias de Búsqueda", "strict_mode_label": "Modo Estricto (Solo mostrar edificios con 0 violaciones)", "borough_label": "Distrito Preferido", "max_rent_label": "Renta Máxima", "listings_label": "Listados Coincidentes", "status_label": "Estado", "status_ready": "Listo para buscar...", "no_listings": "No tengo listados para mostrarte ahora. ¡Por favor busca apartamentos primero!", "no_listings_title": "📋 Sin Listados Actuales", "invalid_listing": "Solo tengo {count} listados disponibles. Por favor pide un listado entre 1 y {count}.", "invalid_listing_title": "❌ Número de Listado Inválido", "showing_listings": "Mostrando {count} listados", "strict_applied": "🔒 Modo estricto aplicado: {count} listados con 0 violaciones", "strict_applied_title": "🔒 Filtro Aplicado", "results_found": "✅ ¡Encontrado {count} listados que aceptan vouchers con información de seguridad!", "results_title": "✅ Resultados Listos", "no_safe_listings": "Ningún listado cumple tus criterios de seguridad. Intenta desactivar el modo estricto para ver todas las opciones disponibles.", "no_safe_title": "⚠️ Sin Listados Seguros", "search_error": "❌ Error de búsqueda: {error}", "search_error_title": "❌ Error de Búsqueda", "error_occurred": "Me disculpo, pero encontré un error: {error}", "error_title": "❌ Error", "general_response_title": "💬 Respuesta General", "conversation_mode": "Modo conversación", "no_criteria": "Ningún listado cumple criterios", "what_if_analysis": "Análisis de qué pasaría si", "what_if_error_title": "❌ Error de Qué Pasaría Si", "error_what_if": "Encontré un error procesando tu escenario de qué pasaría si: {error}", "error_listings_available": "Error - {count} listados disponibles", "error_what_if_processing": "Error en procesamiento de qué pasaría si", "error_conversation": "Error en conversación", "col_address": "Dirección", "col_price": "Precio", "col_risk_level": "Nivel de Riesgo", "col_violations": "Violaciones", "col_last_inspection": "Última Inspección", "col_link": "Enlace", "col_summary": "Resumen", "col_shortlist": "Lista Favorita", "link_not_available": "Sin enlace disponible", "shortlist_save": "➕", "shortlist_saved": "✅", "shortlist_empty": "Tu lista favorita está vacía. ¡Guarda algunos listados para comenzar!", "shortlist_title": "Tu Lista Favorita", "shortlist_added": "Agregado a lista favorita", "shortlist_removed": "Removido de lista favorita", "shortlist_cleared": "Lista favorita limpiada", "intro_greeting": """👋 **¡Hola! Soy Navi, tu Navegadora Personal de Vivienda de NYC!** Estoy aquí para ayudarte a encontrar vivienda segura, asequible y que acepta vouchers en la Ciudad de Nueva York. Entiendo que encontrar el hogar perfecto puede sentirse abrumador, pero no tienes que hacerlo solo - ¡estoy aquí para guiarte en cada paso del camino! 😊 **Así es como puedo ayudarte:** • 🏠 **Encontrar apartamentos que aceptan vouchers** que acepten tu tipo específico de voucher • 🏢 **Verificar la seguridad del edificio** y proporcionar reportes de violaciones para tu tranquilidad • 🚇 **Mostrar estaciones de metro cercanas** y accesibilidad de transporte • 🏫 **Encontrar escuelas cercanas** para familias con niños • 📧 **Redactar emails profesionales** a propietarios y administradores de propiedades • 💡 **Responder preguntas** sobre programas de vouchers, vecindarios y derechos de vivienda **Para comenzar, solo dime:** • ¿Qué tipo de voucher tienes? (Section 8, CityFHEPS, HASA, etc.) • ¿Cuántas habitaciones necesitas? 🛏️ • ¿Cuál es tu presupuesto máximo de renta? 💰 • ¿Tienes un distrito preferido? 🗽 Soy paciente, amable y estoy aquí para apoyarte en este viaje. ¡Encontremos un lugar maravilloso al que puedas llamar hogar! ✨🏡""" }, "zh": { "app_title": "🏠 纽约市住房券导航器", "app_subtitle": "您的个人AI社工,帮助您找到接受住房券的房屋,并提供建筑安全信息。", "language_selector": "语言 / Language / Idioma / ভাষা", "conversation_label": "与VoucherBot对话", "message_label": "您的消息", "message_placeholder": "请先告诉我您的住房券类型、所需卧室数量和最高租金...", "preferences_title": "🎛️ 搜索偏好", "strict_mode_label": "严格模式(仅显示0违规的建筑)", "borough_label": "首选区域", "max_rent_label": "最高租金", "listings_label": "匹配房源", "status_label": "状态", "status_ready": "准备搜索...", "no_listings": "我现在没有房源可以显示给您。请先搜索公寓!", "no_listings_title": "📋 当前无房源", "invalid_listing": "我只有{count}个可用房源。请询问1到{count}之间的房源。", "invalid_listing_title": "❌ 无效房源号码", "showing_listings": "显示{count}个房源", "strict_applied": "🔒 严格模式已应用:{count}个0违规房源", "strict_applied_title": "🔒 已应用过滤", "results_found": "✅ 找到{count}个接受住房券的房源,包含安全信息!", "results_title": "✅ 结果准备就绪", "no_safe_listings": "没有房源符合您的安全标准。尝试禁用严格模式以查看所有可用选项。", "no_safe_title": "⚠️ 无安全房源", "search_error": "❌ 搜索错误:{error}", "search_error_title": "❌ 搜索错误", "error_occurred": "抱歉,我遇到了一个错误:{error}", "error_title": "❌ 错误", "general_response_title": "💬 一般回复", "conversation_mode": "对话模式", "no_criteria": "没有房源符合条件", "what_if_analysis": "假设分析", "what_if_error_title": "❌ 假设错误", "error_what_if": "处理您的假设场景时遇到错误:{error}", "error_listings_available": "错误 - {count}个房源可用", "error_what_if_processing": "假设处理错误", "error_conversation": "对话错误", "col_address": "地址", "col_price": "价格", "col_risk_level": "风险级别", "col_violations": "违规", "col_last_inspection": "最后检查", "col_link": "链接", "col_summary": "摘要", "col_shortlist": "收藏清单", "link_not_available": "无可用链接", "shortlist_save": "➕", "shortlist_saved": "✅", "shortlist_empty": "您的收藏清单为空。保存一些房源开始吧!", "shortlist_title": "您的收藏清单", "shortlist_added": "已添加到收藏清单", "shortlist_removed": "已从收藏清单移除", "shortlist_cleared": "收藏清单已清空", "intro_greeting": """👋 **您好!我是Navi,您的个人纽约市住房导航员!** 我在这里帮助您在纽约市找到安全、经济实惠且接受住房券的住房。我理解找到合适的家可能让人感到不知所措,但您不必独自面对这一切 - 我会在每一步中指导您!😊 **我可以为您提供以下帮助:** • 🏠 **寻找接受住房券的公寓** - 找到接受您特定类型住房券的房源 • 🏢 **检查建筑安全** - 提供违规报告和安全评估,让您安心 • 🚇 **显示附近的地铁站** - 提供交通便利性和可达性信息 • 🏫 **寻找附近的学校** - 为有孩子的家庭提供学校信息 • 📧 **起草专业邮件** - 帮您给房东和物业管理员写邮件 • 💡 **回答问题** - 关于住房券项目、社区特点和住房权利的各种问题 **开始使用时,请告诉我:** • 您有什么类型的住房券?(Section 8联邦住房券、CityFHEPS城市住房援助、HASA艾滋病服务券等) • 您需要多少间卧室?🛏️ • 您的最高租金预算是多少?💰 • 您有首选的行政区吗?(布朗克斯、布鲁克林、曼哈顿、皇后区、史坦顿岛) 🗽 我很有耐心、善良,会在整个找房过程中支持您。让我们一起为您找到一个可以称之为家的美好地方!我了解纽约市的住房市场和各种住房券项目,会帮您找到既安全又符合预算的理想住所。✨🏡""" }, "bn": { "app_title": "🏠 NYC ভাউচার হাউজিং নেভিগেটর", "app_subtitle": "ভাউচার-বান্ধব আবাসন খোঁজার জন্য আপনার ব্যক্তিগত AI কেসওয়ার্কার, বিল্ডিং নিরাপত্তা তথ্যসহ।", "language_selector": "ভাষা / Language / Idioma / 语言", "conversation_label": "VoucherBot এর সাথে কথোপকথন", "message_label": "আপনার বার্তা", "message_placeholder": "আপনার ভাউচারের ধরন, প্রয়োজনীয় বেডরুম এবং সর্বোচ্চ ভাড়া বলে শুরু করুন...", "preferences_title": "🎛️ অনুসন্ধান পছন্দ", "strict_mode_label": "কঠোর মোড (শুধুমাত্র ০ লঙ্ঘনের বিল্ডিং দেখান)", "borough_label": "পছন্দের বরো", "max_rent_label": "সর্বোচ্চ ভাড়া", "listings_label": "মিলে যাওয়া তালিকা", "status_label": "অবস্থা", "status_ready": "অনুসন্ধানের জন্য প্রস্তুত...", "no_listings": "এই মুহূর্তে আপনাকে দেখানোর মতো কোন তালিকা নেই। প্রথমে অ্যাপার্টমেন্ট অনুসন্ধান করুন!", "no_listings_title": "📋 বর্তমান তালিকা নেই", "invalid_listing": "আমার কাছে শুধুমাত্র {count}টি তালিকা উপলব্ধ। অনুগ্রহ করে ১ থেকে {count} এর মধ্যে একটি তালিকা চান।", "invalid_listing_title": "❌ অবৈধ তালিকা নম্বর", "showing_listings": "{count}টি তালিকা দেখাচ্ছে", "strict_applied": "🔒 কঠোর মোড প্রয়োগ করা হয়েছে: ০ লঙ্ঘনের {count}টি তালিকা", "strict_applied_title": "🔒 ফিল্টার প্রয়োগ করা হয়েছে", "results_found": "✅ নিরাপত্তা তথ্যসহ {count}টি ভাউচার-বান্ধব তালিকা পাওয়া গেছে!", "results_title": "✅ ফলাফল প্রস্তুত", "no_safe_listings": "কোন তালিকা আপনার নিরাপত্তা মানদণ্ড পূরণ করে না। সমস্ত উপলব্ধ বিকল্প দেখতে কঠোর মোড নিষ্ক্রিয় করার চেষ্টা করুন।", "no_safe_title": "⚠️ কোন নিরাপদ তালিকা নেই", "search_error": "❌ অনুসন্ধান ত্রুটি: {error}", "search_error_title": "❌ অনুসন্ধান ত্রুটি", "error_occurred": "আমি দুঃখিত, কিন্তু আমি একটি ত্রুটির সম্মুখীন হয়েছি: {error}", "error_title": "❌ ত্রুটি", "general_response_title": "💬 সাধারণ উত্তর", "conversation_mode": "কথোপকথন মোড", "no_criteria": "কোন তালিকা মানদণ্ড পূরণ করে না", "what_if_analysis": "যদি-তাহলে বিশ্লেষণ", "what_if_error_title": "❌ যদি-তাহলে ত্রুটি", "error_what_if": "আপনার যদি-তাহলে পরিস্থিতি প্রক্রিয়া করতে আমি ত্রুটির সম্মুখীন হয়েছি: {error}", "error_listings_available": "ত্রুটি - {count}টি তালিকা উপলব্ধ", "error_what_if_processing": "যদি-তাহলে প্রক্রিয়াকরণে ত্রুটি", "error_conversation": "কথোপকথনে ত্রুটি", "col_address": "ঠিকানা", "col_price": "দাম", "col_risk_level": "ঝুঁকির স্তর", "col_violations": "লঙ্ঘন", "col_last_inspection": "শেষ পরিদর্শন", "col_link": "লিংক", "col_summary": "সারাংশ", "col_shortlist": "পছন্দের তালিকা", "link_not_available": "কোন লিংক উপলব্ধ নেই", "shortlist_save": "➕", "shortlist_saved": "✅", "shortlist_empty": "আপনার পছন্দের তালিকা খালি। শুরু করতে কিছু তালিকা সংরক্ষণ করুন!", "shortlist_title": "আপনার পছন্দের তালিকা", "shortlist_added": "পছন্দের তালিকায় যোগ করা হয়েছে", "shortlist_removed": "পছন্দের তালিকা থেকে সরানো হয়েছে", "shortlist_cleared": "পছন্দের তালিকা পরিষ্কার করা হয়েছে", "intro_greeting": """👋 **নমস্কার! আমি নবি, আপনার ব্যক্তিগত NYC হাউজিং নেভিগেটর!** আমি এখানে আছি নিউইয়র্ক সিটিতে আপনাকে নিরাপদ, সাশ্রয়ী এবং ভাউচার-বান্ধব আবাসন খুঁজে পেতে সাহায্য করার জন্য। আমি বুঝি যে সঠিক বাড়ি খোঁজা অভিভূতকর মনে হতে পারে, কিন্তু আপনাকে একা এটি করতে হবে না - আমি প্রতিটি পদক্ষেপে আপনাকে গাইড করার জন্য এখানে আছি! 😊 **আমি যেভাবে আপনাকে সাহায্য করতে পারি:** • 🏠 **ভাউচার-বান্ধব অ্যাপার্টমেন্ট খুঁজুন** যা আপনার নির্দিষ্ট ভাউচার ধরন গ্রহণ করে • 🏢 **বিল্ডিং নিরাপত্তা পরীক্ষা করুন** এবং মানসিক শান্তির জন্য লঙ্ঘনের রিপোর্ট প্রদান করুন • 🚇 **নিকটবর্তী সাবওয়ে স্টেশন দেখান** এবং ট্রানজিট অ্যাক্সেসিবলিটি • 🏫 **নিকটবর্তী স্কুল খুঁজুন** শিশুদের সাথে পরিবারের জন্য • 📧 **পেশাদার ইমেইল খসড়া করুন** বাড়িওয়ালা এবং সম্পত্তি ব্যবস্থাপকদের কাছে • 💡 **প্রশ্নের উত্তর দিন** ভাউচার প্রোগ্রাম, পাড়া এবং আবাসন অধিকার সম্পর্কে **শুরু করতে, শুধু আমাকে বলুন:** • আপনার কি ধরনের ভাউচার আছে? (Section 8, CityFHEPS, HASA, ইত্যাদি) • আপনার কতটি বেডরুম প্রয়োজন? 🛏️ • আপনার সর্বোচ্চ ভাড়ার বাজেট কত? 💰 • আপনার কি কোন পছন্দের বরো আছে? 🗽 আমি ধৈর্যশীল, দয়ালু, এবং এই যাত্রায় আপনাকে সমর্থন করার জন্য এখানে আছি। আসুন আপনার জন্য একটি চমৎকার জায়গা খুঁজে পাই যাকে আপনি বাড়ি বলতে পারেন! ✨🏡""" } } # Create the I18n instance with keyword arguments for each language i18n = gr.I18n( en=i18n_dict["en"], es=i18n_dict["es"], zh=i18n_dict["zh"], bn=i18n_dict["bn"] ) # --- Initialize Agents and State Management --- print("Initializing VoucherBot Agents...") caseworker_agent = initialize_caseworker_agent() browser_agent = BrowserAgent() violation_agent = ViolationCheckerAgent() print("Agents Initialized. Ready for requests.") # --- State Management Functions --- def create_initial_state() -> Dict: """Create initial app state.""" return { "listings": [], "current_listing": None, # Track the currently discussed listing "current_listing_index": None, # Track the index of the current listing "preferences": { "borough": "", "max_rent": 4000, "min_bedrooms": 1, "voucher_type": "", "strict_mode": False, "language": "en" # Add language to preferences }, "shortlist": [] # Changed from favorites to shortlist } def update_app_state(current_state: Dict, updates: Dict) -> Dict: """Update app state with new data.""" new_state = current_state.copy() for key, value in updates.items(): if key == "preferences" and isinstance(value, dict): new_state["preferences"].update(value) else: new_state[key] = value return new_state def filter_listings_strict_mode(listings: List[Dict], strict: bool = False) -> List[Dict]: """Filter listings based on strict mode (no violations).""" if not strict: return listings return [ listing for listing in listings if listing.get("building_violations", 0) == 0 ] def create_chat_message_with_metadata(content: str, title: str, duration: Optional[float] = None, parent_id: Optional[str] = None) -> Dict: """Create a ChatMessage with metadata for better UX.""" metadata = { "title": title, "timestamp": current_timestamp() } if duration is not None: metadata["duration"] = duration if parent_id is not None: metadata["parent_id"] = parent_id return { "role": "assistant", "content": content, "metadata": metadata } def detect_context_dependent_question(message: str) -> bool: """Detect if the message is asking about something in the current context (like 'which lines?')""" message_lower = message.lower().strip() # Short questions that likely refer to current context context_patterns = [ r'^which\s+(lines?|train|subway)', # "which lines", "which line", "which train" r'^what\s+(lines?|train|subway)', # "what lines", "what line", "what train" r'^how\s+(far|close|near)', # "how far", "how close", "how near" r'^(lines?|train|subway)$', # just "lines", "line", "train", "subway" r'^what\s+about', # "what about..." r'^tell\s+me\s+about', # "tell me about..." r'^more\s+(info|details)', # "more info", "more details" r'^(distance|walk|walking)', # "distance", "walk", "walking" r'^any\s+other', # "any other..." r'^is\s+it\s+(near|close|far)', # "is it near", "is it close", "is it far" # Add patterns for subway and school proximity questions r'nearest\s+(subway|train|school)', # "nearest subway", "nearest school", "nearest train" r'closest\s+(subway|train|school)', # "closest subway", "closest school", "closest train" r'what\'?s\s+the\s+(nearest|closest)\s+(subway|train|school)', # "what's the nearest/closest subway" r'where\s+is\s+the\s+(nearest|closest)\s+(subway|train|school)', # "where is the nearest/closest subway" r'how\s+far\s+is\s+the\s+(subway|train|school)', # "how far is the subway" r'(subway|train|school)\s+(distance|proximity)', # "subway distance", "school proximity" r'^(subway|train|school)\?$', # just "subway?", "school?" r'^closest\s+(subway|train|school)\?$', # "closest subway?", "closest school?" ] # Check if message matches context-dependent patterns import re for pattern in context_patterns: if re.match(pattern, message_lower): return True # Also check for very short questions (likely context-dependent) words = message_lower.split() if len(words) <= 3 and any(word in ['which', 'what', 'how', 'where', 'lines', 'train', 'subway'] for word in words): return True return False def detect_language_from_message(message: str) -> str: """Detect language from user message using simple keyword matching.""" message_lower = message.lower() # Spanish keywords spanish_keywords = [ 'hola', 'apartamento', 'vivienda', 'casa', 'alquiler', 'renta', 'busco', 'necesito', 'ayuda', 'donde', 'como', 'que', 'soy', 'tengo', 'quiero', 'habitacion', 'habitaciones', 'dormitorio', 'precio', 'costo', 'dinero', 'section', 'cityFHEPS', 'voucher', 'bronx', 'brooklyn', 'manhattan', 'queens', 'gracias', 'por favor', 'dime', 'dame', 'encuentro' ] # Chinese keywords (simplified) chinese_keywords = [ '你好', '公寓', '住房', '房屋', '租金', '寻找', '需要', '帮助', '在哪里', '怎么', '什么', '我', '有', '要', '房间', '卧室', '价格', '钱', '住房券', '布朗克斯', '布鲁克林', '曼哈顿', '皇后区', '谢谢', '请', '告诉', '给我', '找到' ] # Bengali keywords bengali_keywords = [ 'নমস্কার', 'অ্যাপার্টমেন্ট', 'বাড়ি', 'ভাড়া', 'খুঁজছি', 'প্রয়োজন', 'সাহায্য', 'কোথায়', 'কিভাবে', 'কি', 'আমি', 'আছে', 'চাই', 'রুম', 'বেডরুম', 'দাম', 'টাকা', 'ভাউচার', 'ব্রঙ্কস', 'ব্রুকলিন', 'ম্যানহাটান', 'কুইন্স', 'ধন্যবাদ', 'দয়া করে', 'বলুন', 'দিন', 'খুঁজে' ] # Count matches for each language spanish_count = sum(1 for keyword in spanish_keywords if keyword in message_lower) chinese_count = sum(1 for keyword in chinese_keywords if keyword in message) bengali_count = sum(1 for keyword in bengali_keywords if keyword in message) # Return language with highest count (minimum 2 matches required) if spanish_count >= 2: return "es" elif chinese_count >= 2: return "zh" elif bengali_count >= 2: return "bn" else: return "en" # Default to English # Define the theme using Origin theme = gr.themes.Origin( primary_hue="indigo", secondary_hue="indigo", neutral_hue="teal", ) # --- Gradio UI Definition --- # Original CSS (for easy revert): # .app-header { text-align: center; margin-bottom: 2rem; } # .app-title { font-size: 2.2rem; margin-bottom: 0.5rem; } # .app-subtitle { font-size: 1.1rem; color: #666; margin-bottom: 1rem; } # .dark .app-title { color: #f9fafb !important; } # .dark .app-subtitle { color: #d1d5db !important; } # .dark .gradio-container { background-color: #1f2937 !important; } # .dark { background-color: #111827 !important; } with gr.Blocks(theme=theme, css=""" /* Material Design-inspired styles - Two-Column Layout */ body, .gr-root { font-family: 'Roboto', 'Helvetica Neue', Arial, sans-serif; color: #222; background: #f5f5f7; } /* Header spanning both columns */ .app-header { text-align: center; margin-bottom: 2rem; padding: 1.5rem; background: linear-gradient(135deg, #00695c 0%, #004d40 100%); border-radius: 12px; color: white; box-shadow: 0 4px 16px rgba(0,105,92,0.15); } .app-title { font-size: 2.5rem; margin-bottom: 0.5rem; font-weight: 700; color: white; letter-spacing: 0.5px; text-shadow: 0 2px 8px rgba(0,0,0,0.1); } .app-subtitle { font-size: 1.2rem; color: rgba(255,255,255,0.9); margin-bottom: 0; font-weight: 400; } /* Header controls */ .header-controls { position: absolute; top: 1rem; right: 1rem; display: flex; gap: 0.5rem; } .header-controls button { background: rgba(255,255,255,0.2); border: 1px solid rgba(255,255,255,0.3); color: white; padding: 0.5rem 1rem; border-radius: 6px; font-size: 0.9rem; } .header-controls button:hover { background: rgba(255,255,255,0.3); } /* Two-column layout */ .main-layout { display: flex; gap: 2rem; min-height: 70vh; } .chat-column { flex: 1; max-width: 50%; display: flex; flex-direction: column; } .info-column { flex: 1; max-width: 50%; display: flex; flex-direction: column; } /* Onboarding/Help Section */ .onboarding-box { background: #fff; border-radius: 12px; padding: 1.5rem; margin-bottom: 1rem; box-shadow: 0 4px 16px rgba(0,105,92,0.08); border-left: 4px solid #00695c; } .onboarding-title { font-size: 1.1rem; font-weight: 600; color: #00695c; margin-bottom: 0.5rem; } .onboarding-text { color: #666; line-height: 1.5; margin-bottom: 1rem; } /* Suggested Prompts */ .suggested-prompts { margin-bottom: 1rem; } .prompt-chips { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 1rem; } .prompt-chip { background: #e8eaf6; color: #6200ea; border: 1px solid #6200ea; border-radius: 20px; padding: 0.5rem 1rem; font-size: 0.9rem; cursor: pointer; transition: all 0.2s; } .prompt-chip:hover { background: #6200ea; color: white; transform: translateY(-1px); box-shadow: 0 2px 8px rgba(98,0,234,0.2); } /* Chat area styling */ .gr-chatbot { flex: 1; margin-bottom: 1rem; border-radius: 12px; box-shadow: 0 4px 16px rgba(0,105,92,0.08); position: relative; } /* Simple fix for green blocks - just target the specific elements causing issues */ .gr-chatbot .prose::marker, .gr-chatbot .prose li::marker, .gr-chatbot .prose ul::marker, .gr-chatbot .prose ol::marker { color: transparent !important; content: '' !important; } .gr-chatbot .prose li::before, .gr-chatbot .prose ul li::before, .gr-chatbot .prose ol li::before { background: transparent !important; color: transparent !important; content: '' !important; display: none !important; } /* Make trash/delete button smaller and positioned correctly */ .gr-chatbot button[aria-label*="Delete"], .gr-chatbot button[aria-label*="Clear"], .gr-chatbot .gr-button[title*="Delete"], .gr-chatbot .gr-button[title*="Clear"] { width: 28px !important; height: 28px !important; min-width: 28px !important; min-height: 28px !important; padding: 4px !important; font-size: 0.75rem !important; position: absolute !important; top: 8px !important; right: 8px !important; z-index: 10 !important; border-radius: 50% !important; background: rgba(0,105,92,0.8) !important; } .gr-chatbot button[aria-label*="Delete"]:hover, .gr-chatbot button[aria-label*="Clear"]:hover, .gr-chatbot .gr-button[title*="Delete"]:hover, .gr-chatbot .gr-button[title*="Clear"]:hover { background: rgba(0,77,64,0.9) !important; transform: scale(1.05) !important; } /* Input area */ .chat-input-area { background: #fff; border-radius: 12px; padding: 1rem; box-shadow: 0 4px 16px rgba(0,105,92,0.08); margin-bottom: 1rem; } /* Toggles section */ .toggles-section { background: #fff; border-radius: 12px; padding: 1rem; box-shadow: 0 4px 16px rgba(0,105,92,0.08); } .toggle-title { font-weight: 600; color: #333; margin-bottom: 0.5rem; } /* Right column - Info panel */ .results-header { background: #fff; border-radius: 12px; padding: 1rem; margin-bottom: 1rem; box-shadow: 0 4px 16px rgba(0,105,92,0.08); text-align: center; font-weight: 600; color: #00695c; } .results-dataframe { flex: 1; background: #fff; border-radius: 12px; padding: 1rem; box-shadow: 0 4px 16px rgba(0,105,92,0.08); margin-bottom: 1rem; } .status-panel { background: #fff; border-radius: 12px; padding: 1rem; box-shadow: 0 4px 16px rgba(0,105,92,0.08); } /* Buttons - Enhanced Material Design */ button, .gr-button { background: #00695c; color: #fff; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,105,92,0.15); font-weight: 600; font-size: 1rem; padding: 0.75em 1.5em; min-height: 44px; position: relative; overflow: hidden; transition: all 0.2s; border: none; } button:hover, .gr-button:hover { background: #004d40; box-shadow: 0 6px 20px rgba(0,105,92,0.2); transform: translateY(-1px); } button:active, .gr-button:active { transform: translateY(0); } /* Inputs - Enhanced styling */ input, textarea, .gr-textbox input, .gr-textbox textarea { border: 2px solid #e0e0e0; border-radius: 8px; padding: 12px 16px; font-size: 1rem; background: #fff; transition: all 0.2s; } input:focus, textarea:focus, .gr-textbox input:focus, .gr-textbox textarea:focus { border-color: #00695c; box-shadow: 0 0 0 3px rgba(0,105,92,0.1); outline: none; } /* DataFrame styling */ .gr-dataframe { border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.05); } /* Responsive design */ @media (max-width: 768px) { .main-layout { flex-direction: column; } .chat-column, .info-column { max-width: 100%; } .header-controls { position: relative; margin-top: 1rem; } .prompt-chips { flex-direction: column; } } /* Dark mode button - Compact styling */ .dark-mode-btn { width: 36px !important; height: 36px !important; min-width: 36px !important; min-height: 36px !important; padding: 6px !important; font-size: 1rem !important; border-radius: 50% !important; background: rgba(0,105,92,0.1) !important; border: 1px solid rgba(0,105,92,0.3) !important; color: #00695c !important; box-shadow: 0 2px 6px rgba(0,105,92,0.1) !important; transition: all 0.2s ease !important; } .dark-mode-btn:hover { background: rgba(0,105,92,0.2) !important; transform: scale(1.05) !important; box-shadow: 0 3px 8px rgba(0,105,92,0.2) !important; } /* Dark mode adaptations */ .dark { background-color: #111827 !important; } .dark .app-title { color: #f9fafb !important; } .dark .app-subtitle { color: #d1d5db !important; } .dark .gradio-container { background-color: #1f2937 !important; } .dark .onboarding-box, .dark .chat-input-area, .dark .toggles-section, .dark .results-header, .dark .results-dataframe, .dark .status-panel { background: #374151 !important; color: #f3f4f6 !important; } .dark .dark-mode-btn { background: rgba(255,255,255,0.1) !important; border: 1px solid rgba(255,255,255,0.2) !important; color: #f3f4f6 !important; } .dark .dark-mode-btn:hover { background: rgba(255,255,255,0.2) !important; } """) as demo: # Header Section with gr.Row(): with gr.Column(): gr.HTML("""
Find safe, voucher-friendly housing in NYC with AI assistance
Click ➕ in the listings table to save properties to your shortlist.
Use chat commands like "show my shortlist" to manage saved listings.
Click ➕ in the listings table to save properties to your shortlist.
Use chat commands like "show my shortlist" to manage saved listings.
Click ➕/✅ in the table or use chat commands