Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -22,6 +22,7 @@ def configure_gemini(api_key):
|
|
| 22 |
return genai.GenerativeModel('gemini-2.0-flash-thinking-exp')
|
| 23 |
|
| 24 |
model = configure_gemini(os.environ['GOOGLE_API_KEY'])
|
|
|
|
| 25 |
# Initialize Gemini models
|
| 26 |
llm_flash_exp = ChatGoogleGenerativeAI(
|
| 27 |
model="gemini-2.0-flash-exp",
|
|
@@ -46,14 +47,16 @@ class SmartShoppingAssistant:
|
|
| 46 |
cutoff=threshold
|
| 47 |
)
|
| 48 |
return matches if matches else []
|
| 49 |
-
|
| 50 |
def match_products_with_catalogue(self, extracted_items):
|
| 51 |
"""Match extracted items with catalogue products using Gemini"""
|
| 52 |
product_list = self.df['ProductName'].tolist()
|
| 53 |
product_string = ", ".join(product_list)
|
|
|
|
| 54 |
prompt = f"""
|
| 55 |
Given these extracted items and quantities:
|
| 56 |
{extracted_items}
|
|
|
|
| 57 |
And this product catalogue:
|
| 58 |
{product_string}
|
| 59 |
|
|
@@ -68,16 +71,18 @@ class SmartShoppingAssistant:
|
|
| 68 |
|
| 69 |
Only include products that have good matches in the catalogue.
|
| 70 |
"""
|
|
|
|
| 71 |
try:
|
| 72 |
matches = llm_flash_exp.predict(prompt)
|
| 73 |
return matches.strip()
|
| 74 |
except Exception as e:
|
| 75 |
return f"Error matching products: {str(e)}"
|
| 76 |
-
|
| 77 |
def search_products_fuzzy(self, product_names_with_quantities):
|
| 78 |
"""Improved fuzzy search with batch processing"""
|
| 79 |
results = pd.DataFrame()
|
| 80 |
matched_products = set()
|
|
|
|
| 81 |
for item in product_names_with_quantities:
|
| 82 |
product_info = item.split('quantity:')
|
| 83 |
clean_name = product_info[0].strip().upper().replace('PRODUCTNAME ==', '').strip(' "\'')
|
|
@@ -95,14 +100,16 @@ class SmartShoppingAssistant:
|
|
| 95 |
results = pd.concat([results, matched])
|
| 96 |
matched_products.add(clean_name)
|
| 97 |
break # Take first good match
|
|
|
|
| 98 |
return results.drop_duplicates(subset=['CleanName'])
|
| 99 |
-
|
| 100 |
def setup_agent(self):
|
| 101 |
"""Set up the LangChain agent with necessary tools"""
|
| 102 |
def search_products(query):
|
| 103 |
try:
|
| 104 |
# Split into individual product entries
|
| 105 |
product_entries = [entry.strip() for entry in query.split('or')]
|
|
|
|
| 106 |
results = self.search_products_fuzzy(product_entries)
|
| 107 |
if not results.empty:
|
| 108 |
# Format results with quantity
|
|
@@ -114,7 +121,7 @@ class SmartShoppingAssistant:
|
|
| 114 |
return "No products found matching your criteria."
|
| 115 |
except Exception as e:
|
| 116 |
return f"Error executing query: {str(e)}"
|
| 117 |
-
|
| 118 |
tools = [
|
| 119 |
Tool(
|
| 120 |
name="Product Search",
|
|
@@ -122,6 +129,7 @@ class SmartShoppingAssistant:
|
|
| 122 |
description="Search for products in the supermarket database using fuzzy matching"
|
| 123 |
)
|
| 124 |
]
|
|
|
|
| 125 |
self.agent = initialize_agent(
|
| 126 |
tools=tools,
|
| 127 |
memory=self.memory,
|
|
@@ -130,7 +138,7 @@ class SmartShoppingAssistant:
|
|
| 130 |
verbose=True,
|
| 131 |
max_iterations=3
|
| 132 |
)
|
| 133 |
-
|
| 134 |
def process_natural_language_query(self, query):
|
| 135 |
"""Process natural language query with two-step matching"""
|
| 136 |
try:
|
|
@@ -138,18 +146,23 @@ class SmartShoppingAssistant:
|
|
| 138 |
extraction_prompt = f"""
|
| 139 |
Extract the products and their quantities from this shopping request.
|
| 140 |
Include any specific requirements mentioned.
|
|
|
|
| 141 |
Shopping request: {query}
|
|
|
|
| 142 |
Format each item with its quantity (assume 1 if not specified).
|
| 143 |
"""
|
|
|
|
| 144 |
extracted_items = llm_flash_exp.predict(extraction_prompt)
|
|
|
|
| 145 |
# Second step: Match with catalogue
|
| 146 |
matched_products = self.match_products_with_catalogue(extracted_items)
|
|
|
|
| 147 |
# Third step: Search and return results
|
| 148 |
result = self.agent.run(f"Search for products matching the specified names: {matched_products}")
|
| 149 |
return result
|
| 150 |
except Exception as e:
|
| 151 |
return f"Error processing query: {str(e)}"
|
| 152 |
-
|
| 153 |
def extract_text_from_image(self, image):
|
| 154 |
"""Extract text from uploaded image using Gemini"""
|
| 155 |
prompt = """
|
|
@@ -162,7 +175,7 @@ class SmartShoppingAssistant:
|
|
| 162 |
return response.text
|
| 163 |
except Exception as e:
|
| 164 |
return f"Error processing image: {str(e)}"
|
| 165 |
-
|
| 166 |
def extract_text_from_pdf(self, pdf_file):
|
| 167 |
"""Extract text from uploaded PDF"""
|
| 168 |
try:
|
|
@@ -179,6 +192,7 @@ class SmartShoppingAssistant:
|
|
| 179 |
def add_to_cart(product):
|
| 180 |
if 'cart' not in st.session_state:
|
| 181 |
st.session_state.cart = []
|
|
|
|
| 182 |
# Check if product exists in cart
|
| 183 |
existing = next((item for item in st.session_state.cart if item['ProductName'] == product['ProductName']), None)
|
| 184 |
if existing:
|
|
@@ -194,15 +208,18 @@ def generate_receipt():
|
|
| 194 |
pdf = FPDF()
|
| 195 |
pdf.add_page()
|
| 196 |
pdf.set_font("Arial", size=12)
|
|
|
|
| 197 |
pdf.cell(200, 10, txt="Bon Marche Receipt", ln=1, align='C')
|
| 198 |
pdf.cell(200, 10, txt=f"Date: {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M')}", ln=1)
|
|
|
|
| 199 |
total = 0
|
| 200 |
for item in st.session_state.cart:
|
| 201 |
price = item['RetailPrice'] * item['Quantity']
|
| 202 |
-
pdf.cell(200, 10,
|
| 203 |
txt=f"{item['ProductName']} x{item['Quantity']} - ${price:.2f}",
|
| 204 |
ln=1)
|
| 205 |
total += price
|
|
|
|
| 206 |
pdf.cell(200, 10, txt=f"Total: ${total:.2f}", ln=1)
|
| 207 |
return pdf.output(dest='S').encode('latin1')
|
| 208 |
|
|
@@ -210,20 +227,21 @@ def generate_receipt():
|
|
| 210 |
def main():
|
| 211 |
st.set_page_config(page_title="Smart Shopping Assistant", layout="wide")
|
| 212 |
st.title("🛒 Smart Shopping Assistant")
|
| 213 |
-
|
| 214 |
@st.cache_data
|
| 215 |
def load_product_data():
|
| 216 |
return pd.read_csv('supermarket4i.csv') # Ensure correct filename
|
| 217 |
-
|
| 218 |
df = load_product_data()
|
| 219 |
assistant = SmartShoppingAssistant(df)
|
| 220 |
-
|
| 221 |
with st.sidebar:
|
| 222 |
st.header("Upload Shopping List")
|
| 223 |
uploaded_file = st.file_uploader(
|
| 224 |
"Upload an image or PDF of your shopping list",
|
| 225 |
type=['png', 'jpg', 'jpeg', 'pdf']
|
| 226 |
)
|
|
|
|
| 227 |
if uploaded_file:
|
| 228 |
try:
|
| 229 |
if uploaded_file.type.startswith('image'):
|
|
@@ -239,6 +257,7 @@ def main():
|
|
| 239 |
st.error(f"Error processing file: {str(e)}")
|
| 240 |
|
| 241 |
col1, col2 = st.columns([2, 1])
|
|
|
|
| 242 |
with col1:
|
| 243 |
st.header("Search Products")
|
| 244 |
query = st.text_area(
|
|
@@ -246,29 +265,32 @@ def main():
|
|
| 246 |
height=100,
|
| 247 |
value=st.session_state.get('query', '')
|
| 248 |
)
|
|
|
|
| 249 |
if st.button("Search"):
|
| 250 |
if query:
|
| 251 |
with st.spinner("Searching..."):
|
| 252 |
results = assistant.process_natural_language_query(query)
|
| 253 |
st.session_state.last_results = results
|
|
|
|
| 254 |
# Display results with add to cart buttons
|
| 255 |
if isinstance(results, str):
|
| 256 |
st.write(results)
|
| 257 |
else:
|
| 258 |
for _, row in results.iterrows():
|
| 259 |
-
cola, colb = st.columns([3,
|
| 260 |
with cola:
|
| 261 |
st.write(f"**{row['ProductName']}**")
|
| 262 |
st.write(f"Price: ${row['RetailPrice']} | Qty: {row['Quantity']}")
|
| 263 |
with colb:
|
| 264 |
if st.button("Add", key=row['ProductName']):
|
| 265 |
add_to_cart(row.to_dict())
|
|
|
|
| 266 |
with col2:
|
| 267 |
st.header("Shopping Cart")
|
| 268 |
if 'cart' in st.session_state and st.session_state.cart:
|
| 269 |
total = 0
|
| 270 |
for item in st.session_state.cart:
|
| 271 |
-
cols = st.columns([3,
|
| 272 |
with cols[0]:
|
| 273 |
st.write(f"{item['ProductName']} x{item['Quantity']}")
|
| 274 |
with cols[1]:
|
|
@@ -280,6 +302,7 @@ def main():
|
|
| 280 |
total += item['RetailPrice'] * item['Quantity']
|
| 281 |
st.divider()
|
| 282 |
st.write(f"**Total: ${total:.2f}**")
|
|
|
|
| 283 |
if st.button("Checkout"):
|
| 284 |
receipt = generate_receipt()
|
| 285 |
st.download_button(
|
|
|
|
| 22 |
return genai.GenerativeModel('gemini-2.0-flash-thinking-exp')
|
| 23 |
|
| 24 |
model = configure_gemini(os.environ['GOOGLE_API_KEY'])
|
| 25 |
+
|
| 26 |
# Initialize Gemini models
|
| 27 |
llm_flash_exp = ChatGoogleGenerativeAI(
|
| 28 |
model="gemini-2.0-flash-exp",
|
|
|
|
| 47 |
cutoff=threshold
|
| 48 |
)
|
| 49 |
return matches if matches else []
|
| 50 |
+
|
| 51 |
def match_products_with_catalogue(self, extracted_items):
|
| 52 |
"""Match extracted items with catalogue products using Gemini"""
|
| 53 |
product_list = self.df['ProductName'].tolist()
|
| 54 |
product_string = ", ".join(product_list)
|
| 55 |
+
|
| 56 |
prompt = f"""
|
| 57 |
Given these extracted items and quantities:
|
| 58 |
{extracted_items}
|
| 59 |
+
|
| 60 |
And this product catalogue:
|
| 61 |
{product_string}
|
| 62 |
|
|
|
|
| 71 |
|
| 72 |
Only include products that have good matches in the catalogue.
|
| 73 |
"""
|
| 74 |
+
|
| 75 |
try:
|
| 76 |
matches = llm_flash_exp.predict(prompt)
|
| 77 |
return matches.strip()
|
| 78 |
except Exception as e:
|
| 79 |
return f"Error matching products: {str(e)}"
|
| 80 |
+
|
| 81 |
def search_products_fuzzy(self, product_names_with_quantities):
|
| 82 |
"""Improved fuzzy search with batch processing"""
|
| 83 |
results = pd.DataFrame()
|
| 84 |
matched_products = set()
|
| 85 |
+
|
| 86 |
for item in product_names_with_quantities:
|
| 87 |
product_info = item.split('quantity:')
|
| 88 |
clean_name = product_info[0].strip().upper().replace('PRODUCTNAME ==', '').strip(' "\'')
|
|
|
|
| 100 |
results = pd.concat([results, matched])
|
| 101 |
matched_products.add(clean_name)
|
| 102 |
break # Take first good match
|
| 103 |
+
|
| 104 |
return results.drop_duplicates(subset=['CleanName'])
|
| 105 |
+
|
| 106 |
def setup_agent(self):
|
| 107 |
"""Set up the LangChain agent with necessary tools"""
|
| 108 |
def search_products(query):
|
| 109 |
try:
|
| 110 |
# Split into individual product entries
|
| 111 |
product_entries = [entry.strip() for entry in query.split('or')]
|
| 112 |
+
|
| 113 |
results = self.search_products_fuzzy(product_entries)
|
| 114 |
if not results.empty:
|
| 115 |
# Format results with quantity
|
|
|
|
| 121 |
return "No products found matching your criteria."
|
| 122 |
except Exception as e:
|
| 123 |
return f"Error executing query: {str(e)}"
|
| 124 |
+
|
| 125 |
tools = [
|
| 126 |
Tool(
|
| 127 |
name="Product Search",
|
|
|
|
| 129 |
description="Search for products in the supermarket database using fuzzy matching"
|
| 130 |
)
|
| 131 |
]
|
| 132 |
+
|
| 133 |
self.agent = initialize_agent(
|
| 134 |
tools=tools,
|
| 135 |
memory=self.memory,
|
|
|
|
| 138 |
verbose=True,
|
| 139 |
max_iterations=3
|
| 140 |
)
|
| 141 |
+
|
| 142 |
def process_natural_language_query(self, query):
|
| 143 |
"""Process natural language query with two-step matching"""
|
| 144 |
try:
|
|
|
|
| 146 |
extraction_prompt = f"""
|
| 147 |
Extract the products and their quantities from this shopping request.
|
| 148 |
Include any specific requirements mentioned.
|
| 149 |
+
|
| 150 |
Shopping request: {query}
|
| 151 |
+
|
| 152 |
Format each item with its quantity (assume 1 if not specified).
|
| 153 |
"""
|
| 154 |
+
|
| 155 |
extracted_items = llm_flash_exp.predict(extraction_prompt)
|
| 156 |
+
|
| 157 |
# Second step: Match with catalogue
|
| 158 |
matched_products = self.match_products_with_catalogue(extracted_items)
|
| 159 |
+
|
| 160 |
# Third step: Search and return results
|
| 161 |
result = self.agent.run(f"Search for products matching the specified names: {matched_products}")
|
| 162 |
return result
|
| 163 |
except Exception as e:
|
| 164 |
return f"Error processing query: {str(e)}"
|
| 165 |
+
|
| 166 |
def extract_text_from_image(self, image):
|
| 167 |
"""Extract text from uploaded image using Gemini"""
|
| 168 |
prompt = """
|
|
|
|
| 175 |
return response.text
|
| 176 |
except Exception as e:
|
| 177 |
return f"Error processing image: {str(e)}"
|
| 178 |
+
|
| 179 |
def extract_text_from_pdf(self, pdf_file):
|
| 180 |
"""Extract text from uploaded PDF"""
|
| 181 |
try:
|
|
|
|
| 192 |
def add_to_cart(product):
|
| 193 |
if 'cart' not in st.session_state:
|
| 194 |
st.session_state.cart = []
|
| 195 |
+
|
| 196 |
# Check if product exists in cart
|
| 197 |
existing = next((item for item in st.session_state.cart if item['ProductName'] == product['ProductName']), None)
|
| 198 |
if existing:
|
|
|
|
| 208 |
pdf = FPDF()
|
| 209 |
pdf.add_page()
|
| 210 |
pdf.set_font("Arial", size=12)
|
| 211 |
+
|
| 212 |
pdf.cell(200, 10, txt="Bon Marche Receipt", ln=1, align='C')
|
| 213 |
pdf.cell(200, 10, txt=f"Date: {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M')}", ln=1)
|
| 214 |
+
|
| 215 |
total = 0
|
| 216 |
for item in st.session_state.cart:
|
| 217 |
price = item['RetailPrice'] * item['Quantity']
|
| 218 |
+
pdf.cell(200, 10,
|
| 219 |
txt=f"{item['ProductName']} x{item['Quantity']} - ${price:.2f}",
|
| 220 |
ln=1)
|
| 221 |
total += price
|
| 222 |
+
|
| 223 |
pdf.cell(200, 10, txt=f"Total: ${total:.2f}", ln=1)
|
| 224 |
return pdf.output(dest='S').encode('latin1')
|
| 225 |
|
|
|
|
| 227 |
def main():
|
| 228 |
st.set_page_config(page_title="Smart Shopping Assistant", layout="wide")
|
| 229 |
st.title("🛒 Smart Shopping Assistant")
|
| 230 |
+
|
| 231 |
@st.cache_data
|
| 232 |
def load_product_data():
|
| 233 |
return pd.read_csv('supermarket4i.csv') # Ensure correct filename
|
| 234 |
+
|
| 235 |
df = load_product_data()
|
| 236 |
assistant = SmartShoppingAssistant(df)
|
| 237 |
+
|
| 238 |
with st.sidebar:
|
| 239 |
st.header("Upload Shopping List")
|
| 240 |
uploaded_file = st.file_uploader(
|
| 241 |
"Upload an image or PDF of your shopping list",
|
| 242 |
type=['png', 'jpg', 'jpeg', 'pdf']
|
| 243 |
)
|
| 244 |
+
|
| 245 |
if uploaded_file:
|
| 246 |
try:
|
| 247 |
if uploaded_file.type.startswith('image'):
|
|
|
|
| 257 |
st.error(f"Error processing file: {str(e)}")
|
| 258 |
|
| 259 |
col1, col2 = st.columns([2, 1])
|
| 260 |
+
|
| 261 |
with col1:
|
| 262 |
st.header("Search Products")
|
| 263 |
query = st.text_area(
|
|
|
|
| 265 |
height=100,
|
| 266 |
value=st.session_state.get('query', '')
|
| 267 |
)
|
| 268 |
+
|
| 269 |
if st.button("Search"):
|
| 270 |
if query:
|
| 271 |
with st.spinner("Searching..."):
|
| 272 |
results = assistant.process_natural_language_query(query)
|
| 273 |
st.session_state.last_results = results
|
| 274 |
+
|
| 275 |
# Display results with add to cart buttons
|
| 276 |
if isinstance(results, str):
|
| 277 |
st.write(results)
|
| 278 |
else:
|
| 279 |
for _, row in results.iterrows():
|
| 280 |
+
cola, colb = st.columns([3,1])
|
| 281 |
with cola:
|
| 282 |
st.write(f"**{row['ProductName']}**")
|
| 283 |
st.write(f"Price: ${row['RetailPrice']} | Qty: {row['Quantity']}")
|
| 284 |
with colb:
|
| 285 |
if st.button("Add", key=row['ProductName']):
|
| 286 |
add_to_cart(row.to_dict())
|
| 287 |
+
|
| 288 |
with col2:
|
| 289 |
st.header("Shopping Cart")
|
| 290 |
if 'cart' in st.session_state and st.session_state.cart:
|
| 291 |
total = 0
|
| 292 |
for item in st.session_state.cart:
|
| 293 |
+
cols = st.columns([3,1,1])
|
| 294 |
with cols[0]:
|
| 295 |
st.write(f"{item['ProductName']} x{item['Quantity']}")
|
| 296 |
with cols[1]:
|
|
|
|
| 302 |
total += item['RetailPrice'] * item['Quantity']
|
| 303 |
st.divider()
|
| 304 |
st.write(f"**Total: ${total:.2f}**")
|
| 305 |
+
|
| 306 |
if st.button("Checkout"):
|
| 307 |
receipt = generate_receipt()
|
| 308 |
st.download_button(
|