diff --git a/Advanced Search Page Queries.docx b/Advanced Search Page Queries.docx
new file mode 100644
index 000000000..c048eeb2a
Binary files /dev/null and b/Advanced Search Page Queries.docx differ
diff --git a/Commercial API Comparisons.pdf b/Commercial API Comparisons.pdf
new file mode 100644
index 000000000..d075d0eac
Binary files /dev/null and b/Commercial API Comparisons.pdf differ
diff --git a/Dynamic Link Format.pdf b/Dynamic Link Format.pdf
new file mode 100644
index 000000000..ec11ce11b
Binary files /dev/null and b/Dynamic Link Format.pdf differ
diff --git a/Prior Correspondence.pdf b/Prior Correspondence.pdf
new file mode 100644
index 000000000..7c327332e
Binary files /dev/null and b/Prior Correspondence.pdf differ
diff --git a/Pros and Cons Analysis - Chatbot APIs.pptx b/Pros and Cons Analysis - Chatbot APIs.pptx
new file mode 100644
index 000000000..dd0052cd4
Binary files /dev/null and b/Pros and Cons Analysis - Chatbot APIs.pptx differ
diff --git a/README.md b/README.md
index b85ecca72..e76344fc4 100644
--- a/README.md
+++ b/README.md
@@ -1,52 +1,48 @@
-# Chatbot Deployment with Flask and JavaScript
-
-In this tutorial we deploy the chatbot I created in [this](https://github.com/python-engineer/pytorch-chatbot) tutorial with Flask and JavaScript.
-
-This gives 2 deployment options:
-- Deploy within Flask app with jinja2 template
-- Serve only the Flask prediction API. The used html and javascript files can be included in any Frontend application (with only a slight modification) and can run completely separate from the Flask App then.
-
-## Initial Setup:
-This repo currently contains the starter files.
-
-Clone repo and create a virtual environment
-```
-$ git clone https://github.com/python-engineer/chatbot-deployment.git
-$ cd chatbot-deployment
-$ python3 -m venv venv
-$ . venv/bin/activate
-```
-Install dependencies
-```
-$ (venv) pip install Flask torch torchvision nltk
-```
-Install nltk package
-```
-$ (venv) python
->>> import nltk
->>> nltk.download('punkt')
-```
-Modify `intents.json` with different intents and responses for your Chatbot
-
-Run
-```
-$ (venv) python train.py
-```
-This will dump data.pth file. And then run
-the following command to test it in the console.
-```
-$ (venv) python chat.py
-```
-
-Now for deployment follow my tutorial to implement `app.py` and `app.js`.
-
-## Watch the Tutorial
-[](https://youtu.be/a37BL0stIuM)
-[https://youtu.be/a37BL0stIuM](https://youtu.be/a37BL0stIuM)
-
-## Note
-In the video we implement the first approach using jinja2 templates within our Flask app. Only slight modifications are needed to run the frontend separately. I put the final frontend code for a standalone frontend application in the [standalone-frontend](/standalone-frontend) folder.
-
-## Credits:
-This repo was used for the frontend code:
-https://github.com/hitchcliff/front-end-chatjs
+# Chatbot Training and Deployment
+
+Objective:
+
+ We are planning to deploy a new chatbot for our FAQ Page.
+ The chatbot should be able to identify certain key phrasings from user input,
+ such as the make and models of a specific product on our site, and often,
+ multiple products names will appear in a single input.
+ Our advanced search function currently yields the relevant results according to user queries.
+ We are looking to implement a chatbot that can aid user navigation via the chatbot by providing dynamic links to our advanced search page
+ (comprising a list of filtered/relevant products according to user queries).
+ We have a specific format for our dynamic link generation but in order to construct it,
+ and we require the extraction of the aforementioned key phrases and entity recognition in order to amalgamate them into the required link structure.
+
+Project Chatbot: https://docs.google.com/spreadsheets/d/17QFaHgvD6dlTbNiOdj4rlE-T3I7H9fXXHaNJVZ4-A0Y/edit?usp=sharing
+
+Research: https://docs.google.com/document/d/11yLE81vcrzVn15s2jeLP5cnzcMiyKryaz84i_KLOtL4/edit?usp=sharing
+
+Various approaches to DiGiCOR chatbot prototypes, ranked from best (1) to worst (7):
+1. IBM Watson Assistant and Discovery
+2. Amazon Lex
+3. Wit.ai with Messenger
+4. Alterra.ai with Messenger
+5. DialogFlow with Google AutoML
+6. Microsoft Azure Cognitive Services for Language
+7. Python Libraries (NLTK/ Spacy)
+
+Conclusion:
+
+ Based on assessments of key features/functionality, IBM Watson Assistant (Plus) is the chosen approach as approved by Roham, Richard and Cathy.
+ Begin by setting up the IBM Cloud Account (requires a credit/debit card, ask Cathy/Roham for it), there should be no immediate charge.
+ Start training the chatbot intents and utterances using IBM Watson Assistant Lite. Request for approval for Plus Subscription when required.
+ Live Agent Handover, Dynamic Link Generation and FAQ training can all be handled by IBM Watson Assistant (Provides entity recognition and key-phrase extraction).
+ Watson Discovery (optional extension) is a powerful search engine that performs web scraping for the site to yield potential relevant responses to unclear user input.
+
+Guidelines:
+
+ Develop your roadmap as required.
+ Training and Testing should take 2- 3 weeks.
+
+Assistance:
+
+ IBM Support - $250/month
+ OutThought Consultant - $1800/day
+
+Links to IBM Assistant Docs that would be useful:
+https://cloud.ibm.com/docs/watson-assistant?topic=watson-assistant-welcome-new-assistant
+Walks you through step by step on how to make and deploy the assistant
\ No newline at end of file
diff --git a/flask chatbot/.idea/chatbot-deployment.iml b/flask chatbot/.idea/chatbot-deployment.iml
new file mode 100644
index 000000000..38af49637
--- /dev/null
+++ b/flask chatbot/.idea/chatbot-deployment.iml
@@ -0,0 +1,25 @@
+
+
+
+
+
2. If you wish to obtain pricing, simply ask to speak to a live agent"
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
with open('intents.json', 'r') as json_data:
@@ -25,7 +28,7 @@
model.load_state_dict(model_state)
model.eval()
-bot_name = "Sam"
+bot_name = "DiGiCOR Chatbot"
def get_response(msg):
sentence = tokenize(msg)
@@ -37,15 +40,15 @@ def get_response(msg):
_, predicted = torch.max(output, dim=1)
tag = tags[predicted.item()]
-
+
probs = torch.softmax(output, dim=1)
prob = probs[0][predicted.item()]
- if prob.item() > 0.75:
+ if prob.item() > 0.85: #Increasing specifisity to reduce incorrect classifications
for intent in intents['intents']:
if tag == intent["tag"]:
return random.choice(intent['responses'])
- return "I do not understand..."
+ return f"I'm sorry, but I cannot understand your query. {samples} "
if __name__ == "__main__":
diff --git a/flask chatbot/data.pth b/flask chatbot/data.pth
new file mode 100644
index 000000000..73092c478
Binary files /dev/null and b/flask chatbot/data.pth differ
diff --git a/flask chatbot/flask_cheatsheet.pdf b/flask chatbot/flask_cheatsheet.pdf
new file mode 100644
index 000000000..8eaf14c5e
Binary files /dev/null and b/flask chatbot/flask_cheatsheet.pdf differ
diff --git a/flask chatbot/intents.json b/flask chatbot/intents.json
new file mode 100644
index 000000000..a772993b9
--- /dev/null
+++ b/flask chatbot/intents.json
@@ -0,0 +1,381 @@
+{
+ "intents": [
+ {
+ "tag": "greeting",
+ "patterns": [
+ "Hi",
+ "Hey",
+ "How are you",
+ "Is anyone there?",
+ "Hello",
+ "Howdy",
+ "Greetings",
+ "I need help",
+ "Can you help",
+ "Good day",
+ "Hi there",
+ "Good morning",
+ "Good evening",
+ "Good afternoon"
+ ],
+ "responses": [
+ "Hey, how can I be of assistance?",
+ "Hello, thanks for visiting.",
+ "Hi there, what can I do for you?",
+ "Hi there, how can I help?",
+ "Welcome to DiGiCOR. How may I be of assistance?"
+ ]
+ },
+ {
+ "tag": "identity",
+ "patterns": ["What are you?", "Who are you?", "What is your name?"],
+ "responses": [
+ "I am a chatbot. If you wish to speak to a live agent, please say 'live agent'",
+ "I am DiGiCOR's chatbot. I can help you with simple queries",
+ "I am DiGiCOR's chatbot. If you wish to speak to a live agent, please say 'Live agent'",
+ "I am a chatbot. I can help you with simple queries"
+ ]
+ },
+ {
+ "tag": "about_digicor",
+ "patterns": ["tell me about DiGiCOR", "What does DiGiCOR do", "about digicor"],
+ "responses": [
+ "DiGiCOR was founded in 1997 with the goal of becoming a major player in the Australian and New Zealand niche ICT Infrastructure market.
Our focus is in providing server, data storage, workstation, networking, edge computing, and IoT solutions. From designing IT infrastructure to deployment of the solution, we cover the whole journey for our customers.
See our range of features and partners in our About Us . "
+
+ ]
+ },
+ {
+ "tag": "digicor_applicator",
+ "patterns": ["What does DiGiCOR sell?", "does DiGiCOR have server", "What hardware does DiGiCOR have", "Do you have", "Does DiGiCOR have stock"],
+ "responses": [
+ "DiGiCOR offers a range of solutions for organisations of all sizes, from local and affordable services to cloud computing solutions, DiGiCOR can provide you with solutions to your IT challenges
Visit DiGiCOR Applicator to view different configurations
"
+
+ ]
+ },
+
+ {
+ "tag": "digicor_configurator_help",
+ "patterns": ["How do I use DiGiCOR's configurator?", "How to use DiGiCOR configurator", " Global Search", "learn how to use DiGiCOR's configurator"],
+ "responses": [
+ "This might help you in using the DiGiiCOR's configurator
Visit DiGiCOR Configurator help
Alternatively, reach out to one of our live agents, and we would be happy to help"
+
+ ]
+ },
+ {
+ "tag": "goodbye",
+ "patterns": ["Bye", "See you later", "Goodbye", "thanks for you help", "that's all for today"],
+ "responses": [
+ "See you later, thanks for visiting",
+ "Have a nice day",
+ "Bye! Come back again soon."
+ ]
+ },
+ {
+ "tag": "thanks",
+ "patterns": ["Thanks", "Thank you", "That's helpful", "Thanks a lot!", "thank you for your assistance"],
+ "responses": ["Happy to help!", "Any time!", "My pleasure"]
+ },
+ {
+ "tag": "items",
+ "patterns": [
+ "Which items do you have?",
+ "What kinds of items are there?",
+ "What does?",
+ "What do you offer?",
+ "What can you ?",
+ "What do you sell?"
+ ],
+ "responses": [
+ "We provide a wide range of industry-leading ICT infrastructure and software solutions, to help meet the demands of different industries and enterprises",
+ "We provide various tech-consulting services and ICT infrastructure to businesses in need of tech support "
+ ]
+ },
+ {
+ "tag": "contact",
+ "patterns": [
+ "How can I contact you",
+ "how do I contact DiGiCOR",
+ "Do you have email",
+ "What is DiGiCOR email",
+ "what is DiGiCOR contact details",
+ "how do I contact the melbourne store",
+ "how can I call the NSW branch",
+ "How can I contact your technical support",
+ "contact technical support",
+ "tech support",
+ "Is there any contact details",
+ "can i get muhamad's digcor email address please",
+ "I dont have an RMA, I need tech support",
+ "can you tell me the contact point ?",
+ "Hi, what is the best contact email address for support?",
+ "Could you please provide me an email address were I can send information from my company",
+ "Where are you guys located ?",
+ "I'm looking to contact someone in Melbourne ",
+ "Hi there, I am wondering if I could get information about"
+
+ ],
+ "responses": [
+ "You can contact our sales team at
1-300-192-308
our support at
788-886
Individual branches contact details can be found at Contact Us .
Alternatively, you can connect with an agent through this chat."
+ ]
+ },
+ {
+ "tag": "vmware_FAQ",
+ "patterns": [
+ "VMware Software",
+ "What is VMware",
+ "how does VMware work?",
+ "What are VMware HCI Kits?",
+ "What are the benefits of VMware?",
+ "Differences between Virtualization and Cloud Computing?",
+ "What is the difference between VMWare and VirtualBox?",
+ "What is a VMware Workstation?",
+ "VMWare Consultation",
+ "VMware vSAN Solutions",
+ "VMWare vSPHERE Solutions",
+ "tell me about vmware",
+ "vmware",
+ "The ASUS-ESC4000A-E10 - does it support GPU carving in BIOS with VMware vSphere ?",
+ "I need something VMware certified - do you guys have anything like that?",
+ "is that server supports VMware",
+ "can u pls confirm VMware support for this "
+ ],
+ "responses": [
+ "You can find out more about our partner VMWare, and get a free VMWare Consultation at our dedicated VMWare page VMWare .
Alternatively, you can connect with an agent through this chat."
+ ]
+ },
+
+ {
+
+ "tag": "live_agent",
+ "comment": "live_agent is a placeholder. We need to figure away to hand over to a live agent",
+ "patterns": [
+ "can I speak with a staff member",
+ "staff",
+ "human",
+ "I need a human",
+ "can I speak to an agent",
+ "I need to clarify",
+ "agent"
+ ],
+ "responses": [
+ "Hi, I am a live agent, how may I assist you"
+ ]
+ },
+
+
+
+ {
+ "tag": "intel_FAQ",
+ "patterns": [
+ "questions about Intel",
+ "Where to buy Intel Server Solutions in Australia?",
+ "How do I configure an Intel server? ",
+ "Who uses Intel servers? ",
+ "Why choose Intel?",
+ "What is an Intel server? ",
+ "Where are Intel servers manufactured? ",
+ "Coyote Pass",
+ "intel NUC",
+ "Intel Data Centre Blocks",
+ "DCB",
+ "Intel Servers",
+ "Intel Hardware",
+ "Intel device",
+ "Intel",
+ "Are you able to do the Intel E-22xx series processors?"
+ ],
+ "responses": [
+ "You can find out more about our partner Intel at our dedicated Intel page Intel .
Alternatively, you can connect with an agent through this chat."
+ ]
+ },
+
+ {
+ "tag": "supermicro_FAQ",
+ "patterns": [
+ "Supermicro Server",
+ "Supermicro Storage",
+ "Supermicro Workstation",
+ "Supermicro Embeded Server",
+ "Supermicro IoT Server",
+ "Supermicro Internet of Things Server",
+ " What is a Supermicro server? ",
+ " Why choose Supermicro? ",
+ "Where to buy Supermicro in Australia? ",
+ "How do I configure a Supermicro server? ",
+ " Who uses Supermicro servers? ",
+ " Where are Supermicro servers manufactured? ",
+ "solutions with Supermicro",
+ "supermicro",
+ "I understand you sell semimicro servers - what are the prices and specs in australia",
+ "Hi, do you guys sell supermicro products?",
+ "Am I able to find the supermicro products on your website?",
+ "We heard that you are an authorised distributor of Supermicro's in Australia. ",
+ "My team in Australia is looking for Super micro rack rails",
+ "Are you a supermicro reseller ?",
+ "Digicor sells supermicro motherboards?"
+
+ ],
+ "responses": [
+ "You can find out more about our partner Supermicro at our dedicated Supermicro page Supermicro .
Alternatively, you can connect with an agent through this chat."
+ ]
+ },
+
+ {
+ "tag": "account_FAQ",
+ "patterns": [
+ "Where can I find my credit terms? ",
+ "Where can my company find it's credit terms? ",
+ "personal credit terms",
+ "What are DiGiCOR's credit terms?",
+ "How do I create an account on the DiGiCOR website? ",
+ "Creating account on your website",
+ "Register New Account",
+ "credit terms",
+ "we are looking for Wholesale account options",
+ "Do you still have a login page?",
+ "How can we signup as a reseller?",
+ "can you setup online access for ",
+ "Good morning, I was wondering if I could please ask for assistance in creating a DiGiCOR website account.",
+ "how does the process of financing work ?"
+
+ ],
+ "responses": [
+ "You might find the answers to your query at our FAQ page, under the section 'Accounts and Credits' Frequently Asked Questions .
Alternatively, you can connect with an agent through this chat."
+ ]
+ },
+
+ {
+ "tag": "purchase_FAQ",
+ "patterns": [
+ "Can I cancel my order? ",
+ "cancel my order",
+ "cancel order",
+ "When is my order eligible for payment? ",
+ "when do I have to pay",
+ "when must I pay",
+ "Can I make adjustments to my order? ",
+ "Can I make ammendments to my order? ",
+ "Can I make changes to my order",
+ "Can I modify my order",
+ "order modification",
+ "change order",
+ "ammend order",
+ "purchase",
+ "buy"
+
+ ],
+ "responses": [
+ "You might find the answers to your query at our FAQ page, under the section 'Purchases and Payments' Frequently Asked Questions .
Alternatively, you can connect with an agent through this chat."
+ ]
+ },
+
+ {
+ "tag": "delivery_FAQ",
+ "patterns": [
+ "How long does delivery take? ",
+ "when do you aim to deliver your orders",
+ "Where can I pickup my purchase? ",
+ "is pickup aviliable?",
+ "is pickup free",
+ "whats the cost of pickup",
+ "What delivery options are there? ",
+ "What delivery companies do you work with",
+ "Do you ship with Australia Post?",
+ "Do you ship with Courier Please?",
+ "Do you ship with Toll?",
+ "Do you ship with FedEx?",
+ "Do you ship local delivery companies?",
+ "delivery",
+ "when will I get my hardware",
+ "when will my server arrive",
+ "What's the turnaround time to receive them?",
+ " may I know how long would delivery take if I were to order?",
+ "rough eta (timeframe weeks or months)",
+ "Hi there, I am wondering if it's possible to pick up our server directly from DiGiCOR.",
+ "what is the lead time please",
+ "can you tell me the lead time regards delivery?",
+ "how long does it take to deliver the server?",
+ "how long does it take to deliver the server?"
+
+
+ ],
+ "responses": [
+ "You might find the answers to your query at our FAQ page, under the section 'Delivery and Pickup Enquiries' Frequently Asked Questions .
Alternatively, you can connect with an agent through this chat."
+ ]
+ },
+
+ {
+ "tag": "warranty_FAQ",
+ "patterns": [
+ "hi, I would like to ask about server warranty status and part avilibility in melborune",
+ "We biught a server for our hypervisor and it's power supply failed",
+ "rm number",
+ "What repair options do you have",
+ "out of warranty",
+ "my device is broken",
+ "how can I fix my device",
+ "can DiGiCOR fix my hardware",
+ "How Should I Contact The DiGiCOR RMA Department?",
+ "My System Is Out Of Warranty, What Repair Options Do You Have?",
+ "How Long Will The RMA Process Take?",
+ "Where Can I Find The Warranty Sticker On My System?",
+ "Where Do I Return My System? ",
+ "replace hardware",
+ "How Do I Order Any Available Parts For My System?",
+ "I Need An Urgent Replacement For My System, What Options Are Available? ",
+ "I need a replacement server",
+ "RM number",
+ "WM number",
+ "RB number",
+ "WB number",
+ "RS number",
+ "WS number",
+ "RP number",
+ "WP number",
+ "BMC card has failed, believe the SN is SMC0CC47A1B1B4E can I get this replaced under warranty?",
+ "We bought a server from you a while ago, and we've had a powersupply go faulty",
+ "I was just wondering what the warranty time limit is on PSU",
+ "Where is your support page?",
+ "i need to check who to contact for parts replacement",
+ "Hi, just wondering if I could confirm if our device is still covered under warranty?",
+ "Hi there, I am wondering if RMA replacement always coming after we return the faulty ones.",
+ "I'm wondering if it's only just out of warranty if there's any chance at all we can get it covered? ",
+ "I wanted to know more about your NBD warranty",
+ "How does NBD work in terms of parts and system down time",
+ "Hi, I have to return products purchased from Digicor"
+ ],
+ "responses": [
+ "It looks like your query is related to RMA support. The following link might help answer your query RMA Support FAQ.
Alternatively, you can connect with an agent through this chat."
+ ]
+ },
+
+ {
+ "tag": "reply_speed_FAQ",
+ "patterns": [
+ "When will I hear back from you",
+ "when will you call me back",
+ "When will you reply",
+ "How long does DiGiCOR take to reply? ",
+ "When will DiGiCOR respond to my query",
+ "I sent an email last week regarding a quote for some items, but never received any confirmation that someone has been processing my request."
+ ],
+ "responses": [
+ "It looks like your query is related to RMA support. The following link might help answer your query How long does DiGiCOR take to reply.
Alternatively, you can connect with an agent through this chat."
+ ]
+ },
+
+ {
+ "tag": "funny",
+ "patterns": [
+ "Tell me a joke!",
+ "Tell me something funny!",
+ "Do you know a joke?"
+ ],
+ "responses": [
+ "Why did the hipster burn his mouth?
He drank the coffee before it was cool.",
+ "What did the buffalo say when his son left for college?
Bison.",
+ " Google "
+ ]
+ }
+ ]
+}
diff --git a/model.py b/flask chatbot/model.py
similarity index 100%
rename from model.py
rename to flask chatbot/model.py
diff --git a/nltk_utils.py b/flask chatbot/nltk_utils.py
similarity index 100%
rename from nltk_utils.py
rename to flask chatbot/nltk_utils.py
diff --git a/standalone-frontend/app.js b/flask chatbot/standalone-frontend/app.js
similarity index 93%
rename from standalone-frontend/app.js
rename to flask chatbot/standalone-frontend/app.js
index 6bdf287d1..4347c7a4e 100644
--- a/standalone-frontend/app.js
+++ b/flask chatbot/standalone-frontend/app.js
@@ -45,7 +45,7 @@ class Chatbox {
let msg1 = { name: "User", message: text1 }
this.messages.push(msg1);
-
+ // consider changing local host here if required. /predict needs to be there.
fetch('http://127.0.0.1:5000/predict', {
method: 'POST',
body: JSON.stringify({ message: text1 }),
@@ -56,7 +56,7 @@ class Chatbox {
})
.then(r => r.json())
.then(r => {
- let msg2 = { name: "Sam", message: r.answer };
+ let msg2 = { name: "Nic", message: r.answer };
this.messages.push(msg2);
this.updateChatText(chatbox)
textField.value = ''
@@ -71,7 +71,7 @@ class Chatbox {
updateChatText(chatbox) {
var html = '';
this.messages.slice().reverse().forEach(function(item, index) {
- if (item.name === "Sam")
+ if (item.name === "Nic")
{
html += '
Hi. My name is Sam. How can I help you?
+Hi. I am a DiGiCOR Chatbot. How may I assist?
optgroup
+ # element, or if there is no more content in the parent
+ # element.
+ if type == "StartTag":
+ return next["name"] in ('option', 'optgroup')
+ else:
+ return type == "EndTag" or type is None
+ elif tagname in ('rt', 'rp'):
+ # An rt element's end tag may be omitted if the rt element is
+ # immediately followed by an rt or rp element, or if there is
+ # no more content in the parent element.
+ # An rp element's end tag may be omitted if the rp element is
+ # immediately followed by an rt or rp element, or if there is
+ # no more content in the parent element.
+ if type == "StartTag":
+ return next["name"] in ('rt', 'rp')
+ else:
+ return type == "EndTag" or type is None
+ elif tagname == 'colgroup':
+ # A colgroup element's end tag may be omitted if the colgroup
+ # element is not immediately followed by a space character or
+ # a comment.
+ if type in ("Comment", "SpaceCharacters"):
+ return False
+ elif type == "StartTag":
+ # XXX: we also look for an immediately following colgroup
+ # element. See is_optional_start.
+ return next["name"] != 'colgroup'
+ else:
+ return True
+ elif tagname in ('thead', 'tbody'):
+ # A thead element's end tag may be omitted if the thead element
+ # is immediately followed by a tbody or tfoot element.
+ # A tbody element's end tag may be omitted if the tbody element
+ # is immediately followed by a tbody or tfoot element, or if
+ # there is no more content in the parent element.
+ # A tfoot element's end tag may be omitted if the tfoot element
+ # is immediately followed by a tbody element, or if there is no
+ # more content in the parent element.
+ # XXX: we never omit the end tag when the following element is
+ # a tbody. See is_optional_start.
+ if type == "StartTag":
+ return next["name"] in ['tbody', 'tfoot']
+ elif tagname == 'tbody':
+ return type == "EndTag" or type is None
+ else:
+ return False
+ elif tagname == 'tfoot':
+ # A tfoot element's end tag may be omitted if the tfoot element
+ # is immediately followed by a tbody element, or if there is no
+ # more content in the parent element.
+ # XXX: we never omit the end tag when the following element is
+ # a tbody. See is_optional_start.
+ if type == "StartTag":
+ return next["name"] == 'tbody'
+ else:
+ return type == "EndTag" or type is None
+ elif tagname in ('td', 'th'):
+ # A td element's end tag may be omitted if the td element is
+ # immediately followed by a td or th element, or if there is
+ # no more content in the parent element.
+ # A th element's end tag may be omitted if the th element is
+ # immediately followed by a td or th element, or if there is
+ # no more content in the parent element.
+ if type == "StartTag":
+ return next["name"] in ('td', 'th')
+ else:
+ return type == "EndTag" or type is None
+ return False
diff --git a/flask chatbot/venv/Lib/site-packages/pip/_vendor/html5lib/filters/sanitizer.py b/flask chatbot/venv/Lib/site-packages/pip/_vendor/html5lib/filters/sanitizer.py
new file mode 100644
index 000000000..aa7431d13
--- /dev/null
+++ b/flask chatbot/venv/Lib/site-packages/pip/_vendor/html5lib/filters/sanitizer.py
@@ -0,0 +1,916 @@
+"""Deprecated from html5lib 1.1.
+
+See `here This is a doc
') +This is a doc
') +,, and