Aniq-63 commited on
Commit
01517d0
·
verified ·
1 Parent(s): aee8965

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +140 -108
app.py CHANGED
@@ -2,6 +2,7 @@ import os
2
  import sqlite3
3
  import streamlit as st
4
  from werkzeug.security import generate_password_hash, check_password_hash
 
5
 
6
  from langchain_groq import ChatGroq
7
  from langchain_huggingface import HuggingFaceEmbeddings
@@ -46,11 +47,13 @@ def init_db():
46
  features TEXT NOT NULL,
47
  stock INTEGER NOT NULL DEFAULT 0)''')
48
 
49
- # Check and update schema if needed
50
- c.execute("PRAGMA table_info(products)")
51
- columns = [column[1] for column in c.fetchall()]
52
- if 'stock' not in columns:
53
- c.execute('ALTER TABLE products ADD COLUMN stock INTEGER NOT NULL DEFAULT 0')
 
 
54
 
55
  # Insert default company settings if empty
56
  c.execute('SELECT COUNT(*) FROM company_settings')
@@ -68,8 +71,40 @@ def init_db():
68
 
69
  conn = init_db()
70
 
71
- # --- Admin Classes ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  class Company:
74
  @staticmethod
75
  def get_settings():
@@ -93,6 +128,15 @@ class Product:
93
  c.execute('SELECT * FROM products')
94
  return c.fetchall()
95
 
 
 
 
 
 
 
 
 
 
96
  @staticmethod
97
  def get_by_id(product_id):
98
  c = conn.cursor()
@@ -132,21 +176,17 @@ class User:
132
  @classmethod
133
  def create(cls, username, password):
134
  hashed_pw = generate_password_hash(password)
135
- conn = sqlite3.connect('users.db')
136
  c = conn.cursor()
137
  c.execute('INSERT INTO users (username, password) VALUES (?, ?)', (username, hashed_pw))
138
  user_id = c.lastrowid
139
  conn.commit()
140
- conn.close()
141
  return cls(user_id, username, hashed_pw)
142
 
143
  @classmethod
144
  def get_by_username(cls, username):
145
- conn = sqlite3.connect('users.db')
146
  c = conn.cursor()
147
  c.execute('SELECT * FROM users WHERE username = ?', (username,))
148
  user = c.fetchone()
149
- conn.close()
150
  if user:
151
  return cls(user[0], user[1], user[2],
152
  eval(user[3]) if user[3] else [],
@@ -155,23 +195,19 @@ class User:
155
 
156
  def update_chat_history(self, new_messages):
157
  updated_history = self.chat_history + new_messages
158
- conn = sqlite3.connect('users.db')
159
  c = conn.cursor()
160
  c.execute('UPDATE users SET previous_chat_history = ? WHERE id = ?',
161
  (str(updated_history), self.id))
162
  conn.commit()
163
- conn.close()
164
- self.chat_history = updated_history # Update in-memory
165
 
166
  def update_products_bought(self, new_products):
167
  updated_products = self.products_bought + new_products
168
- conn = sqlite3.connect('users.db')
169
  c = conn.cursor()
170
  c.execute('UPDATE users SET previous_products_bought = ? WHERE id = ?',
171
  (str(updated_products), self.id))
172
  conn.commit()
173
- conn.close()
174
- self.products_bought = updated_products # Update in-memory
175
 
176
  # --- AI Setup ---
177
  os.environ["GROQ_API_KEY"] = st.secrets["GROQ_API_KEY"]
@@ -200,14 +236,23 @@ def load_data():
200
  retriever = load_data()
201
 
202
  def retrieve_query(query: str):
 
203
  docs = retriever.get_relevant_documents(query)
204
- st.session_state.last_retrieved_docs = docs
205
- return docs
 
 
 
 
 
 
 
 
206
 
207
  tool = Tool(
208
  name="product_retriever",
209
  func=retrieve_query,
210
- description="Useful for retrieving product information including current stock levels"
211
  )
212
 
213
  # --- Admin Dashboard ---
@@ -266,13 +311,39 @@ def admin_dashboard():
266
  st.rerun()
267
  else:
268
  st.info("No products found in database")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
 
270
  # --- Main App ---
271
  def main():
272
  company_settings = Company.get_settings()
273
  company_name = company_settings[1]
274
 
275
- st.title("AI Sales Assistant 🤖")
276
 
277
  if 'user' not in st.session_state:
278
  st.session_state.user = None
@@ -282,7 +353,7 @@ def main():
282
 
283
  # Authentication
284
  if not st.session_state.user and not st.session_state.admin_mode:
285
- st.header("Login/Register Admin")
286
  tab1, tab2, tab3 = st.tabs(["Login", "Register", "Admin"])
287
 
288
  with tab1:
@@ -329,8 +400,8 @@ def main():
329
 
330
  else:
331
  # Chat Interface
332
- st.header(f"Welcome to {company_name}, {st.session_state.user.username}!")
333
- st.subheader("Chat with our AI Sales Assistant")
334
 
335
  # Display Chat History
336
  for msg in st.session_state.chat_history:
@@ -346,67 +417,31 @@ def main():
346
  current_products = "\n".join([f"- {p[1]} (${p[3]}, Stock: {p[6]})" for p in Product.get_all()])
347
 
348
  system_prompt = f"""
349
- You are {company_settings[3]}, the AI Sales Assistant for {company_settings[1]} ({company_settings[2]}). Your primary role is to assist customers with product inquiries, make appropriate recommendations, and facilitate purchases while strictly adhering to company policies.
350
-
351
- ## Company Profile
352
- - Company Name: {company_settings[1]}
353
- - Business Type: {company_settings[2]}
 
 
 
 
 
 
354
  - Key Features: {company_settings[4]}
355
- - Agent Name: {company_settings[3]}
356
-
357
- ## Inventory Management Policy
358
- 1. **Stock Verification**:
359
- - ALWAYS check current stock before recommending any product
360
- - Never suggest out-of-stock items (stock = 0)
361
- - For low stock items (stock ≤ 3), mention: "Only [X] left in stock!"
362
-
363
- 2. **Product Recommendations**:
364
- - Only recommend products from our current inventory
365
- - If asked for unavailable items, respond with: "I apologize, we don't currently carry that item. As a {company_settings[2]}, we specialize in [relevant products]. May I suggest [alternative]?"
366
- - When suggesting alternatives, ensure they're in stock
367
-
368
- 3. **Purchase Process**:
369
- - Confirm product availability before purchase
370
- - Generate payment link only after stock verification
371
- - Update inventory immediately after successful purchase
372
-
373
- ## Conversation Flow Examples
374
-
375
- ### 1. Greeting & Need Assessment
376
- User: "Hi, I need a new laptop"
377
- You: "Hello! I'd be happy to help you find the perfect laptop. Could you tell me what you'll primarily be using it for and your budget range?"
378
-
379
- ### 2. Product Recommendation (In-Stock)
380
- User: "I need a gaming laptop under $1500"
381
- You: "We have the XYZ Gaming Laptop available for $1399 (3 in stock). It features [key specs]. Would you like more details?"
382
-
383
- ### 3. Product Recommendation (Out-of-Stock)
384
- User: "Do you have ABC Smartphone?"
385
- You: "I apologize, the ABC Smartphone is currently out of stock. However, we have the DEF Smartphone with similar features available for $599 (5 in stock). Would you like me to tell you more about it?"
386
-
387
- ### 4. Handling Off-Topic Requests
388
- User: "Do you sell snacks?"
389
- You: "I apologize, as a {company_settings[2]}, we specialize in [tech products]. We don't carry food items. Is there a tech product I can assist you with today?"
390
-
391
- ### 5. Purchase Process
392
- User: "I want to buy the XYZ Laptop"
393
- You: "Great choice! The XYZ Laptop is available for $1399 (2 in stock). I can generate a secure payment link for you. Would you like to proceed with the purchase? [https://www.example.com/payment]"
394
-
395
- ## Communication Guidelines
396
- - Tone: Professional yet friendly (like a knowledgeable salesperson)
397
- - Language: Clear, concise, avoid technical jargon unless requested
398
- - Emojis: Use sparingly (1-2 per message max)
399
- - Branding: Consistently reference {company_settings[1]} when appropriate
400
 
401
- ## Current Inventory Status
402
  {current_products}
403
 
404
- ## Important Reminders
405
- 1. NEVER recommend products not in our inventory
406
- 2. ALWAYS verify stock before discussing any product
407
- 3. Update inventory immediately after purchases
408
- 4. Politely redirect off-topic requests to our product offerings
409
- 5. For complex queries, offer to connect with human support
 
 
 
410
  """
411
 
412
  prompt = ChatPromptTemplate.from_messages([
@@ -421,20 +456,35 @@ def main():
421
  agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
422
 
423
  if prompt_input := st.chat_input("Type your message here..."):
 
 
 
 
 
 
 
424
  with st.chat_message("user"):
425
  st.write(prompt_input)
426
 
427
  with st.chat_message("assistant"):
428
  response = agent_executor.invoke({
429
  "input": prompt_input,
430
- "chat_history": [HumanMessage(content=msg["content"]) if msg["type"] == "human" else AIMessage(content=msg["content"])
 
431
  for msg in st.session_state.chat_history]
432
  })["output"]
 
 
 
 
 
 
 
 
433
  st.write(response)
434
 
435
  # Handle inventory update on purchase
436
  if "https://www.example.com/payment" in response:
437
- # Extract product name from the agent's response
438
  product_name = None
439
  if "proceed with the purchase of" in response:
440
  start = response.find("proceed with the purchase of") + len("proceed with the purchase of")
@@ -442,19 +492,16 @@ def main():
442
  product_name = response[start:end].strip()
443
 
444
  if product_name:
445
- # Retrieve current product details
446
  docs = retrieve_query(product_name)
447
- if docs:
448
  product_doc = docs[0]
449
  product_id = product_doc.metadata.get("id")
450
  current_stock = product_doc.metadata.get("stock")
451
 
452
  if product_id and current_stock > 0:
453
  try:
454
- conn = sqlite3.connect('users.db')
455
- c = conn.cursor()
456
-
457
  # Verify current stock
 
458
  c.execute('SELECT stock FROM products WHERE id = ?', (product_id,))
459
  actual_stock = c.fetchone()[0]
460
 
@@ -464,38 +511,23 @@ def main():
464
  (actual_stock - 1, product_id))
465
 
466
  # Update user's purchase history
467
- c.execute('SELECT previous_products_bought FROM users WHERE id = ?',
468
- (st.session_state.user.id,))
469
- current_purchases = c.fetchone()[0]
470
-
471
- updated_purchases = eval(current_purchases) + [product_name] if current_purchases else [product_name]
472
-
473
- c.execute('UPDATE users SET previous_products_bought = ? WHERE id = ?',
474
- (str(updated_purchases), st.session_state.user.id))
475
 
476
  conn.commit()
477
- st.session_state.user.products_bought.append(product_name)
478
  st.success(f"Successfully purchased {product_name}! Stock updated.")
479
  load_data.clear()
480
 
481
  # Check for low stock
482
- if (actual_stock - 1) <= 3 and (actual_stock - 1) > 0:
483
- st.warning(f"Low stock alert: Only {actual_stock - 1} units of {product_name} remaining!")
484
  else:
485
  st.error(f"Sorry, {product_name} is now out of stock!")
486
  except Exception as e:
487
  st.error(f"Error processing purchase: {str(e)}")
488
- if conn:
489
- conn.rollback()
490
  finally:
491
- if conn:
492
- conn.close()
493
- else:
494
- st.error("Product is out of stock or not found in the inventory.")
495
- else:
496
- st.error("Could not retrieve product details. Please try again.")
497
- else:
498
- st.error("Please specify which product you'd like to purchase.")
499
 
500
  new_messages = [
501
  {"type": "human", "content": prompt_input},
 
2
  import sqlite3
3
  import streamlit as st
4
  from werkzeug.security import generate_password_hash, check_password_hash
5
+ from datetime import datetime
6
 
7
  from langchain_groq import ChatGroq
8
  from langchain_huggingface import HuggingFaceEmbeddings
 
47
  features TEXT NOT NULL,
48
  stock INTEGER NOT NULL DEFAULT 0)''')
