|
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 |
|
from xgboost import Booster |
|
|
|
|
|
xgb = Booster() |
|
xgb.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 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): |
|
msg = msg.lower().replace("ร", "x").replace("into", "x").replace("*", "x") |
|
msg = msg.replace("ft", "").replace("feet", "").replace("mm", "").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 val1 > 20: |
|
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={}): |
|
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 "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 selected {user_state['tile_type']} tiles.\nPlease enter the total area in sq.ft:"), None, user_state |
|
return reply("๐ Hello! Are you looking for Floor or Wall tiles?"), None, user_state |
|
|
|
if user_state["step"] == "get_area": |
|
try: |
|
user_state["area"] = float(message) |
|
user_state["step"] = "get_tile_size" |
|
return reply("๐ Now enter the tile size like 2 x 2 ft or 600x600 MM or 200 * 200:"), None, user_state |
|
except: |
|
return reply("โ Please enter a number for area (e.g. 120)."), None, user_state |
|
|
|
if user_state["step"] == "get_tile_size": |
|
area = extract_tile_area(message) |
|
if area is None: |
|
examples = "2 x 2 ft, 600x600 MM, 200*200, 1.5x1.5" |
|
return reply(f"โ Oops, I didn't catch that size. Try formats like: {examples}"), 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: {area_needed} sq.ft |
|
๐งฎ Tile Size: {round(area, 2)} sq.ft |
|
๐ข Total Tiles Needed (10% buffer): {tile_needed} |
|
|
|
๐ฏ Top Recommendations: |
|
""" |
|
for i, t in enumerate(top3, 1): |
|
summary += f"\n{i}. {t['name']} ({t['size']})\n โน{t['price']}/box โ ~{t['boxes']} boxes\n {t['url']}\n" |
|
|
|
summary += "\nSay 'Floor' or 'Wall' to start a new estimate." |
|
|
|
pdf_path = create_pdf(summary) |
|
return reply(summary), [pdf_path], {} |
|
|
|
return reply("Say 'Floor' or 'Wall' to begin again."), None, {} |
|
|
|
|
|
with gr.Blocks() as demo: |
|
chatbot = gr.ChatInterface( |
|
fn=chat_fn, |
|
title="๐งฑ TileBot โ ChatGPT-Style Tile Estimator", |
|
description="Chat like a human. Enter sizes naturally like 200x200, 2 x 2 ft, or 600x600 MM.\nSay 'Floor' or 'Wall' to begin.", |
|
type="messages", |
|
additional_outputs=[gr.File(label="๐ Download Report")] |
|
) |
|
|
|
demo.launch() |