Filtering Questions for FAQ Chatbot
Apart from typing the text directly to the FAQ chatbot, filtering questions allows users to find their answer in a pre-set flow (similar to IVRS in phones). You can categorize your FAQ entries into 3 (or more) layers, so that users can just select the categories layer by layer until they reach the final answer.
Please make sure you have inputted "Category 1-3" & "Category 1-3 Priority". For details on FAQ data source format, please see here.
What is your Result?
- User goes through 3 layers of filtering questions and ends up with a filtering result.
- In the backend, Stella sorts these questions based on the FAQ data source. Category 1-3 represents the options of chatbot filtering questions 1-3. The order of the options is based on Category 1-3 Priority.
FAQ Data Source Format
Please refer to level one procedure for the the FAQ Data Source Format.
Getting Hands-on
Sample Tree Structure
Create the 1st Tree Node - Category 1
Before proceeding, you should have completed up to level 4.1 and 4.2 (or 4.3 optionally) FAQ procedure. You may edit the level four tree directly or duplicate a new tree.
Create a tree node in the level four tree and rename it as "Category 1".
- Create a Pre-action with the following code and rename it as "Save Category 1 Answers".
return new Promise(async (resolve, reject) => {
try {
console.log("in Save Category 1 answers")
let result = await this.fetchDataFromDataSource({
collectionName: "Insert your Data Source ID here",
filter: {}
})
console.log("result", result)
result = this.lodash.uniqBy(result, "Category 1")
console.log("result", result)
result = this.lodash.reject(result, { "Category 1": "" })
console.log("result", result)
result = this.lodash.sortBy(result, "Category 1 Priority")
this.member.botMeta.tempData.currentAnswers = result.map(obj => obj["Category 1"])
this.member.botMeta.tempData.listLength = result.length
resolve({
member: this.member,
})
} catch (e) {
reject(e)
}
})
- Input the Data Source ID of the FAQ data source you have been using since level one into the above code.
- Create a Response under "Advanced" with the following code and rename it as "Category 1 Question".
return new Promise((resolve) => {
let currentAnswers = this.member.botMeta.tempData.currentAnswers || []
let text = `Please input number to select🔢:\n`
currentAnswers.forEach((str, i) => {
let t = ""
switch (i + 1) {
case 1:
t = "1️⃣"
break
case 2:
t = "2️⃣"
break
case 3:
t = "3️⃣"
break
case 4:
t = "4️⃣"
break
case 5:
t = "5️⃣"
break
case 6:
t = "6️⃣"
break
case 7:
t = "7️⃣"
break
case 8:
t = "8️⃣"
break
case 9:
t = "9️⃣"
break
case 10:
t = "🔟️"
break
case 11:
t = "1️⃣1️⃣"
break
case 12:
t = "1️⃣2️⃣"
break
case 13:
t = "1️⃣3️⃣"
break
case 14:
t = "1️⃣4️⃣"
break
case 15:
t = "1️⃣5️⃣"
break
case 16:
t = "1️⃣6️⃣"
break
case 17:
t = "1️⃣7️⃣"
break
case 18:
t = "1️⃣8️⃣"
break
case 19:
t = "1️⃣9️⃣"
break
case 20:
t = "2️⃣0️⃣"
break
default:
}
text = `${text}\n${t} ${str}`
})
text = `${text}\n\nCan't find what you are looking for? Type keywords to ask Stella. You can also type *#* anytime to return to this menu.`
resolve({
type: "TEXT",
text,
})
})
You can edit the filtering question content (the header & footer of the text message) marked inside the back quotation marks of
text =
in the above code.${text}
refers to the Category 1 options displayed from FAQ data source.Save this tree node.
You can also create a Global Node that redirects user to the Category 1 tree node. As a result, users can easily return to Category 1 by keyword when there are multiple categories.
Create the 2nd Tree Node - Category 2
- Select the Category 1 tree node. Create the next tree node and rename it as "Category 2".
- Create a trigger with the following conditions & relationships:
First condition - Type Text:
this.messageEvent.type === "TEXT"
Second condition - Number within list length:
this.member.botMeta.tempData.listLength ? new RegExp("\\b" + [...Array(this.member.botMeta.tempData.listLength + 1).keys()].slice(1).join("\\b|\\b") + "\\b").test(this.messageEvent.data.text) : false
- Create a Pre-action with the following code and rename it as "Save Category 1".
return new Promise((resolve) => {
console.log("in Save FAQ Category 1")
if (/(\+)/i.test(this.messageEvent.data.text)) {
resolve()
} else {
let match = this.messageEvent.data.text.match(new RegExp("\\b" + [...Array(this.member.botMeta.tempData.listLength + 1).keys()].slice(1).join("\\b|\\b") + "\\b"))
let indexStr = match[0]
let index = parseInt(indexStr) - 1
let currentAnswers = this.member.botMeta.tempData.currentAnswers
let currentAnswer = currentAnswers[index]
this.member.botMeta.tempData.category1 = currentAnswer
resolve({ member: this.member })
}
})
- Create a 2nd Pre-action with the following code and rename it as "Save Category 2 Answers".
return new Promise(async (resolve, reject) => {
try {
console.log("in Save Category 2 Answers")
let result = await this.fetchDataFromDataSource({
collectionName: "Insert your Data Source ID here",
filter: {
"Category 1": this.member.botMeta.tempData.category1
}
})
console.log("result", result)
if (result.length === 1) {
this.member.displayAnswer = true
this.member.botMeta.tempData.faq = result[0]
resolve({ member: this.member })
} else {
result = this.lodash.uniqBy(result, "Category 2")
console.log("result", result)
result = this.lodash.reject(result, { "Category 2": "" })
console.log("result", result)
result = this.lodash.sortBy(result, "Category 2 Priority")
this.member.botMeta.tempData.currentAnswers = result.map(obj => obj["Category 2"])
this.member.botMeta.tempData.listLength = result.length
resolve({
member: this.member,
})
}
} catch (e) {
reject(e)
}
})
- Input the Data Source ID of the FAQ data source you have been using since level one into the above code.
- Create a Response under "Advanced" with the following code and rename it as "Category 2 Question".
return new Promise((resolve) => {
if (this.member.displayAnswer) {
resolve()
} else {
let currentAnswers = this.member.botMeta.tempData.currentAnswers || []
let text = `Do you want to know about ${this.member.botMeta.tempData.category1} ?\n`
currentAnswers.forEach((str, i) => {
let t = ""
switch (i + 1) {
case 1:
t = "1️⃣"
break
case 2:
t = "2️⃣"
break
case 3:
t = "3️⃣"
break
case 4:
t = "4️⃣"
break
case 5:
t = "5️⃣"
break
case 6:
t = "6️⃣"
break
case 7:
t = "7️⃣"
break
case 8:
t = "8️⃣"
break
case 9:
t = "9️⃣"
break
case 10:
t = "1️⃣0️⃣️"
break
case 11:
t = "1️⃣1️⃣"
break
case 12:
t = "1️⃣2️⃣"
break
case 13:
t = "1️⃣3️⃣"
break
case 14:
t = "1️⃣4️⃣"
break
case 15:
t = "1️⃣5️⃣"
break
case 16:
t = "1️⃣6️⃣"
break
case 17:
t = "1️⃣7️⃣"
break
case 18:
t = "1️⃣8️⃣"
break
case 19:
t = "1️⃣9️⃣"
break
case 20:
t = "2️⃣0️⃣"
break
default:
}
text = `${text}\n${t} ${str}`
})
text = `${text}\n\nCan't find what you are looking for? Type keywords to ask Stella. You can also type *#* anytime to return to this menu.`
resolve({
type: "TEXT",
text,
})
}
})
You can edit the filtering question content (the header & footer of the text message) marked inside the back quotation marks of
text =
in the above code.${text}
refers to the Category 2 options displayed from FAQ data source.${this.member.botMeta.tempData.category1}
displays the Category 1 that user has chosen.Toggle on the Advanced tab in "Redirect" and paste the following code:
return new Promise((resolve) => {
if (this.member.displayAnswer) {
resolve({
tree: this.node.tree,
nodeCompositeId: "Input the node composite ID of the filtering result tree node here",
runPreAction: false
})
} else {
resolve()
}
})
Input the node composite ID of the filtering result tree node into the above code. You will be able to retrieve the node composite ID in the coming steps. Remember to come back and edit later.
Save this tree node.
Create the 3rd Tree Node - Category 3
- Select the Category 2 tree node. Create the next tree node and rename it as "Category 3".
- Use the same trigger you made in Step 9 tree node.
- Create a Pre-action with the following code and rename it as "Save Category 2".
return new Promise((resolve) => {
console.log("in Save FAQ Category 2")
let match = this.messageEvent.data.text.match(new RegExp("\\b" + [...Array(this.member.botMeta.tempData.listLength + 1).keys()].slice(1).join("\\b|\\b") + "\\b"))
let indexStr = match[0]
let index = parseInt(indexStr) - 1
let currentAnswers = this.member.botMeta.tempData.currentAnswers
let currentAnswer = currentAnswers[index]
console.log("category2 ans", currentAnswer)
this.member.botMeta.tempData.category2 = currentAnswer
resolve({ member: this.member })
})
- Create a 3rd Pre-action with the following code and rename it as "Save Category 3 Answers".
return new Promise(async (resolve, reject) => {
try {
console.log("in Save Category 3 Answers")
let result = await this.fetchDataFromDataSource({
collectionName: "Insert your Data Source ID here",
filter: {
"Category 1": this.member.botMeta.tempData.category1,
"Category 2": this.member.botMeta.tempData.category2,
}
})
console.log("result", result)
if (result.length === 1) {
this.member.displayAnswer = true
this.member.botMeta.tempData.faq = result[0]
resolve({ member: this.member })
} else {
result = this.lodash.uniqBy(result, "Category 3")
console.log("result", result)
result = this.lodash.reject(result, { "Category 3": "" })
console.log("result", result)
result = this.lodash.sortBy(result, "Category 3 Priority")
this.member.botMeta.tempData.currentAnswers = result.map(obj => obj["Category 3"])
this.member.botMeta.tempData.listLength = result.length
resolve({
member: this.member,
})
}
} catch (e) {
reject(e)
}
})
- Input the Data Source ID of the FAQ data source you have been using since level one into the above code.
- Create a Response under "Advanced" with the following code and rename it as "Category 3 Question".
return new Promise((resolve) => {
if (this.member.displayAnswer) {
resolve()
} else {
let currentAnswers = this.member.botMeta.tempData.currentAnswers || []
let text = `Do you want to know about ${this.member.botMeta.tempData.category2}?\n`
currentAnswers.forEach((str, i) => {
let t = ""
switch (i + 1) {
case 1:
t = "1️⃣"
break
case 2:
t = "2️⃣"
break
case 3:
t = "3️⃣"
break
case 4:
t = "4️⃣"
break
case 5:
t = "5️⃣"
break
case 6:
t = "6️⃣"
break
case 7:
t = "7️⃣"
break
case 8:
t = "8️⃣"
break
case 9:
t = "9️⃣"
break
case 10:
t = "1️⃣0️⃣️"
break
case 11:
t = "1️⃣1️⃣"
break
case 12:
t = "1️⃣2️⃣"
break
case 13:
t = "1️⃣3️⃣"
break
case 14:
t = "1️⃣4️⃣"
break
case 15:
t = "1️⃣5️⃣"
break
case 16:
t = "1️⃣6️⃣"
break
case 17:
t = "1️⃣7️⃣"
break
case 18:
t = "1️⃣8️⃣"
break
case 19:
t = "1️⃣9️⃣"
break
case 20:
t = "2️⃣0️⃣"
break
default:
}
text = `${text}\n${t} ${str}`
})
text = `${text}\n\nCan't find what you are looking for? Type keywords to ask Stella. You can also type *#* anytime to return to this menu.`
resolve({
type: "TEXT",
text,
})
}
})
You can edit the filtering question content (the header & footer of the text message) marked inside the back quotation marks of
text =
in the above code.${text}
refers to the Category 3 options displayed from FAQ data source.${this.member.botMeta.tempData.category2}
displays the Category 1 that user has chosen.Toggle on the Advanced tab in "Redirect" and use the same code as you've made in Step 15.
Input the node composite ID of the filtering result tree node into the above code. You will be able to retrieve the node composite ID in coming steps. Remember to come back and edit later.
Save this tree node.
Create the 4th Tree Node - Filtering Result
- Select the Category 3 tree node. Create the next tree node and rename it as "Filtering Result".
- Use the same trigger you made in Step 9 tree node.
- Create a Pre-action with the following code and rename it as "Save Category 3".
return new Promise((resolve) => {
console.log("in Save FAQ Category 3")
let match = this.messageEvent.data.text.match(new RegExp("\\b" + [...Array(this.member.botMeta.tempData.listLength + 1).keys()].slice(1).join("\\b|\\b") + "\\b"))
let indexStr = match[0]
let index = parseInt(indexStr) - 1
let currentAnswers = this.member.botMeta.tempData.currentAnswers
let currentAnswer = currentAnswers[index]
this.member.botMeta.tempData.category4 = currentAnswer
console.log("Category 3", currentAnswer)
resolve({ member: this.member })
})
- Create a 2nd Pre-action with the following code and rename it as "Save Filtering Result".
return new Promise(async (resolve, reject) => {
try {
console.log("in Save Filtering Result")
let result = await this.fetchDataFromDataSource({
collectionName: "Insert your Data Source ID here",
filter: {
"Category 1": this.member.botMeta.tempData.category1,
"Category 2": this.member.botMeta.tempData.category2,
"Category 3": this.member.botMeta.tempData.category3,
}
})
console.log("result", result)
this.member.botMeta.tempData.faq = result[0]
resolve({
member: this.member,
})
} catch (e) {
reject(e)
}
})
- Input the Data Source ID of the FAQ data source you have been using since level one into the above code.
- Create a Response under "Advanced" with the following code and rename it as "Show Selected Question Content".
return new Promise((resolve, reject) => {
console.log("in Show Selected Question Content")
let result = this.member.botMeta.tempData.faq
console.log("result", result)
let response = {}
let type = result["Type"]
console.log("type", type)
switch (type) {
case "Text":
response.type = "TEXT"
response.text = result.Text
if (result.Preview === true || result.Preview === "TRUE") {
response.preview_url = true
}
break
case "Image":
case "Image_Text":
case "Image with caption":
response.type = "IMAGE"
response.url = result.URL
response.text = result.Text
break
case "Video":
case "GIF":
case "File":
response.type = "FILE"
response.url = result.URL
response.text = result.Text
response.filename = result["File Name"]
break
case "Audio":
response.type = "AUDIO"
response.url = result.URL
break
case "Carousel":
response.type = "CAROUSEL"
response.carousel = [{
title: result.Text,
image_url: result.URL,
buttons: [{
type: "web_url",
title: result["Button title"],
url: result["Button url"],
}]
}]
break
case "Button":
response.type = "BUTTON"
response.text = result.Text,
response.buttons = [{
type: "web_url",
title: result["Button title"],
url: result["Button url"],
}]
break
default:
response = null
break;
}
console.log("response", response)
resolve(response)
})
- Save and see if you can produce the expected outcome.