When running a business, knowing how your customers feel is important. Do they like your product or service? Do they hate it? You can collect a lot of feedback via email or web form. But analyzing all those emails can become a tedious and error-prone task, that’s, using an AI tool like ChatGPT can change everything. Along with Python and the Streamlit library, we will create a Sentiment Analysis dashboard that will read our emails and prepare all the information for us.
Is your system ready?
If you already have the Nylas Python SDK installed and your Python environment configured, continue with the blog.
Previously, we wrote a blog post called Python, Sentiment Analysis, R and Shiny. Today, we will do the same, replacing R and Shiny with Streamlit and for the Sentiment Analysis operation, we’re using ChatGPT.
That said, this application is going to look fairly similar:
Once the key has been created, store it safely, as you cannot recover it, so we can take advantage of our .env file and store it there.
Installing the required packages
As we want to create a Flask web application, our best option is to use Flask, one of the most popular Micro Frameworks in the Python world; also, we want to use Streamlit, the best option to create Dashboards in Python:
We’re to create a folder called sentiment_analysis to hold our scripts. Inside, we will create a folder called templates to hold our HTML templates and another folder called images to hold our emojis images.
Our Flask script is going to be called FeedbackForm.py and our Sentiment Analysis Dashboard script is going to be called sentiment_analysis.py.
Creating the Flask web form
We’re going to create a file called FeedbackForm.py which will handle getting the feedback and sending the email:
# Import your dependencies
from dotenv import load_dotenv
from flask import Flask,render_template,json,request,flash,redirect,url_for
from nylas import APIClient
# Load your env variables
load_dotenv()
app = Flask(__name__)
# Load the Nylas APIs
def load_nylas():
nylas = APIClient(
os.environ.get("CLIENT_ID"),
os.environ.get("CLIENT_SECRET"),
os.environ.get("ACCESS_TOKEN")
)
return nylas
# Main page when launching the app
@app.route("/", methods=['GET','POST'])
def index():
# If we're reading, load FeebackForm.html
if request.method == 'GET':
return render_template('FeedbackForm.html')
# Otherwise, register the information from the form
else:
# Gather the information from the form
name = request.form['name']
email = request.form['email']
rating = request.form['rating']
comments = request.form['comments']
# If a field is missing, display an error
if not name:
flash('Name is required!')
return redirect(url_for('index'))
elif not email:
flash('Email is required!')
return redirect(url_for('index'))
elif not comments:
flash('Comments are required!')
return redirect(url_for('index'))
else:
# No errors, we can continue
# Load the Nylas APIs
nylas = load_nylas()
# Create an email draft
draft = nylas.drafts.create()
# Define the email subject
draft.subject = "VeggiEggs Feedback - {} - {} - {}".format(name, email, rating)
# Define the email body
draft.body = comments
# Who are we sending the email to?
draft.to = [{"name": "Blag", "email": "[email protected]"}]
# Send the email
draft.send()
# Display the confirmation foem
return render_template('ConfirmationForm.html',
name = name, email = email,
rating = rating, comments=comments),{"Refresh":"5;url=/"}
# Call the aplication and run it
if __name__ == "__main__":
app.run()
For this to work properly, we need to create a file called FeedbackForm.html, and we’re going to use TailwindCSS to provide a quick look and feel:
We can execute this script from the terminal by typing:
$ python3 FeedbackForm.py
NOTE: If you are using Flask on a Mac that has Monterey or higher, you may have trouble accessing localhost. You can solve this by disabling the Airplay Receiver in your Sharing folder. (Airplay Receiver uses port 5000.) Follow these instructions: How to disable Airplay Receiver.
We need to open localhost:5000 on our favourite browser:
Once we hit submit, we will get instant feedback:
We use redirection so that it will go back to the main page after 5 seconds. And here, we’re displaying the information entered by the user.
We will find this if we check our mailbox:
We have all the information we need in this email. So we’re ready for the next stage.
Creating the Sentiment Analysis Dashboard application
Now it’s time to create the sentiment_analysis.py Python script. This will take care of getting the emails, producing the sentiment analysis and creating the Streamlit dashboard and use ChatGPT for the Sentiment Analysis part:
# Import your dependencies
import os
from nylas import APIClient
import openai
import json
from dotenv import load_dotenv
import streamlit as st
import pandas as pd
import altair as alt
from PIL import Image
# Load your env variables
load_dotenv()
# Initialize your Nylas API client
nylas = APIClient(
os.environ.get("CLIENT_ID"),
os.environ.get("CLIENT_SECRET"),
os.environ.get("ACCESS_TOKEN"),
)
# Define your OpenAI keys
openai.api_key = os.environ.get("OPEN_AI")
# Helper for ChatGPT
@st.cache_data
def get_completion(prompt):
response = openai.Completion.create(
model="text-davinci-003", prompt=prompt, max_tokens=100, temperature=0
)
return json.loads(response["choices"][0]["text"])
# Gather all messages from the label “VeggiEggs”
messages = nylas.messages.where(in_="VeggiEggs")
# Create variables to hold the information needed for the dashboard
emails = ""
sentiments = []
scores = []
ratings = []
dates = []
# A spinner to let users the app is doing something
with st.spinner("Crunching your emails and waiting for ChatGPT..."):
# Read all messages
for message in messages:
# Split the title in order to get the rating value
line = message.subject.split(" - ")
ratings.append(line[3])
dates.append(str(message.received_at.date()))
# Read each email and get sentiment and score
prompt = f"""
You're going to receive an email, analyze it
and generate the sentiment analysis.
Give me the result as a json using only the sentiment and the score.
The email will be delimited by triple backticks.
```{message.body}```.
Sentiment:
"""
# Record the reponses
response_json = get_completion(prompt)
sentiments.append(response_json["sentiment"].lower())
scores.append(response_json["score"])
# With all the sentiments and scores, give me the median values
prompt = f"""
I'm going to give you two arrays, one of sentiments and one of scores.
Give me the global sentiment and global score
as json using only the sentiment and the score.
Both arrays will be delimited by triple backticks.
```{sentiments}```.
```{scores}```.
result:
"""
# Record the reponses
response_json = get_completion(prompt)
global_sentiment = response_json["global_sentiment"]
global_score = response_json["global_score"]
# Determine which image to use depending on the sentiment score
if global_score < -0.5:
image = Image.open("images/angry-emoji.png")
if global_score > -0.5 and global_score < 0.5:
image = Image.open("images/neutral-emoji.png")
if global_score > 0.5:
image = Image.open("images/happy-emoji.png")
# Create a Dictionary to handle all results
dt = {"Ratings": ratings, "Sentiments": sentiments, "Scores": scores, "Dates": dates}
# Add two emtpy spaces and then the title
st.write("")
st.write("")
st.title("VeggiEggs Dashboard")
# Create a Pandas DataFrame based on the dictionary
entries = pd.DataFrame(dt)
# Count up ratings, dates and sentiments
ratings = entries["Ratings"].value_counts().reset_index(name="count")
ratings.columns = ["Ratings", "Count"]
dates = entries["Dates"].value_counts().reset_index(name="count")
dates.columns = ["Dates", "Count"]
sentiments = entries["Sentiments"].value_counts().reset_index(name="count")
sentiments.columns = ["Sentiments", "Count"]
# Create columns layout
col1, col2 = st.columns(2)
with col1:
bar_chart = (
alt.Chart(ratings)
.mark_bar()
.encode(
alt.X("Ratings", title="Ratings"),
alt.Y("Count", title="Count"),
color="Ratings:N",
)
.properties(title="Ratings Count")
)
# Display Time Series Chart
st.altair_chart(bar_chart, use_container_width=True)
with col2:
bar_chart = (
alt.Chart(dates)
.mark_line()
.encode(alt.X("Dates", title="Dates"), alt.Y("Count", title="Entries"))
.properties(title="Entries per Date")
)
# Display the Bar Chart
st.altair_chart(bar_chart, use_container_width=True)
col1, col2 = st.columns(2)
with col1:
bar_chart = (
alt.Chart(sentiments)
.mark_arc()
.encode(theta="Count", color="Sentiments")
.properties(title="Sentiments Cluster")
)
# Display the Pie Chart
st.altair_chart(bar_chart, use_container_width=True)
with col2:
col1, col2, col3 = st.columns([2, 4, 2])
with col1:
st.write("")
with col2:
st.write("")
st.write("")
st.write("Sentiment Icon")
# Display the image
st.image(image, caption=global_sentiment)
with col3:
st.write("")