Ari Virrey
A Chaotic Neutral Blog:
    About     Archive     Feed

Pyqt5 For Chatbot Frontend

I recently worked on a Chatbot that would recommend a wine based on document similarity. One of the harder aspects I encountered was developing a nice UI that would serve as the front end for my chatbot. To this end, I set out to learn how to use PyQt5.

Here is a visual of the final UI in my project. In the code I’ll walk you through how to establish this kind of setup.

Here are the imports for my project. QtWidgets is the main import as this is what creates our app via QApplication, and when we create an Window class object we use QtWidgets.QWidget. The only functionality I use QtCore.Qt for is to switch the alignment of my QTextEdit object from left to right. This gives me the flexibility to give the impression of a back and forth chat dialog by putting the bot text on the right and the user text on the left. QtGui.QFont is used to resize the font of the button object and the user input QLineEdit object.

# Imports for PyQt5 Lib and Functions to be used
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import QWidget,QApplication

The following stylings are assigned to variables which I’ll use later on to set the style on my QTextEdit and QLineEdit objects.

# alignment to PyQt Widgets
setStyleQte = """QTextEdit {
    font-family: "Courier"; 
    font-size: 12pt; 
    font-weight: 600; 
    text-align: right;
    background-color: Gainsboro;
}"""

setStyletui = """QLineEdit {
    font-family: "Courier";
    font-weight: 600; 
    text-align: left;
    background-color: Gainsboro;
}"""

As the Doc String for __init__ implies here we define all the objects that will be used to construct our PyQt App. There are various layouts available in PyQt. I chose the QVBoxLayout because it worked best for my project. The important thing to note is that I have a QTextEdit and a QLineEdit object. Why do I have two different types of text containers? QTextEdit will expand depending on the size of my app. QLineEdit as the name implies will stay as a single line and will not grow when I adjust the app height or change the window geometries. You can see how I would want the size of my user input to remain static regardless of how I change app dimesions whereas the Chat dialog box (QTextEdit) can expand as this is OK for the project and what I’m trying to do.

class Window(QtWidgets.QWidget):
    def __init__(self):
        '''
        Initilize all the widgets then call the GuiSetup to customize them
        '''
        QtWidgets.QWidget.__init__(self)
        self.v = None
        self.layout = QtWidgets.QVBoxLayout(self)
        self.button2 = QtWidgets.QPushButton('Start New Session')
        self.font = QFont()
        self.font.setPointSize(12)
        self.chatlog = QtWidgets.QTextEdit()
        self.userinput = QtWidgets.QLineEdit()
        self.userinput.returnPressed.connect(self.AddToChatLogUser)
        self.button2.clicked.connect(self.getBot)
        self.GuiSetup()

Yes, GuiSetup() gets called in the __init__. I could have just put these lines in there, but I wanted to be explicit that these lines are tweaking the widgets. Recall that we created ‘styles’ to be applied to the widgets prior. Here we apply those parameters with .setStyleSheet(). The self.layout.addWidget calls sequentially add the widgets from top to bottom (vertically) thus now you know why it is called QVBoxLayout because it is a box laid out vertically. What is important to note is that the button and user input widgets do not expand when the window size changes while chatlog (a QTextEdit object) will expand. This is exactly the flavoring we want for our chat box.

    def GuiSetup(self):
        '''
        Styling and Layout.
        '''
        self.chatlog.setStyleSheet(setStyleQte)
        self.userinput.setStyleSheet(setStyletui)
        self.userinput.setFont(self.font)
        self.button2.setFont(self.font)
        self.layout.addWidget(self.button2)
        self.layout.addWidget(self.chatlog)
        self.layout.addWidget(self.userinput)

In UpdateCycle the app will set the alignment to right justified via setAlignment(Qt.AlignRight) because we want our bot messages to appear on the right side. In the AddToChatLogUser call, we set the alignment left with Qt.AlignLeft append the user’s input with .append and then explicitly reset the alignment back to right as the next output will be the bot responding to the user. While it’s true that the bot’s response is aligned right before posting, it’s always a good idea to be explicit about my default state which I choose as being right aligned.

    def UpdateCycle(self):
        '''
        Retrieves a new bot message and appends to the chat log.
        '''
        bmsg = self.v.getBotMessage()
        self.chatlog.setAlignment(Qt.AlignRight)
        [self.chatlog.append(m) for m in bmsg]
        self.userinput.setFocus()
    def AddToChatLogUser(self):
        '''
        Takes guest's entry and appends to the chatlog
        '''
        umsg = self.userinput.text()
        self.chatlog.setAlignment(Qt.AlignLeft)
        self.chatlog.append(umsg)
        self.chatlog.setAlignment(Qt.AlignRight)
        self.userinput.setText("")

Here’s the call to self when I’m opening this code stand-alone. The important thing to note is setGeometry. Setting the third and fourth parameter to 480 I essentially create a box app 480x480 pixels.

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    win = Window()
    win.setGeometry(10,10,480,480)
    win.show()
    sys.exit(app.exec_())

I hope you enjoyed reading and remember:

Stay Chaotic – Stay Neutral

ARI