Skip to main content

Keyword Groups Match & Diversion

Expected Outcome

  1. When user types the question (i.e. Do you support WhatsApp Chatbot Solution?), the FAQ module will scan the entire data source to see which entry has the highest number of keyword group(s) that matched with the input (score).
Data Source Example of FAQ Chatbot with Keyword Groups Match & Diversion
Data Source Example of FAQ Chatbot with Keyword Groups Match & Diversion
  1. Once matched with an FAQ entry, the chatbot will display the answer of the corresponding entry (In this case, a text response.).
Example of FAQ Chatbot with Keyword Groups Match & Diversion 1
Example of FAQ Chatbot with Keyword Groups Match & Diversion 1
  1. If there are multiple entries with the same score, the chatbot will display all matched questions (diversion).
Example of FAQ Chatbot with Keyword Groups Match & Diversion 2
Example of FAQ Chatbot with Keyword Groups Match & Diversion 2

FAQ Data Source Format

Please refer to level one procedure for the the FAQ Data Source Format.

You may download the sample FAQ data source in .CSV here.


Sample Tree Structure

Tree Structure of FAQ Chatbot with Keyword Groups Match & Diversion
Tree Structure of FAQ Chatbot with Keyword Groups Match & Diversion

Edit the Tree Node for FAQ Module

  1. Before proceeding, you should have completed the level one FAQ Chatbot with Exact Keyword Match. You may edit the level one tree directly or duplicate a new tree.

  2. Edit the pre-action of the FAQ Module tree node. Replace with the following code:

return new Promise(async (resolve, reject) => {
let text = this.messageEvent.data.text
text = text.replace(/\/n/g, "")
// text = text.replace(/(\/|\.|\*|\?|\+)/g, "")

let result = await this.fetchDataFromDataSource({
collectionName: "5fcb61ca1d080a48b09ccfed_stella_kb_faq_copy",
filter: {}
})

let maxScore = 0
result = result.map((doc) => {
try {
let score = 0
if (this.lodash.isArray(doc["Keyword Group 1"]) && this.lodash.get(doc, "Keyword Group 1.length")) {
let keywords = doc["Keyword Group 1"]
keywords = keywords.map((str) => {
str = str.replace("+", "\\+")
str = str.replace("$", "\\$")
str = str.replace(".", "\\.")
str = str.replace("!", "\\!")
return str
})
let reg = new RegExp(keywords.join("|"), "i")
if (reg.test(text)) {
score++
}
}
if (this.lodash.isArray(doc["Keyword Group 2"]) && this.lodash.get(doc, "Keyword Group 2.length")) {
let keywords = doc["Keyword Group 2"]
keywords = keywords.map((str) => {
str = str.replace("+", "\\+")
str = str.replace("$", "\\$")
str = str.replace(".", "\\.")
str = str.replace("!", "\\!")
return str
})
let reg = new RegExp(keywords.join("|"), "i")
if (reg.test(text)) {
score++
}
}
if (this.lodash.isArray(doc["Keyword Group 3"]) && this.lodash.get(doc, "Keyword Group 3.length")) {
let keywords = doc["Keyword Group 3"]
keywords = keywords.map((str) => {
str = str.replace("+", "\\+")
str = str.replace("$", "\\$")
str = str.replace(".", "\\.")
str = str.replace("!", "\\!")
return str
})
let reg = new RegExp(keywords.join("|"), "i")
if (reg.test(text)) {
score++
}
}
if (this.lodash.isArray(doc["Keyword Group 4"]) && this.lodash.get(doc, "Keyword Group 4.length")) {
let keywords = doc["Keyword Group 4"]
keywords = keywords.map((str) => {
str = str.replace("+", "\\+")
str = str.replace("$", "\\$")
str = str.replace(".", "\\.")
str = str.replace("!", "\\!")
return str
})
let reg = new RegExp(keywords.join("|"), "i")
if (reg.test(text)) {
score++
}
}
if (this.lodash.isArray(doc["Keyword Group 5"]) && this.lodash.get(doc, "Keyword Group 5.length")) {
let keywords = doc["Keyword Group 5"]
keywords = keywords.map((str) => {
str = str.replace("+", "\\+")
str = str.replace("$", "\\$")
str = str.replace(".", "\\.")
str = str.replace("!", "\\!")
return str
})
let reg = new RegExp(keywords.join("|"), "i")
if (reg.test(text)) {
score++
}
}
if (this.lodash.isArray(doc["Keyword Group 6"]) && this.lodash.get(doc, "Keyword Group 6.length")) {
let keywords = doc["Keyword Group 6"]
keywords = keywords.map((str) => {
str = str.replace("+", "\\+")
str = str.replace("$", "\\$")
str = str.replace(".", "\\.")
str = str.replace("!", "\\!")
return str
})
let reg = new RegExp(keywords.join("|"), "i")
if (reg.test(text)) {
score++
}
}
if (this.lodash.isArray(doc["Keyword Group 7"]) && this.lodash.get(doc, "Keyword Group 7.length")) {
let keywords = doc["Keyword Group 7"]
keywords = keywords.map((str) => {
str = str.replace("+", "\\+")
str = str.replace("$", "\\$")
str = str.replace(".", "\\.")
str = str.replace("!", "\\!")
return str
})
let reg = new RegExp(keywords.join("|"), "i")
if (reg.test(text)) {
score++
}
}
if (score > maxScore) {
maxScore = score
}
doc.score = score
} catch (error) {

console.log("doc", doc)
console.log("error", error)
}
return doc
})
console.log("maxScore", maxScore)
let ans = result.filter((doc) => {
return doc.score === maxScore && doc.score > 0
})
ans = ans.slice(0, 50)
console.log("ans", ans)

this.member.botMeta.tempData.faqAns = ans

resolve({
member: this.member,
})
})
tip

