Chatbot Guide
Chatbot Guide
Introduction
This guide will walk you through the process of building an AI-powered customer
support chatbot integrated into a web application. We will cover both the backend
(Flask) and frontend (HTML/CSS/JavaScript) development, including AI integration
(using a placeholder for now, with options for advanced models), database setup, and
essential features like human escalation and feedback. By the end of this guide, you
will have a fully functional chatbot that can handle common customer inquiries and
provide a foundation for further enhancements.
Table of Contents
Installing dependencies
Real-time communication
Conversation logging
Before we begin coding, it's crucial to set up a proper development environment. This
ensures that all necessary tools and libraries are available and that your project
dependencies are managed effectively.
A Code Editor: Visual Studio Code, PyCharm, or Sublime Text are recommended.
A Web Browser: Chrome, Firefox, or Edge for testing the frontend.
First, ensure Python is installed. You can check by opening your terminal or command
prompt and typing:
python3 --version
If Python is not installed, please download it from the official Python website
(python.org).
# On Windows (PowerShell):
venc\Scripts\Activate.ps1
Once activated, your terminal prompt should show (venv) indicating that you are
operating within the virtual environment.
We will use Flask, a lightweight Python web framework, for our backend. We'll start by
creating a basic Flask application structure.
Inside your ai-chatbot-project directory, create a new folder named backend and
navigate into it:
cd backend
Now, let's create the main Flask application file, app.py :
# backend/app.py
app = Flask(__name__)
@app.route("/")
def hello_world():
return "<p>Hello, World! This is your AI Chatbot Backend.</p>"
if __name__ == "__main__":
app.run(debug=True)
We will need a few Python packages for our Flask application. With your virtual
environment activated, install them using pip:
Now that our environment is set up, let's build the Flask backend. This will involve
defining our API endpoints, implementing the chatbot logic, and setting up database
interactions.
For a professional and maintainable project, we will organize our Flask application
into a modular structure. Create the following directories and empty files within your
backend folder:
backend/
├── venv/ # Python virtual environment
├── src/
│ ├── main.py # Main Flask application entry point
│ ├── models/ # Database models (e.g., user, conversation, FAQ)
│ │ ├── __init__.py
│ │ ├── user.py
│ │ └── conversation.py
│ ├── routes/ # API route blueprints
│ │ ├── __init__.py
│ │ ├── user.py
│ │ └── chatbot.py
│ ├── static/ # Frontend static files (HTML, CSS, JS)
│ │ ├── index.html
│ │ ├── styles.css
│ │ └── script.js
│ └── database/ # SQLite database file
│ └── app.db
├── populate_sample_data.py # Script to populate initial data
└── requirements.txt # Project dependencies
This file will be the entry point for our Flask application. It will initialize the Flask app,
configure the database, register our API blueprints, and serve static files for the
frontend.
import os
import sys
from flask import Flask, send_from_directory
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy
# Database configuration
app.config['SQLALCHEMY_DATABASE_URI'] =
f"sqlite:///{os.path.join(os.path.dirname(__file__), 'database', 'app.db')}"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
# Register blueprints
app.register_blueprint(user_bp, url_prefix='/api')
app.register_blueprint(chatbot_bp, url_prefix='/api')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5001, debug=True)
Explanation:
Flask and CORS are imported. SQLAlchemy is initialized to manage our
database.
user_bp and chatbot_bp are imported from their respective route files. These
are Flask Blueprints, which help organize routes into modular components.
We will define our database tables using SQLAlchemy models. These models represent
the structure of our data and provide an object-oriented way to interact with the
database.
src/models/user.py
This file will contain a basic User model. While user authentication is a requirement,
for simplicity in this guide, we'll keep it minimal. You can expand upon this for full
authentication features.
# backend/src/models/user.py
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
def __repr__(self):
return f'<User {self.username}>'
def to_dict(self):
return {
'id': self.id,
'username': self.username,
'email': self.email
}
src/models/conversation.py
This file will define models for Conversation logs, Feedback , and FAQ entries. These
are central to our chatbot's functionality.
# backend/src/models/conversation.py
class Conversation(db.Model):
"""Model for storing conversation logs"""
__tablename__ = 'conversations'
id = db.Column(db.Integer, primary_key=True)
session_id = db.Column(db.String(100), nullable=False, index=True)
user_message = db.Column(db.Text, nullable=True)
ai_response = db.Column(db.Text, nullable=True)
escalated = db.Column(db.Boolean, default=False)
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
message_type = db.Column(db.String(20), default='chat') # 'chat',
'escalation', 'feedback'
def to_dict(self):
return {
'id': self.id,
'session_id': self.session_id,
'user_message': self.user_message,
'ai_response': self.ai_response,
'escalated': self.escalated,
'timestamp': self.timestamp.isoformat() if self.timestamp else
None,
'message_type': self.message_type
}
class Feedback(db.Model):
"""Model for storing user feedback"""
__tablename__ = 'feedback'
id = db.Column(db.Integer, primary_key=True)
session_id = db.Column(db.String(100), nullable=False, index=True)
rating = db.Column(db.Integer, nullable=False) # 1-5 scale
comment = db.Column(db.Text, nullable=True)
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'session_id': self.session_id,
'rating': self.rating,
'comment': self.comment,
'timestamp': self.timestamp.isoformat() if self.timestamp else None
}
class FAQ(db.Model):
"""Model for storing frequently asked questions"""
__tablename__ = 'faqs'
id = db.Column(db.Integer, primary_key=True)
question = db.Column(db.Text, nullable=False)
answer = db.Column(db.Text, nullable=False)
category = db.Column(db.String(50), nullable=True)
keywords = db.Column(db.Text, nullable=True) # Comma-separated keywords
for matching
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow,
onupdate=datetime.utcnow)
active = db.Column(db.Boolean, default=True)
def to_dict(self):
return {
'id': self.id,
'question': self.question,
'answer': self.answer,
'category': self.category,
'keywords': self.keywords.split(',') if self.keywords else [],
'created_at': self.created_at.isoformat() if self.created_at else
None,
'updated_at': self.updated_at.isoformat() if self.updated_at else
None,
'active': self.active
}
Explanation:
Conversation : Stores each message exchange between the user and the AI,
including whether it was escalated.
FAQ : Contains frequently asked questions and their answers, along with
keywords for matching user queries. This will be our primary
source of AI responses.
We will define our API endpoints using Flask Blueprints. Blueprints allow us to
organize our routes into separate modules, making the application more scalable and
maintainable.
src/routes/user.py
This blueprint will handle user-related API endpoints. For this project, we will keep it
simple, but it provides a foundation for full user authentication and management.
# backend/src/routes/user.py
@user_bp.route("/users", methods=["GET"])
def get_users():
users = User.query.all()
return jsonify([user.to_dict() for user in users])
@user_bp.route("/users", methods=["POST"])
def create_user():
data = request.json
user = User(username=data["username"], email=data["email"])
db.session.add(user)
db.session.commit()
return jsonify(user.to_dict()), 201
@user_bp.route("/users/<int:user_id>", methods=["GET"])
def get_user(user_id):
user = User.query.get_or_404(user_id)
return jsonify(user.to_dict())
@user_bp.route("/users/<int:user_id>", methods=["PUT"])
def update_user(user_id):
user = User.query.get_or_404(user_id)
data = request.json
user.username = data.get("username", user.username)
user.email = data.get("email", user.email)
db.session.commit()
return jsonify(user.to_dict())
@user_bp.route("/users/<int:user_id>", methods=["DELETE"])
def delete_user(user_id):
user = User.query.get_or_404(user_id)
db.session.delete(user)
db.session.commit()
return "", 204
Explanation:
This blueprint defines standard CRUD (Create, Read, Update, Delete) operations
for User objects. While not directly used by the chatbot frontend, it
demonstrates how to structure API endpoints.
src/routes/chatbot.py
This is the core of our backend, handling all chatbot-related interactions, including
processing messages, providing AI responses, managing escalations, and handling
feedback.
# backend/src/routes/chatbot.py
@chatbot_bp.route("/chat", methods=["POST"])
def chat():
"""Handle chat messages from users"""
try:
data = request.json
user_message = data.get("message", "").strip()
session_id = data.get("session_id", "anonymous")
if not user_message:
return jsonify({"error": "Message cannot be empty"}), 400
# Get AI response
ai_response, needs_escalation = get_ai_response_from_db(user_message)
response = {
"response": ai_response,
"escalated": needs_escalation,
"timestamp": conversation.timestamp.isoformat(),
"conversation_id": conversation.id
}
return jsonify(response)
except Exception as e:
db.session.rollback()
return jsonify({"error": "Internal server error"}), 500
@chatbot_bp.route("/escalate", methods=["POST"])
def escalate():
"""Handle escalation to human agent"""
try:
data = request.json
session_id = data.get("session_id", "anonymous")
reason = data.get("reason", "User requested human agent")
response = {
"message": "Your request has been escalated to a human agent.
Someone will be with you shortly.",
"escalation_id": f"ESC-{escalation.id}",
"estimated_wait": "5-10 minutes"
}
return jsonify(response)
except Exception as e:
db.session.rollback()
return jsonify({"error": "Internal server error"}), 500
@chatbot_bp.route("/conversations", methods=["GET"])
def get_conversations():
"""Get conversation logs (admin endpoint)"""
try:
# In production, this would require admin authentication
page = request.args.get("page", 1, type=int)
per_page = request.args.get("per_page", 50, type=int)
session_id = request.args.get("session_id")
query = Conversation.query
if session_id:
query = query.filter(Conversation.session_id == session_id)
conversations = query.order_by(Conversation.timestamp.desc()).paginate(
page=page, per_page=per_page, error_out=False
)
return jsonify({
"conversations": [conv.to_dict() for conv in conversations.items],
"total": conversations.total,
"pages": conversations.pages,
"current_page": page
})
except Exception as e:
return jsonify({"error": "Internal server error"}), 500
@chatbot_bp.route("/health", methods=["GET"])
def health_check():
"""Health check endpoint"""
return jsonify({
"status": "healthy",
"timestamp": datetime.now().isoformat(),
"service": "chatbot-api",
"database": "connected"
})
@chatbot_bp.route("/feedback", methods=["POST"])
def submit_feedback():
"""Handle user feedback on chatbot interactions"""
try:
data = request.json
session_id = data.get("session_id", "anonymous")
rating = data.get("rating", 0) # 1-5 scale
comment = data.get("comment", "")
return jsonify({
"message": "Thank you for your feedback!",
"feedback_id": f"FB-{feedback.id}"
})
except Exception as e:
db.session.rollback()
return jsonify({"error": "Internal server error"}), 500
@chatbot_bp.route("/faqs", methods=["GET"])
def get_faqs():
"""Get all active FAQs"""
try:
faqs = FAQ.query.filter(FAQ.active == True).order_by(FAQ.category,
FAQ.question).all()
return jsonify({
"faqs": [faq.to_dict() for faq in faqs],
"total": len(faqs)
})
except Exception as e:
return jsonify({"error": "Internal server error"}), 500
@chatbot_bp.route("/faqs", methods=["POST"])
def create_faq():
"""Create a new FAQ (admin endpoint)"""
try:
data = request.json
faq = FAQ(
question=data.get("question"),
answer=data.get("answer"),
category=data.get("category"),
keywords=data.get("keywords", "")
)
db.session.add(faq)
db.session.commit()
except Exception as e:
db.session.rollback()
return jsonify({"error": "Internal server error"}), 500
@chatbot_bp.route("/analytics", methods=["GET"])
def get_analytics():
"""Get basic analytics about chatbot usage"""
try:
total_conversations = Conversation.query.count()
total_escalations = Conversation.query.filter(Conversation.escalated ==
True).count()
total_feedback = Feedback.query.count()
avg_rating = db.session.query(db.func.avg(Feedback.rating)).scalar() or
0
return jsonify({
"total_conversations": total_conversations,
"total_escalations": total_escalations,
"escalation_rate": (total_escalations / total_conversations * 100)
if total_conversations > 0 else 0,
"total_feedback": total_feedback,
"average_rating": round(float(avg_rating), 2),
"recent_conversations_24h": recent_conversations
})
except Exception as e:
return jsonify({"error": "Internal server error"}), 500
Explanation:
/health (GET): A simple endpoint to check the health status of the API and
database connection.
Now, let's build the user interface for our chatbot. This will be a simple yet responsive
web page with a floating chat widget. The frontend files will be placed in the
backend/src/static/ directory, which our Flask application will serve.
3.1. Designing the User Interface ( src/static/index.html )
This HTML file will define the structure of our web application, including the main
content area and the chatbot widget.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Customer Support Chatbot</title>
<link rel="stylesheet" href="styles.css">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-
awesome/6.0.0/css/all.min.css" rel="stylesheet">
</head>
<body>
<!-- Main Application -->
<div class="app-container">
<!-- Header -->
<header class="app-header">
<div class="header-content">
<h1><i class="fas fa-robot"></i> AI Customer Support</h1>
<p>Get instant help with our AI-powered assistant</p>
</div>
</header>
<div class="features-grid">
<div class="feature-card">
<i class="fas fa-clock"></i>
<h3>24/7 Support</h3>
<p>Get help anytime, day or night</p>
</div>
<div class="feature-card">
<i class="fas fa-bolt"></i>
<h3>Instant Responses</h3>
<p>Quick answers to common questions</p>
</div>
<div class="feature-card">
<i class="fas fa-user-tie"></i>
<h3>Human Escalation</h3>
<p>Connect with human agents when needed</p>
</div>
</div>
</div>
</div>
</main>
<script src="script.js"></script>
</body>
</html>
Explanation:
The HTML defines the main layout, including a header, a welcome section with
feature cards, and the chatbot widget itself.
This CSS file will provide the visual styling for our web application and chatbot,
ensuring a clean, modern, and responsive design.
body {
font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto,
Oxygen, Ubuntu, Cantarell, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f8fafc;
}
/* App Container */
.app-container {
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* Header */
.app-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 2rem 0;
text-align: center;
}
.header-content h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
font-weight: 700;
}
.header-content p {
font-size: 1.1rem;
opacity: 0.9;
}
/* Main Content */
.main-content {
flex: 1;
padding: 3rem 1rem;
}
.content-wrapper {
max-width: 1200px;
margin: 0 auto;
}
.welcome-section {
text-align: center;
margin-bottom: 3rem;
}
.welcome-section h2 {
font-size: 2rem;
margin-bottom: 1rem;
color: #2d3748;
}
.welcome-section p {
font-size: 1.1rem;
color: #718096;
margin-bottom: 2rem;
}
/* Features Grid */
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin-top: 2rem;
}
.feature-card {
background: white;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
text-align: center;
transition: transform 0.2s, box-shadow 0.2s;
}
.feature-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.feature-card i {
font-size: 2.5rem;
color: #667eea;
margin-bottom: 1rem;
}
.feature-card h3 {
font-size: 1.3rem;
margin-bottom: 0.5rem;
color: #2d3748;
}
.feature-card p {
color: #718096;
}
/* Chat Widget */
.chat-widget {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1000;
}
.chat-toggle:hover {
transform: scale(1.1);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
}
.chat-badge {
position: absolute;
top: -5px;
right: -5px;
background: #e53e3e;
color: white;
border-radius: 50%;
width: 20px;
height: 20px;
font-size: 0.7rem;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
/* Chat Window */
.chat-window {
position: absolute;
bottom: 80px;
right: 0;
width: 350px;
height: 500px;
background: white;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
display: none;
flex-direction: column;
overflow: hidden;
}
.chat-window.active {
display: flex;
}
/* Chat Header */
.chat-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.chat-header-info {
display: flex;
align-items: center;
gap: 0.75rem;
}
.chat-header-info i {
font-size: 1.5rem;
}
.chat-header-info h4 {
margin: 0;
font-size: 1rem;
}
.status {
font-size: 0.8rem;
opacity: 0.9;
}
.status.online {
color: #68d391;
}
.chat-close {
background: none;
border: none;
color: white;
font-size: 1.2rem;
cursor: pointer;
padding: 0.25rem;
border-radius: 4px;
transition: background-color 0.2s;
}
.chat-close:hover {
background-color: rgba(255, 255, 255, 0.1);
}
/* Chat Messages */
.chat-messages {
flex: 1;
padding: 1rem;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 1rem;
}
.message {
display: flex;
gap: 0.75rem;
max-width: 85%;
}
.bot-message {
align-self: flex-start;
}
.user-message {
align-self: flex-end;
flex-direction: row-reverse;
}
.message-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.9rem;
flex-shrink: 0;
}
.bot-message .message-avatar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.user-message .message-avatar {
background: #e2e8f0;
color: #4a5568;
}
.message-content {
background: #f7fafc;
padding: 0.75rem 1rem;
border-radius: 12px;
position: relative;
}
.user-message .message-content {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.message-content p {
margin: 0;
font-size: 0.9rem;
line-height: 1.4;
}
.message-time {
font-size: 0.7rem;
opacity: 0.7;
margin-top: 0.25rem;
display: block;
}
/* Chat Input */
.chat-input-container {
padding: 1rem;
border-top: 1px solid #e2e8f0;
background: #f8fafc;
}
.chat-input-wrapper {
display: flex;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
#chatInput {
flex: 1;
padding: 0.75rem;
border: 1px solid #e2e8f0;
border-radius: 8px;
font-size: 0.9rem;
outline: none;
transition: border-color 0.2s;
}
#chatInput:focus {
border-color: #667eea;
}
.send-button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
color: white;
padding: 0.75rem;
border-radius: 8px;
cursor: pointer;
transition: opacity 0.2s;
}
.send-button:hover {
opacity: 0.9;
}
.send-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Chat Actions */
.chat-actions {
display: flex;
gap: 0.5rem;
}
.action-button {
flex: 1;
background: white;
border: 1px solid #e2e8f0;
color: #4a5568;
padding: 0.5rem;
border-radius: 6px;
font-size: 0.8rem;
cursor: pointer;
transition: all 0.2s;
}
.action-button:hover {
background: #f7fafc;
border-color: #cbd5e0;
}
/* Modal */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: none;
align-items: center;
justify-content: center;
z-index: 2000;
}
.modal-overlay.active {
display: flex;
}
.modal-content {
background: white;
border-radius: 12px;
width: 90%;
max-width: 400px;
max-height: 90vh;
overflow-y: auto;
}
.modal-header {
padding: 1.5rem;
border-bottom: 1px solid #e2e8f0;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h3 {
margin: 0;
color: #2d3748;
}
.modal-close {
background: none;
border: none;
font-size: 1.2rem;
cursor: pointer;
color: #718096;
padding: 0.25rem;
border-radius: 4px;
transition: background-color 0.2s;
}
.modal-close:hover {
background: #f7fafc;
}
.modal-body {
padding: 1.5rem;
}
/* Star Rating */
.rating-container {
text-align: center;
margin-bottom: 1.5rem;
}
.rating-container p {
margin-bottom: 1rem;
color: #4a5568;
}
.star-rating {
display: flex;
justify-content: center;
gap: 0.25rem;
margin-bottom: 1rem;
}
.star-rating i {
font-size: 1.5rem;
color: #e2e8f0;
cursor: pointer;
transition: color 0.2s;
}
.star-rating i:hover,
.star-rating i.active {
color: #ffd700;
}
#feedbackComment {
width: 100%;
padding: 0.75rem;
border: 1px solid #e2e8f0;
border-radius: 8px;
font-family: inherit;
font-size: 0.9rem;
resize: vertical;
outline: none;
transition: border-color 0.2s;
margin-bottom: 1rem;
}
#feedbackComment:focus {
border-color: #667eea;
}
.submit-feedback {
width: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 0.75rem;
border-radius: 8px;
font-size: 0.9rem;
cursor: pointer;
transition: opacity 0.2s;
}
.submit-feedback:hover {
opacity: 0.9;
}
/* Loading Indicator */
.loading-indicator {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 3000;
display: none;
}
.loading-indicator.active {
display: block;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Responsive Design */
@media (max-width: 768px) {
.header-content h1 {
font-size: 2rem;
}
.main-content {
padding: 2rem 1rem;
}
.features-grid {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.chat-window {
width: calc(100vw - 40px);
height: calc(100vh - 120px);
bottom: 80px;
right: 20px;
left: 20px;
}
.chat-widget {
right: 20px;
bottom: 20px;
}
}
.welcome-section h2 {
font-size: 1.5rem;
}
.feature-card {
padding: 1.5rem;
}
.chat-window {
width: calc(100vw - 20px);
height: calc(100vh - 100px);
bottom: 80px;
right: 10px;
left: 10px;
}
}
/* Scrollbar Styling */
.chat-messages::-webkit-scrollbar {
width: 6px;
}
.chat-messages::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.chat-messages::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.chat-messages::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
.message.new {
animation: slideInUp 0.3s ease-out;
}
/* Typing indicator */
.typing-indicator {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
background: #f7fafc;
border-radius: 12px;
margin: 0.5rem 0;
}
.typing-dots {
display: flex;
gap: 0.25rem;
}
.typing-dots span {
width: 6px;
height: 6px;
background: #cbd5e0;
border-radius: 50%;
animation: typing 1.4s infinite ease-in-out;
}
@keyframes typing {
0%, 80%, 100% {
transform: scale(0.8);
opacity: 0.5;
}
40% {
transform: scale(1);
opacity: 1;
}
}
Explanation:
The CSS provides a clean, modern aesthetic with a purple-to-blue gradient for
headers and buttons.
It defines styles for the main application layout, feature cards, and the chatbot
widget.
Animations are included for new messages and a typing indicator to enhance the
user experience.
This JavaScript file will handle all the dynamic behavior of our chatbot, including
opening/closing the chat window, sending messages, displaying responses, and
managing feedback.
class ChatbotApp {
constructor() {
this.sessionId = this.generateSessionId();
this.isTyping = false;
this.currentRating = 0;
this.initializeElements();
this.bindEvents();
this.initializeChat();
}
generateSessionId() {
return \'session_\' + Date.now() + \'_\' +
Math.random().toString(36).substr(2, 9);
}
initializeElements() {
// Chat widget elements
this.chatToggle = document.getElementById(\'chatToggle\');
this.chatWindow = document.getElementById(\'chatWindow\');
this.chatClose = document.getElementById(\'chatClose\');
this.chatMessages = document.getElementById(\'chatMessages\');
this.chatInput = document.getElementById(\'chatInput\');
this.sendButton = document.getElementById(\'sendButton\');
this.chatBadge = document.getElementById(\'chatBadge\');
// Action buttons
this.escalateButton = document.getElementById(\'escalateButton\');
this.feedbackButton = document.getElementById(\'feedbackButton\');
// Modal elements
this.feedbackModal = document.getElementById(\'feedbackModal\');
this.closeFeedback = document.getElementById(\'closeFeedback\');
this.starRating = document.getElementById(\'starRating\');
this.feedbackComment = document.getElementById(\'feedbackComment\');
this.submitFeedback = document.getElementById(\'submitFeedback\');
// Loading indicator
this.loadingIndicator = document.getElementById(\'loadingIndicator\');
}
bindEvents() {
// Chat toggle
this.chatToggle.addEventListener(\'click\', () => this.toggleChat());
this.chatClose.addEventListener(\'click\', () => this.closeChat());
// Message sending
this.sendButton.addEventListener(\'click\', () => this.sendMessage());
this.chatInput.addEventListener(\'keypress\', (e) => {
if (e.key === \'Enter\' && !e.shiftKey) {
e.preventDefault();
this.sendMessage();
}
});
// Input validation
this.chatInput.addEventListener(\'input\', () => this.validateInput());
// Action buttons
this.escalateButton.addEventListener(\'click\', () =>
this.escalateToHuman());
this.feedbackButton.addEventListener(\'click\', () =>
this.openFeedbackModal());
// Feedback modal
this.closeFeedback.addEventListener(\'click\', () =>
this.closeFeedbackModal());
this.feedbackModal.addEventListener(\'click\', (e) => {
if (e.target === this.feedbackModal) {
this.closeFeedbackModal();
}
});
// Star rating
this.starRating.addEventListener(\'click\', (e) =>
this.handleStarRating(e));
this.starRating.addEventListener(\'mouseover\', (e) =>
this.highlightStars(e));
this.starRating.addEventListener(\'mouseout\', () =>
this.resetStarHighlight());
// Submit feedback
this.submitFeedback.addEventListener(\'click\', () =>
this.submitFeedbackForm());
initializeChat() {
// Hide the notification badge initially
this.chatBadge.style.display = \'none\';
toggleChat() {
const isActive = this.chatWindow.classList.contains(\'active\');
if (isActive) {
this.closeChat();
} else {
this.openChat();
}
}
openChat() {
this.chatWindow.classList.add(\'active\');
this.chatBadge.style.display = \'none\';
this.chatInput.focus();
this.scrollToBottom();
}
closeChat() {
this.chatWindow.classList.remove(\'active\');
}
validateInput() {
const message = this.chatInput.value.trim();
this.sendButton.disabled = !message || this.isTyping;
}
async sendMessage() {
const message = this.chatInput.value.trim();
if (!message || this.isTyping) return;
try {
// Send message to backend
const response = await this.callChatAPI(message);
} catch (error) {
console.error(\'Error sending message:\', error);
this.hideTypingIndicator();
this.addMessage(\'Sorry, I encountered an error. Please try
again.\', \'bot\', true);
}
}
async callChatAPI(message) {
const response = await fetch(\'/api/chat\', {
method: \'POST\',
headers: {
\'Content-Type\': \'application/json\',
},
body: JSON.stringify({
message: message,
session_id: this.sessionId
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
contentDiv.appendChild(textP);
contentDiv.appendChild(timeSpan);
messageDiv.appendChild(avatarDiv);
messageDiv.appendChild(contentDiv);
this.chatMessages.appendChild(messageDiv);
this.scrollToBottom();
showTypingIndicator() {
this.isTyping = true;
this.validateInput();
this.chatMessages.appendChild(typingDiv);
this.scrollToBottom();
}
hideTypingIndicator() {
this.isTyping = false;
this.validateInput();
async escalateToHuman() {
try {
this.showLoading();
this.hideLoading();
if (response.ok) {
const data = await response.json();
this.addMessage(data.message, \'bot\');
this.addMessage(`Escalation ID: ${data.escalation_id}`,
\'bot\');
this.addMessage(`Estimated wait time: ${data.estimated_wait}`,
\'bot\');
} else {
throw new Error(\'Escalation failed\');
}
} catch (error) {
console.error(\'Error escalating:\', error);
this.hideLoading();
this.addMessage(\'Sorry, I couldn\\\'t connect you to a human agent
right now. Please try again later.\', \'bot\', true);
}
}
handleEscalation() {
// Add visual indicator that escalation is happening
this.escalateButton.innerHTML = \'<i class="fas fa-clock"></i>
Escalated\';
this.escalateButton.disabled = true;
this.escalateButton.style.background = \'#fed7d7\';
this.escalateButton.style.color = \'#c53030\';
}
openFeedbackModal() {
this.feedbackModal.classList.add(\'active\');
this.currentRating = 0;
this.resetStarHighlight();
this.feedbackComment.value = \'\';
}
closeFeedbackModal() {
this.feedbackModal.classList.remove(\'active\');
}
handleStarRating(e) {
if (e.target.tagName === \'I\' && e.target.dataset.rating) {
this.currentRating = parseInt(e.target.dataset.rating);
this.updateStarDisplay();
}
}
highlightStars(e) {
if (e.target.tagName === \'I\' && e.target.dataset.rating) {
const rating = parseInt(e.target.dataset.rating);
const stars = this.starRating.querySelectorAll(\'i\');
stars.forEach((star, index) => {
if (index < rating) {
star.style.color = \'#ffd700\';
} else {
star.style.color = \'#e2e8f0\';
}
});
}
}
resetStarHighlight() {
this.updateStarDisplay();
}
updateStarDisplay() {
const stars = this.starRating.querySelectorAll(\'i\');
stars.forEach((star, index) => {
if (index < this.currentRating) {
star.style.color = \'#ffd700\';
star.classList.add(\'active\');
} else {
star.style.color = \'#e2e8f0\';
star.classList.remove(\'active\');
}
});
}
async submitFeedbackForm() {
if (this.currentRating === 0) {
alert(\'Please select a rating before submitting.\');
return;
}
try {
this.showLoading();
if (response.ok) {
const data = await response.json();
this.closeFeedbackModal();
this.addMessage(\'Thank you for your feedback! It helps us
improve our service.\', \'bot\');
} catch (error) {
console.error(\'Error submitting feedback:\', error);
this.hideLoading();
alert(\'Sorry, there was an error submitting your feedback. Please
try again.\');
}
}
showLoading() {
this.loadingIndicator.classList.add(\'active\');
}
hideLoading() {
this.loadingIndicator.classList.remove(\'active\');
}
scrollToBottom() {
setTimeout(() => {
this.chatMessages.scrollTop = this.chatMessages.scrollHeight;
}, 100);
}
formatTime(date) {
return date.toLocaleTimeString([], { hour: \'2-digit\', minute: \'2-
digit\' });
}
}
Explanation:
The frontend and backend are already integrated through the API calls made in
script.js to the Flask endpoints. The Flask application serves the index.html ,
styles.css , and script.js files from its static directory. When the script.js
makes fetch requests to /api/chat , /api/escalate , or /api/feedback , these
requests are routed to the corresponding Flask endpoints we defined.
CORS is a security feature implemented in web browsers that prevents a web page
from making requests to a different domain than the one that served the web page.
Since our Flask backend and frontend are served from the same origin
(localhost:5001), we don't strictly need CORS for local development. However, it's
good practice to include Flask-CORS as we did in src/main.py :
# In src/main.py
from flask_cors import CORS
...
app = Flask(__name__, static_folder=os.path.join(os.path.dirname(__file__),
\'static\'))
CORS(app) # Enable CORS for all routes
This ensures that if you were to deploy your frontend and backend on different
domains in the future, they would still be able to communicate without issues.
To make our chatbot functional from the start, we need to populate the FAQ table in
our database with some sample questions and answers. This script will do exactly
that.
#!/usr/bin/env python3
"""
Script to populate the database with sample FAQ data for the chatbot
"""
import sys
import os
def populate_sample_faqs():
"""Populate the database with sample FAQ data"""
sample_faqs = [
{
\'question\': \'How do I track my order?\',
\'answer\': \'You can track your order by entering your order
number on our tracking page. You can find your order number in the confirmation
email we sent you.\',
\'category\': \'Orders\',
\'keywords\': \'track,order,tracking,status,where is my order\'
},
{
\'question\': \'What is your return policy?\',
\'answer\': \'We offer a 30-day return policy for most items. Items
must be in original condition with tags attached. Please contact our support
team to initiate a return.\',
\'category\': \'Returns\',
\'keywords\': \'return,refund,exchange,policy,money back\'
},
{
\'question\': \'How long does shipping take?\',
\'answer\': \'Standard shipping takes 5-7 business days, while
express shipping takes 2-3 business days. International shipping may take 10-14
business days.\',
\'category\': \'Shipping\',
\'keywords\': \'shipping,delivery,how long,when will,arrive\'
},
{
\'question\': \'What payment methods do you accept?\',
\'answer\': \'We accept all major credit cards (Visa, MasterCard,
American Express), PayPal, Apple Pay, and Google Pay.\',
\'category\': \'Payment\',
\'keywords\': \'payment,pay,credit card,paypal,billing,checkout\'
},
{
\'question\': \'How do I reset my password?\',
\'answer\': \'Click on "Forgot Password" on the login page and
enter your email address. We\\\'ll send you a link to reset your password.\',
\'category\': \'Account\',
\'keywords\': \'password,reset,forgot,login,account,access\'
},
{
\'question\': \'Do you offer international shipping?\',
\'answer\': \'Yes, we ship to most countries worldwide. Shipping
costs and delivery times vary by destination. International orders may be
subject to customs duties.\',
\'category\': \'Shipping\',
\'keywords\': \'international,worldwide,global,shipping,countries\'
},
{
\'question\': \'How can I contact customer support?\',
\'answer\': \'You can contact our customer support team via email
at support@example.com, phone at 1-800-123-4567, or through this chat
system.\',
\'category\': \'Support\',
\'keywords\': \'contact,support,help,phone,email,customer service\'
},
{
\'question\': \'What sizes do you offer?\',
\'answer\': \'We offer sizes XS through XXL for most clothing
items. Please check the size chart on each product page for specific
measurements.\',
\'category\': \'Products\',
\'keywords\': \'size,sizing,measurements,fit,chart\'
},
{
\'question\': \'Can I cancel my order?\',
\'answer\': \'You can cancel your order within 1 hour of placing
it. After that, the order may have already been processed for shipping.\',
\'category\': \'Orders\',
\'keywords\': \'cancel,cancellation,stop,order,change\'
},
{
\'question\': \'Do you have a loyalty program?\',
\'answer\': \'Yes! Our loyalty program offers points for every
purchase, exclusive discounts, and early access to sales. Sign up for free on
your account page.\',
\'category\': \'Rewards\',
\'keywords\': \'loyalty,rewards,points,program,discounts,benefits\'
}
]
with app.app_context():
# Clear existing FAQs (optional)
print("Clearing existing FAQs...")
FAQ.query.delete()
# Commit changes
db.session.commit()
print(f"Successfully added {len(sample_faqs)} FAQs to the database!")
if __name__ == \'__main__\':
populate_sample_faqs()
Explanation:
This script defines a list of sample FAQs, each with a question, answer, category,
and keywords.
It then iterates through the sample_faqs list, creates FAQ objects, and adds
them to the database.
To run this script and populate your database, navigate to the backend directory in
your terminal (with your virtual environment activated) and execute:
python populate_sample_data.py
Before we consider our project complete, it's crucial to test all components to ensure
they are working as expected. We will perform both manual testing of the web
interface and verify the API endpoints directly.
First, ensure your Flask application is running. Navigate to your backend directory and
start the server:
cd backend
source venv/bin/activate
python src/main.py
The server should start on http://127.0.0.1:5001 (or http://localhost:5001 ).
Keep this terminal window open.
Open your web browser and navigate to http://localhost:5001 . You should see the
main web application page with the chatbot widget in the bottom right corner.
1. Open/Close Chat: Click the chat bubble to open and close the chat window.
2. Send Messages: Type various messages into the input field and click the send
button or press Enter. Observe the AI's responses.
Try questions from your FAQ list (e.g., "How do I track my order?", "What is
your return policy?").
Try questions not in your FAQ list (e.g., "Tell me a joke."). This should trigger
the escalation message.
3. Escalate to Human: Click the "Talk to Human" button and observe the
escalation message.
4. Submit Feedback: Click the "Rate Chat" button, select a star rating, add a
comment, and submit. Observe the confirmation message.
You can use curl (a command-line tool for making HTTP requests) or a tool like
Postman/Insomnia to directly test your API endpoints. Open a new terminal window
(keep the Flask server running in the first one) and activate your virtual environment.
curl http://localhost:5001/api/health
Expected output:
{
"database": "connected",
"service": "chatbot-api",
"status": "healthy",
"timestamp": "..."
}
{
"ai_response": "Click on \"Forgot Password\" on the login page and enter your
email address. We\'ll send you a link to reset your password.",
"conversation_id": 1,
"escalated": false,
"response": "Click on \"Forgot Password\" on the login page and enter your
email address. We\'ll send you a link to reset your password.",
"timestamp": "..."
}
Expected output:
{
"escalation_id": "ESC-...",
Expected output:
{
"feedback_id": "FB-...",
"message": "Thank you for your feedback!"
}
curl http://localhost:5001/api/analytics
{
"average_rating": 5.0,
"escalation_rate": 33.33333333333333,
"recent_conversations_24h": 3,
"total_conversations": 3,
"total_feedback": 1,
"total_escalations": 1
}
Congratulations! You have successfully built and tested your AI-powered customer
support chatbot. This section will discuss how to prepare your application for
deployment and explore potential next steps for enhancing its capabilities.
For a production environment, you would typically deploy your Flask application using
a production-ready web server like Gunicorn or uWSGI, and serve it behind a reverse
proxy like Nginx or Apache. The frontend static files would also be served by the web
server directly for better performance.
6. Logging: Implement proper logging for errors and application events. Flask has
built-in logging capabilities that can be configured to write to files or external
logging services.
This chatbot provides a solid foundation. Here are some ideas for future
enhancements:
OpenAI GPT (e.g., GPT-3.5, GPT-4): For highly conversational and context-
aware responses. You would replace the get_ai_response_from_db
function with calls to the OpenAI API.
Live Agent Handoff: Enhance the human escalation feature to integrate with a
live chat platform (e.g., Zendesk, LiveChat) where human agents can seamlessly
take over conversations.
Multimedia Support: Allow the chatbot to send and receive images, videos, or
other media.
Conclusion
Building an AI-powered chatbot is an iterative process. This guide has provided you
with a robust starting point, covering the essential components of a web-based
chatbot application. By understanding each part and exploring the suggested
enhancements, you can continue to develop and refine your chatbot to meet evolving
user needs and technological advancements. The combination of a flexible Flask
backend and an interactive JavaScript frontend provides a powerful platform for
creating engaging and intelligent conversational experiences.
References