Stella Platform Documentation

Stella Platform Documentation

  • Docs
  • Procedures
  • Reference
  • FAQ
  • Bot API
  • API
  • Languages iconEnglish
    • 中文

›FAQ Chatbot Setup

Get Started

  • Build your First Chatbot

Basic Facebook Chatbot Setup

  • Overview
  • 1.1: Connect to Channels
  • 1.2: Create New Tree
  • 1.3: Build the First Node
  • 1.4: Create a Global Node
  • 1.5: Build Child Nodes
  • 2.1: Production Channel
  • 3.1: Draft a Post
  • 3.2: Create Comment Reply

Basic Instagram Chatbot Setup

  • Overview
  • 1.1: Connect to Channels
  • 1.2: Create New Tree
  • 1.3: Build the First Node
  • 1.4: Create Global Node
  • 1.5: Build Child Nodes
  • 2.1: Create Story Mention
  • 2.2: Create Comment Reply
  • 2.3: Send Push Message

Basic Web Chat Chatbot Setup

  • Overview
  • 1.1: Connect to Channels
  • 1.2: Create New Tree
  • 1.3: Build the First Node
  • 1.4: Create a Global Node
  • 1.5: Build Child Nodes

Basic WhatsApp Chatbot Setup

  • Overview
  • 1.1: WABA Subscription
  • 1.2: Connect WABA
  • 1.3: Check Approval Status of WABA
  • 1.4: Setup WABA with Used WhatsApp Number
  • 1.5: Reset / Terminate WABA
  • 2.1: Create New Tree
  • 2.2: Build the First Node
  • 2.3: Create a Global Node
  • 2.4: Build Child Nodes
  • 2.5: Connect to Datasource
  • 3.1: Create Priority Group
  • 4.1: Create Product Message

Common Use Case Application

  • Overview
  • 1.1: Apply Fail-Safe to Chatbot
  • 1.2: Apply Member Unsubscription Flow to Chatbot

Advanced Chatbot Application

  • Overview
  • Apply Datasource to Chatbot
  • Apply Payload Value to Chatbot
  • Apply API to Chatbot
  • Apply Custom Locale to Chatbot
  • Apply Opt-in Flow to Chatbot
  • Apply Agenda to Chatbot
  • Apply Stella API to Chatbot
  • Apply Web Event to Webchat Chatbot

Error Handling

  • Handle Errors for Chatbot

Stella Partner Portal Setup

  • Overview
  • 1.1: Partner Portal Setup
  • 1.2: New Customer Onboarding
  • 1.3: WABA Setup
  • 1.4: Check Approval Status of WABA
  • 1.5: Setup WABA with Used WhatsApp Number
  • 1.6: Reset / Terminate WABA

Template Tree

  • Overview
  • Create Template Tree
  • Manage Proxy Tree

Chatbot Testing & Deployment

  • Overview
  • Stage One - Tree Building
  • Stage Two - Testing Stage
  • Stage Three - Production Deployment

Slack Live Chat Tree Setup

  • Overview
  • 1.1: Create Tree for Live Chat Assignment
  • 2.1: Set Up Pick Ticket Status
  • 2.2: Create Done Command
  • 2.3: Create Transfer Command
  • 2.4: Create Archive Function
  • 2.5: Create Member Tagging/Remarks Command
  • 3.1: Automatic End Live Chat Function

Zendesk Live Chat Tree Setup

  • Overview
  • 1.1: Create Tree for Assignment
  • 2.1: Create Done Command
  • 2.2: Auto End Live Chat Function

FAQ Chatbot Setup

  • Overview
  • 1.1: Exact Keyword Match
  • 1.2: Keyword Groups Match & Diversion
  • 2.1: Redirect to Existing Chatbot Tree
  • 2.2: Data Analytics for FAQ Chatbot
  • 2.3: NLP for FAQ Chatbot
  • 3.1: Filtering Questions for FAQ Chatbot

NLP Chatbot Setup

  • Overview
  • 1.1: Apply NLP to your Chatbot
  • 2.1: Set Up an NLP Fallback Tree
  • 3.1: NLP Fallback to Other Languages

Connect Shopify Store to Stella

  • Overview
  • 1.1: Integrate Stella to Shopify
  • 2.1: Setup Facebook Messenger Chatbot for Shopify
  • 2.2: Setup WhatsApp Chatbot for Shopify
  • 2.3: WhatsApp Customer Care Notification for Shopify

Stella Inbox Setup

  • Overview
  • 1.1: Connect Channel to Slack
  • 1.2: Access Control & Admin Panel
  • 1.3: Set up Customizable Message
  • 1.4: Inbox Channel
  • 2.1: Turn on Live Chat Directly
  • 2.2: End Live Chat Mode
  • 2.3: Ticketing
  • 2.4: Manage Ticket Helper
  • 2.5: Add Member Tag
  • 3.1: Send Chatbot Message

Keyword Groups Match & Diversion

What is your Result?

  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
  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
  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

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.

Getting Hands-on

Sample Tree Structure

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) => {
  try {
    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,
    })
  } catch {
    reject(e)
  }
})

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."
      })
    }
  }

)
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 conditions:
Trigger for Diversion

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

  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((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 and see if you can produce the expected outcome.
← 1.1: Exact Keyword Match2.1: Redirect to Existing Chatbot Tree →
  • What is your Result?
  • FAQ Data Source Format
  • Getting Hands-on
    • Sample Tree Structure
    • Edit the Tree Node for FAQ Module
    • Create a Tree Node for Diversion
Stella Platform Documentation
Docs
Get StartedBot API ReferenceAPI ReferenceStandard Procedures
Community
FAQUser ShowcaseChat with Us
Copyright © 2023 Sanuker Inc. Limited