如何用Flex对话建立一个不活动超时器

104 阅读7分钟

正如你可能知道的,Twilio已经发布了Flex Conversations。这是在Flex生态系统内使协调工作更加可靠和直接的一大步。在这里的一篇博文中,你可以了解更多关于Flex Conversations的新好处。Flex Conversations的推出也为实现新的很酷的功能提供了可能性,因为Conversations API为管理参与者、地址和对话生命周期提供了广泛的工具。

我们的客户经常询问的一个联络中心功能是不活动超时。为了保持你的座席的高生产力,有必要有一种自动的方式来跟踪被放弃的任务并将其清理掉。有了对话API的状态计时器功能和Twilio功能,现在就可以在Flex中实现不活动超时了。在这篇博文中,我将指导你通过配置和代码来实现这一目的。

教程的先决条件

在我们开始构建之前,你需要确保你有一个Twilio的账户。你可以在这里免费注册。

一旦你的Twilio账户创建完毕,你就可以继续创建Twilio Flex项目。

你可能需要确保对话已启用。如果你为本教程创建一个新的项目,对话应该是默认启用的。

如果你有一个现有的项目,你想迁移到对话,看一下这里的博文的入门部分

要创建一个Flex帐户,在你的Twilio控制台导航到Flex,然后**概述,**然后点击"创建我的Flex帐户"按钮。

一旦你得到了你的Flex帐户,请注意以下细节 - 你以后会需要它们。

有了这些,你就可以开始了。

该解决方案将如何工作

我们将利用Twilio功能和对话API,即其状态定时器。这里的目标是能够配置一个超时 - 这重置每次有一个新的消息,无论是从客户或代理 - 完成任务的Flex,如果没有新的消息是在超时期间发送。此外,我们想让客户知道,通过发送消息,对话已经超时了。

在我们开始写代码之前,这里是我们的高水平计划。

代码

  • 我们将编写一个名为on_conversation_state_updated.ts的函数。顾名思义,这个函数将在对话状态通过onConversationStateUpdated 事件发生变化时被调用,这将作为清理不活动任务的触发器。该函数将有以下职责。
    • 将与对话相关的任务设置为completed 状态
    • 向客户发送一条消息,说明会话已经超时了
    • 将对话状态设置为closed (这是一个必要的步骤,以便为进一步的通信准备一个对话,使其能够被重复使用)
  • 我们将编写另一个名为on_reservation_accepted.ts. 的函数,该函数将在任务被代理接受的时刻首先被调用,因此选择这个名字是为了反映该函数将被调用的TaskRouter事件:onReservationAccepted 。该函数将有两个职责。
    • 在对话上创建一个webhook,与任务相联系,在指向onConversationStateUpdated 事件的URL上触发on_conversation_state_updated.ts 函数。
    • 在与任务相连的对话上创建一个不活动定时器,其值在环境变量中定义。

配置

  • 我们将配置TaskRouter Workspace,以便在一个onReservationAccepted 事件中向on_reservation_accepted.ts 函数的URL发送一个webhook。

下面是我们将建立的解决方案的图示。

Inactivity timeout with Flex Conversations architecture diagram

开发者环境设置

让我们确保你有你需要的软件。

在本教程中,我将使用Typescript,但用JavaScript也应该很好。

现在我们可以开始编码了

创建项目

我们将首先使用Twilio Serverless Toolkit创建一个项目。对于这个运行,在你的shell中发出以下命令。

twilio serverless:init flex-chat-inactivity-timeout --typescript

这里有几个注意事项。

  • 在命令中,我使用flex-chat-inactivity-timeout 作为我的项目名称,请随意使用不同的名称
  • 添加--typescript 参数将创建一个准备用于Typescript的项目。如果你喜欢JavaScript,你可以省略这个参数。

完成这一步后,你就有了一个可以在本地环境中运行或直接部署到Twilio Functions的项目。

你会发现在src/functions 文件夹下有几个函数的例子,在src/assets 下有几个资产的例子,你可以安全地删除它们。(或者你可以忽略它们,随你喜欢。)

配置你的环境