In default, the maximum number of diversions is 50. The limit can be manually changed by editing ans = ans.slice(0, 50)

  1. Edit the transformed response in "Advanced" of the FAQ Module tree node. Replace with the following code:
return new Promise((resolve, reject) => {
console.log("in FAQ Keyword")
let ans = this.member.botMeta.tempData.faqAns || []
console.log("ans", ans)
function convertNumberToEmoji(number) {
let result = ""
for (const digit of number) {
switch (digit) {
case "0":
result = `${result}0๏ธโƒฃ`
break
case "1":
result = `${result}1๏ธโƒฃ`
break
case "2":
result = `${result}2๏ธโƒฃ`
break
case "3":
result = `${result}3๏ธโƒฃ`
break
case "4":
result = `${result}4๏ธโƒฃ`
break
case "5":
result = `${result}5๏ธโƒฃ`
break
case "6":
result = `${result}6๏ธโƒฃ`
break
case "7":
result = `${result}7๏ธโƒฃ`
break
case "8":
result = `${result}8๏ธโƒฃ`
break
case "9":
result = `${result}9๏ธโƒฃ`
break
default:
}
}
return result
}

if (ans.length > 1) {
const questionList = ans.map((a, i) => {
let t = convertNumberToEmoji((i + 1).toString())
return `${t} ${a["Question"]}`
})
let response = {
type: "TEXT",
text: `Which questions below fit your meaning the best? Please select the option.\n\n${questionList.join("\n")}`,
}
console.log("response", response)
resolve(response)
} else if (ans.length == 1) {
let response = {}
switch (ans[0].Type) {
case "Text":
response.type = "TEXT"
response.text = ans[0].Text
if (ans[0].Preview === true || ans[0].Preview === "TRUE") {
response.preview_url = true
}
break
case "Image":
case "Image_Text":
response.type = "IMAGE"
response.url = ans[0].URL
response.text = ans[0].Text
break
case "Video":
case "GIF":
case "File":
response.type = "FILE"
response.url = ans[0].URL
response.text = ans[0].Text
response.filename = ans[0]["File Name"]
break
case "Audio":
response.type = "AUDIO"
response.url = ans[0].URL
break
default:
response = null
break;
}
resolve(response)
} else {
resolve({
type: "TEXT",
text: "Sorry, we don't have the answer for you at the moment. You may try to ask us in another way or reach our support team at hello@sanuker.com."
})
}
}

)
tip

The above code has included the matched questions option display for diversions in the form of emojis.

  1. Create a "post-action" (this is for handling the diversion logic) with the following code:
return new Promise((resolve, reject) => {
let ans = this.member.botMeta.tempData.faqAns
console.log("if ans exist, save compositeId")
console.log("ans", ans)
console.log("ans.length", this.lodash.size(ans))
this.member.botMeta.tempData.listLength = 0
if (this.lodash.size(ans) > 1) {
this.member.botMeta.nodeCompositeId = this.node.compositeId
this.member.botMeta.tree = this.node.tree
this.member.botMeta.tempData.listLength = this.lodash.size(ans)
}
resolve({
member: this.member,
})
})

Create a Tree Node for Diversion

  1. Create a tree node for diversion. Please remember to set the priority of this node to be higher than that of the FAQ Module Global Node. For example, set the priority value of FAQ Global Node to "10" and keep the Diversion Node as "0".
  1. Create a trigger in this tree node with the following code:
return this.messageEvent.type === "TEXT" && 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

Trigger for Diversion
Trigger for Diversion
  1. Create a pre-action (this is for handling the diversion logic) in this tree node with the following code:
return new Promise((resolve) => {
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 faqAns = this.member.botMeta.tempData.faqAns
let faq = faqAns[index]
this.member.botMeta.tempData.faq = faq
resolve({ member: this.member })
})
  1. Create a transformed response in โ€œAdvancedโ€ in this tree node for displaying the chatbot answer based on the diversion option chosen by the user with the following code:
return new Promise(async (resolve, reject) => {
let result = this.member.botMeta.tempData.faq
let response = {}
switch (result.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":
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
default:
response = null
break;
}
console.log("response", response)
resolve(response)
})
  1. Save the tree. Test your chatbot and see if you can get the expected outcome.