49
 
50
+ # Inventory log table
51
+ c.execute('''CREATE TABLE IF NOT EXISTS inventory_log
52
+ (id INTEGER PRIMARY KEY AUTOINCREMENT,
53
+ timestamp TEXT NOT NULL,
54
+ product_name TEXT NOT NULL,
55
+ found INTEGER NOT NULL,
56
+ username TEXT)''')
57
 
58
  # Insert default company settings if empty
59
  c.execute('SELECT COUNT(*) FROM company_settings')
 
71
 
72
  conn = init_db()
73
 
74
+ # --- Inventory Management Functions ---
75
+ def log_inventory_access(product_name: str, found: bool, username: str = None):
76
+ """Log all inventory access attempts"""
77
+ c = conn.cursor()
78
+ c.execute('''INSERT INTO inventory_log
79
+ (timestamp, product_name, found, username)
80
+ VALUES (?, ?, ?, ?)''',
81
+ (datetime.now().isoformat(), product_name, int(found), username))
82
+ conn.commit()
83
+
84
+ def validate_product_in_inventory(product_name: str) -> bool:
85
+ """Check if product exists and is in stock"""
86
+ c = conn.cursor()
87
+ c.execute('''SELECT id FROM products
88
+ WHERE LOWER(name) LIKE LOWER(?) AND stock > 0''',
89
+ (f'%{product_name}%',))
90
+ return c.fetchone() is not None
91
 
