Tilo / app.py
Coots's picture
Update app.py
1e74a1e verified
raw
history blame
7.05 kB
import gradio as gr
import joblib
import json
import math
import re
from fpdf import FPDF
import tempfile
from deep_translator import GoogleTranslator
import warnings
import xgboost as xgb
warnings.filterwarnings("ignore")
# Load models
xgb_model = xgb.Booster()
xgb_model.load_model("xgb_model.json")
rf = joblib.load("rf_model.pkl")
# Load product catalog and tile 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("feet", "").replace("ft", "").replace("mm", "").strip()
msg = re.sub(r"\s*(x|×|\*|into)\s*", "x", msg) # Normalize separators to 'x'
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]))
if unit == "mm":
sqft = (val1 * val2) / 92903.04
else:
sqft = 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 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 ["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
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 or 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("❌ 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"""
🧱 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
# Gradio UI
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 tile needs for your floor or wall.
👉 Start by typing **"Floor"** or **"Wall"**
📏 Then give me the **area** and **tile size** in `mm` or `ft`
✏️ Example: `600 x 600`, `2 x 2`, `4`
📋 I’ll recommend tiles and generate a **PDF Report**!
""",
elem_id="header"
)
chatbot = gr.ChatInterface(
fn=chat_fn,
type="messages",
chatbot=gr.Chatbot(height=500, bubble_full_width=False),
additional_outputs=[gr.File(label="📄 Download Report")]
)
gr.Markdown(
"""
---
💡 Tip: Type `"pdf"` anytime to get a downloadable report.
🚀 Need a new estimate? Just say `"Floor"` or `"Wall"` to start over.
""",
elem_id="footer"
)
demo.launch()