Skip to main content

Routes

Basically, routes map user inputs to actions which are in fact React Components.

User Inputs

The user input is captured as an object with the following fields:

  • Type: the input type, which can be one of the following: text, postback, audio, image, video, document, location, referral
  • Data: the raw text (or attachment URL if it's a media type).
  • Payload: the input caused by the user clicking on a button or quick reply.
  • Intent: the intent of the user according to your bot's NLU
{
type: 'text',
data: 'Hello!'
payload: '',
intent: 'Greetings'
}

Mapping User Inputs Into Actions

Every route, which is an entry in the routes' array can be defined in src/routes.js:

export const routes = []

Conceptually, a route is composed by a matching rule and an action. A matching rule looks like this: {attribute: test}, which basically means: "take that attribute from the user input and apply the test" if the test passes, the action defined in that route will be triggered.

There are 5 different ways of passing these tests:

  • String: Perfect match
  • Regexp: Pass the regular expression
  • Function: Passes if the function returns true
  • Input: Passes if the input satisfies the condition
  • Session: Passes if the condition of the session is met
  • Request: Passes if the conditions of the request are met (input, session or lastRoutePath)

The rules are tested in such a way that, if the first rule matches, Botonic does not test other routes and executes the first action. If there are several matching rules in the same route, all of them have to pass to consider a match.

In the following example, the first rule matches if and only if we get the text start and will trigger the action defined in src/actions/start.js

{path: 'start', text: 'start', action: Start}

Below, another text rule (perfect match) to trigger the end action

{path: 'end', text: 'end', action: End}

These rules use a case insensitive regexp to match text messages that contain a certain text. For the example below, will capture 'BUTTONS', 'Buttons', etc.

{path: 'buttons', text: /^buttons$/i, action: Buttons}

If you want to use regexp with grouped values, you need to upgrade Node to v.10 or above. This regular expression match age-{NUMBER} where NUMBER can be any digit. Then, in your component bye, you can access this in req.params

{path: 'age', text: /^age-(?<age>\d*)/, action: Age}

Below, a few examples of how to capture different payloads.

{path: 'carousel', payload: 'carousel', action: Carousel},
{path: 'quickreply', payload: /^(yes|no)$/, action: QuickreplyResponse}

It is posible to use a function test to capture any text that starts with bye

{path: 'bye', text: (t) => t.startsWith('bye'), action: Bye}

You can move to a new action if a condition over the user input is met

{path: 'greet', input: (i) => i.type === 'text' && i.intent === 'greetings', action: Greet}

Otherwise, you can move forward to new actions depending on session values

{ path: 'promotion', session: (s) => s.user.id < 150000, action: Promotion }

Below, we see how to capture different intents

  { path: 'greetings', intent: 'Greetings', action: Start },
{ path: 'book-restaurant', intent: 'BookRestaurant', action: ShowRestaurants },
{ path: 'get-directions', intent: 'GetDirections', action: ShowDirections }

There's an implicit rule that captures any other input and maps it to the 404 action, it would be the equivalent to:

{path: '404', type: /^.*$/, action: NotFound}

The Request Matcher is used to define and check that the conditions of the request are met. To use this new matcher, you can add something like the following:

{
path: 'step',
request: request =>
request.input.data === 'trousers' && request.session.user.id === '1234' && request.lastRoutePath === 'shopping',
action: PersonalShopperSuggestion,
},

Dynamic Routes

At times, you might want to render only some routes in function of a certain condition, so you can optionally define them as a function receiving the input and the session object.

import { MainFlowRoutes } from './actions/main_flow/main_flow.routes'

export function routes({ input, session }) {
if (session.is_first_interaction) {
return [
{
path: 'userWelcome',
text: /.*/,
action: UserWelcome,
},
]
} else return [...MainFlowRoutes]
}

Use Case

Sometimes you need to build a bot with deep flows, where users navigate a decision tree using interactive elements like buttons. This is useful when you want to guide the user through a conversation with predefined flows that consist of several steps.

The Childs example comes by default with an example.

This type of bots can include (but are not limited to):

  • Surveys
  • Pre-qualifiers of leads before human handoff
  • On-boarding processes
  • FAQs (when you have a very limited set of options)

Example

In the same way you build a website with a deep tree of routes, in botonic you make use of childRoutes to describe actions that are only accessible if the user is in the parent route.

With the following example you will get an idea of how childRoutes work.

src/routes.js

import Hi from './actions/hi'
import Pizza from './actions/pizza'
import Sausage from './actions/sausage'
import Bacon from './actions/bacon'
import Pasta from './actions/pasta'
import Cheese from './actions/cheese'
import Tomato from './actions/tomato'

export const routes = [
{
path: 'hi',
text: /^hi$/i,
action: Hi,
childRoutes: [
{
path: 'pizza',
payload: /^pizza$/i,
action: Pizza,
childRoutes: [
{ path: 'sausage', payload: /^sausage$/i, action: Sausage },
{ path: 'bacon', payload: /^bacon$/i, action: Bacon },
],
},
{
path: 'pasta',
payload: /^pasta$/i,
action: Pasta,
childRoutes: [
{ path: 'cheese', payload: /^cheese$/i, action: Cheese },
{ path: 'tomato', payload: /^tomato$/i, action: Tomato },
],
},
],
},
]

When the user starts the conversation the bot will ask whether he wants to eat pizza or pasta. You "force" the user to select either one by prompting two quick replies:

src/actions/hi.js

import React from 'react'
import { Text, Reply } from '@botonic/react'

export default class extends React.Component {
render() {
return (
<Text>
Hi! Choose what you want to eat:
<Reply payload='pizza'>Pizza</Reply>
<Reply path='pasta'>Pasta</Reply>
</Text>
)
}
}

In the example, we have the option of pizza or pasta. This is where childRoutes come in. Depending on the choice made, the chatbot will ask different things. When choosing pizza, the chatbot asks if you want sausage or bacon, whilst with pasta the chatbot will ask if you want cheese or tomato. Because we are using childRoutes, if you try to access the components of the ingredients directly you will not find them. Since the path to access is hi -> pizza / pasta -> ingredient, it is guaranteed you can only choose the ingredient if you have chosen the food first.

Alternatives

In certain cases, it could be preferable to keep your 'routes.js' file as clean as possible. Let's take the previous example and replace the text attributes with payloads.

src/routes.js

import { routes as hiRoutes } from './actions/hiFlow/hi.routes'
import Hi from './actions/hi'

export const routes = [
{ path: 'hi', text: /^hi$/i, action: Hi, childRoutes: hiRoutes },
]

You can achieve the same flows behavior by preserving the parent of the previous flow and doing the following modifications in the corresponding action files:

src/actions/hiFlow/hi.routes.js

import Pizza from '../pizza'
import Sausage from '../sausage'
import Bacon from '../bacon'
import Pasta from '../pasta'
import Cheese from '../cheese'
import Tomato from '../tomato'

export const routes = [
{ path: 'pizza', payload: 'pizza', action: Pizza },
{ path: 'sausage', action: Sausage },
{ path: 'bacon', action: Bacon },
{ path: 'pasta', action: Pasta },
{ path: 'cheese', action: Cheese },
{ path: 'tomato', action: Tomato },
]

Note that instead of a payload, you can use a path to trigger an action, for example path="pasta".

Working with URL Parameters

Since every action is linked in the same way as URLs, you can also pass them additional parameters to have a better control of your responses. So, the following piece of code will fill our params object:

src/actions/hi.js

import React from 'react'
import { Text, Reply } from '@botonic/react'

export default class extends React.Component {
render() {
return (
<Text>
What's your favourite flavour?
<Reply path='pizza?ans=spicy'>Spicy</Reply>
<Reply path='pizza?ans=salty'>Salty</Reply>
</Text>
)
}
}

Then you can access its parameters in the following way in src/actions/pizza.js:

src/actions/pizza.js

import React from 'react'
import { Text, RequestContext } from '@botonic/react'

export default class extends React.Component {
static contextType = RequestContext

render() {
if (this.context.params.ans == 'spicy') {
return <Text>Be sure you have the air conditioner turned on.</Text>
} else if (this.context.params.ans == 'salty') {
return <Text>Be sure you have a bottle of water nearby.</Text>
} else {
return <Text>This option is not available.</Text>
}
}
}