沟通是建立伟大品牌的关键,在这个数字时代,我们有很多机会和方式来进行伟大的沟通。从社交媒体到电子邮件,再到短信和电话,通信的极限已经过考验。通讯方面的一项重大创新必须是聊天机器人。

简单地说,chatbot是一个软件应用程序,用于通过文本或文本转语音进行在线聊天对话。它可以用来代替与真人代理的真实聊天。它已被用于各种场景,包括但不限于个人助理、客户支持、订票、电子购物,还有电子银行等等。

本教程将向您展示如何使用 Next.js 和 Tailwind CSS 作为前端和 Strapi 无头 CMS 作为后端来构建基于规则的聊天机器人助手。 .

您可以在此处找到完成的前端代码的链接,以及在此处的后端代码的链接。

使用聊天机器人的优势

在我们进一步阅读本文之前,有必要了解一下聊天机器人在充分使用时会带来什么。使用聊天机器人的一些优点包括:

  1. 可用性:由于聊天机器人是一个可以托管的软件应用程序,这意味着它不会休眠,因此它在一天中的任何时间都可以使用。这为公司或个人提供了良好的品牌形象,客户可以在一天中的任何时间获得针对他们不同要求的支持。

2、降低成本:作为一个可以多地部署的应用,降低了运行大量客服代表的成本。

3.效率:单个聊天机器人可以部署在不同的地方,也可以同时处理多个请求。

  1. 改善在线形象:一些聊天机器人允许集成到其他消息传递平台,这允许跨多个平台发送一致的响应,从而提高品牌识别度。

先决条件

在开始本教程之前,您需要拥有

  • Node.js 安装在您的本地机器上(v12 或 v14) - 查看此教程以获取有关如何安装 Node.js 的说明

  • 基本了解 Strapi - 开始使用此快速指南

  • Next.js 基础知识

  • Tailwind CSS 基础知识

下一个 Js 是什么

Next.js 是一个很棒的 React 框架,用于构建高度动态的应用程序。它具有预渲染、自动代码拆分以及许多其他开箱即用的强大功能。

什么是 Tailwind CSS

Tailwind CSS 是一个实用程序优先的 CSS 框架,用于快速构建自定义用户界面。使用 Tailwind CSS,我们可以直接在 HTML 类中编写 CSS。这非常有用,因为我们不需要导入外部样式表或使用单独的库进行 UI 设计。

什么是Strapi

Strapi 是一个 Node.js 开源无头 CMS,它允许我们轻松开发 API 和管理内容,而无需从头开始构建项目的麻烦。与我们习惯的僵化的传统 CMS 不同,它允许自定义和自托管。

我们可以使用任何 REST API 客户端或 GraphQL 轻松地更快地构建 API 并通过 API 使用内容。

脚手架 Strapi 项目

设置一个新的 Strapi 项目非常简单,只需运行以下命令:

    npx create-strapi-app chatbot-assistant --quickstart

进入全屏模式 退出全屏模式

chatbot-assistant更改为项目的首选名称

注:在设置过程中,我们不会使用任何 Strapi 模板

这将安装并创建一个 Strapi 应用程序并在本地设置项目。

安装后,浏览器会在 localhost:1337 上打开一个页面,提示设置超级管理员帐户以继续使用 Strapi。

构建交换集合

接下来,我们将创建一个新的集合类型,它将存储每个问题的详细信息及其各自的答案。

因此,创建一个名为“interchange”的集合类型,其中包含以下字段:questionanswer

