Coots commited on
Commit
a1626c0
·
verified ·
1 Parent(s): d7b327e

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +178 -0
app.py ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import joblib
3
+ import numpy as np
4
+ import json
5
+ import math
6
+ import re
7
+ from fpdf import FPDF
8
+ import tempfile
9
+ from deep_translator import GoogleTranslator
10
+ import warnings
11
+ warnings.filterwarnings("ignore")
12
+
13
+ # Load models
14
+ xgb = joblib.load("xgb_model.pkl")
15
+ rf = joblib.load("rf_model.pkl")
16
+
17
+ # Load catalog and sizes
18
+ with open("tile_catalog.json", "r", encoding="utf-8") as f:
19
+ tile_catalog = json.load(f)
20
+
21
+ with open("tile_sizes.json", "r", encoding="utf-8") as f:
22
+ tile_sizes = json.load(f)
23
+
24
+ tile_size_map = {s["label"].lower().replace(" ", ""): s["area_sqft"] for s in tile_sizes}
25
+
26
+ def translate(text, lang="en"):
27
+ try:
28
+ return GoogleTranslator(source="auto", target=lang).translate(text)
29
+ except:
30
+ return text
31
+
32
+ def remove_emojis(text):
33
+ return re.sub(r'[^\x00-\x7F]+', '', text)
34
+
35
+ def create_pdf(text):
36
+ pdf = FPDF()
37
+ pdf.add_page()
38
+ pdf.add_font("FreeSans", "", "FreeSans.ttf", uni=True)
39
+ pdf.set_font("FreeSans", size=12)
40
+ pdf.cell(0, 10, "Tile Estimate Report", ln=True, align="C")
41
+ pdf.ln(5)
42
+ for line in text.strip().split("\n"):
43
+ pdf.multi_cell(0, 10, remove_emojis(line))
44
+ tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf")
45
+ pdf.output(tmp.name)
46
+ return tmp.name
47
+
48
+ def extract_tile_area(msg, unit):
49
+ msg = msg.lower().replace("×", "x").replace("into", "x").replace("*", "x")
50
+ msg = msg.replace("mm", "").replace("ft", "").replace("feet", "").strip()
51
+ if "x" in msg:
52
+ parts = msg.split("x")
53
+ if len(parts) == 2:
54
+ try:
55
+ val1 = float(re.sub(r"[^\d.]", "", parts[0]))
56
+ val2 = float(re.sub(r"[^\d.]", "", parts[1]))
57
+ if unit == "mm":
58
+ sqft = (val1 * val2) / 92903.04
59
+ else:
60
+ sqft = val1 * val2
61
+ return round(sqft, 2)
62
+ except:
63
+ return None
64
+ return None
65
+
66
+ def chat_fn(message, history, user_state={}):
67
+ # Reset state if user types Floor or Wall
68
+ if message.strip().lower() in ["floor", "wall"]:
69
+ user_state.clear()
70
+
71
+ # Language detection
72
+ if "lang" not in user_state:
73
+ try:
74
+ user_state["lang"] = GoogleTranslator(source="auto", target="en").detect(message)
75
+ except:
76
+ user_state["lang"] = "en"
77
+ lang = user_state["lang"]
78
+ def reply(text): return translate(text, lang)
79
+
80
+ # Handle PDF command
81
+ if message.strip().lower() in ["pdf", "report", "download"]:
82
+ if "summary" in user_state:
83
+ pdf_path = create_pdf(user_state["summary"])
84
+ return reply("Here’s your PDF report 📄"), [pdf_path], user_state
85
+ else:
86
+ return reply("No estimate yet. Please start by typing 'Floor' or 'Wall'."), None, user_state
87
+
88
+ # Start flow
89
+ if "step" not in user_state:
90
+ if message.lower() in ["floor", "wall"]:
91
+ user_state["tile_type"] = message.capitalize()
92
+ user_state["step"] = "get_area"
93
+ return reply(f"Great! You chose {user_state['tile_type']} tiles.\nWhat’s the total area to cover (in sq.ft)?"), None, user_state
94
+ return reply("Hi there! Are you planning for Floor or Wall tiles?"), None, user_state
95
+
96
+ if user_state["step"] == "get_area":
97
+ try:
98
+ user_state["area"] = float(message)
99
+ user_state["step"] = "get_unit"
100
+ return reply("Would you like to enter the tile size in mm or ft?"), None, user_state
101
+ except:
102
+ return reply("That doesn't look like a number. Please enter the area in sq.ft (e.g. 120)."), None, user_state
103
+
104
+ if user_state["step"] == "get_unit":
105
+ if message.lower() not in ["mm", "ft"]:
106
+ return reply("Please type either mm or ft to choose your preferred unit."), None, user_state
107
+ user_state["unit"] = message.lower()
108
+ user_state["step"] = "get_tile_size"
109
+ unit_label = "mm" if user_state["unit"] == "mm" else "feet"
110
+ return reply(f"Please enter the tile size in {unit_label} (e.g. 600 x 600):"), None, user_state
111
+
112
+ if user_state["step"] == "get_tile_size":
113
+ area = extract_tile_area(message, user_state["unit"])
114
+ if area is None:
115
+ return reply("I couldn’t understand that size. Try something like 600 x 600 or 2 x 2."), None, user_state
116
+
117
+ user_state["tile_area"] = area
118
+ user_state["step"] = "done"
119
+
120
+ area_needed = user_state["area"]
121
+ tile_type = user_state["tile_type"]
122
+ tile_needed = math.ceil((area_needed / area) * 1.1)
123
+
124
+ best = []
125
+ for tile in tile_catalog:
126
+ if tile["type"].lower() == tile_type.lower():
127
+ per_box = tile["coverage"] / area
128
+ if per_box > 0:
129
+ boxes = math.ceil(tile_needed / per_box)
130
+ total = boxes * tile["price"]
131
+ best.append({
132
+ "name": tile["name"],
133
+ "size": tile["size"],
134
+ "price": tile["price"],
135
+ "boxes": boxes,
136
+ "total": total,
137
+ "url": tile["url"]
138
+ })
139
+
140
+ best.sort(key=lambda x: x["total"])
141
+ top3 = best[:3]
142
+
143
+ summary = f"""
144
+ 🧱 Tile Type: {tile_type}
145
+ 📐 Area to Cover: {area_needed} sq.ft
146
+ 🧮 Tile Size Area: {round(area, 2)} sq.ft
147
+ 🔢 Estimated Tiles Needed: {tile_needed} (with 10% buffer)
148
+
149
+ 🎯 Suggested Products:
150
+ """
151
+ for i, t in enumerate(top3, 1):
152
+ summary += f"\n{i}. {t['name']} ({t['size']})\n ₹{t['price']} per box → ~{t['boxes']} boxes\n {t['url']}\n"
153
+
154
+ summary += "\nYou can type 'pdf' to download the report, or start fresh with 'Floor' or 'Wall'."
155
+
156
+ user_state["summary"] = summary
157
+ pdf_path = create_pdf(summary)
158
+ return reply(summary), [pdf_path], user_state
159
+
160
+ return reply("Type 'Floor' or 'Wall' to begin a new estimate."), None, user_state
161
+
162
+ # Launch with clean intro
163
+ with gr.Blocks() as demo:
164
+ chatbot = gr.ChatInterface(
165
+ fn=chat_fn,
166
+ title="🧱 TileBot – Your Tile Estimation Assistant",
167
+ description=(
168
+ "🧱 TileBot is here to help you estimate tiles for your space.\n\n"
169
+ "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 — "
170
+ "I'll do the rest.\n\n"
171
+ "I’ll calculate how many tiles you need, recommend suitable products, and give you a PDF report.\n\n"
172
+ "Type 'Floor' or 'Wall' to begin!"
173
+ ),
174
+ type="messages",
175
+ additional_outputs=[gr.File(label="📄 Download Report")]
176
+ )
177
+
178
+ demo.launch()