为了运行这些代码,我们需要环境变量到位。进入项目根目录下的.env ,并更新文件,使其具有以下键和值。确保将我的占位符替换成你在前面步骤中收集的值。

下面配置中的超时对应于1 minute ,并根据ISO 8601持续时间标准进行格式化。

你可以在Twilio控制台的工作空间部分的TaskRouter下找到TaskRouter工作空间的SID。寻找一个名为 "Flex Task Assignment "的工作区。

ACCOUNT_SID=<Twilio Account SID>
AUTH_TOKEN=<Twilio Auth Token>
TIMEOUT=PT1M
TASK_ROUTER_WORKSPACE=<TaskRouter Workspace SID>

创建功能

现在是时候创建你将在里面工作的文件了。导航到src/functions,并创建两个文件,名为on_reservation_accepted.protected.tson_conversation_state_updated.protected.ts

虽然你可以自由地为你的函数选择任何其他名称,但要确保名称以.protected.ts (或.protected.js )结尾,因为这定义了函数的可见性等级。受保护的函数只能从Twilio平台上调用。

下面是on_reservation_accepted.protected.ts 函数的完整代码,其中增加了解释代码的注释。

// Imports global types
import '@twilio-labs/serverless-runtime-types'
// Fetches specific types
import {
  Context,
  ServerlessCallback,
  ServerlessFunctionSignature,
} from '@twilio-labs/serverless-runtime-types/types'

// Environment variables
type Env = {
  TIMEOUT: string
}

// Webhook event type with needed fields
type OnReservationAccepted = {
  TaskAttributes: string,
}

export const handler: ServerlessFunctionSignature<Env, OnReservationAccepted> = async function (
  context: Context<Env>,
  event: OnReservationAccepted,
  callback: ServerlessCallback,
) {

  console.log(event)

  // Parse task attributes JSON string into object
  let taskAttributes = JSON.parse(event.TaskAttributes)

  // Get Conversation SID for task attributes
  let conversationSid = taskAttributes["conversationSid"]

  // Create a TwilioResponse object
  const response = new Twilio.Response()
  response.appendHeader('Content-Type', 'application/json')

  // Create Conversation Context object
  const conversationContext = context
    .getTwilioClient()
    .conversations
    .conversations(conversationSid)

  // Create a webhook on the Conversation to be fired on onConversationStateUpdated
  // targeting on_conversation_state_updated function
  try {
    await conversationContext
      .webhooks
      .create({
        target: 'webhook',
        configuration: {
          url: `https://${context.DOMAIN_NAME}/on_conversation_state_updated`,
          method: 'POST',
          filters: ['onConversationStateUpdated'],
        },
      })
  } catch (err) {
    console.error(err)
    response.setStatusCode(500)
    return callback(err, response)
  }

  // Create an inactivity timeout on the Conversation using timeout from environment variable
  try {
    await conversationContext
      .update({
        timers: {
          inactive: context.TIMEOUT,
        },
      })
  } catch (err) {
    console.error(err)
    response.setStatusCode(500)
    return callback(err, response)
  }

  // Return success response
  console.log('OK')
  response.setStatusCode(200)
  response.setBody({
    ConversationSid: conversationSid,
  })
  return callback(null, response)
}

这里是on_conversation_state_updated.protected.ts 函数的完整代码,并增加了对代码的解释注释。

// Imports global types
import '@twilio-labs/serverless-runtime-types'
// Fetches specific types
import {
  Context,
  ServerlessCallback,
  ServerlessFunctionSignature,
} from '@twilio-labs/serverless-runtime-types/types'

// Environment variables
type Env = {
  TASK_ROUTER_WORKSPACE: string
}

// Webhook event type with needed fields
type OnConversationStateUpdated = {
  ConversationSid: string,
  ChatServiceSid: string,
  StateTo: State,
  Reason: string
}

// Conversation state type
type State = 'active' | 'inactive'