[](https://res.cloudinary.com/practicaldev/image/fetch/s--D0aytoBs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox。 com/s_61EC144653D77A5A3225C05DD53ECF56DD8B12AF0D4564DE52DDD1C630653BE1_1636572411431_Screenshot%2B2021-11-10%2Bat%2B20.26.45.png)

单击“继续”将打开另一个屏幕以选择此集合的字段。从列表中选择“文本”字段并提供question作为其名称。

[](https://res.cloudinary.com/practicaldev/image/fetch/s--O3OuwMmF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox。 com/s_61EC144653D77A5A3225C05DD53ECF56DD8B12AF0D4564DE52DDD1C630653BE1_1636572552842_Screenshot%2B2021-11-10%2Bat%2B20.29.07.png)

接下来,我们在 Base Settings 中选择Long Text类型,这将允许我们在创建interchange时输入更长且更具描述性的问题。

[](https://res.cloudinary.com/practicaldev/image/fetch/s--xVRfsUe8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox。 com/s_61EC144653D77A5A3225C05DD53ECF56DD8B12AF0D4564DE52DDD1C630653BE1_1636572663219_Screenshot%2B2021-11-10%2Bat%2B20.30.59.png)

接下来,我们进入“高级设置”选项卡并选中“必填字段”框以确保在创建新交换时需要此字段。此外,我们选中“唯一字段”框以防止在我们的交换中出现相同的问题。

[](https://res.cloudinary.com/practicaldev/image/fetch/s--gtEajNuy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox。 com/s_61EC144653D77A5A3225C05DD53ECF56DD8B12AF0D4564DE52DDD1C630653BE1_1636572861278_Screenshot%2B2021-11-10%2Bat%2B20.34.17.png)

我们点击添加另一个字段来添加答案字段。下表显示了此集合中两个字段的属性:

字段名称

字段类型

必需的

独特的

问题

长文本

真的

真的

回答

富文本

真的

错误的

播种带

接下来我们将继续定义我们的机器人给出的问题和相应的答案。

要将数据添加到集合中,我们选择左侧边栏的Interchanges集合,单击“添加新的交换”并填写详细信息。

[交换集合](https://res.cloudinary.com/practicaldev/image/fetch/s--xT0TnOks--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments。 dropbox.com/s_61EC144653D77A5A3225C05DD53ECF56DD8B12AF0D4564DE52DDD1C630653BE1_1636573966990_Screenshot%2B2021-11-10%2Bat%2B20.52.41.png)

在本教程中,我将在交换时考虑到关于我的交换,因此我们的机器人可以充当助手,告诉访问者更多关于我们和我们的服务的信息。播种后,我们的集合看起来是这样的:

[种子交换集合](https://res.cloudinary.com/practicaldev/image/fetch/s--UqlvmqiL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments .dropbox.com/s_61EC144653D77A5A3225C05DD53ECF56DD8B12AF0D4564DE52DDD1C630653BE1_1636582185036_Screenshot%2B2021-11-10%2Bat%2B23.09.41.png)

允许公共访问

默认情况下,每当您创建 API 时,Strapi 都会根据 API 的名称创建六个端点。为Interchange生成的端点应如下所示:

[](https://res.cloudinary.com/practicaldev/image/fetch/s--ajpZtLQk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox。 com/s_61EC144653D77A5A3225C05DD53ECF56DD8B12AF0D4564DE52DDD1C630653BE1_1636582427444_Screenshot%2B2021-11-10%2Bat%2B23.13.40.png)

默认情况下,它们都将被限制为公共访问。我们需要告诉 Strapi,您可以将这些经过检查的端点公开给公众。转到_设置>用户和权限插件>角色_并单击以编辑_公共角色_。接下来,向下滚动到权限并检查find是否为Interchange

此端点:http://localhost:1337/interchanges现在应该可用。示例响应如下:

    [{"id":2,"question":"What are your skills?","answer":"I am skilled at frontend development as well as backend development.","published_at":"2021-11-10T22:04:01.379Z","created_at":"2021-11-10T22:00:18.983Z","updated_at":"2021-11-10T22:04:01.445Z"},
    {"id":3,"question":"How can I reach you?","answer":"You can reach me via my phone line: 0900000000 or via my twitter handle: interchange_demo.","published_at":"2021-11-10T22:04:09.033Z","created_at":"2021-11-10T22:01:07.886Z","updated_at":"2021-11-10T22:04:09.039Z"},
    {"id":4,"question":"How long have you been a developer?","answer":"6 years.","published_at":"2021-11-10T22:04:15.757Z","created_at":"2021-11-10T22:01:50.037Z","updated_at":"2021-11-10T22:04:15.765Z"},
    {"id":5,"question":"Do you have leadership experience?","answer":"Yes, I do.","published_at":"2021-11-10T22:04:21.346Z","created_at":"2021-11-10T22:02:23.115Z","updated_at":"2021-11-10T22:04:21.354Z"},
    {"id":6,"question":"What other skills do you have apart from software development?\n","answer":"I am a technical writer and also a community builder.","published_at":"2021-11-10T22:04:26.091Z","created_at":"2021-11-10T22:03:21.103Z","updated_at":"2021-11-10T22:04:26.102Z"}]

进入全屏模式 退出全屏模式

搭建 NextJs 项目

创建 Next.js 应用程序

要创建 Next.js 应用程序,请打开终端cd到您要在其中创建应用程序的目录,然后运行以下命令:

    npx create-next-app -e with-tailwindcss nextjs-chatbot

进入全屏模式 退出全屏模式

这也将使用项目配置 Tailwind CSS

运行 Next.js 开发服务器

接下来我们cd进入新创建的目录,在我们的例子中是nextjs-chatbot

    cd nextjs-chatbot

进入全屏模式 退出全屏模式

之后,我们通过运行以下命令启动开发服务器:

    npm run dev

进入全屏模式 退出全屏模式

如果一切正常,next.js 服务器现在应该在http://localhost:3000上运行,我们应该在浏览器上显示:

[](https://res.cloudinary.com/practicaldev/image/fetch/s--vr2ocKp2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox。 com/s_1F009F9E3EE9CBA1F20D406E4458E971D06EA9323A4B53D1F0303E815DE8B180_1634387555599_Screenshot%2B2021-10-16%2Bat%2B13.32.30.png)

构建 NextJs 组件

接下来,我们继续使用我们选择的任何文本编辑器来开发应用程序的其余部分。我们在本教程中使用 Visual Studio Code。打开已安装的项目,我们应该有一个文件夹结构,如下所示:

[](https://res.cloudinary.com/practicaldev/image/fetch/s--FkP5Flqv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox。 com/s_61EC144653D77A5A3225C05DD53ECF56DD8B12AF0D4564DE52DDD1C630653BE1_1636639126217_Screenshot%2B2021-11-11%2Bat%2B14.58.42.png)

要开始界面设计,我们将删除pages文件夹中的 index.js 文件的所有内容,这是 NextJs 项目的入口点。我们将其替换为以下内容:

    import Head from 'next/head'
    import { useState, useEffect } from 'react'
    import { createMarkup, tranformInterchanges, showBotTyping, getBotAnswer, fetchQuery } from '../utils/helper'
    export default function Home( { interchanges }) {
      const [userQuestion, setUserQuestion] = useState('')
      const [allow, setAllow] = useState(false)
      const [interchange, setInterchange] = useState([])

    useEffect(async () => {
     await showBotTyping(setInterchange, [], setAllow)
     setInterchange([{
      owner: false,
      text: tranformInterchanges(interchanges, true)
    }])
    }, [interchanges])

    const handleSubmit = async (e) => {
      e.preventDefault()
      if(!userQuestion || !allow) return
      const uQ = userQuestion
      const newInterchange = [...interchange, {
        owner: true,
        text: userQuestion
      }]
      setInterchange(newInterchange)
      setUserQuestion('')
      setAllow(false)
      getBotAnswer(interchanges, setInterchange,  uQ, newInterchange, setAllow)
    }
      return (
        <div className="flex flex-col font-mono items-center justify-center min-h-screen">
          <Head>
            <title>ChatBot Assistant</title>
            <link rel="icon" href="/favicon.ico" />
          </Head>
          <form className="flex flex-col w-full flex-1" onSubmit={handleSubmit}>

            <header className="flex w-full h-24 fixed bg-black border-b">

              <span className="flex items-center text-white font-bold text-lg p-2"> Bot Assistant</span>
           </header>
            <div className="flex flex-col mt-24 bg-gray-200  overflow-scroll p-2 w-full" style={{ height: "80vh"}}>
            {interchange.map((chat,i) => (
              chat.owner ? 
              <div key={i} className = "user flex flex-row my-2 w-full p-2">
              <span className = "w-2/3"></span>
              <span className = "w-1/3 bg-gray-100 p-2 rounded">
               {chat.text}
              </span>
            </div>
             :   
              <div key={i} className = "bot my-2 bg-gray-100 w-1/2 lg:w-1/3  p-2 rounded">
                <span dangerouslySetInnerHTML={createMarkup(chat.text)} />
              </div>
            ))}
            <div id="scrollTo"></div>
            </div>
            <footer className = "flex flex-row justify-between items-center p-1 h-5/6  w-full -bottom-5">
            <div className = "flex flex-row justify-between flex-1 bg-white w-full">
              <input className = " bg-gray-200 w-2/3 p-2 " placeholder="Type a message" value={userQuestion} onChange={ (e) => { setUserQuestion(e.target.value)}}/>
              <button className = " bg-black p-2 ml-2 w-1/3  text-white" type="submit"> Send</button>
            </div>
            </footer>
          </form>

        </div>
      )
    }

    export async function getStaticProps() {
      const interchanges = await fetchQuery('interchanges')
      return {
        props: {
          interchanges
        }
      }
    }

进入全屏模式 退出全屏模式

上面代码的解释

上面的代码用于显示聊天界面的 UI,即消息、输入区域和发送按钮。

在上面的部分中,我们从 helper.js 文件中导入了帮助函数,这些函数将用于使应用程序正常运行:

    import Head from 'next/head'
    import { useState, useEffect } from 'react'
    import { createMarkup, tranformInterchanges, showBotTyping, getBotAnswer, fetchQuery } from '../utils/helper'

进入全屏模式 退出全屏模式

接下来,使用 useState 钩子,我们创建多个变量,我们稍后将在应用程序中使用以实现各种目的:

userQuestion用于跟踪用户输入的当前问题,即输入框中的文本。

allow状态用于防止用户在机器人当前仍在回复先前发送的消息时发送多条消息。

interchange状态用于内部存储botuser之间的消息

      const [userQuestion, setUserQuestion] = useState('')
      const [allow, setAllow] = useState(false)
      const [interchange, setInterchange] = useState([])

进入全屏模式 退出全屏模式

useEffect中的代码部分在应用程序启动后触发并执行以下操作:

首先,它显示了机器人打字几秒钟的动画。

接下来,它设置机器人发送给用户的第一条/默认消息,该消息包含应用程序启动时从Strapi API获得的问题。

    useEffect(async () => {
     await showBotTyping(setInterchange, [], setAllow)
     setInterchange([{
      owner: false,
      text: tranformInterchanges(interchanges, true)
    }])
    }, [interchanges])

进入全屏模式 退出全屏模式

handleSubmit函数是一旦用户向机器人发送消息就会被调用的处理程序。

只有当用户确实在文本框中输入了值并且机器人没有当前正在处理的问题时,我们才允许成功提交。

接下来,将用户的新问题添加到交换状态,从输入框中删除用户输入的文本并将允许状态设置为 false,以防止用户在机器人返回结果之前发送另一个文本。

然后使用getBotAnswer辅助函数从机器人获取答案并更新 UI。

    const handleSubmit = async (e) => {
      e.preventDefault()
      if(!userQuestion || !allow) return
      const uQ = userQuestion
      const newInterchange = [...interchange, {
        owner: true,
        text: userQuestion
      }]
      setInterchange(newInterchange)
      setUserQuestion('')
      setAllow(false)
      getBotAnswer(interchanges, setInterchange,  uQ, newInterchange, setAllow)
    }

进入全屏模式 退出全屏模式

index.js文件中可以看出,我们导入了一个helper.js文件。这意味着我们需要创建这个文件,为此我们在应用程序的根目录下创建一个名为utils的文件夹,然后在其中创建一个名为helper.js的文件。

接下来,将下面文件的内容复制到新创建的helper.js文件中:

    const baseUrl = process.env.BASE_URL || 'localhost:1337'
    export const createMarkup = (text) => {
      return {__html: text};
    }
    export const tranformInterchanges = (interchanges, initial = false) => {
      let initialText = initial ? `<b>Welcome to my page, glad to have you here 🥰</b> <br/>
     Tell me what you would like to know: <br/> <br/> `: ''

      interchanges.map((e, i) => {
          initialText += `${(i+1)}. ${e.question} <br /> <br />`
      })
      return initialText
    }
    export const searchInterchange = (interchanges, question) => {
      let result = interchanges.find(e => e.question.toLowerCase().includes(question.toLowerCase()))
      if(result) return result.answer
      return `Cant seem to understand your question, please try again 😔<br><br>
        Here are the options again: <br/> <br/>
        ${tranformInterchanges(interchanges)}
      `
    }

    export const showBotTyping = async (setInterchange, prevState, setAllow) => {
      scrollDown()
      await new Promise(resolve => setTimeout(resolve, 1000));
      setInterchange([...prevState, {
        owner: false,
        text: 'Bot Assistant is typing.'
      }])
      scrollDown()
      await new Promise(resolve => setTimeout(resolve, 1000));
      setInterchange([...prevState, {
        owner: false,
        text: 'Bot Assistant is typing..'
      }])
      scrollDown()
      await new Promise(resolve => setTimeout(resolve, 1000));
      setInterchange([...prevState, {
        owner: false,
        text: 'Bot Assistant is typing...'
      }])
      scrollDown()

      await new Promise(resolve => setTimeout(resolve, 1000));
      setAllow(true)
      scrollDown()
    }

    export const getBotAnswer = async (interchanges, setInterchange, question, prevState, setAllow) => {
      await showBotTyping(setInterchange, prevState, setAllow)
      setInterchange([...prevState, {
        owner: false,
        text: searchInterchange(interchanges,question)
      }])
      scrollDown()
    }

    const scrollDown = () => {
      document.getElementById('scrollTo').scrollIntoView({behavior: "smooth", block: "start"});
    }

    export const fetchQuery = async (path, params = null) => {
      let url
      if (params !== null) {
        url = `${baseUrl}/${path}/${params}`
      } else {
        url = `${baseUrl}/${path}`
      }
      const response = await fetch(`http://${url}`)
      const data = await response.json()
      return data
    }

进入全屏模式 退出全屏模式

上述代码解释

上面的代码包含应用程序中使用的各种帮助函数。

首先,我们为 API 设置base URL,在我们的例子中是localhost:1337,但如果我们最终托管Strapi API,我们会将.env文件中的BASE_URL更新为远程Strapi API

接下来,我们使用createMarkup来显示来自机器人的消息以及它附带的任何 HTML 格式。

    const baseUrl = process.env.BASE_URL || 'localhost:1337'
    export const createMarkup = (text) => {
      return {__html: text};
    }

进入全屏模式 退出全屏模式

tranformInterchanges函数用于显示机器人发送给用户的第一条消息,其中包含可以以精细格式提出的所有可能问题:

    export const tranformInterchanges = (interchanges, initial = false) => {
      let initialText = initial ? `<b>Welcome to my page, glad to have you here 🥰</b> <br/>
     Tell me what you would like to know: <br/> <br/> `: ''

      interchanges.map((e, i) => {
          initialText += `${(i+1)}. ${e.question} <br /> <br />`
      })
      return initialText
    }

进入全屏模式 退出全屏模式

searchInterchange是应用程序的核心,它是扫描所有用户问题的地方,以查看是否有足够的响应。如果有,它会从提供的answers返回一个响应,否则它会返回一个响应,表明没有找到结果,同时还显示用户可以提出的所有问题。

    export const searchInterchange = (interchanges, question) => {
      let result = interchanges.find(e => e.question.toLowerCase().includes(question.toLowerCase()))
      if(result) return result.answer
      return `Cant seem to understand your question, please try again 😔<br><br>
        Here are the options again: <br/> <br/>
        ${tranformInterchanges(interchanges)}
      `
    }

    export const showBotTyping = async (setInterchange, prevState, setAllow) => {
      scrollDown()
      await new Promise(resolve => setTimeout(resolve, 1000));
      setInterchange([...prevState, {
        owner: false,
        text: 'Bot Assistant is typing.'
      }])
      scrollDown()
      await new Promise(resolve => setTimeout(resolve, 1000));
      setInterchange([...prevState, {
        owner: false,
        text: 'Bot Assistant is typing..'
      }])
      scrollDown()
      await new Promise(resolve => setTimeout(resolve, 1000));
      setInterchange([...prevState, {
        owner: false,
        text: 'Bot Assistant is typing...'
      }])
      scrollDown()

      await new Promise(resolve => setTimeout(resolve, 1000));
      setAllow(true)
      scrollDown()
    }

进入全屏模式 退出全屏模式

showBotTyping用于模仿人类,它通过稍微延迟其响应并利用该时间显示机器人正在输入的消息来给机器人一种人类感觉。一旦机器人返回响应,这个打字动画就会从聊天记录中删除。

    export const showBotTyping = async (setInterchange, prevState, setAllow) => {
      scrollDown()
      await new Promise(resolve => setTimeout(resolve, 1000));
      setInterchange([...prevState, {
        owner: false,
        text: 'Bot Assistant is typing.'
      }])
      scrollDown()
      await new Promise(resolve => setTimeout(resolve, 1000));
      setInterchange([...prevState, {
        owner: false,
        text: 'Bot Assistant is typing..'
      }])
      scrollDown()
      await new Promise(resolve => setTimeout(resolve, 1000));
      setInterchange([...prevState, {
        owner: false,
        text: 'Bot Assistant is typing...'
      }])
      scrollDown()

      await new Promise(resolve => setTimeout(resolve, 1000));
      setAllow(true)
      scrollDown()
    }

进入全屏模式 退出全屏模式

getBotAnswer函数将用户问题发送给机器人,然后使用机器人响应更新聊天。

    export const getBotAnswer = async (interchanges, setInterchange, question, prevState, setAllow) => {
      await showBotTyping(setInterchange, prevState, setAllow)
      setInterchange([...prevState, {
        owner: false,
        text: searchInterchange(interchanges,question)
      }])
      scrollDown()
    }

    const scrollDown = () => {
      document.getElementById('scrollTo').scrollIntoView({behavior: "smooth", block: "start"});
    }

进入全屏模式 退出全屏模式

我们使用scrollDown功能将botuser发送的当前消息平滑地显示在视图中。这使它更像chat app,用户无需手动向下滚动即可查看最近的消息。

    const scrollDown = () => {
      document.getElementById('scrollTo').scrollIntoView({behavior: "smooth", block: "start"});
    }

进入全屏模式 退出全屏模式

这就是本教程前端部分的代码部分。在下一节中,我们将看到已完成应用程序的演示。

完成应用

完成的应用程序如下所示:

结论

毫无疑问,聊天机器人助手带来的好处超过了它可能产生的任何负面影响。这种方法允许我们通过让这个机器人回答关于我们、我们的业务、我们的组织或事件的重复性问题来腾出我们的时间。这种可能性是无穷无尽的,只能留给一个人的想象。

本教程演示了使用已经非常常见的技术构建机器人是多么容易。如果您对此感兴趣,请查看Strapi 的网站以了解有关无头 CMS 的更多信息。

Logo

更多推荐