92
+ def get_inventory_report():
93
+ """Generate inventory report for admin"""
94
+ c = conn.cursor()
95
+ c.execute('''SELECT name, category, price, stock
96
+ FROM products ORDER BY category, name''')
97
+ return c.fetchall()
98
+
99
+ def get_inventory_alerts():
100
+ """Get low stock alerts"""
101
+ c = conn.cursor()
102
+ c.execute('''SELECT name, stock FROM products
103
+ WHERE stock <= 3 AND stock > 0
104
+ ORDER BY stock ASC''')
105
+ return c.fetchall()
106
+
107
+ # --- Admin Classes ---
108
  class Company:
109
  @staticmethod
110
  def get_settings():
 
128
  c.execute('SELECT * FROM products')
129
  return c.fetchall()
130
 
131
+ @staticmethod
132
+ def search(query):
133
+ c = conn.cursor()
134
+ c.execute('''SELECT * FROM products
135
+ WHERE LOWER(name) LIKE LOWER(?)
136
+ OR LOWER(category) LIKE LOWER(?)''',
137
+ (f'%{query}%', f'%{query}%'))
138
+ return c.fetchall()
139
+
140
  @staticmethod
141
  def get_by_id(product_id):
142
  c = conn.cursor()
 
176
  @classmethod
