Coots commited on
Commit
1f0bb49
ยท
verified ยท
1 Parent(s): b627340

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -73
app.py CHANGED
@@ -1,20 +1,20 @@
1
  import gradio as gr
2
  import joblib
 
3
  import json
4
  import math
5
  import re
 
 
6
  from deep_translator import GoogleTranslator
7
  from xgboost import Booster
8
- import warnings
9
-
10
- warnings.filterwarnings("ignore")
11
 
12
  # Load models
13
  xgb = Booster()
14
  xgb.load_model("xgb_model.json")
15
  rf = joblib.load("rf_model.pkl")
16
 
17
- # Load tile catalog and sizes
18
  with open("tile_catalog.json", "r", encoding="utf-8") as f:
19
  tile_catalog = json.load(f)
20
 
@@ -29,25 +29,35 @@ def translate(text, lang="en"):
29
  except:
30
  return text
31
 
32
- def extract_tile_area(msg, unit):
33
- msg = msg.lower().replace("ร—", "x").replace("into", "x").replace("*", "x").replace("x", "x")
34
- msg = msg.replace("mm", "").replace("ft", "").replace("feet", "").strip()
35
-
36
- # match single value (like 2 or 4)
37
- if re.fullmatch(r"\d+(\.\d+)?", msg):
38
- val = float(msg)
39
- return round(val * val, 2) if unit == "ft" else None
 
 
 
 
 
 
 
 
 
 
 
40
 
41
- # match pattern like 2x2 or 600x600
42
  if "x" in msg:
43
- parts = re.split(r"x", msg)
44
  if len(parts) == 2:
45
  try:
46
  val1 = float(re.sub(r"[^\d.]", "", parts[0]))
47
  val2 = float(re.sub(r"[^\d.]", "", parts[1]))
48
- if unit == "mm":
49
- sqft = (val1 * val2) / 92903.04
50
- else:
51
  sqft = val1 * val2
52
  return round(sqft, 2)
53
  except:
@@ -55,8 +65,6 @@ def extract_tile_area(msg, unit):
55
  return None
56
 
57
  def chat_fn(message, history, user_state={}):
58
- message = message.strip().lower()
59
-
60
  if "lang" not in user_state:
61
  try:
62
  user_state["lang"] = GoogleTranslator(source="auto", target="en").detect(message)
@@ -65,58 +73,38 @@ def chat_fn(message, history, user_state={}):
65
  lang = user_state["lang"]
66
  def reply(text): return translate(text, lang)
67
 
68
- # Reset / greet
69
- if message in ["hi", "hello", "hey", "start", "help"]:
70
- user_state.clear()
71
- return reply("๐Ÿ‘‹ Hello! Are you planning for Floor or Wall tiles?"), None, user_state
72
-
73
- # Start
74
- if message in ["floor", "wall"]:
75
- user_state.clear()
76
- user_state["tile_type"] = message.capitalize()
77
- user_state["step"] = "get_length"
78
- return reply(f"Great! You chose {user_state['tile_type']} tiles.\nPlease enter the **length** of your space in feet:"), None, user_state
79
 
80
- if user_state.get("step") == "get_length":
81
  try:
82
- user_state["length"] = float(re.sub(r"[^\d.]", "", message))
83
- user_state["step"] = "get_width"
84
- return reply("Now enter the **width** of your space in feet:"), None, user_state
85
  except:
86
- return reply("Please enter a valid number for length (e.g. 12.5):"), None, user_state
87
-
88
- if user_state.get("step") == "get_width":
89
- try:
90
- user_state["width"] = float(re.sub(r"[^\d.]", "", message))
91
- user_state["area"] = round(user_state["length"] * user_state["width"], 2)
92
- user_state["step"] = "get_unit"
93
- return reply(f"โœ… Total area is {user_state['area']} sq.ft.\nWould you like to enter tile size in **mm** or **ft**?"), None, user_state
94
- except:
95
- return reply("Please enter a valid number for width (e.g. 10):"), None, user_state
96
-
97
- if user_state.get("step") == "get_unit":
98
- if message not in ["mm", "ft"]:
99
- return reply("Please type either **mm** or **ft** to choose your preferred unit."), None, user_state
100
- user_state["unit"] = message
101
- user_state["step"] = "get_tile_size"
102
- return reply(f"Enter the tile size in {message.upper()} (e.g. 600x600, 2x2, or 4):"), None, user_state
103
 