export const handler: ServerlessFunctionSignature<Env, OnConversationStateUpdated> = async function (
  context: Context<Env>,
  event: OnConversationStateUpdated,
  callback: ServerlessCallback,
) {

  console.log(event)

  // Create a TwilioResponse object
  const response = new Twilio.Response()
  response.appendHeader('Content-Type', 'application/json')

  // We proceed only if both conditions are met event.StateTo === 'inactive' AND event.Reason === 'TIMER'
  if (event.StateTo !== 'inactive' || event.Reason !== 'TIMER') {
    // Nothing to do
    response.setStatusCode(200)
    return callback(null, response)
  }

  // Initialize Twilio client
  const client = context.getTwilioClient()

  // Fetch tasks for Conversation SID from the event
  let tasks
  try {
    tasks = await client.taskrouter
      .workspaces(context.TASK_ROUTER_WORKSPACE)
      .tasks
      .list({
          evaluateTaskAttributes: `conversationSid="${event.ConversationSid}"`,
        },
      )
  } catch (err) {
    console.error(err)
    response.setStatusCode(500)
    return callback(err, response)
  }

  // No tasks found or more than one task found, but both should never happen.
  if (tasks.length != 1) {
    response.setStatusCode(200)
    response.setBody({message: `Tasks found ${tasks.length}`})
    return callback(null, response)
  }

  // Variable to store task instance
  let task = tasks[0]

  // Completing task in TaskRouter
  try {
    await client.taskrouter
      .workspaces(context.TASK_ROUTER_WORKSPACE)
      .tasks(task.sid)
      .update({
        assignmentStatus: 'completed',
      })
  } catch (err) {
    console.error(err)
    response.setStatusCode(500)
    return callback(err, response)
  }

  // Send timeout notification to the customer
  try {
    await client.conversations
      .conversations(event.ConversationSid)
      .messages
      .create({
        body: 'Your session is timed out'
      })
  } catch (err) {
    console.error(err)
    response.setStatusCode(500)
    return callback(err, response)
  }

  // Update Conversation state to closed
  try {
    await client.conversations
      .conversations(event.ConversationSid)
      .update({
        state: 'closed',
      })
  } catch (err) {
    console.error(err)
    response.setStatusCode(500)
    return callback(err, response)
  }

  // Return success response
  console.log('OK')
  response.setStatusCode(200)
  response.setBody({
    ConversationSid: event.ConversationSid,
    TaskSid: task.sid,
  })
  return callback(null, response)

}


部署到Twilio功能

下一步是将我们的代码部署到Twilio Functions。要做到这一点,在你的项目根部执行以下命令。

npm run deploy

如果你在部署代码时遇到问题,请检查ACCOUNT_SIDAUTH_TOKEN 环境变量是否在你的.env 文件中正确配置。

一旦部署完成,你将看到你新创建的函数的URLs。它将看起来像下面这样。

Functions:
   [protected] https://flex-chat-inactivity-timeout-0000-dev.twil.io/on_conversation_state_updated
   [protected] https://flex-chat-inactivity-timeout-0000-dev.twil.io/on_reservation_accepted

复制on_reservation_accepted 函数的 URL - 我们现在准备配置 TaskRouter 工作区。

配置TaskRouter工作区

在你的Twilio控制台,导航到TaskRouter,然后是工作空间部分,然后找到一个名为 "Flex Task Assignment "的工作空间。打开工作区,进入设置。在设置页面,找到 "事件回调 "部分,选择 "特定事件 "单选按钮。确保在事件列表中只有 "接受预订 "复选框被选中。

测试

为了测试我们的解决方案,请采取以下措施。

  1. 登录到Flex作为一个代理,并确保你是在Available 状态。
  2. 发送短信到您的Flex项目配置的号码之一(查看配置的电话号码,去Twilio控制台->Flex->消息,并点击 "对话地址 "标签)。
  3. 接受传入的任务的Flex,并开始与 "客户 "的聊天。
  4. 等待一分钟,检查任务是否自动完成,你的 "客户 "得到一个消息说,该会议是超时的。

如果你得到这一切: voilà!它正在工作!现在你可以根据自己的需要自由地构建它了。

总结

使用Twilio功能和对话API,我们能够建立一个自动化的方式来处理不活跃的任务清理,以确保我们腾出时间给新的客户聊天。

该项目的完整代码可在GitHub上找到:github.com/kuschanton/…