177
  def create(cls, username, password):
178
  hashed_pw = generate_password_hash(password)
 
179
  c = conn.cursor()
180
  c.execute('INSERT INTO users (username, password) VALUES (?, ?)', (username, hashed_pw))
181
  user_id = c.lastrowid
182
  conn.commit()
 
183
  return cls(user_id, username, hashed_pw)
184
 
185
  @classmethod
186
  def get_by_username(cls, username):
 
187
  c = conn.cursor()
188
  c.execute('SELECT * FROM users WHERE username = ?', (username,))
189
  user = c.fetchone()
 
190
  if user:
191
  return cls(user[0], user[1], user[2],
192
  eval(user[3]) if user[3] else [],
 
195
 
196
  def update_chat_history(self, new_messages):
197
  updated_history = self.chat_history + new_messages
 
198
  c = conn.cursor()
199
  c.execute('UPDATE users SET previous_chat_history = ? WHERE id = ?',
200
  (str(updated_history), self.id))
201
  conn.commit()
202
+ self.chat_history = updated_history
 
203
 
204
  def update_products_bought(self, new_products):
205
  updated_products = self.products_bought + new_products
 
206
  c = conn.cursor()
207
  c.execute('UPDATE users SET previous_products_bought = ? WHERE id = ?',
208
  (str(updated_products), self.id))
209
  conn.commit()
210
+ self.products_bought = updated_products
 
211
 
212
  # --- AI Setup ---
213
  os.environ["GROQ_API_KEY"] = st.secrets["GROQ_API_KEY"]
 
236
  retriever = load_data()
237
 
238
  def retrieve_query(query: str):
239
+ """Enhanced product retrieval with strict inventory checks"""
240
  docs = retriever.get_relevant_documents(query)
241
+ valid_docs = [doc for doc in docs if doc.metadata.get('stock', 0) > 0]
242
+
243
+ if not valid_docs:
244
+ return [Document(
245
+ page_content=f"No matching in-stock products found for '{query}'",
246
+ metadata={"id": -1, "stock": 0}
247
+ )]
248
+
249
+ st.session_state.last_retrieved_docs = valid_docs
250
+ return valid_docs
251
 
252
  tool = Tool(
253
  name="product_retriever",
254
  func=retrieve_query,
255
+ description="Useful for retrieving product information from CURRENT INVENTORY only"
256
  )
257
 
258
  # --- Admin Dashboard ---
 
311
  st.rerun()
312
  else:
313
  st.info("No products found in database")
314
+
315
+ with st.expander("Inventory Reports"):
316
+ st.subheader("Current Inventory Status")
317
+ inventory = get_inventory_report()
318
+ if inventory:
319
+ st.table(inventory)
320
+ else:
321
+ st.warning("No inventory data available")
322
+
323
+ st.subheader("Low Stock Alerts")
324
+ alerts = get_inventory_alerts()
325
+ if alerts:
326
+ for item in alerts:
327
+ st.warning(f"{item[0]} - Only {item[1]} left!")
328
+ else:
329
+ st.info("No low stock alerts")
330
+
331
+ st.subheader("Inventory Access Logs")
332
+ c = conn.cursor()
333
+ c.execute('''SELECT timestamp, product_name, found, username
334
+ FROM inventory_log ORDER BY timestamp DESC LIMIT 100''')
335
+ logs = c.fetchall()
336
+ if logs:
337
+ st.table(logs)
338
+ else:
339
+ st.info("No inventory access logs yet")
340
 
341
  # --- Main App ---
342
  def main():
343
  company_settings = Company.get_settings()
344
  company_name = company_settings[1]
345
 
346
+ st.title(f"{company_name} AI Sales Assistant 🤖")
347
 
348
  if 'user' not in st.session_state:
349
  st.session_state.user = None
 
353
 
354
  # Authentication
355
  if not st.session_state.user and not st.session_state.admin_mode:
356
+ st.header("Login/Register")
357
  tab1, tab2, tab3 = st.tabs(["Login", "Register", "Admin"])
358
 
359
  with tab1:
 
400
 
401
  else:
402
  # Chat Interface
403
+ st.header(f"Welcome, {st.session_state.user.username}!")
404
+ st.caption(f"You're chatting with {company_settings[3]}, your AI sales assistant")
405
 
406
  # Display Chat History
407
  for msg in st.session_state.chat_history:
 
417
  current_products = "\n".join([f"- {p[1]} (${p[3]}, Stock: {p[6]})" for p in Product.get_all()])
418
 
419
  system_prompt = f"""
420
+ STRICT INVENTORY POLICY:
421
+ - You MUST ONLY recommend products that exist in our CURRENT INVENTORY
422
+ - NEVER suggest, mention, or describe products not in our database
423
+ - If asked for unavailable items, respond: "I apologize, we don't currently carry that product."
424
+ - For out-of-stock items, clearly state they're unavailable
425
+ - ALWAYS check stock levels before recommending products
426
+
427
+ COMPANY PROFILE:
428
+ - Name: {company_settings[1]}
429
+ - Business: {company_settings[2]}
430
+ - Agent: {company_settings[3]}
431
  - Key Features: {company_settings[4]}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
432
 
433
+ CURRENT INVENTORY:
434
  {current_products}
435
 
436
+ RESPONSE RULES:
437
+ 1. First verify product exists in inventory before discussing
438
+ 2. For unavailable products:
439
+ - Don't suggest alternatives unless they exist in inventory
440
+ - Don't make up product details
441
+ 3. For purchases:
442
+ - Verify stock is available
443
+ - Update inventory after purchase
444
+ - Generate payment link only when stock is confirmed
445
  """
446
 
447
  prompt = ChatPromptTemplate.from_messages([
 
456
  agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
457
 
458
  if prompt_input := st.chat_input("Type your message here..."):
459
+ # Log inventory access attempt
460
+ log_inventory_access(
461
+ product_name=prompt_input,
462
+ found=validate_product_in_inventory(prompt_input),
463
+ username=st.session_state.user.username
464
+ )
465
+
466
  with st.chat_message("user"):
467
  st.write(prompt_input)
468
 
469
  with st.chat_message("assistant"):
470
  response = agent_executor.invoke({
471
  "input": prompt_input,
472
+ "chat_history": [HumanMessage(content=msg["content"]) if msg["type"] == "human"
473
+ else AIMessage(content=msg["content"])
474
  for msg in st.session_state.chat_history]
475
  })["output"]
476
+
477
+ # Post-process response to ensure inventory compliance
478
+ if "last_retrieved_docs" in st.session_state:
479
+ if st.session_state.last_retrieved_docs[0].metadata.get("id") == -1:
480
+ response = f"I apologize, we don't currently carry that product in our inventory."
481
+ elif not st.session_state.last_retrieved_docs:
482
+ response = f"I couldn't find that product in our current inventory."
483
+
484
  st.write(response)
485
 
486
  # Handle inventory update on purchase
487
  if "https://www.example.com/payment" in response:
 
488
  product_name = None
489
  if "proceed with the purchase of" in response:
490
  start = response.find("proceed with the purchase of") + len("proceed with the purchase of")
 
492
  product_name = response[start:end].strip()
493
 
494
  if product_name:
 
495
  docs = retrieve_query(product_name)
496
+ if docs and docs[0].metadata.get("id") != -1:
497
  product_doc = docs[0]
498
  product_id = product_doc.metadata.get("id")
499
  current_stock = product_doc.metadata.get("stock")
500
 
501
  if product_id and current_stock > 0:
502
  try:
 
 
 
503
  # Verify current stock
504
+ c = conn.cursor()
505
  c.execute('SELECT stock FROM products WHERE id = ?', (product_id,))
506
  actual_stock = c.fetchone()[0]
507
 
 
511
  (actual_stock - 1, product_id))
512
 
513
  # Update user's purchase history
514
+ st.session_state.user.products_bought.append(product_name)
515
+ st.session_state.user.update_products_bought([product_name])
 
 
 
 
 
 
516
 
517
  conn.commit()
 
518
  st.success(f"Successfully purchased {product_name}! Stock updated.")
519
  load_data.clear()
520
 
521
  # Check for low stock
522
+ if (actual_stock - 1) <= 3:
523
+ st.warning(f"Low stock alert: Only {actual_stock - 1} units remaining!")
524
  else:
525
  st.error(f"Sorry, {product_name} is now out of stock!")
526
  except Exception as e:
527
  st.error(f"Error processing purchase: {str(e)}")
528
+ conn.rollback()
 
529
  finally:
530
+ c.close()
 
 
 
 
 
 
 
531
 
532
  new_messages = [
533
  {"type": "human", "content": prompt_input},