104
- if user_state.get("step") == "get_tile_size":
105
- tile_area = extract_tile_area(message, user_state["unit"])
106
- if not tile_area:
107
- return reply("โŒ I couldn't understand the tile size. Try `600x600`, `2x2`, or just `4`."), None, user_state
 
108
 
109
- user_state["tile_area"] = tile_area
110
  user_state["step"] = "done"
111
 
112
  area_needed = user_state["area"]
113
  tile_type = user_state["tile_type"]
114
- tile_needed = math.ceil((area_needed / tile_area) * 1.1)
115
 
116
  best = []
117
  for tile in tile_catalog:
118
  if tile["type"].lower() == tile_type.lower():
119
- per_box = tile["coverage"] / tile_area
120
  if per_box > 0:
121
  boxes = math.ceil(tile_needed / per_box)
122
  total = boxes * tile["price"]
@@ -132,28 +120,32 @@ def chat_fn(message, history, user_state={}):
132
  best.sort(key=lambda x: x["total"])
133
  top3 = best[:3]
134
 
135
- summary = f"""๐Ÿงฑ Tile Type: {tile_type}
136
- ๐Ÿ“ Area: {area_needed} sq.ft
137
- ๐Ÿงฎ Tile Size Area: {tile_area} sq.ft
138
- ๐Ÿ”ข Tiles Needed: {tile_needed} (10% extra included)
 
139
 
140
- ๐ŸŽฏ Top Suggestions:
141
  """
142
  for i, t in enumerate(top3, 1):
143
- summary += f"{i}. {t['name']} ({t['size']}) โ€“ โ‚น{t['price']}/box (~{t['boxes']} boxes)\n๐Ÿ”— {t['url']}\n"
144
 
145
- summary += "\nType 'Floor' or 'Wall' to start again."
146
 
147
- return reply(summary), None, user_state
 
148
 
149
- return reply("Type 'hi' or 'floor' or 'wall' to begin!"), None, user_state
150
 
151
- # UI
152
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
153
- gr.ChatInterface(
154
  fn=chat_fn,
155
- title="๐Ÿงฑ TileBot โ€“ Smart Tile Estimator",
156
- description="Type `hi`, `floor`, or `wall` to begin. I'll guide you step-by-step to estimate tile needs."
 
 
157
  )
158
 
159
- demo.launch()
 
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
  from xgboost import Booster
 
 
 
11
 
12
  # Load models
13
  xgb = Booster()
14
  xgb.load_model("xgb_model.json")
15
  rf = joblib.load("rf_model.pkl")
16
 
17
+ # Load tile catalog and size mappings
18
  with open("tile_catalog.json", "r", encoding="utf-8") as f:
19
  tile_catalog = json.load(f)
20
 
 
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):
49
+ msg = msg.lower().replace("ร—", "x").replace("into", "x").replace("*", "x")
50
+ msg = msg.replace("ft", "").replace("feet", "").replace("mm", "").strip()
51
 
 
52
  if "x" in msg:
53
+ parts = msg.split("x")
54
  if len(parts) == 2:
55
  try:
56
  val1 = float(re.sub(r"[^\d.]", "", parts[0]))
57
  val2 = float(re.sub(r"[^\d.]", "", parts[1]))
58
+ if val1 > 20: # treat as mm
59
+ sqft = (val1 * val2) / 92903.04 # mmยฒ to ftยฒ
60
+ else: # treat as feet
61
  sqft = val1 * val2
