import gradio as gr import joblib import numpy as np import json import math import re from fpdf import FPDF import tempfile from deep_translator import GoogleTranslator import warnings warnings.filterwarnings("ignore") # Load models xgb = joblib.load("xgb_model.pkl") rf = joblib.load("rf_model.pkl") # Load catalog and sizes with open("tile_catalog.json", "r", encoding="utf-8") as f: tile_catalog = json.load(f) with open("tile_sizes.json", "r", encoding="utf-8") as f: tile_sizes = json.load(f) tile_size_map = {s["label"].lower().replace(" ", ""): s["area_sqft"] for s in tile_sizes} def translate(text, lang="en"): try: return GoogleTranslator(source="auto", target=lang).translate(text) except: return text def remove_emojis(text): return re.sub(r'[^\x00-\x7F]+', '', text) def create_pdf(text): pdf = FPDF() pdf.add_page() pdf.add_font("FreeSans", "", "FreeSans.ttf", uni=True) pdf.set_font("FreeSans", size=12) pdf.cell(0, 10, "Tile Estimate Report", ln=True, align="C") pdf.ln(5) for line in text.strip().split("\n"): pdf.multi_cell(0, 10, remove_emojis(line)) tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") pdf.output(tmp.name) return tmp.name def extract_tile_area(msg, unit): msg = msg.lower().replace("×", "x").replace("into", "x").replace("*", "x") msg = msg.replace("mm", "").replace("ft", "").replace("feet", "").strip() if "x" in msg: parts = msg.split("x") if len(parts) == 2: try: val1 = float(re.sub(r"[^\d.]", "", parts[0])) val2 = float(re.sub(r"[^\d.]", "", parts[1])) if unit == "mm": sqft = (val1 * val2) / 92903.04 else: sqft = val1 * val2 return round(sqft, 2) except: return None return None def chat_fn(message, history, user_state={}): # Reset state if user types Floor or Wall if message.strip().lower() in ["floor", "wall"]: user_state.clear() # Language detection if "lang" not in user_state: try: user_state["lang"] = GoogleTranslator(source="auto", target="en").detect(message) except: user_state["lang"] = "en" lang = user_state["lang"] def reply(text): return translate(text, lang) # Handle PDF command if message.strip().lower() in ["pdf", "report", "download"]: if "summary" in user_state: pdf_path = create_pdf(user_state["summary"]) return reply("Here’s your PDF report 📄"), [pdf_path], user_state else: return reply("No estimate yet. Please start by typing 'Floor' or 'Wall'."), None, user_state # Start flow if "step" not in user_state: if message.lower() in ["floor", "wall"]: user_state["tile_type"] = message.capitalize() user_state["step"] = "get_area" return reply(f"Great! You chose {user_state['tile_type']} tiles.\nWhat’s the total area to cover (in sq.ft)?"), None, user_state return reply("Hi there! Are you planning for Floor or Wall tiles?"), None, user_state if user_state["step"] == "get_area": try: user_state["area"] = float(message) user_state["step"] = "get_unit" return reply("Would you like to enter the tile size in mm or ft?"), None, user_state except: return reply("That doesn't look like a number. Please enter the area in sq.ft (e.g. 120)."), None, user_state if user_state["step"] == "get_unit": if message.lower() not in ["mm", "ft"]: return reply("Please type either mm or ft to choose your preferred unit."), None, user_state user_state["unit"] = message.lower() user_state["step"] = "get_tile_size" unit_label = "mm" if user_state["unit"] == "mm" else "feet" return reply(f"Please enter the tile size in {unit_label} (e.g. 600 x 600):"), None, user_state if user_state["step"] == "get_tile_size": area = extract_tile_area(message, user_state["unit"]) if area is None: return reply("I couldn’t understand that size. Try something like 600 x 600 or 2 x 2."), None, user_state user_state["tile_area"] = area user_state["step"] = "done" area_needed = user_state["area"] tile_type = user_state["tile_type"] tile_needed = math.ceil((area_needed / area) * 1.1) best = [] for tile in tile_catalog: if tile["type"].lower() == tile_type.lower(): per_box = tile["coverage"] / area if per_box > 0: boxes = math.ceil(tile_needed / per_box) total = boxes * tile["price"] best.append({ "name": tile["name"], "size": tile["size"], "price": tile["price"], "boxes": boxes, "total": total, "url": tile["url"] }) best.sort(key=lambda x: x["total"]) top3 = best[:3] summary = f""" 🧱 Tile Type: {tile_type} 📐 Area to Cover: {area_needed} sq.ft 🧮 Tile Size Area: {round(area, 2)} sq.ft 🔢 Estimated Tiles Needed: {tile_needed} (with 10% buffer) 🎯 Suggested Products: """ for i, t in enumerate(top3, 1): summary += f"\n{i}. {t['name']} ({t['size']})\n ₹{t['price']} per box → ~{t['boxes']} boxes\n {t['url']}\n" summary += "\nYou can type 'pdf' to download the report, or start fresh with 'Floor' or 'Wall'." user_state["summary"] = summary pdf_path = create_pdf(summary) return reply(summary), [pdf_path], user_state return reply("Type 'Floor' or 'Wall' to begin a new estimate."), None, user_state # Launch with clean intro with gr.Blocks() as demo: chatbot = gr.ChatInterface( fn=chat_fn, title="🧱 TileBot – Your Tile Estimation Assistant", description=( "🧱 TileBot is here to help you estimate tiles for your space.\n\n" "Start by telling me if you're working on a Floor or Wall. Then share your room size and tile size in mm or feet — " "I'll do the rest.\n\n" "I’ll calculate how many tiles you need, recommend suitable products, and give you a PDF report.\n\n" "Type 'Floor' or 'Wall' to begin!" ), type="messages", additional_outputs=[gr.File(label="📄 Download Report")] ) demo.launch()