|
import gradio as gr |
|
import joblib |
|
import json |
|
import math |
|
import re |
|
from deep_translator import GoogleTranslator |
|
import warnings |
|
import xgboost as xgb |
|
|
|
warnings.filterwarnings("ignore") |
|
|
|
|
|
xgb_model = xgb.Booster() |
|
xgb_model.load_model("xgb_model.json") |
|
rf = joblib.load("rf_model.pkl") |
|
|
|
|
|
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 extract_tile_area(msg, unit): |
|
msg = msg.lower().replace("feet", "").replace("ft", "").replace("mm", "").strip() |
|
msg = re.sub(r"\s*(x|\u00d7|\*|into)\s*", "x", msg) |
|
parts = msg.split("x") |
|
|
|
try: |
|
if len(parts) == 2: |
|
val1 = float(re.sub(r"[^\d.]", "", parts[0])) |
|
val2 = float(re.sub(r"[^\d.]", "", parts[1])) |
|
sqft = (val1 * val2) / 92903.04 if unit == "mm" else val1 * val2 |
|
return round(sqft, 2) |
|
elif len(parts) == 1 and parts[0]: |
|
return float(re.sub(r"[^\d.]", "", parts[0])) |
|
else: |
|
return None |
|
except: |
|
return None |
|
|
|
def chat_fn(message, history, user_state={}): |
|
if "step" not in user_state and message.strip().lower() in ["floor", "wall"]: |
|
user_state.clear() |
|
|
|
|
|
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) |
|
|
|
|
|
if message.strip().lower() in ["hi", "hello", "hey", "start", "tile", "help"]: |
|
return reply("\U0001F44B Hello! I'm TileBot, your assistant for tile estimates.\nAre you planning for Floor or Wall tiles?"), None, user_state |
|
|
|
|
|
if "step" not in user_state: |
|
if message.lower() in ["floor", "wall"]: |
|
user_state["tile_type"] = message.capitalize() |
|
user_state["step"] = "get_length" |
|
return reply(f"You selected {user_state['tile_type']} tiles.\nEnter the length of the space in feet:"), None, user_state |
|
return reply("Hi! Are you planning for Floor or Wall tiles?"), None, user_state |
|
|
|
if user_state["step"] == "get_length": |
|
try: |
|
user_state["length"] = float(message) |
|
user_state["step"] = "get_width" |
|
return reply("Now enter the width of the space in feet:"), None, user_state |
|
except: |
|
return reply("That doesn't seem like a number. Please enter the length in feet (e.g. 12)."), None, user_state |
|
|
|
if user_state["step"] == "get_width": |
|
try: |
|
user_state["width"] = float(message) |
|
user_state["area"] = round(user_state["length"] * user_state["width"], 2) |
|
user_state["step"] = "get_unit" |
|
return reply("Got it! Would you like to enter tile size in mm or ft?"), None, user_state |
|
except: |
|
return reply("Please enter a valid width in feet (e.g. 10)."), 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, 2 x 2, or 4):"), None, user_state |
|
|
|
if user_state["step"] == "get_tile_size": |
|
area = extract_tile_area(message, user_state["unit"]) |
|
if not area or area <= 0: |
|
return reply("\u274C That tile size didn’t work. Try something like:\n- `600 x 600`\n- `2 x 2`\n- `4` (if in ft)"), 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""" |
|
\U0001F9F1 Tile Type: {tile_type} |
|
\U0001F4D0 Space: {user_state['length']} ft x {user_state['width']} ft |
|
\U0001F4CF Area to Cover: {area_needed} sq.ft |
|
\U0001F9EE Tile Size Area: {round(area, 2)} sq.ft |
|
\U0001F522 Estimated Tiles Needed: {tile_needed} (with 10% buffer) |
|
|
|
\U0001F3AF Top Suggestions: |
|
""" |
|
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 += "\nType 'Floor' or 'Wall' to start another estimate." |
|
return reply(summary), None, user_state |
|
|
|
return reply("I didn't understand that. Please continue or type 'Floor' or 'Wall' to start over."), None, user_state |
|
|
|
|
|
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", radius_size="lg")) as demo: |
|
gr.Markdown( |
|
""" |
|
# 🧱 <span style="color:#3B82F6">TileBot</span> – Smart Tile Estimator |
|
Welcome! I'm here to help you estimate tiles for your floor or wall. |
|
|
|
🔹 Start with **"Floor"** or **"Wall"** |
|
📀 Enter **length and width** (in feet) |
|
📏 Then your tile size (in `mm` or `ft`) – like `600 x 600`, `2x2`, or `4` |
|
📋 I’ll recommend tiles and calculate how many you need. |
|
""", |
|
elem_id="header" |
|
) |
|
|
|
chatbot = gr.ChatInterface( |
|
fn=chat_fn, |
|
type="messages", |
|
chatbot=gr.Chatbot(height=500, bubble_full_width=False), |
|
) |
|
|
|
gr.Markdown("---\nNeed to start over? Just type **Floor** or **Wall** again.") |
|
|
|
demo.launch() |
|
|