62
  return round(sqft, 2)
63
  except:
 
65
  return None
66
 
67
  def chat_fn(message, history, user_state={}):
 
 
68
  if "lang" not in user_state:
69
  try:
70
  user_state["lang"] = GoogleTranslator(source="auto", target="en").detect(message)
 
73
  lang = user_state["lang"]
74
  def reply(text): return translate(text, lang)
75
 
76
+ if "step" not in user_state:
77
+ if message.lower() in ["floor", "wall"]:
78
+ user_state["tile_type"] = message.capitalize()
79
+ user_state["step"] = "get_area"
80
+ return reply(f"โœ… Great! You selected {user_state['tile_type']} tiles.\nPlease enter the total area in sq.ft:"), None, user_state
81
+ return reply("๐Ÿ‘‹ Hello! Are you looking for Floor or Wall tiles?"), None, user_state
 
 
 
 
 
82
 
83
+ if user_state["step"] == "get_area":
84
  try:
85
+ user_state["area"] = float(message)
86
+ user_state["step"] = "get_tile_size"
87
+ return reply("๐Ÿ“ Now enter the tile size like 2 x 2 ft or 600x600 MM or 200 * 200:"), None, user_state
88
  except:
89
+ return reply("โ— Please enter a number for area (e.g. 120)."), None, user_state
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
+ if user_state["step"] == "get_tile_size":
92
+ area = extract_tile_area(message)
93
+ if area is None:
94
+ examples = "2 x 2 ft, 600x600 MM, 200*200, 1.5x1.5"
95
+ return reply(f"โ— Oops, I didn't catch that size. Try formats like: {examples}"), None, user_state
96
 
97
+ user_state["tile_area"] = area
98
  user_state["step"] = "done"
99
 
100
  area_needed = user_state["area"]
101
  tile_type = user_state["tile_type"]
102
+ tile_needed = math.ceil((area_needed / area) * 1.1)
103
 
104
  best = []
105
  for tile in tile_catalog:
106
  if tile["type"].lower() == tile_type.lower():
107
+ per_box = tile["coverage"] / area
108
  if per_box > 0:
109
  boxes = math.ceil(tile_needed / per_box)
110
  total = boxes * tile["price"]
 
120
  best.sort(key=lambda x: x["total"])
121
  top3 = best[:3]
122
 
123
+ summary = f"""
124
+ ๐Ÿงฑ Tile Type: {tile_type}
125
+ ๐Ÿ“ Area: {area_needed} sq.ft
126
+ ๐Ÿงฎ Tile Size: {round(area, 2)} sq.ft
127
+ ๐Ÿ”ข Total Tiles Needed (10% buffer): {tile_needed}
128
 
129
+ ๐ŸŽฏ Top Recommendations:
130
  """
131
  for i, t in enumerate(top3, 1):
132
+ summary += f"\n{i}. {t['name']} ({t['size']})\n โ‚น{t['price']}/box โ†’ ~{t['boxes']} boxes\n {t['url']}\n"
133
 
134
+ summary += "\nSay 'Floor' or 'Wall' to start a new estimate."
135
 
136
+ pdf_path = create_pdf(summary)
137
+ return reply(summary), [pdf_path], {}
138
 
139
+ return reply("Say 'Floor' or 'Wall' to begin again."), None, {}
140
 
141
+ # Launch the bot
142
+ with gr.Blocks() as demo:
143
+ chatbot = gr.ChatInterface(
144
  fn=chat_fn,
145
+ title="๐Ÿงฑ TileBot โ€“ ChatGPT-Style Tile Estimator",
146
+ description="Chat like a human. Enter sizes naturally like 200x200, 2 x 2 ft, or 600x600 MM.\nSay 'Floor' or 'Wall' to begin.",
147
+ type="messages",
148
+ additional_outputs=[gr.File(label="๐Ÿ“„ Download Report")]
149
  )
150
 
151
+ demo.launch()