frontend html code
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" href="data:," />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>The 308 Boutique Virtual Assistant</title>
<style>
/* Body and General Styles */
body {
font-family: "Roboto", sans-serif;
background-color: #eef2f3;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
overflow: hidden;
}
/* Chat Widget Styles */
#chat-widget {
position: fixed;
bottom: 20px;
right: 20px;
width: 60px;
height: 60px;
background-color: #4267b2;
border-radius: 50%;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
transition: transform 0.3s ease, background-color 0.3s ease;
color: #fff;
font-size: 1.5rem;
}
#chat-widget:hover {
transform: scale(1.1);
background-color: #375a99;
}
/* Chatbox Container */
#chatbox {
position: fixed;
bottom: 90px;
right: 20px;
width: 400px;
height: 600px;
background: linear-gradient(
to bottom,
#a8edea,
#fed6e3
); /* Chatbox background */
border-radius: 15px;
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.15);
display: none;
flex-direction: column;
overflow: hidden;
}
/* Chat Log Styles */
#chat-log {
flex: 1;
padding: 20px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 15px;
}
/* User Input Section */
#user-input {
display: flex;
padding: 10px;
background-color: #e9ecef;
border-top: 1px solid #dee2e6;
}
/* Message Styles */
.message {
display: flex;
align-items: flex-end;
}
.message.bot {
justify-content: flex-start;
}
.message.user {
justify-content: flex-end;
}
.message-content {
max-width: 70%;
padding: 12px 16px;
border-radius: 20px;
font-size: 0.9rem;
position: relative;
word-wrap: break-word;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
.message.bot .message-content {
background: linear-gradient(to right, #eff9ff, #cde7f6);
color: #004d73;
border: none;
}
.message.user .message-content {
background: linear-gradient(to right, #d4fc79, #96e6a1);
color: #083d1a;
border: none;
}
.message-avatar img {
display: inline-block;
width: 30px;
height: 30px;
border-radius: 50%;
object-fit: cover;
}
.message-avatar {
display: inline-block;
vertical-align: top;
margin-right: 8px;
}
.message-avatar img {
border: 2px solid blue; /* Debugging: Red border around images */
}
.timestamp {
font-size: 0.75rem;
color: #6c757d;
margin-top: 5px;
}
/* Typing Indicator */
.typing-indicator {
display: flex;
align-items: center;
}
.typing-indicator span {
width: 8px;
height: 8px;
background-color: #4267b2;
border-radius: 50%;
margin: 0 2px;
animation: blink 1s infinite;
}
.typing-indicator span:nth-child(2) {
animation-delay: 0.2s;
}
.typing-indicator span:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes blink {
0%,
100% {
opacity: 0.2;
}
50% {
opacity: 1;
}
}
@media screen and (max-width: 768px) {
#chatbox {
width: 90%;
height: 70%;
bottom: 10px;
right: 5%;
}
#user-input {
flex-direction: column;
}
#user-message {
margin-bottom: 10px;
}
}
body {
font-size: 1rem; /* Base font size */
}
.message-content {
font-size: 0.9rem;
}
button {
padding: 12px 16px; /* Larger touch target */
font-size: 1rem;
}
/* Buttons */
button {
padding: 10px 16px;
margin: 5px;
border: none;
border-radius: 8px;
background-color: #4267b2;
color: #fff;
font-size: 0.9rem;
cursor: pointer;
transition: background-color 0.3s, box-shadow 0.3s;
}
button:hover {
background-color: #375a99;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
}
button:hover {
transform: translateY(-3px);
background-color: #2d4373;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.3);
}
button:active {
transform: scale(0.98);
}
/* Input Field */
input[type="text"] {
flex: 1;
padding: 12px;
border: 1px solid #d4d9de;
border-radius: 20px;
font-size: 1rem;
margin-right: 10px;
}
input[type="text"]:focus {
outline: none;
border-color: #4267b2;
}
</style>
</head>
<body>
<div id="chat-widget" onclick="toggleChat()">💬</div>
<div id="chatbox">
<div id="chat-log"></div>
<div id="user-input">
<input type="text" id="user-message" placeholder="Type a message..." />
<button onclick="sendMessage()">Send</button>
</div>
</div>
<script>
let userInfo = {
name: "",
phone: "",
address: "",
product: "",
};
let step = 0;
function sendMessage() {
const input = document.getElementById("user-message").value.trim();
if (!input) return;
simulateUserResponse(input); // Display the user's message
document.getElementById("user-message").value = ""; // Clear the input
field
if (step === 0) {
botMessageWithDelay(
"Hello! Welcome to The 308 Boutique's virtual assistant. How can I
assist you today?",
[
{
label: "I want to make a purchase",
onClick: startLeadCollection,
},
{ label: "I have some queries", onClick: queryResponse },
]
);
step = 1; // Update the step to avoid repeating the intro
} else if (step === 1) {
sendToGPT(input); // Handle GPT queries
} else if (step >= 2 && step <= 5) {
captureLeadInfo(input); // Collect user details in steps
} else {
botMessageWithDelay(
"I'm not sure how to handle that. Please try again."
);
}
}
async function sendToGPT(message) {
const typingIndicator = displayTypingIndicator(); // Show typing indicator
try {
const response = await fetch("http://127.0.0.1:5000/query", {
method: "POST",
headers: {
"Content-Type": "application/json", // Indicate JSON payload
},
body: JSON.stringify({ message }), // Send the message to the server
credentials: "include", // Include cookies or session data
});
if (!response.ok) {
throw new Error(`Server responded with status: ${response.status}`);
}
const data = await response.json();
removeTypingIndicator(typingIndicator); // Remove typing indicator
if (data && data.response) {
simulateBotResponse(data.response); // Handle valid response
} else {
simulateBotResponse("Sorry, I couldn't process your request."); //
Handle missing response
}
} catch (error) {
console.error("Error:", error); // Log error for debugging
removeTypingIndicator(typingIndicator); // Remove typing indicator
simulateBotResponse("An error occurred. Please try again later."); //
Show fallback error message
}
}
document
.getElementById("user-message")
.addEventListener("keypress", function (e) {
if (e.key === "Enter") {
sendMessage();
}
});
function displayProductCarousel(products) {
let carouselHTML =
'<div class="carousel" style="display: flex; overflow-x: auto;">';
products.forEach((p) => {
carouselHTML += `
<div class="carousel-item" style="min-width: 150px; margin-right: 15px;
text-align: center;">
<h4>${p.name}</h4>
<p>${p.price}</p>
<button onclick="selectProduct('${p.name}')">Select</button>
</div>
`;
});
carouselHTML += "</div>";
simulateBotResponse(carouselHTML);
}
function selectProduct(productName) {
simulateUserResponse(`I am interested in ${productName}`);
sendToGPT(productName);
}
function toggleChat() {
const chatbox = document.getElementById("chatbox");
if (!chatbox.style.display || chatbox.style.display === "none") {
chatbox.style.display = "flex"; // Open the chatbox
if (step === 0) {
botMessage(
"Hello! Welcome to The 308 Boutique's virtual assistant. How can I
help you find the perfect outfit today?",
[
{
label: "I want to make a purchase",
onClick: startLeadCollection,
},
{ label: "I have some queries", onClick: queryResponse },
]
);
step++;
}
} else {
chatbox.style.display = "none"; // Close the chatbox
}
}
function botMessage(message, buttons = []) {
const chatLog = document.getElementById("chat-log");
const botMsg = document.createElement("div");
botMsg.className = "message bot";
botMsg.innerHTML = `
<div class="message-avatar">
<img src="robot-avatar.png" alt="Bot Avatar" />
</div>
<div class="message-content">
${message}
<div class="timestamp">${new Date().toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}</div>
<div class="button-container">
${buttons
.map(
(button, index) =>
`<button class="action-button" data-index="${index}">$
{button.label}</button>`
)
.join("")}
</div>
</div>
`;
chatLog.appendChild(botMsg);
chatLog.scrollTop = chatLog.scrollHeight;
const buttonElements = botMsg.querySelectorAll(".action-button");
buttonElements.forEach((btn, index) => {
btn.addEventListener("click", () => {
disableAllButtons();
simulateUserResponse(buttons[index].label);
buttons[index].onClick();
});
});
}
function disableAllButtons() {
document.querySelectorAll(".action-button").forEach((button) => {
button.disabled = true;
button.style.opacity = 0.5;
button.style.pointerEvents = "none";
});
}
function simulateBotResponse(responseText) {
const chatLog = document.getElementById("chat-log");
const botMsg = document.createElement("div");
botMsg.className = "message bot";
botMsg.innerHTML = `
<div class="message-avatar">
<img src="robot-avatar.png" alt="Bot Avatar" />
</div>
<div class="message-content">
${responseText}
<div class="timestamp">${new Date().toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}</div>
</div>
`;
chatLog.appendChild(botMsg);
chatLog.scrollTop = chatLog.scrollHeight;
}
function botMessageWithDelay(message, buttons = [], delay = 1000) {
const typingIndicator = displayTypingIndicator();
setTimeout(() => {
removeTypingIndicator(typingIndicator);
botMessage(message, buttons);
}, delay);
}
function displayTypingIndicator() {
const chatLog = document.getElementById("chat-log");
const typingIndicator = document.createElement("div");
typingIndicator.className = "typing-indicator message bot";
typingIndicator.innerHTML = `
<div class="message-avatar">
<img src="robot-avatar.png" alt="Bot Avatar" />
</div>
<div class="message-content">
<div class="typing-indicator">
<span></span><span></span><span></span>
</div>
</div>
`;
chatLog.appendChild(typingIndicator);
chatLog.scrollTop = chatLog.scrollHeight;
return typingIndicator;
}
function removeTypingIndicator(typingIndicator) {
if (typingIndicator && typingIndicator.parentNode) {
typingIndicator.parentNode.removeChild(typingIndicator);
}
}
function startLeadCollection() {
step = 2;
botMessageWithDelay(
"Great! We'll walk you through a few steps to ensure your purchase is
secure."
);
botMessageWithDelay("What is your name?");
}
function queryResponse() {
step = 1;
botMessageWithDelay("Please go on. What would you like to ask?");
}
function captureLeadInfo(input) {
if (step === 2) {
userInfo.name = input;
step = 3;
botMessageWithDelay(
`Thank you, ${userInfo.name}. What is your phone number?`
);
} else if (step === 3) {
userInfo.phone = input;
step = 4;
botMessageWithDelay("Got it! Where are you located?");
} else if (step === 4) {
userInfo.address = input;
step = 5;
botMessageWithDelay("What product are you interested in?");
} else if (step === 5) {
userInfo.product = input;
botMessageWithDelay(
`Thanks ${userInfo.name}. Here is the information you provided:<br>
Name: ${userInfo.name}<br>
Phone number: ${userInfo.phone}<br>
Location and address: ${userInfo.address}<br>
Product: ${userInfo.product}<br>
Are the above information correct?`,
[
{ label: "Yes it is", onClick: confirmDetails },
{
label: "No there is a mistake",
onClick: restartLeadCollection,
},
]
);
}
}
function confirmDetails() {
botMessageWithDelay(
`Great, your data was sent to our company. <a href="tel:+13082702206"
style="color:blue; text-decoration:underline;">Click here for a direct call with
our customer service representative.</a>`
);
}
function restartLeadCollection() {
step = 2;
botMessageWithDelay("Let's start over. What is your name?");
}
function simulateUserResponse(responseText) {
const chatLog = document.getElementById("chat-log");
const userMsg = document.createElement("div");
userMsg.className = "message user";
userMsg.innerHTML = `
<div class="message-avatar">
<img src="humanAvatar.png" alt="User Avatar" />
</div>
<div class="message-content">
${responseText}
<div class="timestamp">${new Date().toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}</div>
</div>
`;
chatLog.appendChild(userMsg);
chatLog.scrollTop = chatLog.scrollHeight;
}
</script>
</body>
</html>
backend python code
import logging
from pathlib import Path
from fuzzywuzzy import process
from flask import Flask, request, jsonify, session
from flask_cors import CORS
from openai import OpenAI
import random
import os
# Initialize Flask app
app = Flask(__name__)
app.secret_key = os.getenv("FLASK_SECRET_KEY", "supersecretkey") # Use environment
variable for security
CORS(app, resources={r"/*": {"origins": "http://127.0.0.1:5500"}},
supports_credentials=True)
# Allow all origins
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %
(message)s')
# Load OpenAI API Key from environment variable
api_key = "sk-proj-XG59K-99ghLLWTnFy0n-s7SoZJXsGsFx5lGaJAJ_SYK6yjpekpZg0gYRaU_EPYL-
dWuG41JB32T3BlbkFJFqz0vf7I9M-
r8zPDpxIyuTBGuixbREsjLmuI2SPawYOlcC70h1P0utN1ifFyaCNx4lME9DwWcA"
if not api_key:
raise EnvironmentError("OpenAI API key is not set in environment variables.")
client = OpenAI(api_key=api_key)
# System prompt
SYSTEM_PROMPT = (
"You are a helpful assistant for The 308 Boutique. Always provide responses
based on the provided product list "
"and database file (database.txt).\n"
"- Extract relevant keywords from the user's query to identify product
categories or items of interest.\n"
"- Recommend between 6 to 10 items with their prices that match the query. If
no products match, offer alternatives "
"or ask clarifying questions to help the user.\n"
"- Respond accurately to any questions related to the database.txt file,
prioritizing accuracy over assumptions.\n"
"- Provide helpful and context-aware suggestions for products or store policies
based on user input."
)
# File paths
PRODUCTS_FILE = Path("D:/chatbots/308boutique/products.txt")
DATABASE_FILE = Path("D:/chatbots/308boutique/database.txt")
# Global products list
products = []
def load_products(file_path):
"""Load products from the given file."""
try:
with file_path.open("r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line and ":" in line: # Expecting format 'Product Name: $Price'
try:
product_name, price = line.split(":", 1)
products.append({"name": product_name.strip(), "price":
price.strip()})
except ValueError:
logging.warning(f"Skipping invalid product entry: {line}")
logging.info(f"Loaded {len(products)} products.")
except FileNotFoundError:
logging.error(f"Error: '{file_path}' not found.")
def find_products(query):
"""Find products matching the query."""
if not products:
return []
# General product matching
matches = process.extract(query, [p['name'] for p in products], limit=20)
relevant_products = [
{"name": match, "price": next((p['price'] for p in products if p['name'] ==
match), "Unknown")}
for match, score in matches if score > 50 # Match threshold
]
# Seasonal keyword matching
seasonal_keywords = []
if "winter" in query.lower():
seasonal_keywords = ["sweater", "coat", "jacket", "scarf", "beanie",
"boots", "thermal", "knit"]
elif "summer" in query.lower():
seasonal_keywords = ["dress", "tank top", "shorts", "sandals", "hat",
"sunglasses", "linen", "lightweight"]
if seasonal_keywords:
keyword_matches = [
{"name": p['name'], "price": p['price']}
for p in products if any(keyword in p['name'].lower() for keyword in
seasonal_keywords)
]
relevant_products.extend(keyword_matches)
# Deduplicate and prioritize
unique_products = {product['name']: product for product in relevant_products}
relevant_products = list(unique_products.values())
# Ensure a minimum of 6 items
if len(relevant_products) < 6:
additional_products = [p for p in products if p not in relevant_products]
random.shuffle(additional_products)
relevant_products.extend(additional_products[:6 - len(relevant_products)])
# Limit to a maximum of 10 items
random.shuffle(relevant_products)
return relevant_products[:10]
def get_database_response(file_path, query=None):
"""Fetch relevant database information based on the query."""
try:
with file_path.open("r", encoding="utf-8") as f:
lines = f.readlines()
if query:
if "contact" in query.lower() or "phone" in query.lower():
for line in lines:
if "contact" in line.lower() or "phone" in line.lower():
return line.strip()
matching_lines = [line for line in lines if query.lower() in
line.lower()]
if matching_lines:
return "\n".join(matching_lines).strip()
else:
return "Sorry, no relevant information found in the database."
return "".join(lines).strip()
except FileNotFoundError:
logging.error(f"Error: '{file_path}' not found.")
return "The database is currently unavailable."
@app.route('/query', methods=['POST'])
def query():
"""Handle queries from the frontend and return GPT-generated responses."""
try:
data = request.json
user_input = data.get('message', '').strip()
matched_products = find_products(user_input)
product_details = (
"\n".join([f"{p['name']}: {p['price']}" for p in matched_products])
if matched_products else "No matching products found."
)
database_content = get_database_response(DATABASE_FILE, user_input)
prompt = f"{SYSTEM_PROMPT}\nUser Query: {user_input}"
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "system", "content": SYSTEM_PROMPT}, {"role":
"user", "content": prompt}],
max_tokens=250,
temperature=0.2
)
return jsonify({"response": response.choices[0].message.content.strip()})
except Exception as e:
logging.error(f"Error processing query: {e}")
return jsonify({"response": "An error occurred while processing the
query."}), 500
if __name__ == '__main__':
load_products(PRODUCTS_FILE)
app.run(debug=True, port=5000)