Hello! 🎉

Hello and welcome to talksuite! Talksuite is our enterprise-grade bot building platform which is designed for us to focus on the security, scalability and privacy of your bot so you can focus on crafting the best experience for your users.

If you have received and invitation to join talksuite please click here for step-by-step instructions on how to write you first simple bot.

If you have already joined talksuite, or are just curious about what it can do, see below

Overview

talksuite is dedicated to constantly improve and evolve and does release continuously so that you can access new features and fix problems as fast as possible. That being said, we’re dedicated to not breaking any features that you rely on. We make sure that talksuite is always backwards compatible and if we are planning on depreciating a feature, we will communicate it well in advance of doing so.

The talksuite studio allows an author to specify the rules for chatbot-style applications that run on a number of messaging apps including Skype and Slack, as well as our own apps which are available on iOS, Android, Mac and Windows. The rules for the application are expressed in JSON notation.

JSON notation allows structured data to be defined, with nested property name and value pairs. Throughout the document, JSON snippets are displayed in tabs on the right.

The talksuite engine runs the applications you have created.

The documentation has the following sections:

We’ll use colours throughout to highlight the text:

Hints and tips.

Useful information.

Something may not happen if you try and do this.

Something bad will happen if you do this.

Sometimes we will add items under construction to the document, so you can see in detail what is coming. We’ll mark these with an under construction sign 🚧

Updates

🎉 New Features

This section allows you to see all the details of the latest talksuite release. We will update this section on a regular basis with the latest features available to you as a talksuite author.

The latest release for talksuite includes the following features:

  • Data Store – The talksuite studio data store has been enhanced to allow dialogues to be created that can Read and use the information stored. This enhances talksuite ability to support customers who do not have the ability to store information needed in the process. To enhance this feature and make the implementation simple, studio users can now import data into the data store.
  • Performance Improvements – Web hooks are now queued on the on the low priority queue, this ensures that the high priority queue is reserved for responding to end users conversations. Additional changes have been made to the Update/Add Dialogue API to remove redundant operations when in validation mode.
  • Upgrades – Talksuite has upgraded its packages for the Notification Hub and Service Bus, ensuing it remains compliant and using the latest technology.
  • Validation – Bot validation on all projects used by a bot has been added once saved, to ensure that there are no known conflicts.

Signing in

In order to use talksuite you will need to be invited to an organisation. The invite link will take you to the talksuite welcome screen

Selecting the sign up link will take you to a sign-in screen where you can sign in or sign up to talksuite using the email address displayed on the welcome screen

If you have been invitied as an administrator, you will be taken to the talksuite Administration area where you can invite others to join. If you have been invited as an author, you will be taken to the talksuite studio, where you can build chat-bots

We’d advise using Chrome for the moment. Support for other browsers will be added later

The sections below explain how to build chat-bots using the studio

Terminology

Here’s a quick list of some of the terms used in talksuite & in the rest of this documentation:

  • Channel. A channel is a messaging app supported by the Microsoft bot framework. Examples are Skype, Slack, Web chat and MHR’s own native iOS and Android apps, used for the people first and iTrent chatbots

  • Organisation. Your organisation. Each talksuite user is invited to join an organisation. In some cases, you may be invited to more than one organisation.

  • Bot. A bot is a configuration record that defines the authentication process and determines which channels can be used. It can also contain constant data. Users communicate with bots via a channel.

  • Dialogue. A dialogue is a configuration record that defines the workflow of an interaction with the user (e.g. when the user types “book a holiday”, ask for a start date and end date and book a holiday using these dates. Confirm to the user that the booking is successful).

  • Node. A node is an individual step within a dialogue (e.g. output a message)

  • Conversation. A conversation is the term for all interactions between a user and a bot. When a user leaves a conversation, all data associated with the dialogues is removed. A user may interact with several dialogues in a conversation.

  • Project. A project is a collection of dialogues. A project can be allocated to a bot, allowing you to control which users can use which dialogues.

Dialogue Structure

A dialogue has the following components:

  • Id gives the dialogue a unique name

  • Project places the dialogue in a project

  • Trigger defines how the dialogue will be started

  • Nodes are the individual steps of the dialogue process

  • Model in which variables are given their initial values

  • Entities in which variables populated from LUIS are defined

  • Priority defines how the dialogue runs in relation to other dialogues. See Dialogue Priority

The id, trigger and nodes must always be present.

In the example on the right, the dialogue is triggered by a LUIS intent “BookAHoliday”.

{
   "id": "example dialogue",
   "priority" : "interrupt",
   "trigger": {
      "type": "intent",
       "intent": "BookAHoliday"
    },
    "model": {
        "allowance": "25"
    },
    "entities": {
        "date": "builtin.datetimeV2.date"
    },
    "nodes": [
      {
        "type": "message",
        "message": "Okay, I'll book {date} for you. Remember your holiday allowance is {allowance}"
      }
     ]          
}        

Types of Data

Introduction

The following types of data can be used within talksuite:

  • Text (e.g. “Hello”)

  • Numbers (e.g. 23, -1, 3.14159)

  • Dates (e.g. 1st December 1965)

  • Times (e.g. 15:30)

  • Date and times (e.g. 1st December 1965 15:30:00)

  • True/False (true or false)

  • Lists Collections of values (e.g. [“red”, “white”, “blue”])

  • Objects An object can have multiple properties. For example a person object could have a first name, a last name and a date of birth

You do not need to tell talksuite what type of data you are using. talksuite will look at the data and what you are trying to do with it and act accordingly

The Model

Variables can be initiated with constant values in the model section of a dialogue

All values in the model must be in quotes, even if they are numbers or true/false values

  • Numbers must be entered as text (but they will be treated as numbers)

  • Dates should be entered in the format 1 December 1965

  • Times should be entered as in the 24 clock (e.g.17:30)

  • Lists should be entered as comma-separated values enclosed in []

  • Object properties are separated from the object name by a /. E.g. person/firstname

There is no need for items in a list to be of the same type, in the list [“1”, “hello”, “1 December 1965”], the first item will be treated as a number in a numeric operation and the third item will be treated as a date in a date operation

Leading zeros in numeric model data will be lost. If you want to assign a value such as 007, you must assign a 0 and a 7 in the model and concatenate, two 0s and a 7

See the “Model” snippet on the right hand menu for an example

Referencing variables

Variables names can be referenced in three ways:

  • When a property always has to be a variable name, just use the variable name. E.g. An output property of a node. - “output” : “myVariable”.

  • Variables can be embedded inside text by enclosing the variables in curly brackets. E.g a message property - “message” : “Hello {myNameVariable}”.

  • When parameters can contain text or a variable, enclose the variable in a var property. E.g. {“var” : “myVariable”}

Object properties are referenced by preceding the object name with the parent properties of the object. E.g. person/address/houseNumber

Items within lists are referenced by the numeric offset from the first item in the list (e.g. myList/0 for the first item, mylist/1 for the second and mylist/{n} for the nth item)

There are a few characters you can’t use in a variable name, incuding curly brackets, forward slash and double quotes

Lists of objects

Lists of objects cannot be created in the model. Objects should be appended to a list using the addItem method See lists . See the List of Objects snippet in the right hand menu for an example or look at list logic

Conversation variables

If a variable is preceded by “conversation/” it will be stored in the database for the duration of the conversation. Conversation variables are available to all dialogues running in the conversation and not running in the background. Conversation variables are not available to other conversations. In other words, the data is private to the user.

If you are storing personal information, take into account the data protection implications.

Bot Constants

If a constant has been defined in your bot configuration record, it is available to all dialogues as a read-only value. To access it, precede the constant name with “Bot/”

Processing data

A variety of operations (e.g. addition, concatenation and sorting) can be performed on data using logic operations

{
  "id": "model example",
  "trigger": {
    "type": "message",
    "values": [
      "model example"
    ]
  },
  "model": {
    "date": "1 December 1965 12:13",
    "text": "hello",
    "number": "1.5",
    "list": [
      "1",
      "2",
      "3"
    ]
  },
  "nodes": [
    {
      "description": "Example of the use of a number initiated in the model",
      "type": "operation",
      "output": "number",
      "operation": {
        "+": [
          {
            "var": "number"
          },
          {
            "var": "number"
          }
        ]
      }
    },
    {
      "description": "Example of the use of a date-time initiated in the model",
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "date"
          },
          "addDays",
          [
            1
          ]
        ]
      },
      "output": "date"
    },
    {
      "description": "Example of the use of an element in a list initiated in the model",
      "type": "message",
      "message": "date {date} number {number} list/0 {list/0} "
    }
  ]
}  
{
  "id": "model example 2",
  "trigger": {
  "type": "message",
    "values": [
        "model example 2"
      ]
  },
  "nodes": [
    {
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "people"
          },
          "addItem",
          [
            {
              "var": "janeSmith"
            }
          ]
        ]
      },
      "output": "people"
    },
    {
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "people"
          },
          "addItem",
          [
            {
              "var": "johnJones"
            }
          ]
        ]
      },
      "output": "people"
    },
    {
      "type": "message",
      "message": "I've built a list containing {people/0/firstname} {people/0/lastname} and {people/1/firstname} {people/1/lastname}"
    }
  ]
}    title: List of Objects

Logic.

talksuite allows a range of calculations and logic to be performed

We’ve grouped the operations into the following areas:

  • Comparisons are used to compare two values. E.g. is one value the same as another? Is one number greater than another?

  • Logic allows comparisons to be combined with logical operations (and, or and not). E.g. is a number greater than 10 and less than 20?

  • Date calculations. A variety of date and time calculations are available, including getting the current date and time, timezones, date formatting, difference between dates and shifting dates by a period

  • Text operations. You can combine and split text items, search within text, get the length of a text item and change case

  • Arithmetic. You can add, subtract, divide, multiply, round and calculate a remainder from a division

  • Lists. You can build and modify a list, sort and filter lists and construct a list from a text item with a separator

  • XML. You can convert XML and escaped XML to a talksuite object

The implementation of talksuites logic is based on JSON logic. Click here to see the documentation

Built-in Variables

When in a dialogue, you have access to a number of built-in settings and variables

  • Conversation settings allows you to control the region and timezeone and to switch to verbose mode

  • Dialogue variables give you access to the phrase the user used to trigger the dialogue and the HTTP status code of the last API call in the dialogue

  • Bot settings give you access to selected properties in the bot

  • Authentication settings tells you what systems the user has signed into and allows you to set a unique identifier for the conversation that will enable external systems to start dialogues in that conversation

Node Flow

By default, once a node has run, the next node in the dialogue is run (e.g. node A, thenn node B, then node C)

You can alter the flow, so that node C is run after node A, set the id property of node C and the nextNode property of node A to the same value.

To do this:

  • Set an id property on node C (to, say “Node C”)

  • Set the nextNode property of node A to “Node C”

You can only move forward in a dialogue, never backwards.

If you want to stop executing a dialogue, set the nextNode property to “dialogue.stop”. If you are in a nested dialogue and don’t want the calling dialogue(s) to resume, use the Event node with an event of “endDialogue”.

If you want to skip a node, set the skip property of the node to true. The node will not run, but the nextNode property of the node will still be used to determine the next node.

If you want to run another dialogue, use the Dialogue node. When the called dialogue ends, the calling dialogue will resume.

If you want to loop through a list of records, use the Sequence Dialogue node.

If you want to loop until a condition is met, use the Repeat Dialogue node.

If you want to loop through a list of records and return data not contained within the record set, you’ll need to use the Repeat Dialogue node.

Any variables used or declared in the model in a calling dialogue will not be available in the called dialogue unless they are passed in as parameters

[
    {
      "type": "message",
      "message": "First line displayed",
      "nextNode": "second line"
    },
    {
      "type": "message",
      "message": "Not executed",
      "nextNode": "second line"
    },
    {
      "id": "second line",
      "type": "message",
      "message": "Second line displayed"
    },
    {
      "type": "message",
      "message": "Third line displayed",
      "nextNode": "dialogue.stop"
    },
    {
      "type": "message",
      "message": "Not executed"
    }
]
[
      {
        "type": "dialogue",
        "dialogueId": "example called dialogue",
        "description": "passOut and getBack are in this dialogue. receiveVar and outputVar are in the called dialogue",
        "inputs": {
          "receiveVar": {
            "var": "passOut"
          }
        },
        "outputs": {
          "getBack": {
            "var": "outputVar"
          }
        }  
      },
      {
        "type": "sequenceDialogue",
        "description": "people is a list of objects in this dialogue. person is populated in the called dialogue with each people record in turn",
        "dialogueId": "example called for each record",
        "inputItem": "person",
        "listName": "people"          
      },
      {
        "type": "repeatDialogue",
        "dialogueId": "example called repeatedly",
        "description" : "i is a variable in the calling dialogue, passed into the called dialogue as index. finished is a boolean in both dialogues",
        "inputs": {
          "index": {
            "var": "i"
          }
        },
        "outputs": {
          "i": {
            "var": "index"
          },
          "finished": {
            "var": "finished"
          }
        },
        "repeatUntil": {
          "===": [
            {
              "var": "finished"
            },
            "finished"
          ]
        }
      }       
]    
{
    "data": {
      "attributes": {
        "json": {
          "id": "example called dialogue",
          "trigger": {
            "type": "nestedDialogue"
          },
          "nodes": [
            {
              "type": "operation",
              "description" : "receiveVar is passed in and outputVar is returned",
              "operation": {
                "var": "receiveVar"
              },
              "output": "outputVar"
            }
          ]
        }
      }
    }
}
{
   "data": {
      "attributes": {
        "json": {
          "id": "example called for each record",
          "trigger": {
            "type": "nestedDialogue"
          },
          "nodes": [
            {
              "description" : "person is populated for each record in the list in the calling dialogue",
              "type": "message",
              "message": "{person/firstName}"
              
            }
          ]
        }
      }
    }
}
{
    "data": {
      "attributes": {
        "json": {
          "id": "example called repeatedly",
          "trigger": {
            "type": "nestedDialogue"
          },
          "nodes": [
            {
              "type": "operation",
              "output": "index",
              "operation": {
                "+": [
                  {
                    "var": "index"
                  },
                  1
                ]
              },
              "nextNodeIndex": 1
            },
            {
              "type": "choicePrompt",
              "message": "{index}: I'll keep asking you this until you select Stop",
              "retryMessage": "?",
              "listName": "menuButtons",
              "output": "menuChoice",
              "nextNodeIndex": 2
            },
            {
              "type": "decision",
              "rule": {
                "===": [
                  {
                    "var": "menuChoice"
                  },
                  {
                    "var": "menuButtons/0"
                  }
                ]
              },
              "passNodeIndex": 3,
              "failNodeIndex": null
            },
            {
              "type": "operation",
              "operation": {
                "var": "finishedText"
              },
              "output": "finished",
              "nextNodeIndex": null
            }
          ],
          "model": {
            "finishedText": "finished",
            "menuButtons": [
              "Stop",
              "Continue"
            ]
          }
        }
      }
    }
}

Language

To make your dialogues easily translatable into different languages, don’t embed natural language text in your dialogue. Place them in a language store.

You can have a separate language store for each country/language you support (e.g. en-GB for British English, en-US for US English). Each item in the store consists of a name and value. The name is used to reference the text in a dialogue. The value contains the text in the appropriate language/culture.

If you want language to vary in order to make your bot seem more human, you can assign an array of text items. An item will be randomly selected from the list each time it is referenced.

In the examples to the right, the definition of a store is shown along with its use in dialogue trigger node. The dialogue will trigger when any of “hello”, “hi” or “howdy” is entered. The dialogue will respond with a message chosen randomly from list list “bye”, “cherrio” etc.

Built-in messages

The systemText section allows you to define alternative texts for any fixed messages output by talksuite in the channel.

The fixed messages are:

  • Push Notification Text Applies to the people first and iTrent apps only. Sets the default push notification text for the language. This is the notifcation message displayed on the user’s phone when a message is received, the app is closed and there is no suitable text to display (e.g. when an image is displayed)

  • Dialogue Not Found Sets the default message output when a user’s input does not trigger a dialogue.

  • Brief and De-brief Text and button labels displayed at the start of a brief of debrief

The image below shows the language records for British, US and Australian English

"texts": [
    {
      "name": "hello",
      "values": [
        "hello",
        "hi",
        "howdy"
      ]
    },
    {
      "name": "goodbye",
      "values": [
        "bye",
        "cheerio",
        "see ya",
        "goodbye",
        "laters"
      ]
    }
  ],
  "region": "en-GB"
}
{
  "trigger" : "message",
  "values" : [{"var" : "{language/hello}"],
}
"nodes" : [
  {
    "type": "message",
    "message" : "{language/goodbye}"          
  }
]

Projects

Use projects to break up your applications into manageable chunks, control what features of the application are available to a customer and to hold multiple versions of software.

Language records and dialogues can be placed inside a project. Dialogue ID and trigger validation will only be performed for dialogues in the project and dialogues without a project (i.e. global dialogues).

A project’s only attribute is a name (in addition to the generated key). Adding a projectId property with the project key to a language or dialogue record adds the item to the project.

In order for the dialogues in a project to run, the project must be added to the list of bot projects in the Bot config record.

Any dialogues which are not in a project are held in a global area.

To create a project, click on Add project on the left-hand menu

Introducing LUIS

Introduction

Microsoft’s Language Understanding Intelligent Service (LUIS) application can be trained to understand natural language. It determines the intention of the user from a sentence entered by the user. Each intention understood is referred to as an intent. You can trigger a dialogue when LUIS understands an intent using an intent trigger

For example, LUIS might have been trained to understand that the sentences “I want to book a holiday” and “Book me a holiday” all map to the intent bookHoliday.

A sentence entered by the user is referred to as an utterance. Each user utterance is evaluated against all the configured intents. A score for the utterance is assigned to each intent based on the strength of the match. The intent with the highest score is returned to the calling application. If the highest scoring intent is None, LUIS has come to the conclusion that the user’s intention is not amongst the configured intents.

LUIS can also extract information from the utterance. The types of information returned are referred to as entities. For example if location is an entity and LUIS has been trained to extract a location from the bookHoliday intent, the utterance “Book me a holiday in Cornwall” would return the intent of bookHoliday and an entity of location with a value of Cornwall. You can place entities directly into variables or you get them from the raw LUIS response. The LUIS date-time built-in entity type understands a wide range of date, time and duration phrases. However, it can return very many different types of data, depending on the utterance. In order to help the author make sense of all the possible responses, you can use the NLP Entities node

LUIS specific prompts are avaialable. Using these prompts will ensure consistency between the resolution of dates in utterances and in prompts

You can also construct an utterance and send it to LUIS directly using the NLP Query node

More information can be found at the LUIS home page

Bot Config

Bot configuration allows you to define the following:

  • Bot Registration and Localisation, language and timezone setting, plus the basic set-up to allow messages to move between the user’s device and talksuite

  • Authentiation Providers. A list of applications the bot can connect to along with the authentication parameters

  • Constants. Constant values available to dialogues

  • Natural Language. Determines which LUIS app to use for natural language understanding

  • Specify which Projects are active for a bot

  • There are also some other optional properties available

Patterns

talksuite can match pattterns in text. Pattern matching works in two ways. You can trigger a dialogue when a pattern is matched. You can also match a pattern in a dialogue using a pattern match operation

A pattern is defined using a regular expression. There are several dialects of regular expressions. We use the .NET version. Here are some simple examples:

  • hello matches text containing hello
  • (?i)hello matches text containing hello in any case
  • ^hello matches text starting with hello
  • hello$ matches text ending with hello
  • (hello|goodbye) matches text which contains either hello or goodbye
  • \\d matches text which contains a digit.

Where a backslash is required by the regular expression language, you need to use two backslashes to prevent JSON interpreting it as a special character

You can find more detail on the language and test your patterns at regexstorm.net

Scheduler

Talksuite allows you to schedule a dialogue to run on a recurring schedule (e.g. every 10 minutes, or at 10am on the 2nd of every month)

The schedule is defined in the bot config and the dialogue is linked to the schedule using a schedule trigger

Brief and De-brief

talksuite supports daily brief and de-brief processes. These are designed to help your users start and end their day and to shield them from noise for the rest of the day, allowing them to get on with their work.

You decide what goes into the brief and de-brief and when they run. The processes run at the specified time in the user’s own time zone.

In order to set up a brief or debrief:

  • Configure a start time for each bot

  • Choose which days of the week you want it to run

  • Define the dialogues to run in the brief or debrief by defining Process triggers

Before a brief or debrief is run, the user must agree to start the process.

You can write a dialogue to allow the user to choose their own days and times

Dialogue Priority

Dialogue property to control the way in which a dialogue runs

Properties
priority

set to interrupt, supersede or background

By default a dialogue runs in the context of a conversation (so that it can interact with the user via messages and prompts etc.) and can only start once the current dialogue has finished.

Using the dialogue priority property, a dialogue can be made to run in three other ways:

  • An interrupt dialogue will suspend the running dialogue, run itself, then resume the original dialogue. Therefore, you could trigger a dialogue based on phrase, even if the existing dialogue is waiting for prompt input. This is useful for initiating dialogue cancellation or help dialogues.

  • A supersede dialogue will stop the running dialogue, run itself and not return to the original dialogue

  • A background dialogue, as the name suggests, runs in the background while the current dialogue is allowed to continue running. Background dialogues do not run in the context of a conversation, so cannot use card, message or prompt nodes or conversation variables.

If a dialogue does not need to run in the context of a conversation, setting it to background may improve performance, as it will not interfere with the conversation dialogues. Validation will be performed to ensure that the dialogue and any dialogues it calls do not include nodes or variables that need to be in a conversation.

{
  "id": "interrupt message",
  "priority": "interrupt",
  "trigger": {
    "type": "message",
    "values": [
      "there is an emergency"
    ]
  },
  "nodes": [
    {
      "type": "message",
      "message": "DON'T PANIC!"
    }
  ]
}

Bot-to-bot communication

What is it used for?

A talksuite bot can communicate with other talksuite bots or another user using the same bot. Examples of this are :

  • Employee requests to managers
  • Users asking for help from a support desk

Bot to bot communication only works if there are two active conversations. Therefore is it is more suited to two people communicating via their respective bots.

If you want to design a “server” bot that responds to requests without human intervention you would need to ensure that there is a persistent conversation in the sever bot. The employee/manager case would probably involve two conversations in the same bot. The user/support case would probably involve two bots as the user interface for a support analyst would be very different from an end-user.

How does it work?

talksuite is able to receive requests from external systems via API calls (web hooks). Each request can initiate a dialogue. The request consists of:

  • The destination organisation
  • The destination bot within the organisation
  • An event name which identifies the dialogue to run
  • An address which identifies which conversation in the bot to communicate with
  • Data to send to the dialogue

A bot has a web hook secret key. The external call must supply this key for the request to be accepted. AWSv4 security is used to securely supply the key. talksuite can now make API calls using AWSv4 security. Therefor it is possible for conversation in a bot to initiate a dialogue in another conversation within the same bot, in another bot, or even another organisation, provided the key is known. Each side of the conversation must set an address, so the messages can be routed to the correct people. Multiple addresses can be set up, so a conversation could have a team address which is shared by several conversation and a unique address. Using the team address would broadcast a message to the whole team. Using the unique address would target one conversation.

For more detail on writing dialogues that receive requests see the External Event trigger. For more detail on making requests, see the AWS v4 Action node

Data Stores

Data stores are an add-on to basic talksuite, and may not be turned on in your organisation.

Data stores allow dialogues to store and read data inside the talksuite database. This data can be viewed from the studio and exported to a spreadsheet. Data can also be imported from a spreadsheet

Example uses of data stores are:

  • Capture data entered by bot users. E,g, surveys or queries
  • Record data about dialogue use. E.g. record utterances not understood
  • Record debugging data
  • Maintaining reference data by importing via a spreadsheet

A dialogue can only create and read records, so data stores cannot be used as a general purpose database, just to capture data or import data

Instant FAQS

Instant FAQ turns a spreadsheet of questions and answers into a question and answer bot without the need to write dialogues. This feature needs to be enabled for you organisation by the talksuit administrator. Once enabled InstantFAQ will appear on the main studio menu

Firstly create an instant FAQ set.

Create a spreadsheet with questions in the first column and corresponding answers in the second column. The first column must have a heading of Question and the second column must have a heading of answer. Use the import menu option to import the spreadsheet into the studio and select Update to save the set.

Write click on the Instant FAQ set in the left hand menu to obtain the ID. Paste the ID into the id propoerty in the instantFaqSet section of your bot. If you set the publish property in the instantFAQ section of the bot to true, any changes in the questions and answers will not be made in the bot until you select the publish option, otherwise changes will come into effect immediately on updating the JSON

The bot config also has a threshold property. Talksuite allocates a score to each reply - a score of zero means no replies match at all and a score of 1 means a certain match. If a score is below the threshold then no reply will be given. If you are getting incorrect answers, try setting the threshold closer to one. If you are getting no answers when you think you should be getting an answer, try setting the threshold closer to zero.

You can edit the JSON directly, changing the question and answer properties, or you can replace all the JSON by uploading a new version of the spreadsheet.

Trigger Types

Here is a summary of the different ways that a dialogue can be started

Type Trigger Mechanism
Message Exact match on a trigger phrase typed by the user
Pattern Match on text contained within a phrase typed by the user
Intent User input is matched against a LUIS intent
Attachment A file is uploaded by the user
Nested Dialogue A dialogue is called by another diaogue
No Trigger Match A user types something that does not trigger a dialogue by any of the above mechanisms
External Event A third party application requests a dialogue to run
Converation start A user uses the bot for the first time
Custom Event The (directline) client app requests a dialogue to run
Shedule Run a dialogue according to a recurrence schedule
Process Include a dialogue in a brief or debrief process
Form Data Run a dialogue when a button is pressed on an adaptive card

Message and pattern triggers will be checked for before talksuite goes to LUIS to match on an intent

Message

Starts a dialogue upon a successful keyword match

Properties
type

message

values

An array of strings or language file references to match the message against.

A trigger type of message will trigger a dialogue on a specific phrase or set of phrases. The examples to the right show:

  • A trigger on a single phrase (e.g. Hello)

  • A trigger on multiple phrases (e.g. Hello, Hi there)

  • A trigger on phrases from a language store (e.g “hello” or “hi there” in English, or “bonjour” or “salut” in French)

The match is against the whole input, so input of “hello bot” would not match the Hello trigger. Matching is not case sensitive, so HELLO will match Hello

If you want to match on input that contains a phrase (e.g anything starting with Hello), use a pattern trigger

Message and pattern trigger matching occurs before talksuite goes to LUIS to match intents.

There is also a node type of message. This sends a message to the user, whereas the message trigger enables the user to send a message to talksuite to start a dialogue

"trigger": {
  "type": "message",
  "values": [
    "Hello"          
  ]
}
"trigger": {
  "type": "message",
  "values": [
    "Hello",
    "Hi there"
    
  ]
}
"trigger": {
  "type": "message",
  "values": [
    { "var": "language/greetings" }
  ]
}

Intent

Starts a dialogue when LUIS matches input to an intent

Properties
type

intent

intent

The name of the LUIS intent you want this dialogue to trigger on.

output

Raw Luis response

A dialogue can be triggered when user input matches a LUIS intent. Firstly you must create a LUIS app and intent and link it to your bot by setting up a bot Natural Language section. Then, all text entered by a user outside of a dialogue is sent to LUIS. If the highest scoring intent is not “None” and you’ve defined a trigger for that intent, that dialogue is started.

LUIS can pick out key variables from a sentence (known as entities). These variables can be received by your dialogue. Create an “entities” section in your dialogue (at the same level as “model” and “nodes”). Create properties in that section. Each property name will be a variable in your dialogue. Each property value is the name of a LUIS entity.

Alternatively you can parse the raw LUIS response yourself. Add an output property to the trigger section of the dialogue. The value of the property will be a variable that will be populated with the LUIS response.

If you are using LUIS built-in date-time entities, you may find that LUIS returns a variety of different responses (e.g. date-times or date then time, a date range or two dates). We’ve written an NLP Entities node to help you extract just the data you need

"trigger": {
  "type": "intent",
  "intent": "sayHello"
}
"nodes" :[
    {
      "type" : "message",
      "message" : "hello {name}"
    }
]
"entities" : {
  "name" : "nameEntity"
}      
"trigger": {
  "type": "intent",
  "intent": "sayHello",
  "output": "response"
}
"nodes" :[
    {
      "type" : "message",
      "message" : "hello {response/entities/0/entity}"
    }
]            

Attachment

Starts a dialogue upon an attachment being received with a successful content type match.

Properties
type

attachment

contentTypes

An array of valid mime types that triggers this dialogue.

output

An object that maps the incoming properties of the attachment to local variables.

A dialogue can be triggered by the upload of a file of a particular type. In the example, the dialogue is triggered by the upload of a jpeg file.

// Snippet
"trigger": {
    "type": "attachment",
    "contentTypes": [ "image/jpeg" ],
    "output": "data"      
    }
}

Pattern

Starts a dialogue upon a successful pattern match

Properties
type

pattern

values

An array of strings or language file references. Each item should be a .NET regular expression. The dialogue is run if the user input matches the pattern

The snippet starts a dialogue matching on input that starts with “approve” in any case.

See Patterns for more information on patterns.

Pattern matching occurs before the phrase is sent to LUIS, so watch out for patterns that stop appropriate LUIS intents firing.

// Snippet
"trigger": {
  "type": "pattern",
  "values": [
    "^(?i)approve"
  ]
}

Nested Dialogues

A dialogue called from another dialogue

Properties
type

nestedDialogue

A dialogue of trigger type nestedDialogue can only be called from another dialogue. The following nodes can be used to call a nested dialogue

[
      {
        "type": "dialogue",
        "dialogueId": "example called dialogue",
        "description": "passOut and getBack are in this dialogue. receiveVar and outputVar are in the called dialogue",
        "inputs": {
          "receiveVar": {
            "var": "passOut"
          }
        },
        "outputs": {
          "getBack": {
            "var": "outputVar"
          }
        }  
      },
      {
        "type": "sequenceDialogue",
        "description": "people is a list of objects in this dialogue. person is populated in the called dialogue with each people record in turn",
        "dialogueId": "example called for each record",
        "inputItem": "person",
        "listName": "people"          
      },
      {
        "type": "repeatDialogue",
        "dialogueId": "example called repeatedly",
        "description" : "i is a variable in the calling dialogue, passed into the called dialogue as index. finished is a boolean in both dialogues",
        "inputs": {
          "index": {
            "var": "i"
          }
        },
        "outputs": {
          "i": {
            "var": "index"
          },
          "finished": {
            "var": "finished"
          }
        },
        "repeatUntil": {
          "===": [
            {
              "var": "finished"
            },
            "finished"
          ]
        }
      }       
]    
{
  "id": "example called dialogue",
  "trigger": {
    "type": "nestedDialogue"
   },
  "nodes": [
    {
     "type": "operation",
      "description" : "receiveVar is passed in and outputVar is returned",
       "operation": {
         "var": "receiveVar"
       },
      "output": "outputVar"
    }
  ]
}            
{
  "id": "example called for each record",
  "trigger": {
    "type": "nestedDialogue"
  },
  "nodes": [
    {
      "description" : "person is populated for each record in the list in the calling dialogue",
      "type": "message",
      "message": "{person/firstName}"
    }
  ]
}
{
          "id": "example called repeatedly",
          "trigger": {
            "type": "nestedDialogue"
          },
          "nodes": [
            {
              "type": "operation",
              "output": "index",
              "operation": {
                "+": [
                  {
                    "var": "index"
                  },
                  1
                ]
              }
            },
            {
              "type": "choicePrompt",
              "message": "{index}: I'll keep asking you this until you select Stop",
              "retryMessage": "?",
              "listName": "menuButtons",
              "output": "menuChoice"
            },
            {
              "type": "decision",
              "rule": {
                "===": [
                  {
                    "var": "menuChoice"
                  },
                  {
                    "var": "menuButtons/0"
                  }
                ]
              },
              
              "failNode": "dialog.stop"
            },
            {
              "type": "operation",
              "operation": {
                "var": "finishedText"
              },
              "output": "finished"
            }
          ],
          "model": {
            "finishedText": "finished",
            "menuButtons": [
              "Stop",
              "Continue"
            ]
          }
        }
      }
    }
}

No Trigger Match

Runs when no other triggers are matched (i.e the bot does not understand what has been typed)

Properties
type

event

event

noTriggerMatch

output

Raw Luis response

This event enables you to control what response the user gets when talksuite cannot match any other dialogue. The response could be a simple “I don’t understand” message or perhaps a menu of available options.

If there is no noTriggerMatch dialogue, a default “I’m sorry I didn’t understand that message (in English only) will be displayed

The built-in variable ‘dialogue/triggerUtterance’ can be used to find out what the user typed.

// Snippet
{
  "id": "Example no trigger match",
  "trigger": {
    "type": "event",
     "event": "noTriggerMatch"
  },
  "nodes": [
     {
       "type": "message",
       "message": "What does {dialogue/triggerUtterance} mean?"
     }
   ]
}            

External Event

A dialogue called from outside the system

Properties
type

customEvent

event

Event name

output

Variable populated with event data

broadcast

This setting has been deprecated and has no effect. The event will be run in all conversations with a matching user identifier

In order to trigger dialogues from outside talksuite, you will need to define a webHookSecretKey in your bot.

The API to call is /organisations/org-id/bots/bot-id/dialogue. Where org-id is the id of you organisation and bot-id is the id of your bot. The request should be a POST

The body of the document should consist of the following properties:

  • name Event name. The dialogue with an event trigger with this name will be called.

  • address User identifier. The dialogue will be run in the context of a user with a user.setting/identifier property which matches the address. user.settings/identifier is a list, so a user can have different identifiers for different authentication providers

  • value A JSON block which is passed into the dialogue

External events can also be used by the People First app to communicate with talksuite. See People First events

{
  "name": "hmactest",
  "address": "john.smith",
  "value": {
          "message":  "Hello John"
  }
}    
{
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "User.Settings/Identifiers"
          },
          "addItem",
          [
            {
              "var": "myId"
            }
          ]
        ]
      },
      "output": "User.Settings/Identifiers"
},
{
  "id": "webhook-test",
  "trigger": {
    "type": "customEvent",
    "name": "hmactest",
    "output": "data"
  },
  "nodes": [
    {
      "type": "message",
      "message": "This is your message {data/message}"
    }
  ]        
}

Conversation Start

A dialogue called at the start of a conversation

Properties
type

event

event

conversationStart

An event dialogue with an event name of “conversationStart” will be run after the first message is received from the user

You can use this to store infomration about a person that never changes (e.g. employee number)

This can be used as an alternative to an Introduction custom event

{
  "type": "event",
  "event": "conversationStart"        
}    

Custom Event

A dialogue called on receipt of a custom event from the [People First app](#peoplefirstintro)

Properties
type

customEvent

name

Event name

output

Event data

This trigger can only be used with the talksuite and People First/iTrent apps and bots embeded in websites

Apps will send an introduction custom event after the user has signed on. In the People First/iTrent app an event will also be sent when the device locale or time zone changes.

{
  "type": "customEvent",
  "name": "introduction"        
}    
{
  "id": "UpdateRegionAndTimeZone",
  "trigger": {
    "type": "customEvent",
    "name": "updateLocaleAndTimeZone",
    "output": "RegionTimeZoneData"
  },
  "nodes": [
    {
      "type": "operation",
      "output": "language",
      "operation": {
        "substr": [
          {
            "var": "RegionTimeZoneData/locale"
          },
          0,
          2
        ]
      }
    },
    {
      "type": "operation",
      "output": "country",
      "operation": {
        "substr": [
          {
            "var": "RegionTimeZoneData/locale"
          },
          3,
          2
        ]
      }
    },
    {
      "type": "operation",
      "operation": {
        "cat": [
          {
            "var": "language"
          },
          "-",
          {
            "var": "country"
          }
        ]
      },
      "output": "botlocale"
    },
    {
      "type": "operation",
      "operation": {
        "var": "RegionTimeZoneData/timeZone"
      },
      "output": "conversation.settings/timeZone"
    },
    {
      "type": "operation",
      "operation": {
        "var": "botlocale"
      },
      "output": "conversation.settings/region"
    }
  ]
}  

Scheduler

A dialogue to be run at dates and times defined in a bot schedule

Properties
type

schedule

name

name of the schedule in the bot config

The schedule in the bot config defines a recurring set of dates and times. By linking the dialogue to the schedule, the dialogue will run at those dates and times.

Examples are

  • Run every hour
  • Run at 9am on the first of every month
  • Run every quarter of an hour on every Monday and Tuesday

The recurrence schedule is specified in the schedules section of the bot config

"trigger": {
  "type": "schedule",
  "name": "firstOfEveryMonth",          
  
}

Process

Forms part of a set of dialogues that will run for all users at a specified time each day

Properties
type

process

name

brief or debrief

precedence

The order in which the dialogue will display within the process. Options are start, urgent, standard, additional or finish

A brief of debrief can be configured to run at a set time every day by configuring a bot process

A brief or debrief consists of a set of dialogues which run with a pre-set precedence. The set of dialogues is defined by the process triggers. The precedence is defined as a property within the trigger

"trigger": {
  "type": "process",
  "name": "brief",          
  "precedence" : "start" 
}

Form Data

Runs a dialogue when an adaptive form is submitted

Properties
type

formData

fieldName

Runs the dialogue when if the submitted from has an input field with this value as an id property or if an action item has a property with this value

values

Run the dialogue if the fieldName value matches a value in this list

output

Form data

Forms can be triggered in three ways

  • Based on the presence of a field with a specific id
  • Based on the presence of a field with a specfic id and a set of values
  • Based on a specific button being pressed

In the code sections on the right Field form and Field trigger show triggering on a field, Value form and value triger show triggering on a field and value and Button form and Button trigger show handling multiple buttons

Make sure you form has a unique id, not present in any other form in the bot. You can create a hidden field with a unique id. Just set the isVisible property to false and set a dummy default by propulating the value property

{
  "id": "field",
  "trigger": {
    "type": "message",
    "values": [
      "field"
    ]
  },
  "nodes": [
    {
      "type": "message",
      "message": "Not available",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.adaptive",
        "content": {
          "type": "AdaptiveCard",
          "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
          "version": "1.2",
          "body": [
            {
              "type": "TextBlock",
              "text": "Name"
            },
            {
              "type": "Input.Text",
              "placeholder": "Please enter your name",
              "id": "name"
            }
          ],
          "actions": [
            {
              "type": "Action.Submit",
              "title": "OK"
            }
          ]
        }
      }
    }
  ]
}
{
  "id": "field trigger",
  "trigger": {
    "type": "formData",
    "fieldName": "name",
    "values": [],
    "output": "data"
  },
  "nodes": [
    {
      "type": "message",
      "message": "Name is {data/name}"
    }
  ]
}
{
  "id": "value",
  "trigger": {
    "type": "message",
    "values": [
      "value"
    ]
  },
  "nodes": [
    {
      "type": "message",
      "message": "Not available",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.adaptive",
        "content": {
          "type": "AdaptiveCard",
          "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
          "version": "1.2",
          "body": [
            {
              "type": "TextBlock",
              "text": "What's your favourite cola?"
            },
            {
              "type": "Input.ChoiceSet",
              "id": "cola",
              "choices": [
                {
                  "title": "Pepsi",
                  "value": "Pepsi"
                },
                {
                  "title": "Coke",
                  "value": "Coke"
                }
              ]
            }
          ],
          "actions": [
            {
              "type": "Action.Submit",
              "title": "OK"
            }
          ]
        }
      }
    }
  ]
}    
{
  "id": "value trigger",
  "trigger": {
    "type": "formData",
    "fieldName": "cola",
    "values": [
      "Coke"
    ],
    "output": "data"
  },
  "nodes": [
    {
      "type": "message",
      "message": "You selected Coke"
    }
  ]
}
{
  "id": "buttons",
  "trigger": {
    "type": "message",
    "values": [
      "buttons"
    ]
  },
  "nodes": [
    {
      "type": "message",
      "message": "Not available",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.adaptive",
        "content": {
          "type": "AdaptiveCard",
          "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
          "version": "1.2",
          "body": [
            {
              "type": "Are you sure?",
              "text": "Name"
            }
          ],
          "actions": [
            {
              "type": "Action.Submit",
              "title": "Yes",
              "data": {
                "button": "Yes"
              }
            },
            {
              "type": "Action.Submit",
              "title": "Yes",
              "data": {
                "button": "No"
              }
            }
          ]
        }
      }
    }
  ]
}
{
  "id": "button trigger",
  "trigger": {
    "type": "formData",
    "fieldName": "button",
    "values": [],
    "output": "data"
  },
  "nodes": [
    {
      "type": "message",
      "message": "You pressed {data/button}"
    }
  ]
} 

Instant FAQ reply

Runs a dialogue when an Instat FAQ reply is generated

Properties
type

instandFaqReply

output

Reply text

On;y applicable when Instant FAQs are turned on for your organisation

The dialogue is triggered when user input matches an Instant FAQ question.

{
  "id": "reply",
  "trigger": {
    "type": "instantFaqReply",
    "output": "anwser"          
  },
  "nodes": [
    {
      "type": "message",
      "message": "The answer is {answer}"
    }
  ]
}

Getting Started

This section contain examples of all the types of nodes. When you want to add a node to a dialogue, you don’t have to type it all in by hand. You can use Snippets to paste template JSON for the node.

A few common actions are listed below, along with the node types that might be helpful

I want to display a message

I want to capture typed user input

I want to display a card

I want to capture user input via buttons

I want to validate data

I want to perform a calculation

I want to set up data

I want to talk to APIs

I want to upload a file

I want to call another dialogue

I want to create a loop

I want to cancel a dialogue

I want to end a conversation

I want to send information to a directline client app

I want to extract data from a LUIS response

Message

Send a message to the user

Properties
type

message

message

The message sent to the user. Can be a mixture of fixed text, references to variables or a reference to a language file

customContent

Enables the display of [Hero cads](#desktophero] and for MHR apps only, other Besppoke Cards

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

Use \n\r to split a message over multiple lines. Outputting a single message with multple lines will eliminate the delay the user experiences after each message is displayed.

Use the customContent property if you want to create cards using custom JSON. Many channels support Hero cards. Also MHR apps support additional Bespoke cards. An example of a Hero card is shown to the right. Also, if buttons are displayed with no other text or images a set of quick reply buttons will be displayed

There is also a trigger type of message. This enables the user to start a dialogue by sending a message to talksuite, whereas the message node sends a message to the user

{
  "type": "message",
  "message": "Hello! I'm a bot!"
}
{
      "type": "message",
      "message": "Message displayed if the card is not available in the channel",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.hero",
        "content": {
          "title": "Place Title Here",
          "subtitle": "Place Subtitle Here",
          "text": "PLace Card Text Here",
          "images": [
            {
              "url": "https://media.giphy.com/media/3owzWmHgH4Mbh0Omre/giphy.gif"
            }
          ],
          "buttons": [
            {
              "type": "postBack",
              "title": "Place button label here",
              "value": "enter input that will trigger a dialogue"
            },
            {
              "type": "openUrl",
              "title": "BBC site",
              "value": "http:/bbc.co.uk"
            }
          ]
        }
      }
}      
{
  "type": "message",
  "message": "Message displayed if the card is not available in the channel",
  "customContent": {
    "contentType": "application/vnd.microsoft.card.hero",
    "content": {
      "buttons": [
        {
          "type": "postBack",
          "title": "Quick reply 1",
          "value": "enter input that will trigger a dialogue"
        },
        {
          "type": "postBack",
          "title": "Quick reply 2",
          "value": "enter input that will trigger a dialogue"
        }
      ]
    }
  }
}    

String Prompt

Ask the user for text input

Properties
type

stringPrompt

message

The message output to the user. A string or reference to a language file

retryMessage

Message to be output if validation fails.

maxRetries

Maximum number of retries allowed after validation failure. Default value is 3

continueOnFail

If set to true after the maximum failed attemps, the dialogue will continue with null output. If false, the dialogue will stop. Default is false

validation

Logical expression evaluating to true or false using JSON logic. This will normally be a comparison. The retryMessage will be displayed if the expression evaluates to false

customContent

Enables the display of Bespoke Cards

output

Variable name in which the user input will be stored. If not supplied the user will not be prompted for input

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

If the output variable is populated, the user will not be prompted. Therefore if you have two prompt nodes in the same dialogue with the same output variable, the second prompt will never be run.

The retryMessage property is mandatory, but is not used unless you have a validation rule in the node.

You can reference the output variable in the retryMessage

{
  "type": "stringPrompt",
  "message": "Enter your name",
  "retryMessage": "Enter your name",
  "output": "name"
}
{
  "type": "stringPrompt",
  "message": "Please enter your sort code (xx-xx-xx)",
  "retryMessage": "Please enter a string like xx-xx-xx",
  "output": "sortCode",
  "validation": {
    "method": [
       { "var": "sortCode" },
       "matchesPattern",
        [ "^\\d\\d-\\d\\d-\\d\\d$" ]
     ]
  }
}

Number Prompt

Ask the user for numeric input

Properties
type

numberPrompt

message

The message output to the user. A string or reference to a language file

retryMessage

Message to be output if validation fails. talksuite will validate that the input is a valid number. Additional validation can be performed using the validation property

maxRetries

Maximum number of retries allowed after validation failure. Default value is 3

continueOnFail

If set to true after the maximum failed attemps, the dialogue will continue with null output. If false, the dialogue will stop. Default is false

validation

Logical expression evaluating to true or false using JSON logic. This will normally be a comparison. The retryMessage will be displayed if the expression evaluates to false

customContent

Enables the display of Bespoke Cards

output

Variable name in which the user input will be stored.

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

If the output variable is populated, the user will not be prompted. Therefore if you have two prompt nodes in the same dialogue with the same output variable, the second prompt will never be run.

{
  "type": "numberPrompt",
  "message": "Please enter your age",
  "retryMessage": "That's not a valid number",
  "output": "age"
}
{
      "type": "numberPrompt",
      "message": "How old are you, if you don't mind me asking",
      "retryMessage": "That's not a valid age. Plese try again",
      "output": "age",
      "validation": {
        ">=": [
          {
            "var": "age"
          },
          0
        ]
}

Date Prompt

Ask the user for date input

Properties
type

datePrompt

message

The message output to the user. A string or reference to a language file

retryMessage

Message to be output if validation fails. talksuite will validate that the input in a valid date. Additional validation can be performed using the validation property

maxRetries

Maximum number of retries allowed after validation failure. Default value is 3

continueOnFail

If set to true after the maximum failed attemps, the dialogue will continue with null output. If false, the dialogue will stop. Default is false

validation

Logical expression evaluating to true or false using JSON logic. This will normally be a comparison. The retryMessage will be displayed if the expression evaluates to false

customContent

Enables the display of Bespoke Cards

output

Variable name in which the user input will be stored.

context

When date input is ambiguous (e.g. Friday or 1st June), if the context is set to “earliest” the earlier possible date is chosen (e.g. last Friday, last June). If the context is “latest” then the later date is chosen (e.g. next Friday, next June)

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

If the output variable is populated, the user will not be prompted. Therefore if you have two prompt nodes in the same dialogue with the same output variable, the second prompt will never be run.

If you are using LUIS, you can use the NLP Date Prompt node as an alternative. This will ensure consistency between date entities extracted from an utterance and data captured via prompts

{
  "type": "datePrompt",
  "message": "Enter the start date of your holiday",
  "retryMessage": "That's not a valid date",
  "output": "startDate"
}
[
    {
      "type": "operation",
      "operation": {
        "Date.currentDate": []
      },
      "output": "DateToday"
    },
    {
      "type": "datePrompt",
      "message": "Enter your overtime start date",
      "retryMessage": "Cannot be in the future", 
      "output": "startDate",
      "context" : "earliest",
      "validation": {
        "<=": [
          {
            "var": "startDate"
          },
          {
            "var": "DateToday"
          }
        ]
      }
    }
]

Date-time Prompt

Ask the user for date-time input

Properties
type

dateTimePrompt

message

The message output to the user. A string or reference to a language file

retryMessage

Message to be output if validation fails. talksuite will validate that the input is a valid date-time. Additional validation can be performed using the validation property

maxRetries

Maximum number of retries allowed after validation failure. Default value is 3

continueOnFail

If set to true after the maximum failed attemps, the dialogue will continue with null output. If false, the dialogue will stop. Default is false

validation

Logical expression evaluating to true or false using JSON logic. This will normally be a comparison. The retryMessage will be displayed if the expression evaluates to false

customContent

Enables the display of Bespoke Cards

output

Variable name in which the user input will be stored.

context

When date input is ambiguous (e.g. Friday or 1st June), if the context is set to “earliest” the earlier possible date is chosen (e.g. last Friday, last June). If the context is “latest” then the later date is chosen (e.g. next Friday, next June)

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

If the output variable is populated, the user will not be prompted. Therefore if you have two prompt nodes in the same dialogue with the same output variable, the second prompt will never be run.

If you want to validate that the input is not in the past (or future), obtain the current date and time in the validation section of the prompt, rather than in a preceding node. This will refresh the current time each time the user inputs a value, so if the user leave the system for a while, the current date won’t get stale. See the “Validate not in past” snippet

{
  "type": "dateTimePrompt",
  "message": "Enter the start date and time of your holiday",
  "retryMessage": "That's not a valid date",
  "output": "startDate"
}
{
  "type": "dateTimePrompt",
  "message": "When do you want to book your appointment?",
  "retryMessage": "You cannot book an appointment in the past",
  "output": "appointment",
  "validation": {
    ">": [
      {
        "var": "appointment"
      },
      {
        "DateTime.currentDateTime": []
      }
    ]
  }
}

Time Prompt

Ask the user for time input

Properties
type

timePrompt

message

The message output to the user. A string or reference to a language file

retryMessage

Message to be output if validation fails. talksuite will validate that the input is a valid time. Additional validation can be performed by the validation property

maxRetries

Maximum number of retries allowed after validation failure. Default value is 3

continueOnFail

If set to true after the maximum failed attemps, the dialogue will continue with null output. If false, the dialogue will stop. Default is false

validation

Logical expression evaluating to true or false using JSON logic. This will normally be a comparison. The retryMessage will be displayed if the expression evaluates to false

customContent

Enables the display of Bespoke Cards

output

Variable name in which the user input will be stored.

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

If the output variable is populated, the user will not be prompted. Therefore if you have two prompt nodes in the same dialogue with the same output variable, the second prompt will never be run.

If you want to validate that the input is not in the past (or future), obtain the current date and time in the validation section of the prompt, rather than in a preceding node. This will refresh the current time each time the user inputs a value, so if the user leavse the system for a while, the current date won’t get stale. See the “Validate not in the past” snippet in the DateTime Prompt

{
  "type": "timePrompt",
  "message": "Enter the start time of your holiday",
  "retryMessage": "That's not a valid time",
  "output": "startTime"
}

Decision

Decides which of two paths to take through a dialogue

Properties
type

decision

rule

Logical expression evaluating to true or false using JSON logic. This will normally be a comparison

passNode

id of the node to run if the rule evaluates to true

failNode

id of the node to run if the rule evaluates to false

You don’t need to specify a pass node and a fail node. Often you may just have one and fall through to the next node for the alternative path.

[
    {
      "type": "confirmationPrompt",
      "message": "Do you prefer Pepsi or Coke",
      "retryMessage": "Please select one of the Pepsi or Coke buttons",
      "positiveMessage": "Pepsi",
      "negativeMessage": "Coke",
      "output": "likesPepsi"
    },
    {
      "type": "decision",
      "rule": {
        "===": [
          {
            "var": "likesPepsi"
          },
          true
        ]
      },
      "passNode": "Pepsi",
      "failNode": "Coke"
    },
    {
      "id": "Pepsi",
      "type": "message",
      "message": "I prefer Pepsi too",
      "nextNode": "Dialogue.stop"
    },
    {
      "id": "Coke",
      "type": "message",
      "message": "I'm more of a Pepsi person myself"      
    }
]
{
      "type": "decision",
      "description": "Check if  : (animal is Dog and noise is Woof) or (animal is Cat and noise is Meow)",
      "rule": {
        "or": [
          {
            "and": [
              {
                "===": [
                  {
                    "var": "animal"
                  },
                  "Dog"
                ]
              },
              {
                "===": [
                  {
                    "var": "noise"
                  },
                  "Woof"
                ]
              }
            ]
          },
          {
            "and": [
              {
                "===": [
                  {
                    "var": "animal"
                  },
                  "Cat"
                ]
              },
              {
                "===": [
                  {
                    "var": "noise"
                  },
                  "Meow"
                ]
              }
            ]
          }
        ]
      },
      "passNode": "correct",
      "failNode": "wrong"
}

Confirmation Prompt

Displays a card with two buttons, one of which returns a true response and one a false response

Properties
type

confirmationPrompt

message

The message displayed on the card. A string or reference to a language file

retryMessage

Message to be displayed if the user response is not a button press (or one of the button labels typed)

maxRetries

Maximum number of retries allowed after validation failure. Default value is 3

continueOnFail

If set to true after the maximum failed attemps, the dialogue will continue with null output. If false, the dialogue will stop. Default is false

positiveMessage

Label of the button, which if pressed will return true

negativeMessage

Label of the button, which if pressed will return false

output

Name of the variable in which the true or false response is stored

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

If you want more than two buttons, use a Choice Prompt

{
       "type": "confirmationPrompt",
       "message": "Do you prefer Pepsi or Coke",
       "retryMessage": "Please enter Pepsi or Coke",
       "positiveMessage": "Pepsi",
       "negativeMessage": "Coke",
       "output": "likesPepsi"
 },
 {
       "type": "message",
       "message": "Output is {likesPepsi}"
       
 }

Choice Prompt

Displays a card with a list of buttons. The label of the selected button is returned.

Properties
type

choicePrompt

message

The message displayed on the card. A string or reference to a language file

retryMessage

Message to be displayed if the user response is not a button press (or one of the button labels typed)

maxRetries

Maximum number of retries allowed after validation failure. Default value is 3

continueOnFail

If set to true after the maximum failed attemps, the dialogue will continue with null output. If false, the dialogue will stop. Default is false

listName

List containing the button labels

displayName

If the list is a list of objects, this is the property within the object that contains the button label

output

The item from listName that the user selected.

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

{
      "type": "choicePrompt",
      "message": "What is your favourite drink",
      "retryMessage": "Please selected from the list of drinks",
      "listName": "drinks",
      "output": "selectedDrink"
},
{
      "type": "message",
      "message": "Output is {selectedDrink}"
      
}
{
      "type": "choicePrompt",
      "message": "What is your favourite drink",
      "retryMessage": "Please selected from the list of drinks",
      "listName": "drinks",
      "displayName" :"name",
      "output": "selectedDrink"
},
{
      "type": "message",
      "message": "Output is {selectedDrink/name}"
      
}

Hero Card

Display a card.

Properties
type

message

contentType

application/vnd.peoplefirst.card.hero

content

Card content

content/title

Card title

content/subtitle

Card subtitle

content/text

Card text

content/images

Card image section

content/images/url

Card image

content/buttons

List of buttons

buttons/type

Type of button

buttons/title

Button label

buttons/value

Value to be returned when the button is pressed

Hero cards can display text and images and contain buttons which trigger web sites or other dialogues

If you want to use a card as a prompt to capture data in the dialogue, use a card

The hero card is a generic card

Use in a message node to display a single card

Use in a custom card collection node to display a carousel of cards

{
      "type": "message",
      "message": "Message displayed if the card is not available in the channel",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.hero",
        "content": {
          "title": "Place Title Here",
          "subtitle": "Place Subtitle Here",
          "text": "PLace Card Text Here",
          "images": [
            {
              "url": "https://media.giphy.com/media/3owzWmHgH4Mbh0Omre/giphy.gif"
            }
          ],
          "buttons": [
            {
              "type": "postBack",
              "title": "Place button label here",
              "value": "enter input that will trigger a dialogue"
            },
            {
              "type": "openUrl",
              "title": "BBC site",
              "value": "http:/bbc.co.uk"
            }
          ]
        }
      }
}      
{
  "type": "message",
  "message": "Message displayed if the card is not available in the channel",
  "customContent": {
    "contentType": "application/vnd.microsoft.card.hero",
    "content": {
      "buttons": [
        {
          "type": "postBack",
          "title": "Quick reply 1",
          "value": "enter input that will trigger a dialogue"
        },
        {
          "type": "postBack",
          "title": "Quick reply 2",
          "value": "enter input that will trigger a dialogue"
        }
      ]
    }
  }
}    

Adaptive Card

Display a complex card or form.

Properties
type

message

contentType

application/vnd.microsoft.card.adaptive

content

Adaptive card JSON. Use the adaptive card designer to design a card and generate the JSON

Adaptive cards can display images, input fields, text and buttons. Text can be formatted.

Adaptive cards can be used in the talksuite universal client, web chat and MS Teams.

For more info see the section on Adaptive cards

Adaptive cards cannot be used in the talksuite mobile app (which predates the universal client) or the people first app.

Action

Interact with APIs

Properties
type

action

service

Properties containing information to be sent to the API

service/url

URL of the API

service/method

GET to read data, POST to create data, PUT or PATCH to update data and DELETE to delete data

service/headers

Header names and values

service/body

Body data to be sent for POSTs, PUTs and PATCHs

output

Properties containing information returned by the API

output/body

Response body from the API

output/header

Response headers from the API

authorised

If set to true, the Authorization header need not be specified, as it will be generated by talksuite

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

Use the built-in variable dialogue/lastApiStatusCode to get the HTTP response code from the API call.

PUTs and DELETEs in people first requires an If-Match concurrency header to be supplied. This can be obtained from the ETag response header from a GET on the record to be updated.

It’s a good idea to specify the common base of URLs as a bot constant.

If you are accessing APIs which require authentication, the built-in variable User/Authentication will be populated with a bearer token once the user has signed into the application. The application will be determined from the domain specified in the bot configuration record, so there is no need to specify which token you need.

When a dialogue is triggered, talksuite will analyse the action nodes in the dialogue and look at the urls being used. If the url matches one of the service domains of the secondary authentication providers in the bot config record, it will ask the user to authenticate.

If you want to bypass automatic secondary authentication, hold the url in two parts and concatentate it in the dialogue. You can now catch the 401 yourself.

When accessing an API that uses XML, if you need to use attributes, prefix the attribute name with the @ character.

If you want to see the structure of a request or response body, use Request Bin to generate a unique URL. If you post the JSON object to that URL, you can use Request Bin to view its contents

{
      "type": "action",
      "service": {
        "url": "https://www.gov.uk/bank-holidays.json",
        "method": "GET",
        "headers": {
          "Accept": "application/json"
        }
      },
      "outputs": {
        "body": {
          "holidays": "/england-and-wales/events"
        }
      }
}
{
      "type": "action",
      "service": {
        "body": {
          "friendsfamily": "{friendsfamily}"
        },
        "method": "POST",
        "url": "{Bot/baseUrl}/hrm/people/{personId}/friendsfamily?annotate=t",
        "headers": {
          "Accept": "application/json",
          "TenantCode": "{Bot/TenantCode}",
          "EnvironmentCode": "{Bot/EnvironmentCode}",
          "Authorization": "Bearer {User/AuthenticationToken}"
        }
}  
{
      "type": "action",
      "service": {
        "url": "{Bot/baseUrl}/profile",
        "method": "GET",
        "headers": {
          "Accept": "application/json",
          "TenantCode": "{Bot/TenantCode}",
          "EnvironmentCode": "{Bot/EnvironmentCode}",
          "Authorization": "Bearer {User/AuthenticationToken}"
        }
      },
      "outputs": {
        "body": {
          "timeZoneId": "/data/profile/timeZoneId",
          "regionId": "/data/profile/regionId"
        }
      }
}  
{
      "type": "action",
      "service": {
        "url": "{Bot/baseUrl}/hrm/friendsfamily/{selectedContact/emergencyContactId}?annotate=t&personId={personId}",
        "method": "GET",
        "headers": {
          "Accept": "application/json",
          "TenantCode": "{Bot/TenantCode}",
          "EnvironmentCode": "{Bot/EnvironmentCode}",
          "Authorization": "Bearer {User/AuthenticationToken}"
        }
      },
      "outputs": {              
        "header": {
          "etagResponse": "ETag"
        }
      }
    },
    {
      "type": "decision",
      "rule": {
        "===": [
          {
            "var": "Dialogue/lastApiStatusCode"
          },
          200
        ]
      },
      
      "failNodeIndex": "Error"
    },
    {
      "type": "action",
      "service": {
        "body": {
          "friendsfamily": "{contact}"
        },
        "method": "PUT",
        "url": "{Bot/baseUrl}/hrm//friendsfamily/{selectedContact/emergencyContactId}?annotate=t",
        "headers": {
          "Accept": "application/json",
          "TenantCode": "{Bot/TenantCode}",
          "EnvironmentCode": "{Bot/EnvironmentCode}",
          "Authorization": "Bearer {User/AuthenticationToken}",
          "If-Match": "{etagResponse}"
        }
      }
}
{
      "type": "action",
      "service": {
        "url": "{Bot/baseUrl}/hrm/friendsfamily/{selectedContact/emergencyContactId}?annotate=t&personId={personId}",
        "method": "GET",
        "headers": {
          "Accept": "application/json",
          "TenantCode": "{Bot/TenantCode}",
          "EnvironmentCode": "{Bot/EnvironmentCode}",
          "Authorization": "Bearer {User/AuthenticationToken}"
        }
      },
      "outputs": {
        "body": {
          "contact": "/data/friendsfamily"
        },
        "header": {
          "etagResponse": "ETag"
        }
      }
    },
    {
      "type": "decision",
      "rule": {
        "===": [
          {
            "var": "Dialogue/lastApiStatusCode"
          },
          200
        ]
      },
      "failNode": "Error"
},
{
      "type": "action",
      "service": {
        "method": "DELETE",
        "url": "{Bot/baseUrl}/hrm//friendsfamily/{selectedContact/emergencyContactId}?annotate=t",
        "headers": {
          "Accept": "application/json",
          "TenantCode": "{Bot/TenantCode}",
          "EnvironmentCode": "{Bot/EnvironmentCode}",
          "Authorization": "Bearer {User/AuthenticationToken}",
          "If-Match": "{etagResponse}"
        }
      }
}      
   
{
  "type": "action",
   "service": {
     "url": "{bot/trentApiBaseUrl}/wrd_rest/run/PerAbsHols.xml?abs_id=new",
      "authorised": true,
      "headers": {
         "Accept": "text/xml",
         "Content-Type": "text/xml"
        },
      "body": {
         "?xml": {
           "@version": "1.0",
           "@encoding": "UTF-8"
          },
          "itrent": {
             "@xmlns:xl": "http://www.w3.org/1999/xlink",
              "absence": {
                 "@status": "new",
                 "@crc": "",
                 "@id": "",
                 "abs_d": "{dayOffDateString}",
                 "hol_period": "FULL",
                 "abs_type_id": "022550005C",
                 "dl_avail_jobs": "T",
                 "update_i": "T",
                 "notes": null
                 }
           }
         },
      "method": "POST"
     },
     "outputs": {
       "body": {
       "response": ""
    }
  }
}

Operation

Perform a calculation

Properties
type

operation

operation

Variable name for an assignment or a JSON logic method or date function

output

Name of the variable in which the result is stored

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

Operation nodes are used to copy the value of one variable to another and to perform JSON logic operations and execute date functions.

The operation can’t be a constant value. It needs to be a function or a variable. If you want to assign a constant to a variable, define the constant in the model, then you can assign the model variable to another variable

{
      "description" : "Assign the value of x to y",
      "type": "operation",
      "operation": {
          "var" :"x"
      },
      "output": "y"
}
{
      "description" : "Concatenate hello, a space and world into the text variable",
      "type": "operation",
      "operation": {
        "cat": [
          "hello",
          " ",
          "world"
        ]
      },
      "output": "text"
}
{
      "description" : "Store the number of characters in the variable text in lengthOfText",
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "text"
          },
          "length"
        ]
      },
      "output": "lengthOfText"
}
{
      "description" : "Get the current date",
      "type": "operation",
      "operation": {
        "Date.currentDate": []
      },
      "output": "DateToday"
}

Build Object

Build a data object

Properties
type

buildObject

object

Properties and values defining the structure of the object

output

Name of the variable in which the result is stored

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

The object can be built using constant property values or individual variables for each property. Examples are shown on the right

A build object node removes the need for multiple property assignment and addItem operation nodes

Use this node to build action node bodies and the data for carousels of cards

You can only assign individual values to an object in a buildObject node

If you want to insert list variables or other object variables into an object, use an operation node

You can build a simple list in two steps. Firstly build an object with one property which is a list. Secondly copy the property of the object to another object. See the Simple list example on the right

{
  "type" : "buildObject",
  "object" : {        
    "customer": {
      "firstName": "Tony",
      "lastName": "Hancock",
      "favourites" : [
        "Books",
        "Albums",
        "Games"
      ]
    },
    "orders": [
      {
        "productType": "Book",
        "productDetails": {
          "title": "1984",
          "author": "George Orwell"
        },
        "price": 7.99
      },
      {
        "productType": "Album",
        "productDetails": {
          "title": "Aqualung",
          "artist": "Jethro Tull"
        },
        "price": 15
      }
    ],
    "address": {
      "houseNumber": 23,
      "streetName": "Railway Cuttings",
      "town": "East Cheam",
      "billingAddress": true
    }
  },
  "output" : "orders"
}
{
  "type" : "buildObject",
  "object" : {        
    "customer": {
      "firstName": "{firstName}",
      "lastName": "{lastName}",
      "favourites" : [
        "{fav1}",
        "{fav2}",
        "{fav3}"
      ]
    },
    "orders": [
      {
        "productType": "{prodType1}",
        "productDetails": {
          "title": "{prodTitle1}",
          "author": "{prodauthor1}"
        },
        "price": "{price1}"
      },
      {
        "productType": "{prodType21}",
        "productDetails": {
          "title": "{prodTitle2}",
          "author": "{prodauthor2}"
        },
        "price": "{price2}"
      }
    ],
    "address": {
      "houseNumber": "{houseNum}",
      "streetName": "{street}",
      "town": "{town}",
      "billingAddress": "{billing}"
    }
  },
  "output" : "orders"
}
{
      {
      "type": "buildObject",
      "object": {
        "tempProperty": [
          "a",
          "b"
        ]
      },
      "output": "tempObject"
    },
    {
      "type": "operation",
      "operation": {
        "var": "tempObject/tempProperty"
      },
      "output": "myList"
    },
    {
      "type": "message",
      "message": "{myList/1}"
    }
}      

Dialogue

Run another dialogue

Properties
type

dialogue

dialogueId

Name of the dialogue to call.

inputs

Define the transfer of data from local variables to variables in the called dialogue. See the snippets for details

outputs

Define the transfer of data from variables in the called dialogue back to local variables in the called dialogue. See the snippets for details

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

The snippets show one input variable and one output variable, but multiple variables can be specified for input and output.

If the calling dialogue is in a project, the called dialogue cannot be in another project.

See Node Flow for other ways of controlling the flow of dialogues

The dialogueId is validated, so you’ll have to create the called dialogue before referencing it in a node

Any variables used or declared in the model in a calling dialogue will not be available in the called dialogue unless they are passed in as parameters using the inputs property

{
  "type": "dialogue",
  "dialogueId": "example called dialogue",
  "description": "passOut and getBack are in this dialogue. receiveVar and outputVar are in the called dialogue",
  "inputs": {
    "receiveVar": {
      "var": "passOut"
    }
  },
  "outputs": {
    "getBack": {
      "var": "outputVar"
    }
  }  
}
{
  "id": "example called dialogue",
  "trigger": {
    "type": "nestedDialogue"
   },
   "nodes": [
     {
       "type": "operation",
       "description": "receiveVar is passed in and outputVar is returned",
       "operation": {
          "var": "receiveVar"
       },
       "output": "outputVar"
     }
    ]
}

Sequence Dialogue

Run another dialogue repeatedly for each item in a list

Properties
type

sequenceDialogue

dialogueId

Name of the dialogue to call.

inputItem

The name of the list variable

listName

The name of the variable in the called dialogue that will be populated for each record in the list, one record at a time, with one call for each record

inputs

Additional parameters to be passed into the called dialogue. Each parameter consists of a mapping from a local variable to a variable in the called dialogue. See the advance snippets for details

outputs

Additional parameters returned from the called dialogue. Each parameter consists of a mapping from a local variable to a variable in the called dialogue. See the advance snippets for details

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

If the calling dialogue is in a project, the called dialogue cannot be in another project.

See Node Flow for other ways of controlling the flow of dialogues

The dialogueId is validated, so you’ll have to create the called dialogue before referencing it in a node

Use the inputs and outputs properties to return data aggregated from multiple records

Any variables used or declared in the model in a calling dialogue will not be available in the called dialogue unless they are passed in as parameters using the inputs property

{
  "id": "sequence example 1",
  "trigger": {
    "type": "message",
    "values": [
      "sequence example 1"
    ]
  },
  "nodes": [
    {
      "type": "buildObject",
      "object": {
        "order": [
          {
            "item": "Ham and pineapple pizza",
            "cost": 10.99
          },
          {
            "item": "Diet coke",
            "cost": "2.99"
          },
          {
            "item": "Garlic bread",
            "cost": "3.99"
          }
        ]
      },
      "output": "data"
    },
    {
      "type": "sequenceDialogue",
      "dialogueId": "order item",
      "inputItem": "item",
      "listName": "data/order"
    }
  ]
}     
{
  "id": "order item",
  "trigger": {
    "type": "nestedDialogue"
  },
  "nodes": [
    {
      "type": "message",
      "message": "{item/item} @ {item/cost}"
    }
  ]
}
{
  "id": "sequence example 2",
  "trigger": {
    "type": "message",
    "values": [
      "sequence example 2"
    ]
  },
  "model": {
    "total": "0"
  },
  "nodes": [
    {
      "type": "buildObject",
      "object": {
        "order": [
          {
            "item": "Ham and pineapple pizza",
            "cost": 10.99
          },
          {
            "item": "Diet coke",
            "cost": "2.99"
          },
          {
            "item": "Garlic bread",
            "cost": "3.99"
          }
        ]
      },
      "output": "data"
    },
    {
      "type": "sequenceDialogue",
      "dialogueId": "order value",
      "inputItem": "item",
      "listName": "data/order",
      "inputs": {
        "total": {
          "var": "total"
        }
      },
      "outputs": {
        "total": {
          "var": "total"
        }
      }
    },
    {
      "type": "message",
      "message": "Order value is {total}"
    }
  ]
}  
{
  "id": "order value",
  "trigger": {
    "type": "nestedDialogue"
  },
  "nodes": [
    {
      "type": "operation",
      "operation": {
        "+": [
          {
            "var": "item/cost"
          },
          {
            "var": "total"
          }
        ]
      },
      "output": "total"
    }
  ]
}

Repeat Dialogue

Run another dialogue repeatedly until a condition is met

Properties
type

repeatDialogue

dialogueId

Name of the dialogue to call.

inputs

Variables passed to the called dialogue

outputs

Variables received from the called dialogue

repeatUntil

A Boolean expression in JSON logic. The nested dialogue will be called until this expression evaluates to true

maximumRepetitions

Maximum number iterations allows

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

If the calling dialogue is in a project, the called dialogue cannot be in another project.

See Node Flow for other ways of controlling the flow of dialogues

If you are using a numeric variable, incremented in the called dialogue to control the loop, if you don’t initialise the variable to “0” in the model, the variable will always be null, as null + 1 = null. This will put you into an infinite loop

After 1000 iterations of a loop, the talksuite will assume you are in an infinite loop and stop. You can also stop infinite loops using the maximumRepetitions property

The dialogueId is validated, so you’ll have to create the called dialogue before referencing it in a node

When you are debugging, to get out of an infinite loops, put a string prompt in the called node and write an interrupt dialogue that cancels the current dialogue. You can remove the string prompt when the loop logic is tested.

Any variables used or declared in the model in a calling dialogue will not be available in the called dialogue unless they are passed in as parameters using the inputs property

{
  "type": "repeatDialogue",
  "dialogueId": "example called repeatedly",
  "description" : "i is a variable in the calling dialogue, passed into the called dialogue as index. finished is a boolean in both dialogues",
  "inputs": {
    "index": {
      "var": "i"
    }
  },
  "outputs": {
    "i": {
      "var": "index"
    },
    "finished": {
      "var": "finished"
    }
  },
  "repeatUntil": {
    "===": [
      {
        "var": "finished"
      },
      "finished"
    ]
  }
} 
{
    "data": {
      "attributes": {
        "json": {
          "id": "example called repeatedly",
          "trigger": {
            "type": "nestedDialogue"
          },
          "nodes": [
            {
              "type": "operation",
              "output": "index",
              "operation": {
                "+": [
                  {
                    "var": "index"
                  },
                  1
                ]
              },
              "nextNodeIndex": 1
            },
            {
              "type": "choicePrompt",
              "message": "{index}: I'll keep asking you this until you select Stop",
              "retryMessage": "?",
              "listName": "menuButtons",
              "output": "menuChoice",
              "nextNodeIndex": 2
            },
            {
              "type": "decision",
              "rule": {
                "===": [
                  {
                    "var": "menuChoice"
                  },
                  {
                    "var": "menuButtons/0"
                  }
                ]
              },
              "passNodeIndex": 3,
              "failNodeIndex": null
            },
            {
              "type": "operation",
              "operation": {
                "var": "finishedText"
              },
              "output": "finished",
              "nextNodeIndex": null
            }
          ],
          "model": {
            "finishedText": "finished",
            "menuButtons": [
              "Stop",
              "Continue"
            ]
          }
        }
      }
    }
}      

Download Action

GET binary data from an API

Properties
type

action

service

Properties containing information to be sent to the API

service/url

URL of the API

service/headers

Header names and values

output

Name of the object to store the returned data

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

The output variable has a number of properties. If the output variable name is image, use image/url to access the data and image/contentType to get the content type.

If you are accessing APIs which require authentication, the built-in variable User/Authentication will be populated with a bearer token once the user has signed in in to the application. The application will be determined from the domain specified in the bot configuration record, so there is no need to specify which token you need.

{
      "type": "downloadAction",
      "service": {
        "url": "{Bot/BaseUrl}/hrm/people/{person/personId}/photo",
        "headers": {
          "TenantCode": "{Bot/TenantCode}",
          "EnvironmentCode": "{Bot/EnvironmentCode}",
          "Authorization": "Bearer {User/AuthenticationToken}",
          "Accept": "*/*"
        }
      },
      "outputs": {
        "content": "image"
}

Attachment Prompt

Ask the user to upload a file

Properties
type

attachmentPrompt

message

The message output to the user. A string or reference to a language file

retryMessage

Message to be output if validation fails

contentTypes

Array of valid file types (e.g. image/jpeg)

customContent

See Custom Content for reference

output

Variable name of the object in which the uploaded file information will be stored. The image is stored in the url property of the object

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

{
    "type": "attachmentPrompt",      
    "message": "Upload an image",
    "retryMessage": "Invalid file type - please supply a jpg, jpeg, png or gif file type",
    "contentTypes": [ "image/jpeg", "image/png", "image/gif" ],
    "output": "attachment"
}

Card

Display a card with text, buttons and an image

Properties
type

card

content

Properties describing the content of the card

content/title

Card title

content/subtitle

Subtitle

content/text

Body text of the card

content/image

Image to display on the card. Use the /url property of the image object

content/isThumbnail

If set to true the image is displayed as a thumbnail. Defaults to false

content/tapOptions

Properties controlling actions when the card is tapped

content/tapOptions/displayName

Text to be output when the card is tapped

content/tapOptions/value

Value to be returned when the card is tapped

content/tapOptions/output

Variable in which the returned tap value is stored.

buttonOptions

Properties controlling the buttons on the card

buttonOptions/listName

List of button labels

buttonOptions/displayName

If button list consists of objects, this is the object property to use as the label

buttonOptions/output

Variable which will be populated with the button pressed

content/retryMessage

Message output if the user types anything other than the label of a button

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

If an output property is specified, the node will wait for user input (or a button press or tap). It is possible to have a card with buttons and no output property. In this case the node will not wait for input.

You can write a dialogue triggered on the button label, which will allow the user to press a button or start another dialogue.

{
      "type": "card",
      "content": {
        "title": "Button test",
        "subtitle": "{subTitle}",
        "text": "{text}",
        "image": "{image/Url}",
        "buttonOptions": {
          "listName": "buttonSet",
          "output": "selectedButton"
        },
        "retryMessage": "Please select one of the buttons"
      }
},
{
      "type": "card",
      "content": {
        "title": "Button test",
        "subtitle": "{subTitle}",
        "text": "{text}",
        "image": "{image/Url}"              
      }
}
{
      "type": "card",
      "content": {
        "title": "Tap test",
        "isThumbnail": true,
        "tapOptions": {
          "displayName": "tap",
          "output": "tapped",
          "value": "tap value"
        },
        "subtitle": "{subTitle}",
        "text": "{text}",
        "image": "{image/Url}"
      }
}

Card Collection

Display a scrolling list (carousel) of cards

Properties
type

cardCollection

listName

Name of a list of objects that contains the data for the cards

contentItem

Name to be used within the node to access each object in the list in turn. E.g. if the listName is people, then the content item would be person.

pageSize

Maximum number of cards to display

content

Properties describing the content of the card

content/title

Card title

content/subtitle

Subtitle

content/text

Body text of the card

content/image

Image to display on the card. Use the /url property of the image object

content/isThumbnail

If set to true the image is displayed as a thumbnail. Defaults to false

content/tapOptions

Properties controlling actions when the card is tapped

content/tapOptions/displayName

Text to be output when the card is tapped

content/tapOptions/value

Value to be returned when the card is tapped

content/tapOptions/output

Variable in which the returned tap value is stored.

buttonOptions

Properties controlling the buttons on the card

buttonOptions/listname

List of button labels

buttonOptions/displayName

If button list consists of objects, this is the object property to use as the label

buttonOptions/output

Variable which will be populated with details of the button pressed. If the finishMessage property is present, this can be a list of buttons

content/retryMessage

Message output if the user types anything other than the label of a button

content/finishMessage

A label for a button to be displayed below the carousel. Use this property if you want to allow multiple button selections. The node will only progress when this button is pressed.

content/messageText

Message output above the carousel.

content/output

Object from the listName object list for which the button was pressed. If finishMessage is specified, this can be a list of objects for which a button has been pressed. If not output property is specified, then the node will end without waiting for input.

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

If an output property is specified, the node will wait for user input (or a button press or tap). It is possible to have a card with buttons and no output property. In this case the node will not wait for input. You can write a dialogue triggered on the button label, which will allow the user to press a button or start another dialogue.

{
      "type": "cardCollection",
      "listName": "people",
      "contentItem": "person",
      "pageSize": 3,
      "content": {
        "title": "{person/lastName}",
        "subtitle": "{person/firstName}",
        "image": "{person/photo}",
        "buttonOptions": {
          "listName": "buttons",
          "output": "selectedOption"
        },
        "retryMessage": "Please press one of the buttons"
      },
      "output": "selectedPerson"
}
{
      "type": "cardCollection",
      "listName": "people",
      "contentItem": "person",
      "pageSize": 3,
      "content": {
        "title": "{person/lastName}",
        "subtitle": "{person/firstName}",
        "image": "{person/photo}",
        "buttonOptions": {
          "listName": "buttons",
          "output": "selectedButtons"
        },
        "retryMessage": "Please press one of the buttons"
      },
      "messageText": "You can press a button for several people. Press done when you've finished",
      "finishMessage": "Done",
      "output": "selectedPeople"
}

Event

Change the state of an app

Properties
type

event

event

Set to endDialogue to end the dialogue, leaveConversation to leave the conversation or resettDialogue to re-start the dialogue

You may want to set the priority of dialogues which end dialogues or conversations to interrupt, allowing the user to end a dialogue or conversation while inside a dialogue

{
  "id": "cancel",
   "trigger": {
      "type": "message",
      "values": [
         "cancel"
       ]
    },
    "nodes": [
      {
        "type": "event",
         "event": "endDialogue"
       }
    ],
    "priority": "interrupt"        
}
{
  "data": {
      "attributes": {
      "json": {
          "id": "exit",
          "trigger": {
          "type": "message",
          "values": [
              "exit"
          ]
          },
          "nodes": [
          {
              "type": "event",
              "event": "leaveConversation"
          }
          ],
          "priority": "interrupt"
      }
      }
}
{
     "type": "event",
     "event": "resetDialogue"
}      

Custom Card Collection

Display a scrolling list (carousel) of bespoke cards

Properties
type

customCardCollection

listName

Name of a list of objects that contains the data for the cards

contentItem

Name to be used within the node to access each object in the list in turn. E.g. if the listName is people, then the content item would be person.

pageSize

Maximum number of cards to display

customContent

Enables the display of Bespoke Cards

messageText

Message to display above the carousel

output

Indicates which button on which card has been pressed. Uses the value property of the button section within the custom content

outputOperation.

Allow the button output to be transformed

outputOperation/operation

A JSON logic operation allowing button output to be transformed

outputOperation/output

Variable populated with results of the output operation transformation

validation

A JSON logic operation. If this does not evaluate to true the user input is treated as invalid

retryMessage

A message displayed when validation fails

selected

The object in listName associated with the card for which the button is pressed

finishMessage

The label of a button that will be displayed below the carousel. If specified the user can selected buttons from multiple cards. Only when the finishMessage button is pressed will the node move on to the next node. The output and selected properties will now be lists, containing details of all the cards on which buttons were pressed and each of the button presses

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

This node requires that Bespoke Cards be available in the channel. This is true for Web chat and MHR apps.

The example to the right uses a Build Object to create a static carousel. If the number or content of the cards in the carousel can vary, then build an object up one record at a time using the additem list function

{
  "type": "buildObject",
  "object": {
    "cardList": [
      {
        "title": "Place Title 1 Here",
        "subtitle": "Place Subtitle 1 Here",
        "text": "PLace Card Text  1  Here",
        "images": [
          {
            "url": "https://media.giphy.com/media/3owzWmHgH4Mbh0Omre/giphy.gif"
          }
        ],
        "buttons": [
          {
            "type": "postBack",
            "title": "Place button 1 label here",
            "value": "enter input that will trigger a dialogue"
          }
        ]
      },
      {
        "title": "Place Title 2 Here",
        "subtitle": "Place Subtitle 2 Here",
        "text": "PLace Card Text  2  Here",
        "images": [
          {
            "url": "https://media.giphy.com/media/3owzWmHgH4Mbh0Omre/giphy.gif"
          }
        ],
        "buttons": [
          {
            "type": "postBack",
            "title": "Place button 21 label here",
            "value": "enter input that will trigger a dialogue"
          }
        ]
      }
    ]
  },
  "output": "cards"
},
{
  "type": "customCardCollection",
  "listName": "cards/cardList",
  "contentItem": "card",
  "customContent": {
    "contentType": "application/vnd.microsoft.card.hero",
    "content": {
      "title": "{card/title}",
      "text": "{card/text}",
      "images": [
        {
          "url": "{card/images/0/url}"
        }
      ],
      "buttons": "{card/buttons}"
    }
  }
}      

Custom Event

Send an event to the People First app

Properties
type

customEvent

name

The name of the event. Must be a name accepted by the client app

data

Data object to send to the app

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

This node can only be used when the bot is connected to the People First app. See People First Outbound Events for details

{
  "type": "customEvent",
  "name": "clockInStatus",
  "data": {"var" : "status"}
}

NLP Entities

Natural language processor entity analysis. Allows you to specify what information you want from a LUIS response.

Properties
type

nlpEntities

input

The JSON response from the output property of the intent dialogue

requiredEntities

A list of entities types in the repsonse that you want to pick out. You don’t have to use the full LUIS date-time built-in entity name, so you can specify “builtin.datetimeV2.date” or just “date”

rules

Rules to remove ambiguity

rules/dateContext

Set to latest if ambiguous dates should resolve to a future date or earliest if they should resolve to a past date. The default is latest

rules/likelyStartTime

Set to the hour of the day which defines a 12 hour period that ambiguous times will default to. For example a value of “7” will define a period of 7am to 7pm, which means that 10 o’clock will be resolved to 10am.

outputs

Processed outputs from the LUIS response

outputs/matchedEntities

A list of entity objects matched against the requirements. Each object has a type property (the LUIS entity type), a value property (the resolved LUIS value) and in the case of half day period, a period property which can be AM or PM.

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

Only applicable when you are using Luis

See Luis Entities for details of how the nlpEntities node matches the raw luis response to your list of required entities

{
  "id": "nlp emtities node example",
  "trigger": {
    "type": "message",
    "values": [
      "nlp entities node example"
    ]
  },
  "model": {
    "datesAndDurations": [
      "date",
      "date"
    ]
  },
  "nodes": [
    {
      "type": "message",
      "message": "Running luis query"
    },
    {
      "type": "nlpQuery",
      "utterance": "Book holiday from today to tomorrow ",
      "output": "luisResponse"
    },
    {
      "type": "nlpEntities",
      "input": "luisResponse",
      "requiredEntities": "datesAndDurations",
      "outputs": {
        "matchedEntities": "results"
      }
    },
    {
      "type": "message",
      "message": "OK. I've booedk holiday from {results/0/value} to {results/1/value}"
    }
  ]
}

NLP Date Prompt

Ask the user for date input and resolve using LUIS

Properties
type

nlpDatePrompt

message

The message output to the user. A string or reference to a language file

retryMessage

Message to be output if validation fails. talksuite will validate that the input in a valid date. Additional validation can be performed using the validation property

maxRetries

Maximum number of retries allowed after validation failure. Default value is 3

continueOnFail

If set to true after the maximum failed attemps, the dialogue will continue with null output. If false, the dialogue will stop. Default is false

validation

Logical expression evaluating to true or false using JSON logic. This will normally be a comparison. The retryMessage will be displayed if the expression evaluates to false

customContent

Enables the display of Bespoke Cards

output

Variable name in which LUIS’s resolution of the user input will be stored.

rules/context

When date input is ambiguous (e.g. Friday or 1st June), if the context is set to “earliest” the earlier possible date is chosen (e.g. last Friday, last June). If the context is “latest” then the later date is chosen (e.g. next Friday, next June)

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

If the output variable is populated, the user will not be prompted. Therefore if you have two prompt nodes in the same dialogue with the same output variable, the second prompt will never be run.

This node will only work if you have specified a LUIS application in your bot

{
  "type": "nlpDatePrompt",
  "message": "Enter the start date of your holiday",
  "retryMessage": "That's not a valid date",
  "output": "startDate"
}
[
    {
      "type": "operation",
      "operation": {
        "Date.currentDate": []
      },
      "output": "DateToday"
    },
    {
      "type": "nlpDatePrompt",
      "message": "Enter your overtime start date",
      "retryMessage": "Cannot be in the future", 
      "output": "startDate",
      "rules" : {
        "context" : "earliest"
      },
      "validation": {
        "<=": [
          {
            "var": "startDate"
          },
          {
            "var": "DateToday"
          }
        ]
      }
    }
]

NLP Time Prompt

Ask the user for time input. Resolve the time using LUIS

Properties
type

nlpTimePrompt

message

The message output to the user. A string or reference to a language file

retryMessage

Message to be output if validation fails. talksuite will validate that the input is a valid time. Additional validation can be performed by the validation property

maxRetries

Maximum number of retries allowed after validation failure. Default value is 3

continueOnFail

If set to true after the maximum failed attemps, the dialogue will continue with null output. If false, the dialogue will stop. Default is false

validation

Logical expression evaluating to true or false using JSON logic. This will normally be a comparison. The retryMessage will be displayed if the expression evaluates to false

rules/likelyStartTime

Set to the hour of the day which defines a 12 hour period that ambiguous times will default to. For example a value of “7” will define a period of 7am to 7pm, which means that 10 o’clock will be resolved to 10am.

customContent

Enables the display of Bespoke Cards

output

Variable name in which the user input will be stored.

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

If the output variable is populated, the user will not be prompted. Therefore if you have two prompt nodes in the same dialogue with the same output variable, the second prompt will never be run.

If you want to validate that the input is not in the past (or future), obtain the current date and time in the validation section of the prompt, rather than in a preceding node. This will refresh the current time each time the user inputs a value, so if the user leavse the system for a while, the current date won’t get stale. See the “Validate not in the past” snippet in the DateTime Prompt

This node will only work if you have specified a LUIS application in your bot

{
  "type": "nlpTimePrompt",
  "message": "Enter the start time of your holiday",
  "retryMessage": "That's not a valid time",
  "output": "startTime"
}

NLP Date-time Prompt

Ask the user for date-time input and resolve using LUIS

Properties
type

nlpDateTimePrompt

message

The message output to the user. A string or reference to a language file

retryMessage

Message to be output if validation fails. talksuite will validate that the input is a valid date-time. Additional validation can be performed using the validation property

maxRetries

Maximum number of retries allowed after validation failure. Default value is 3

continueOnFail

If set to true after the maximum failed attemps, the dialogue will continue with null output. If false, the dialogue will stop. Default is false

validation

Logical expression evaluating to true or false using JSON logic. This will normally be a comparison. The retryMessage will be displayed if the expression evaluates to false

customContent

Enables the display of Bespoke Cards

output

Variable name in which the user input will be stored.

rules/context

When date input is ambiguous (e.g. Friday or 1st June), if the context is set to “earliest” the earlier possible date is chosen (e.g. last Friday, last June). If the context is “latest” then the later date is chosen (e.g. next Friday, next June)

rules/likelyStartTime

Set to the hour of the day which defines a 12 hour period that ambiguous times will default to. For example a value of “7” will define a period of 7am to 7pm, which means that 10 o’clock will be resolved to 10am.

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

If the output variable is populated, the user will not be prompted. Therefore if you have two prompt nodes in the same dialogue with the same output variable, the second prompt will never be run.

If you want to validate that the input is not in the past (or future), obtain the current date and time in the validation section of the prompt, rather than in a preceding node. This will refresh the current time each time the user inputs a value, so if the user leave the system for a while, the current date won’t get stale. See the “Validate not in past” snippet

This node will only work if you have specified a LUIS application in your bot

{
  "type": "nlpDateTimePrompt",
  "message": "Enter the start date and time of your holiday",
  "retryMessage": "That's not a valid date",
  "output": "startDate"
}
{
  "type": "nlpDateTimePrompt",
  "message": "When do you want to book your appointment?",
  "retryMessage": "You cannot book an appointment in the past",
  
  "rules" : 
  {
    "context" : "earliest",
    "likelyStartTime" : 7
  },
  "context" : "latest"
  "output": "appointment",
  "validation": {
    ">": [
      {
        "var": "appointment"
      },
      {
        "DateTime.currentDateTime": []
      }
    ]
  }
}

nlpQuery

Sends an utterance to LUIS and receives a response

Properties
type

nlpQuery

utterance

The text to send to LUIS. Can be a mixture of fixed text, references to variables or a reference to a language file

output

Name of the variable containing the LUIS response. This response can be used in an NLP Entities node

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

This node will only work if you have specified a LUIS application in your bot

{
  "type": "nlpQuery",
  "utterance": "Book holiday for {date}",
  "output": "luisResponse",        
}

AWS v4 Action

Outbound web hook API call

Properties
type

awsv4action

service

Properties containing information to be sent to the API

service/url

URL of the API

service/method

GET to read data, POST to create data, PUT or PATCH to update data and DELETE to delete data

service/headers

Header names and values

service/body

Body data to be sent for POSTs, PUTs and PATCHs

service/secretkey

Key used to generate AWSv4 headers

output

Properties containing information returned by the API

output/body

Response body from the API

output/header

Response headers from the API

secretkey

The value of the webHookSecretKey property in the target bot

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

This is a variant of an action node that generates AWSv4 security headers. AWSv4 security is used to secure talksuite webhooks. You can use this node to initiate a dialogue in another conversation in the bot or in another bot.

In the code examples on the right, the Set address dialogue sets up the user identifier. The Outbound dialgue asks for the address of anotgher conversation and allows a message to be entered. A dialogue is then initiated in the conversation which has the address. In Inbound dialogue displays the message in the destination dialogue.

A key is required to allow the call. Set the webHookSecretKey property in the destination bot. In the calling bot set the destination organisation and bot IDs and key in the bot constatns.

{
  "id": "set id",
  "trigger": {
    "type": "customEvent",
    "name": "introduction"
  },
  "nodes": [
    {
      "type": "stringPrompt",
      "message": "Enter your name",
      "retryMessage": "Error message",
      "output": "id"
    },
    {
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "User.Settings/Identifiers"
          },
          "addItem",
          [
            {
              "var": "id"
            }
          ]
        ]
      },
      "output": "User.Settings/Identifiers"
    },
    {
      "type": "message",
      "message": "Hello {User.Settings/Identifiers/0}. Type chat to talk to another bot user"
    }
  ]
}
{
  "id": "aws out",
  "trigger": {
    "type": "message",
    "values": [
      "chat"
    ]
  },
  "nodes": [
    {
      "type": "stringPrompt",
      "message": "Who would you like to chat to",
      "retryMessage": "Error message",
      "output": "id"
    },
    {
      "type": "stringPrompt",
      "message": "Enter your message",
      "retryMessage": "Error message",
      "output": "message"
    },
    {
      "type": "awsv4action",
      "service": {
        "url": "https://bb-qa-uks-app.azurewebsites.net/api/organisations/{bot/destOrg/bots/{bot/destBot}/dialogue",
        "method": "POST",
        "headers": {
          "Accept": "application/json"
        },
        "secretkey": "{bot/destKey}",
        "body": {
          "name": "awsin",
          "address": "{id}",
          "value": {
            "message": "{message}"
          }
        }
      }
    }
  ]
}  
{
  "id": "aws in",
  "trigger": {
    "type": "customEvent",
    "name": "awsin",
    "output": "data"
  },
  "nodes": [
    {
      "type": "message",
      "message": "Message is {data/message}"
    }
  ]
}     

Data

Create a record in a data store or read all records from a data store

Properties
type

data

action

Must be CREATE or READ

data

JSON object consisting of property-value pairs with no nested properties or simple lists

dataStore

Name of the talksuite data store to write to. Must be present in bot config

user

Identifies the user creating the record

label

Record label

response

JSON containing the record(s) including generated fields such as id and timestamp

description

Text to help explain what the node is doing

id

A unique identifier. See Node Flow for details

nextNode

See Node Flow for details

skip

If set to true, the node will not be executed. See Node Flow for details

Writes a record to a named talksuite data store or read all records from a data store

{
      "type": "data",
      "action": "CREATE",
      "data": {
          "field name" : "{fieldValue}",
          "list name" : ["{listValue1}","{listValue1}"]
      },
      "dataStore" : "Test data",
      "user": "John Smith",
      "label" : "{label}",
      "response" : "returnJSON"
}          
{
      "type": "data",
      "action": "READ",
      "dataStore" : "Test data",
      "response" : "datastorerecords"
}          

Property Order

Your dialogues will be easier for others to read if the properties are in a consistent and logical order. We’d recommend the following ordering:

Property Example
id Only if the node is jumped to by another node
description You don’t need a description for every node, just when some explanation is needed
skip Only put this is if the value is true
type Node type
Output to the user before any logic is performed e.g message or title, subtitle etc. for cards
Logic e.g. validation for prompts, repeatUntil for loops
Output to the user after logic e.g. retryMessage
Variables returned from the node e.g. output
Next node logic e.g. nextNode or passNode and failNode for decisions

Comparisons

The following operations can be used to compare two values. They all return true or false. They are useful in decision nodes, the validation section of prompts and within a conditional assignment operation (if)

Equal to
  • ”==” : [#1,#2]. Returns true if #1 equals #2, even if one is a number and the other equivalent text (e.g. 1 == “1” is true)
Equivalent to
  • ”===” : [#1,#2]. Returns true if #1 equals #2, and both are text or both are numbers. (e.g. 1 == 1 is true, 1 === “1” is false)
Greater Than
  • ”>” : [#1,#2] . Returns true if #1 is greater than #2
Greater Than or equal to
  • ”>=” : [#1,#2]. Returns true if #1 is greater than or equal to #2
Less than
  • ”<” : [#1,#2]. Returns true if #1 is less than #2
Less than or equal to
  • ”<=” : [#1,#2]. Returns true if #1 is less than or equal to #2
Not equal to
  • ”!==” : [#1,#2]. Returns true if #1 does not equal #2, where numbers and text are regarded as different
Not equivalent to
  • ”!=” : [#1,#2]. Returns true if #1 does not equal #2, where numbers and text are regarded as the same
Negation
  • ”!” : [#1]. Returns true if #1 is false and false if #1 is true
Matches a pattern
  • “method” : [ #1,”matchesPattern” [#2]]. Returns true if #1 matches the .NET regular expression in #2

The \ character in a regular expression needs to be replaced with \\ to prevent JSON treating it as a special character

Avoid using language references directly in comparisons. Assign the language reference to a variable in the model and use the variable in the comparison

{
 "type": "decision",
  "description": "Check likesPepsi is true",    
 "rule": {
   "===": [
     {
       "var": "likesPepsi"
     },
     true
   ]
 },
 "passNode": "Pepsi",
 "failNode": "Coke"
},
{
  "type": "decision",
  "description": "Check count is zero where count could be a number or text",
  "rule": {
    "==": [
      {
        "var": "count"
      },
      "10"
    ]
  },
  "passNode": "ten",
  "failNode": "not ten"
},
{
      "type": "datePrompt",
      "description": "Check that endDate is after start date",
      "message": "When do you want your holiday to end?",
      "retryMessage": "Enter a valid date which must be after the start date",
      "output": "endDate",
      "validation": {
          ">": [
            {
              "var": "endDate"
            },
            {
              "var": "startDate"
            }
          ] 
      }
}
{
      "type": "datePrompt",
      "description": "Check that endDate is after or equal to startDate",
      "message": "When do you want your holiday to end?",
      "retryMessage": "Enter a valid date which can't be before the start date",
      "output": "endDate",
      "validation": {
          ">=": [
            {
              "var": "endDate"
            },
            {
              "var": "startDate"
            }
          ] 
      }
}
{
      "type": "datePrompt",
      "description": "Check that startDate is before endDate",
      "message": "When do you want your holiday to start?",
      "retryMessage": "Enter a valid date which must be before the end date",
      "output": "startDate",
      "validation": {
          "<": [
            {
              "var": "startDate"
            },
            {
              "var": "enedDate"
            }
          ] 
      }
}
{
      "type": "datePrompt",
      "description": "Check that startDate is not after endDate",
      "message": "When do you want your holiday to start?",
      "retryMessage": "Enter a valid date which cannot be after the end date",
      "output": "startDate",
      "validation": {
          "<=": [
            {
              "var": "startDate"
            },
            {
              "var": "enedDate"
            }
          ] 
      }
}
{
 "type": "decision",
  "description": "Check likesPepsi is not true",    
 "rule": {
   "!==": [
     {
       "var": "likesPepsi"
     },
     true
   ]
 },
 "passNode": "Coke",
 "failNode": "Pepsi"
},
{
  "type": "decision",
  "description": "Check count is not zero where count could be a number or text",
  "rule": {
    "!=": [
      {
        "var": "count"
      },
      "10"
    ]
  },
  "passNode": "not ten",
  "failNode": "ten"
},
{
  "type": "decision",
  "description": "! true returns false",
  "rule": {
    "!": [
      {
        "var": "Boolean"
      }
      
    ]
  },
  "passNode": "false",
  "failNode": "true"
},
{
  "type": "stringPrompt",
  "message": "Please enter your sort code (nn-nn-nn)",
  "retryMessage": "Please enter a string like nn-nn-nn where n is a digit",
  "output": "sortCode",
  "validation": {
    "method": [
         { "var": "sortCode" },
         "matchesPattern",
          [ "^\\d\\d-\\d\\d-\\d\\d$" ]
     ]
   }
}      

Logical expressions

In the following examples #1, #2 etc can be replaced by a variable using the “var” property or a constant value. See Using Variables

The following operations can be used to evaluate logical expressions. Use them in decisions and prompt validation. These operations can be nested to evaluate expressions like “(a = b or c =d) and (e =f or g = h)”. They can also be combined with comparions to evaluate expressions like (a > 10) or (b > 20)

And

“and” : [#1,#2]. Returns true if #1 and #2 are true

Or
  • “or” : [#1,#2]. Returns true if #1 or #2 is true
Not
  • ”!” : [#1]. Returns true if #1 is false and false if #1 is true
Conditional assignment
  • “if” : [#c1][#v1] … [#cN][#vN][#d]. Returns #v1 if #c1 is true. Returns #v2 if #c2 is true etc. Returns #d if all of #c1 to #cN are false.
{
  "type": "decision",
  "description": "Check for a blue circle",
  "rule": {
    "and": [
      {
        "===": [
          {
            "var": "colour"
          },
          "Blue"
        ]
      },
      {
        "===": [
          {
            "var": "shape"
          },
          "Circle"
        ]
      }
    ]
  },
  "passNode": "Blue Circle",
  "failNode": "something else"
} 
{
  "type": "decision",
  "description": "Check if colour is Blue or Red",
  "rule": {
    "or": [
      {
        "===": [
          {
            "var": "colour"
          },
          "Blue"
        ]
      },
      {
        "===": [
          {
            "var": "colour"
          },
          "Red"
        ]
      }
    ]
  },
  "passNode": "Blue or Red",
  "failNode": "something else" 
}    
{
  "type": "decision",
  "description": "Check if name is null or undefined",
  "rule": {
    "!": [
          {
            "var": "name"
          }
    ]
  },
  "passNode": "name not set",
  "failNode": "name is set"
}
{
   "type": "operation",
   "description": "Output zero if number is 0 , one if number is 1 and something elese otherwise",
   "output": "output",
   "operation": {
     "if": [
       {
         "===": [
           {
             "var": "number"
           },
           0
         ]
       },
        "zero",
       {
         "===": [
           {
             "var": "number"
           },
           1
         ]
       },
       "one",
       "something else"
     ]
   }
}

Dates and Times

In the following examples #1, #2 etc can be replaced by a variable using the “var” property or a constant value. See Using Variables

Getting the current date and time

The operations:

  • “Date.currentDate” : []
  • “DateTime.currentDateTime” : []
  • “Time.currentTime” : []

can be used to get the current date/time in the user’s own time zone

The current year can be obtained using Date.currentYear : [] or DateTime. currentYear : []. Similarly for months, days, hours minutes and seconds.

Formatting

The default format for a date is 1 December 1965 for the en-GB region, but will vary according to the conversation region. The default format for a time is 17:30 if the region is en-GB and 5:30 PM for en-US.

Custom formatting is available using:

  • “Date.format” : [#1,#2]
  • “DateTime.format” : [#1,#2]

Where #1 is a date variable and #2 is a format string that can consist of the following components:

  • “dd” The day of the month, from 01 through 31.
  • “ddd” The abbreviated name of the day of the wee for the region.
  • “dddd” The full name of the day of the week for the region.
  • “do” The day of the month from as an ordinal - 1st through to 31st, English language regions only
  • “M” The month, from 1 through 12.
  • “MM” The month, from 01 through 12.
  • “MMM” The abbreviated name of the month for the region.
  • “MMMM” The full name of the month for the region.
  • “yy” The year, from 00 to 99.
  • “yyyy” The year as a four-digit number.
  • “hh” Double digit hour of 12 hour clock (01 to 12)
  • “HH” Double digit hour of 24 hour clock (00 to 23)
  • “mm” Double digit minute (00 to 59)
  • “ss” Double digit second (00 to 59)
  • “tt” AM or PM

  • “d” Short date for the region (e.g. 7/12/2019 for en-US)
  • “D” Long date for the region (e.g. Friday, July 19, 2019 for en-US)
  • “f” Full date (short time)for the region (e.g. Friday, July 19, 2019 12:00 AM for en-US)
  • “F” Full date (short time)for the region (e.g. Friday, July 19, 2019 12:00:00 AM for en-US)

Durations between dates

  • “Date.difference” : [#1,#2] calculates the duration between #1 and #2 expressed in a combination of years, months and days, allowing output such as “John is 26 years, 2 months and 5 days old. The DateTime.difference variant also calculates the difference in hours, minutes and seconds. The output variable has sub-properties of Year, Months etc. populated. E.g. duration/Years.

  • “Date.totalDifference” : [#1,#2,#3] allows you express a duration between two dates (#1 and #2) in units of #3. Units can be either “years”, “months” or “days” (e.g. 1 year or 12 months or 365 days). DateTime.totaldifference also can calculate durations in hours, minutes or seconds.

Shifting a date by a period

It is possible to add or substract a number of years, months, weeks, days, hours to a date or date-time and a number of hours, minutes or seconds to a date-time.

  • “method” : [ #1,”addYears”,[#2]]. Returns a date or date-time #2 years later than #1. Similarly for months, weeks and days

  • “method” : [ #1,”subtractYears”,[#2]]. Returns a date or date-time #2 years earlier than #1. Similarly for months, weeks and days

  • “method” : [ #1,”addHours”,[#2]]. Returns a date-time #2 hours later than #1. Similarly for minutes and seconds

  • “method” : [ #1,”subtractHours”,[#2]]. Returns a date-time #2 hours earlier than #1. Similarly for minutes and seconds

Timezones

Input dates are treated as local dates. The user’s time zone can be obtained from conversation.settings/timeZone

  • “DateTime.toUTC” : [#1,#2] Returns the UTC date/time equivalent to local date/time #1 in time zone #2 (as an IANA location e.g. Europe/London)

  • “DateTime.toUTC” : [#1,#2,#3] Returns the UTC date/time equivalent to local date #1 and local time #2 in time zone #3

  • “Date.toUTC” : [#1,#2] Returns the UTC date equivalent to local date #1 in time zone #2

  • “Date.fromUTC” : [#1,#2] Returns the local date equivalent to UTC date #1 in time zone #2

  • “DateTime.fromUTC” : [#1,#2] Returns the UTC date equivalent to UTC date #1 in time zone #2

[
    {
      "type": "operation",
      "operation": {
        "Date.currentDate": []
      },
      "output": "DateToday"
    },
    {
      "type": "operation",
      "operation": {
        "DateTime.currentDateTime": []
      },
      "output": "DateTimeNow"
    },
    {
      "type": "operation",
      "operation": {
        "Time.currentTime": []
      },
      "output": "TimeNow"
    },
    {
      "type": "message",
      "message": "Today's date is {DateToday}"
    },
    {
      "type": "message",
      "message": "Today's date and time is {DateTimeNow}"
    },
    {
      "type": "message",
      "message": "Current time is {TimeNow}"
    }
]
[
    {
      "type": "operation",
      "operation": {
        "DateTime.currentDateTime": []
      },
      "output": "DateTimeNow"            
    },
    {
      "type": "operation",
      "operation": {
        "DateTime.format": [
          {
            "var": "DateTimeNow"
          },
          "dddd do MMMM yyyy HH:mm:ss"
        ]
      },
      "output": "24HourFormat"
    },
    {
      "type": "operation",
      "operation": {
        "DateTime.format": [
          {
            "var": "DateTimeNow"
          },
          "dddd do MMMM yyyy hh:mm:ss tt"
        ]
      },
      "output": "12HourFormat"
    },
    {
      "type": "message",
      "message": "Today's date and time in 24 hour format is {24HourFormat}"
    },
    {
      "type": "message",
      "message": "Today's date and time in 12 hour format is {12HourFormat}"
    }
]
[
    {
      "type": "dateTimePrompt",
      "message": "Enter a start date",
      "retryMessage": "That’s not a valid date",
      "output": "startDate"
    },
    {
      "type": "dateTimePrompt",
      "message": "Enter an end date",
      "retryMessage": "That’s not a valid date",
      "output": "endDate"
    },
    {
      "type": "operation",
      "operation": {
        "Date.difference": [
          {
            "var": "startDate"
          },
          {
            "var": "endDate"
          }
        ]
      },
      "output": "age"
    },
    {
      "type": "message",
      "message": "Difference between dates {startDate} and {endDate} is {age/Years} years, {age/Months} Months and {age/Days} days"
    },
    {
      "type": "operation",
      "operation": {
        "Date.totalDifference": [
          {
            "var": "startDate"
          },
          {
            "var": "endDate"
          },
          "Days"
        ]
      },
      "output": "durationInDays"
    },
    {
      "type": "message",
      "message": "Total Difference between dates {startDate} and {endDate} is {durationInDays} days"
    }
]
[
    {
      "type": "operation",
      "operation": {
        "DateTime.currentDateTime": []
      },
      "output": "DateTimeNow"
    },
    {
      "type": "operation",
      "operation": {
        "DateTime.format": [
          {
            "var": "DateTimeNow"
          },
          "dddd do MMMM yyyy HH:mm:ss"
        ]
      },
      "output": "localTime"
    },
    {
      "type": "message",
      "message": "The time in your current timezone of {conversation.settings/timeZone} is {localTime}",
      "nextNodeIndex": 3
    },
    {
      "type": "operation",
      "operation": {
        "DateTime.toUTC": [
          {
            "var": "DateTimeNow"
          },
          {
            "var": "conversation.settings/timeZone"
          }
        ]
      },
      "output": "UTCTime"
    },
    {
      "type": "message",
      "message": "UTC time is {UTCTime}",
      "nextNodeIndex": 5
    },
    {
      "type": "operation",
      "operation": {
        "DateTime.fromUTC": [
          {
            "var": "UTCtime"
          },
          {
            "var": "conversation.settings/timeZone"
          }
        ]
      },
      "output": "localTime"
    },
    {
      "type": "message",
      "message": "Converting back to local time gives {localTime}"
    }
]
{ 
      "type": "operation",
      "description": "Add one day to startDate",
      "operation": {
        "method": [
          {
            "var": "startDate"
          },
          "addDays",
          [
            1
          ]
        ]
      },
      "output": "nextDay"
 },

Text

In the following examples #1, #2 etc can be replaced by a variable using the “var” property or a constant value. See Using Variables

The following operations can be used to manipulate variables containing Text

Add text items together
  • “cat” : [#1,#2, .,. #N]. Concatenates #1 to #N

If you reference a variable x in a cat method using the notation {x} and use the output from the cat in another dialogue, the value of x will be blank. Use {“var” : “x”} instead. The reason for this is that the notation {x} does not copy the value of x but makes a link to it. As x changes the output value changes. When you leave the dialogue the link to x is broken.

Get the number of characters in text
  • “method” : [ #1,”length”]. Returns the number of characters in #1
Find the position of text within a variable
  • “method” : [ #1,”indexOf”, [#2,#3]]. Returns the position of the first instance of #2 in the portion of #1 starting from character position #3. Returns -1 if not found. Matches are not case sensitive.
Get a portion of a text variable
  • “substr” : “[#1,#2,#3]. Returns #3 characters from #1 starting at character position #2. If #3 is omitted all characters from position #2 will be returned. If #3 is omitted and #2 is negative, then the last #2 characters (ignoring the minus) of #1 are returned
Convert the case of text
  • “String.toUpper” : [#1]. Converts #1 to upper case. Also toLower and toTitleCase
Replace text
  • “String.replace” : [#1,#2,#3,#4]. Replaces all occurrences of #2 in #1 with #3. If the optional parameter #4 is set to true, instances of #2 in any case will be replaced, otherwise an exact case match is performed
{
      "type": "operation",
      "operation": {
        "cat": [
           {
            "var": "firstWord"
          },
          " ",
           {
            "var": "secondWord"
          },
        ]
      },
      "output": "sentence"
}
{
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "text"
          },
          "length"
        ]
      },
      "output": "lengthOfText"
}
{
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "text"
          },
          "indexOf",
          [
            "w"
          ]
        ]
      },
      "output": "firstW"
}
{
      "type": "operation",
      "operation": {
        "substr": [
          {
            "var": "text"
          },
          2,
          3
        ]
      },
      "output": "3from2"      
}
{
      "type": "operation",
      "operation": {
        "String.toUpper": [
          {
            "var": "text"
          }
        ]
      },
      "output": "upper"
}          
{
      "type": "operation",
      "operation": {
        "String.replace": [
          {
            "var": "text"
          },
          "e",
          "*",
          true
        ]
      },
      "output": "replaceEsWithAsterisks"
}          

Numeric

In the following examples #1, #2 etc can be replaced by a variable using the “var” property or a constant value. See Using Variables

The following arithmetic operations can be performed

Addition
  • ”+” : [#1,#2]. Returns the sum of #1 and #2
Subtraction
  • ”-“ : [#1,#2]. Returns #1 minus #2
Multiplication
  • ”*” : [#1,#2]. Returns #1 multiplied by #2
Division
  • ”/” : [#1,#2]. Returns #1 divided by #2
Rounding
  • “method” : [ #1,”round”, [#2]. Returns #1 rounded to #2 decimal places
Remainder of
  • ”%” : [#1,#2]. Returns the remainder of #1 divided by #2 (e.g. 12 % 10 is 2)
Convert to an integer
  • “method” : [#1,”toInteger”]. Converts #1 to an integer. Use when an API requires an integer field
Convert to a floating point number
  • “method” : [#1,”toFloat”]. Converts #1 to a floating point number. Use when an API requires an floating point field
Number formatting
  • “Number.format” : [#1,#2,#3”]. Formats #1 to #3 decial places according to the region. #2 is one of:

    “n” includes region thousand separators “e” for scientific notation “f” excludes thousand separators “p” percentage

{
      "type": "operation",
      "output": "four",
      "operation": {
        "+": [
          {
            "var": "two"
          },
          2
        ]
      }
}
{
      "type": "operation",
      "output": "two",
      "operation": {
        "-": [
          {
            "var": "four"
          },
          2
        ]
      }
}
{
      "type": "operation",
      "output": "eight",
      "operation": {
        "*": [
          {
            "var": "four"
          },
          2
        ]
      }
}
{
      "type": "operation",
      "output": "twoAndTwoThirds",
      "operation": {
        "/": [
          {
            "var": "eight"
          },
          3
        ]
      }
}
{
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "twoAndTwoThirds"
          },
          "round",
          [
            2
          ]
        ]
      },
      "output": "twoPoint66"
}
{
      "type": "operation",
      "output": "two",
      "operation": {
        "%": [
          {
            "var": "eight"
          },
          3
        ]
      }
}      
{
      "type": "operation",
      "output": "3dp",
      "operation": {
        "Number.format": [
          {
            "var": "number"
          },
          "n"
          3
        ]
      }
}      
{
  "type": "operation",
  "operation": {
    "method": [
      {
        "var": "oneAndTwo"
      },
      "toInteger"
    ]
  },
  "output": "twelve"
},       
{
  "type": "operation",
  "operation": {
    "method": [
      {
        "var": "piString"
      },
      "toFloat"
    ]
  },
  "output": "pi"
},       

Lists

Lists can be simple lists of values (e.g. “Cat, “Dog”, “Horse”) or be lists of objects.

You access an item of a list using a numeric index starting from 0 (e.g. myList/2 is the third item in the list).

You can use a variable index when accessing a variable as part of a text string (e.g. “The item is {mylist{n}})”)

You cannot use variable indexes when accessing variables using the “var” property. You will have to use the getItem method to extract an item from a list.

In the following examples #1, #2 etc can be replaced by a variable using the “var” property or a constant value. See Using Variables

The following operations can be used to build and manipulate lists:

Add an item to the end of a list
  • “method” : [ #1,”addItem”, [#2]]. Returns list #1 with object #2 appended to it
Get an item from a list
  • “method” : [ #1,”getItem”, [#2]]. Returns the element of #1 at position #2 (zero being the first element)
Update a list item
  • “method” : [ #1,”updateItem”,[#2,#3]]. Returns list #1 with the element at position #2 replaced with object #3
Insert an item before another item in a list
  • “method” : [ #1,”insertItem”,[#2,#3]]. Returns list #1 with object #3 inserted before position #2 (zero being the first element)
Remove an items from a list
  • “method” : [ #1,”removeItem”, [#2]]. Returns list #1 with the element at position #2 removed
Get the number of items in a list
  • “method” : [ #1,”getCount”]. Returns the number of elements in list #1
Create a list from a text item with a separator
  • “method” : [ #1,”split”, [#2]. Returns a list created from text #1 using #2 as a separator (e.g. #1 = “a,b,c”, “#2 = “,”, list contains 3 items “a”,”b” and “c”)
Sort a list
  • “method” : [ #1,”sort”, [#2,#3]]. Returns list #1 sorted by property name #2. #3 controls the sort direction (“ascending” or “descending”). Additional property/direction parameter pairs can be specified. If the list is a simple list with no properties replace [#2,#3] with []
Filter a list
  • “method” : [ #1,”filter”, [#2,#3]]. Returns list #1, excluding any elements where the property #2 does not equal the value #3. #2 and #3 can be replaced with a logical expression. Elements where the expression evaluates to false are excluded from the results. In the logical expression, references to properties should be preceded by the property “current” :
Filter a list and return the indexes of the maching elements
  • “method” : [ #1,”indexFilter”, [#2,#3]]. Returns list #1 containing the indexes from a list that excludes any elements where the property #2 does not equal the value #3. #2 and #3 can be replaced with a logical expression. Elements where the expression evaluates to false are excluded from the results. In the logical expression, references to properties should be preceded by the property “current” :
Concatenate lists
  • “method” : [ #1,”concatList”, [#2]]. Returns the concatenation of list #1 and list (or lists) #2
{
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "myList"
          },
          "addItem",
          [
            {
              "var": "text"
            }
          ]
        ]
      },
      "output": "myList"
}
{
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "myList"
          },
          "getItem",
          [
            {
              "var": "index"
            }
          ]
        ]
      },
      "output": "myItem"
}          
{
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "myList"
          },
          "updateItem",
          [
            {
              "var": "index"
            },
            {
              "var": "myItem"
            }
          ]
        ]
      },
      "output": "myList"
}          
{
       "type": "operation",
       "operation": {
         "method": [
           {
             "var": "myList"
           },
           "insertItem",
           [
             {
               "var": "index"
             },
             {
               "var": "myItem"
             }
           ]
         ]
       },
       "output": "myList"
 }          
{
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "myList"
          },
          "removeItem",
          [                  
            {
              "var": "index"
            }
          ]
        ]
      },
      "output": "myList"
}          
{
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "myList"
          },
          "getCount"                
        ]
      },
      "output": "count"
}          
{
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "csvText"
          },
          "split",
          [
            ","
          ]
        ]
      },
      "output": "myList"
}          
{
  "type": "operation",
  "operation": {
    "method": [
      {
        "var": "values"
      },
      "sort",
      [
        "value",
        "ascending"
      ]
    ]
  },
  "output": "values"
}
{
  "type": "operation",
  "operation": {
    "method": [
      {
        "var": "chosenArea"
      },
      "filter",
      [
        {
          ">=": [
            {
              "current": "date"
            },
            {
              "var": "startDate"
            }
          ]
        }
      ]
    ]
  }
  ,
  "output": "filteredDates"
}     
{
  "type": "operation",
  "operation": {
    "method": [
      {
        "var": "chosenArea"
      },
      "indexFilter",
      [
        {
          ">=": [
            {
              "current": "date"
            },
            {
              "var": "startDate"
            }
          ]
        }
      ]
    ]
  }
  ,
  "output": "filteredIndexes"
}     
{
  "type": "operation",
    "operation": {
      "method": [
        {
          "var": "list1"
        },
       "concatList",
        [
          {
            "var": "list2"
          }
        ]
     ]
  },
  "output": "list3"
}

XML

The following operations can be used to convert XML to a talksuite object (which is stored as JSON):

Convert XML to an object
  • “String.xmlToJson” : [ #1]. Returns an object with properties and data defined as XML in #1
Convert escaped XML to an object
  • “String.escapedXmlToJson” : [ #1]. Returns an object with properties and data defined as escaped XML in #1

XML attributes are converted to a property of the element preceded by @

If an element has attributes or child element plus a value, then the value is converted to a property with the name #text, otherwise just use the property name

In the examples on the right Example XML contains unescaped XML and Object contains the talksuite object it is converted to, expressed as JSON

Escaped XML

Escaped XML replaces special characters with codes.

Here are the codes:

{
      "type": "operation",
      "operation": {
        "String.xmlToJson": [
           {
            "var": "xml"
          }
        ]
      },
      "output": "object"
}
{
      "type": "operation",
      "operation": {
        "String.escapedXmlToJson": [
           {
            "var": "escapedXml"
          }
        ]
      },
      "output": "object"
}
<people>
  <person gender="female">
    001
    <firstname>Anna</firstname>
    <lastname>Smith</lastname>
  </person><person gender="male">
    002
    <firstname>John</firstname>
     <lastname>Jones</lastname>
  </person>
</people>   
{
  "people": {
    "person": [
      {
        "@gender": "female",
        "#text": "001",
        "firstname": "Anna",
        "lastname": "Smith"
      },
      {
        "@gender": "male",
        "#text": "002",
        "firstname": "John",
        "lastname": "Jones"
      }
    ]
  }
}

Using Variables

There are three ways of accessing variables in talksuite. This section explains when to use each of them

Mixed text and variables

There are a number of properties that can contain a mixture of text and variables. In these cases the variables names are enclosed in curly brakets, like this

“Hello {name}”

This syntax can be used in the following places:

Property Nodes
message message, stringprompt, numberPrompt, datePrompt,dateTimePrompt, timePrompt,choicePrompt, confirmationPrompt, attachmentPrompt
retryMessage stringPrompt, numberPrompt, datePrompt,dateTimePrompt, timePrompt,choicePrompt, confirmationPrompt, attachmentPrompt
service/url action, downloadAction
service/body action
content/title card, cardcollection
content/subtitle card, cardcollection
content/text card, cardcollection
content/image card, cardcollection
customContent customCardCollection, stringPrompt, messagePrompt

Variables using the var property or constants

Parameters to logical operations can be variables or constants (but not a mix of both). Logical operations (eg. <=, and, +) are used in operation nodes and the validation properties of prompts

Variables are references as :

{ “var” : “myVar” }

Constants are entered as numbers e.g. 123, text, e.g. “123” or booleans e.g. true

talskuite will allow you to use the {} notation in JSON logic parameters. Avoid this and use the “var” notations

Variables name only

The content of output, listname and display name properties always just contain the variable name

List Indexes

You access an item of a list using a fixed numeric index (e.g. myList/2) but you cannot access a list item using an index in a variable (e.g. mylist/{index} will not work). Use getItem or updateItem operations to update lists using variable indexes

{
  "type": "message",
  "message": "Hello {name}"           
}
{
  "type": "operation",
  "output": "two",
   "operation": {
      "-": [
        {
          "var": "four"
        },
        2
      ]
   }
}
{
  "type": "operation",
   "operation": {
     "Date.currentDate": []
    },
    "output": "DateToday"
}

Combining logic

Parameters in logic operations can be substituted with logical operations provided the data makes sense. For example you can add one to the length of a text string in one operation, but you cannot add one to “dog”. Several levels of nested substitution can be performed. In the example on th right, the current date is obtained, it is formatted, its length is obtained and one is added to the length in one operation.

{
  "type": "operation",
     "operation": {
       "+": [
         {
           "method": [
            {
              "Date.format": [
                 {
                   "Date.currentDate": []
                 },
                 "dddd do MMMM yyyy"
               ]
            },
            "length"
          ]
        },
        1
      ]
    },
    "output": "dateLengthPLus1"
},

Conversation settings

Here are the conversation settings available to you:

Regional settings

  • conversation.settings/timezone User’s time zone as an IANA location (e.g. Europe/London). Read/Write. Defaults to the bot time zone
  • conversation.settings/region User’s culture (language/country e.g. en-GB). Read/Write. Defaults to the bot region. This controls which Language store will be used and number and date formatting

Verbose mode

  • conversation.settings/verboseMode If set to true, debugging information will be displayed for each node executed. Read/Write

Conversation technical information

  • conversation.settings/conversationId Conversation ID. Include this in error messages to help talksuite support track down issues. Read only

  • conversation.settings/conversationKey Conversation Key. Include this in error messages to help talksuite support track down issues. Read only

Dialogue variables

The following variables give information on the running dialogue:

API HTTP Status Code

  • dialogue/lastApiStatusCode The HTTP status code of the last call to an API. Read only

Last Utterance

  • dialogue/triggerUtterance The user input that caused the dialogue to run. Read only

Bot settings

Here are the bot settings that can be accessed in a dialogue:

  • bot.settings/botId The id of the current bot. Read only
  • bot.settings/botHost Where the bot and dialogues are hosted. Read only
  • bot.settings/organisationId The id of the organisation in which the bot is stored. Read only

Authentication settings

Here are the authentication settings that can be accessed in a dialogue:

  • user/AuthenticationToken Authentication token for a secure API. Read only. Can only be used in an action node header
  • user/AuthenticatedProviders A list of the authentication provider names (from bot config) that the user is currently signed into. Read only
  • user.settings/identifiers A list of unique identifiers for a user, used in external initiation of dialogues. Read/Write

Brief and de-brief

Bot settings

  • bot.settings/processes Read-only bot process settings

This reads the whole processes section of the bot config into an array of objects

Conversation settings

  • conversation.settings/processes Read-write conversation-level overrides for bot process settings

This variable is a list of objects, with each object holding the settings for a type of process. An example of the structure of an item is shown on the right

Add an item to the list with a name “brief” to schedule the brief, and a name of “debrief” to schedule the de-brief

If the active property on an item is false, the process will not run for this conversation

The hours and minutes properties give the daily start time for the process to run in the conversation

The daysOfWeek property gives the days of the week that the process will run.

Although hours and minutes are lists, currently only one hour and one minute value are currently supported for each process

{
  "name" :"brief",
  "active": true,
  "recurrence" : 
  {
      "hours" : [9],
      "minutes" : [30],
      "daysOfWeek" : ["monday","tuesday"]
  }
}

Creating a dialogue

To create a new dialogue, select the icon from the global area or a project

Then select the Add dialogue from the pop-up menu

Template JSON for a new dialogue is displayed. You must provide in the id, trigger and at least one node. You can then use the Create menu option to create the dialogue.

If there are errors in the dialogue, they will be displayed above the JSON.

If the dialogue has been successfully created, the menu will change to provide the following options:

  • Move. Move the dialogue to another project
  • Update. Update the dialogue
  • Delete. Delete the dialogue

The talskuite studio doesn’t have any backup or version control features. Use the talksuite CLI utility to back up your dialogues

If you have been in the studio for a while, your session may time out. If this happens you will get a 401 error. If you have unsaved changes in your dialogue, you will need to copy the dialogue contents to the clipboard before you sign out and back in again. You can then past the contents into your dialogue

An introduction to JSON

JSON is a notation for storing structured data. It’s similar to, but simpler than, XML. An example is shown to the right.

All JSON files start and end with curly brackets. The data is made up of property names (always in quotes) and property values. The property names and values are separated by colons. The simplest possible JSON file is a single property and value. E.g.

{ "firstName" : "Tony" }

Properties are separated by commmas. E.g.

{ "firstName" : "Tony" },
{ "lastNameName" : "Hancock" },

Property values can contain text (as above) which is in quotes, or numbers (see price below), which are not in quotes, or true/false values (see billingAddress below)

{ "price" : 7.99 }

{ "billingAddress" : true }

As well as a single value, properties can contain other properties. Properties can be nested within properties to several levels (see town within address).

Properties can contain lists of values. Lists are enclosed within square brackets (see favourites).

Lists can be made up of collections of properties (see orders)

{
  "customer": {
    "firstName": "Tony",
    "lastName": "Hancock",
    "favourites" : [
      "Books",
      "Albums",
      "Games"
    ]
  },
  "orders": [
    {
      "productType": "Book",
      "productDetails": {
        "title": "1984",
        "author": "George Orwell"
      },
      "price": 7.99
    },
    {
      "productType": "Album",
      "productDetails": {
        "title": "Aqualung",
        "artist": "Jethro Tull"
      },
      "price": 15
    }
  ],
  "address": {
    "houseNumber": 23,
    "streetName": "Railway Cuttings",
    "town": "East Cheam",
    "billingAddress": true
  }
}              

Editor features

Press F1 to get a full list of editor features. Here a few of the most useful:

  • Find text (F3)
  • Format document (on the right click menu)
  • Change all occurences (on the right click menu)

Help with syntax

If your dialogue is not valid JSON, the first text after the error will be underlined in red.

If your dialogue is valid JSON, but does not match the dialogue schema, the invalid text will be underlined in green.

Lines in error (due to invalid JSON or a schema error) will also be highlighted in red to the left.

If you hover over green underlined text, a schema error will be displayed.

If you hover over red underlined text, a json error will be displayed

Cross dialogue validation

There is a validate button on the bot configuration form which will check the projects associated with the bot for duplicate triggers and calls to nested dialogues that do not exist

Snippets

Don’t type JSON in from scratch! Use snippets to insert template JSON

Trigger snippets

In the trigger section of a dialogue, type “trigger” to see a list of the trigger types available

Select the required trigger type and the JSON for that type will be inserted

Node snippets

There is at least one snippet for each node type. Typing in the node area will display a list of matching snippets

Selecting the snippet will insert a sample node of that type with default properties

Default property values are highlighted. You can tab between the highlighted values to edit them.

Individual properties within a node can be selected by pressing space and selecting the property name

Combining Node and Logic Snippets

When a property of a node can contain logic, the property value is left as blank placeholder in the snippet. You need to insert the appropriate logic snippet into the blank property value.

The following properties are left as placeholders:

  • rule in a decision node snippet

  • operation in an operation node snippet

  • validation in prompt node snippets

  • both logic parameters in “and” and “or” logic snippets

Here is an example of inserting the current date into an operation node:

First, start typing the name of the node to insert (operation)

Then select the snippet from the menu to insert the node text

Position the cursor inside the empty operation property and start typing the name of the logical operation you want to use (current date)

Select the required operation from the list

Built-in Variable Snippets

You can insert built-in variable snippets. The example below shows the insertion of the conversation timezone variable into a cat operation.

Select the timezone variable

Insert it into the node

Syntax Overview

The diagram summarises the syntax of a dialogue

Office 365 APIs

Miscrosoft provides APIs to access Office 365 services such as: SharePoint, OneDrive, Outlook/Exchange, Microsoft Teams, OneNote, Planner, and Excel

There is no special support for Office 365 APIs in talksuite. They are treated like any other external API. However some detail on how to use them is inculded here as they can be used to provide data storage that can be accessed by all bot users

The APIS are referred to as MS Graph

Accessing data in Excel

If you want to hold data that can be accessed by multiple users, you can host the data in an area that will be accessible to your bot users, such as sharepoint. Data can be stored in Excel files and accessed via (MS graph) APIs.

Follow these steps to gain access to your data

  1. Make sure all the users that need to access the data to have access to the sharepoint site.

  2. Set up an Authentication Provider in your bot config. You will need to give talksuite appropriate access to Office 365 files using the scope property (e.g. “Files.Read”)

  3. Find the site ID of the sharepoint site. You can get the ID from the following API call

    https://graph.microsoft.com/v1.0/sites?search=my-site-name

    Get the id property from the result of this call and put it in a bot constant

  4. Create a data file and write a dialogue to access it. See the example snippet on the right

Bot users will be asked to sign-in to Office 365 when they trigger the dialogue

The example reads the following Excel file.

and displays the data

{
  "id": "shared data",
  "trigger": {
    "type": "message",
    "values": [
      "shared data"
    ]
  },
  "nodes": [
    {
      "type": "action",
      "service": {
        "method": "GET",
        "authorised": true,
        "url": "https://graph.microsoft.com/v1.0/sites/{Bot/SharepointSite}/drive/root:/Book.xlsx:/workbook/worksheets('Sheet1')/usedRange",
        "headers": {
          "Accept": "application/json"
        }
      },
      "outputs": {
        "body": {
          "matrix": "text"
        }
      }
    },
    {
      "type": "message",
      "message": "Salaries are:\n\r{matrix/1/0} - {matrix/1/1}\n\r{matrix/2/0} - {matrix/2/1}\n\r{matrix/3/0} - {matrix/3/1}"
    },
    {
      "type": "action",
      "service": {
        "method": "GET",
        "authorised": true,
        "url": "https://graph.microsoft.com/v1.0/sites/{Bot/SharepointSite}/drive/root:/Book.xlsx:/workbook/worksheets('Sheet1')/charts/salaries/image",
        "headers": {
          "Accept": "application/json"
        }
      },
      "outputs": {
        "body": {
          "chart": "value"
        }
      }
    },
    {
      "type": "card",
      "content": {
        "title": "Salaries",
        "image": "data:image/png;base64,{chart}"
      }
    }
  ]
}           

Date built-in

The NLP Entities node can be used to extract the date built-in entity (builtin.datetimeV2.date) from a LUIS response. talksuite also adds a period property to the results, so phrases such as morning or afternoon generate an AM or PM period value

The code example on the right assumes a LUIS entitiy which will match an utterance starting “I want to book a holiday…”. If the date is Monday 12th November 2018, the utterances below will produce the following output in the matchedEntities property

Utterance First date First period Second date Second period
i want to book a holiday for tuesday 2018-11-13 blank blank blank
i want to book a holiday for tuesday afternoon 2018-11-13 PM blank blank
i want to book a holiday from tuesday afternoon to wednesday morning 2018-11-13 PM 2018-11-14 AM
i want to book a holiday starting from the afternoon of next monday through to next wednesday morning 2018-11-19 PM 2018-11-21 AM

The code example (NLP Node) uses the default ambiguity rules, which chooses a future date when the utterance could be past of future (so on a Tuesday, “wednesday” will be interpreted as tomorrow, rather than the previous Wednesday). See the Ambiguity rules for details

The LUIS response for the third utterance in the table above is shown on the right

{
        "id": "NLP Node",
        "trigger": {
          "type": "intent",
          "intent": "BookHoliday",
          "output": "luisJson"
         },
         "nodes": [
         {
           "type": "nlpEntities",
           "input": "luisJson",
            "requiredEntities": "twodates",
            "outputs": {
              "matchedEntities": "results"
            }
         },
         {
           "type": "message",
           "message": "Book holiday intent"
         },
         {
            "type": "sequenceDialogue",
            "dialogueId": "NLP Output",
            "inputItem": "result",
            "listName": "results"
         }
       ],
       "model": {
         "twodates": [
            "date",
            "date"			
         ]
      }
}
{
  "id": "NLP Output",
  "trigger": {
    "type": "nestedDialogue"
  },
  "nodes": [
    {
      "type": "message",
      "message": "Type: [{result/type}] Value: [{result/value}] Period: [{result/period}] Mod: [{result/mode}]"
    }
  ]          
}
{
  "query": "i want to book a holiday from tuesday afternoon to wednesday morning",
  "topScoringIntent": {
    "intent": "BookHoliday",
    "score": 0.9775836
  },
  "entities": [
    {
      "entity": "tuesday afternoon",
      "type": "builtin.datetimeV2.datetimerange",
      "startIndex": 30,
      "endIndex": 46,
      "resolution": {
        "values": [
          {
            "timex": "XXXX-WXX-2TAF",
            "type": "datetimerange",
            "start": "2018-11-27 12:00:00",
            "end": "2018-11-27 16:00:00"
          },
          {
            "timex": "XXXX-WXX-2TAF",
            "type": "datetimerange",
            "start": "2018-12-04 12:00:00",
            "end": "2018-12-04 16:00:00"
          }
        ]
      }
    },
    {
      "entity": "wednesday morning",
      "type": "builtin.datetimeV2.datetimerange",
      "startIndex": 51,
      "endIndex": 67,
      "resolution": {
        "values": [
          {
            "timex": "XXXX-WXX-3TMO",
            "type": "datetimerange",
            "start": "2018-11-28 08:00:00",
            "end": "2018-11-28 12:00:00"
          },
          {
            "timex": "XXXX-WXX-3TMO",
            "type": "datetimerange",
            "start": "2018-12-05 08:00:00",
            "end": "2018-12-05 12:00:00"
          }
        ]
      }
    }
  ],
  "sentimentAnalysis": {
    "label": "negative",
    "score": 0.2434367
  }
}

Duration built-in

The NLP Entities node can be used to extract the duration built-in entity (builtin.datetimeV2.duration) from a LUIS response. The value returns the duration expressed as a number of seconds. In some cases the node can infer a pair of dates from a date and a duration. See the third example in the table below

The code example on the right assumes a LUIS entitiy which will match an utterance starting “I want to book a holiday…”. The required entities are date, duration, date and duration so talksuite will match up to two dates and two durations in the LUIS repsonse and will ignore anything else. If the date is Monday 12th November 2018, the utterances below will produce the following output in the matchedEntities property

Utterance First date First duration Second date Second duration
i want to book a holiday taking 5 hours on sunday through to 3 hours on monday 2018-11-18 18000 2018-11-19 10800
i want to book a holiday for 3 days starting on wednesday 2018-11-21 blank 2018-11-23 blank

The LUIS response for the first utterance in the table above is shown on the right

{
        "id": "NLP Node",
        "trigger": {
          "type": "intent",
          "intent": "BookHoliday",
          "output": "luisJson"
         },
         "nodes": [
         {
           "type": "nlpEntities",
           "input": "luisJson",
            "requiredEntities": "datesAndDurations",
            "outputs": {
              "matchedEntities": "results"
            }
         },
         {
           "type": "message",
           "message": "Book holiday intent"
         },
         {
            "type": "sequenceDialogue",
            "dialogueId": "NLP Output",
            "inputItem": "result",
            "listName": "results"
         }
       ],
       "model": {
         "datesAndDurations": [
            "date",
            "duration"
            "date",
            "duration"			
         ]
      }
}
{
  "id": "NLP Output",
  "trigger": {
    "type": "nestedDialogue"
  },
  "nodes": [
    {
      "type": "message",
      "message": "Type: [{result/type}] Value: [{result/value}] Period: [{result/period}] Mod: [{result/mode}]"
    }
  ]          
}
{
  "query": "i want to book a holiday taking 5 hours on sunday through to 3 hours on monday",
  "topScoringIntent": {
    "intent": "BookHoliday",
    "score": 0.980723858
  },
  "entities": [
    {
      "entity": "5 hours",
      "type": "builtin.datetimeV2.duration",
      "startIndex": 32,
      "endIndex": 38,
      "resolution": {
        "values": [
          {
            "timex": "PT5H",
            "type": "duration",
            "value": "18000"
          }
        ]
      }
    },
    {
      "entity": "sunday",
      "type": "builtin.datetimeV2.date",
      "startIndex": 43,
      "endIndex": 48,
      "resolution": {
        "values": [
          {
            "timex": "XXXX-WXX-7",
            "type": "date",
            "value": "2018-11-25"
          },
          {
            "timex": "XXXX-WXX-7",
            "type": "date",
            "value": "2018-12-02"
          }
        ]
      }
    },
    {
      "entity": "3 hours",
      "type": "builtin.datetimeV2.duration",
      "startIndex": 61,
      "endIndex": 67,
      "resolution": {
        "values": [
          {
            "timex": "PT3H",
            "type": "duration",
            "value": "10800"
          }
        ]
      }
    },
    {
      "entity": "monday",
      "type": "builtin.datetimeV2.date",
      "startIndex": 72,
      "endIndex": 77,
      "resolution": {
        "values": [
          {
            "timex": "XXXX-WXX-1",
            "type": "date",
            "value": "2018-11-26"
          },
          {
            "timex": "XXXX-WXX-1",
            "type": "date",
            "value": "2018-12-03"
          }
        ]
      }
    },
    {
      "entity": "5",
      "type": "builtin.number",
      "startIndex": 32,
      "endIndex": 32,
      "resolution": {
        "subtype": "integer",
        "value": "5"
      }
    },
    {
      "entity": "3",
      "type": "builtin.number",
      "startIndex": 61,
      "endIndex": 61,
      "resolution": {
        "subtype": "integer",
        "value": "3"
      }
    }
  ],
  "sentimentAnalysis": {
    "label": "negative",
    "score": 0.210899085
  }
}     

Date-times built-in

The NLP Entities node can be used to extract the datetime built-in entity (builtin.datetimeV2.datetime) from a LUIS response. The value returns a combined date and time.

The code example on the right assumes a LUIS entitiy which will match an utterance starting “I want to book a holiday…”. The required entities are two datetimes, so talksuite will match up to two date-times and will ignore anything else. If the date is Monday 12th November 2018, the utterances below will produce the following output in the matchedEntities property

Utterance First date-time Second date-time
i want to book a holiday from 5 oclock on sunday to 3oclock on monday 2018-11-18 17:00:00 2018-11-19 15:00:00
i want to book a holiday starting at 9am on 20th November 2018-11-20 09:00:00 blank

The code example uses the default ambiguity rules, which chooses a future date when the utterance could be past of future (so on a Tuesday, “wednesday” will be interpreted as tomorrow, rather than the previous Wednesday. If the time is ambiguous, it will prefer a time in the range 7am to 7pm (so 5 o’clock will default to 5pm). See the Ambiguity rules for details

The LUIS response for the first utterance in the table above is shown on the right

{
        "id": "NLP Node",
        "trigger": {
          "type": "intent",
          "intent": "BookHoliday",
          "output": "luisJson"
         },
         "nodes": [
         {
           "type": "nlpEntities",
           "input": "luisJson",
            "requiredEntities": "dateTimes",
            "outputs": {
              "matchedEntities": "results"
            }
         },
         {
           "type": "message",
           "message": "Book holiday intent"
         },
         
            "type": "sequenceDialogue",
            "dialogueId": "NLP Output",
            "inputItem": "result",
            "listName": "results"
         }
       ],
       "model": {
         "dateTimes": [
            "datetime",                 
            "datetime"			
         ]
      }
}
{
  "id": "NLP Output",
  "trigger": {
    "type": "nestedDialogue"
  },
  "nodes": [
    {
      "type": "message",
      "message": "Type: [{result/type}] Value: [{result/value}] Period: [{result/period}] Mod: [{result/mode}]"
    }
  ]          
}
{
  "query": "i want to book a holiday from 5 oclock on sunday to 3oclock on monday",
  "topScoringIntent": {
    "intent": "BookHoliday",
    "score": 0.886165559
  },
  "entities": [
    {
      "entity": "from 5 oclock on sunday to 3oclock on monday",
      "type": "builtin.datetimeV2.datetimerange",
      "startIndex": 25,
      "endIndex": 68,
      "resolution": {
        "values": [
          {
            "timex": "(XXXX-WXX-7T05,XXXX-WXX-1T03,PT22H)",
            "type": "datetimerange",
            "start": "2018-11-25 05:00:00",
            "end": "2018-11-26 03:00:00"
          },
          {
            "timex": "(XXXX-WXX-7T05,XXXX-WXX-1T03,PT22H)",
            "type": "datetimerange",
            "start": "2018-12-02 05:00:00",
            "end": "2018-12-03 03:00:00"
          },
          {
            "timex": "(XXXX-WXX-7T17,XXXX-WXX-1T15,PT22H)",
            "type": "datetimerange",
            "start": "2018-11-25 17:00:00",
            "end": "2018-11-26 15:00:00"
          },
          {
            "timex": "(XXXX-WXX-7T17,XXXX-WXX-1T15,PT22H)",
            "type": "datetimerange",
            "start": "2018-12-02 17:00:00",
            "end": "2018-12-03 15:00:00"
          }
        ]
      }
    },
    {
      "entity": "5",
      "type": "builtin.number",
      "startIndex": 30,
      "endIndex": 30,
      "resolution": {
        "subtype": "integer",
        "value": "5"
      }
    }
  ],
  "sentimentAnalysis": {
    "label": "negative",
    "score": 0.266002953
  }
}  

Time built-in

The NLP Entities node can be used to extract the time built-in entity (builtin.datetimeV2.time) from a LUIS response. The value returns a time.

The code example on the right assumes a LUIS entitiy which will match an utterance starting “I want to book a holiday…”. The required entitiy is a time, so talksuite will match a single time entity and will ignore anything else. The utterances below will produce the following output in the matchedEntities property

Utterance Time
i want to book a holiday at 10 o’clock 10:00:00
i want to book a holiday at 10pm 22:00:00

The code example uses the default ambiguity rules, which chooses times in the range 7am to 7pm. If the time is ambiguous, it will prefer a time in the range 7am to 7pm (so 5 o’clock will default to 5pm). See the Ambiguity rules for details

The LUIS response for the first utterance in the table above is shown on the right

{
        "id": "NLP Node",
        "trigger": {
          "type": "intent",
          "intent": "BookHoliday",
          "output": "luisJson"
         },
         "nodes": [
         {
           "type": "nlpEntities",
           "input": "luisJson",
            "requiredEntities": "time",
            "outputs": {
              "matchedEntities": "results"
            }
         },
         {
           "type": "message",
           "message": "Book holiday intent"
         },
         
            "type": "sequenceDialogue",
            "dialogueId": "NLP Output",
            "inputItem": "result",
            "listName": "results"
         }
       ],
       "model": {
         "timet": [
            "time"                  
         ]
      }
}
{
  "id": "NLP Output",
  "trigger": {
    "type": "nestedDialogue"
  },
  "nodes": [
    {
      "type": "message",
      "message": "Type: [{result/type}] Value: [{result/value}] Period: [{result/period}] Mod: [{result/mode}]"
    }
  ]          
}
{
  "query": "i want to book a holiday at 10 o’clock",
  "topScoringIntent": {
    "intent": "BookHoliday",
    "score": 0.8900969
  },
  "entities": [
    {
      "entity": "10 o’clock",
      "type": "builtin.datetimeV2.time",
      "startIndex": 28,
      "endIndex": 37,
      "resolution": {
        "values": [
          {
            "timex": "T10",
            "type": "time",
            "value": "10:00:00"
          },
          {
            "timex": "T22",
            "type": "time",
            "value": "22:00:00"
          }
        ]
      }
    },
    {
      "entity": "10",
      "type": "builtin.number",
      "startIndex": 28,
      "endIndex": 29,
      "resolution": {
        "subtype": "integer",
        "value": "10"
      }
    }
  ],
  "sentimentAnalysis": {
    "label": "negative",
    "score": 0.138628185
  }
}      

Resolving ambiguity

The NLP Entities node can be used to extract entity data from a LUIS response (including the date time build-ins - builtin.datetimeV2). These built-in entities return a choice of values when the utterance is ambiguous. For example “Monday” can mean the Monday coming or the Monday just gone. “10 o’clock” can mean 10am or 10pm. The node allows you to specify which of the choices you prefer.

The rules property of the node can have the following sub-properties:

  • dateContext. If set to “latest”, the later of a choice of ambiguous dates is chosen. If set to “earliest”, the earlier of the choices is chosen. “latest” is appropriate for forward looking applications such as holiday booking or meeting planning. “earliest” is appropriate for backward looking applications such as expenses or overtime booking. The default is “latest”

  • likelyStartTime. This allows a preferred 12 hour period for ambiguous times to be chosen. The value is an hour to start the period at. For example if the application is office-based then a value of “7” would produce a preferred period of 7am to 7pm. “10 o’clock” would be interpreted as 10 am. If the application was (say) pizza delivery, then a time context of “12” would set a preferred period of noon to midnight, so “10 o’clock” would be 10pm. The default is “7”

{
        "id": "NLP Node",
        "trigger": {
          "type": "intent",
          "intent": "BookHoliday",
          "output": "luisJson"
         },
         "nodes": [
         {
           "type": "nlpEntities",
           "input": "luisJson",
            "requiredEntities": "dateTimes",
            "rules" : {
              "dateContext" : "latest",
              "likelyStartTime" : "7"
            }
            "outputs": {
              "matchedEntities": "results"
            }
         },
         {
           "type": "message",
           "message": "Book holiday intent"
         },
         
            "type": "sequenceDialogue",
            "dialogueId": "NLP Output",
            "inputItem": "result",
            "listName": "results"
         }
       ],
       "model": {
         "dateTimes": [
            "datetime",                 
            "datetime"			
         ]
      }
}
{
  "id": "NLP Output",
  "trigger": {
    "type": "nestedDialogue"
  },
  "nodes": [
    {
      "type": "message",
      "message": "Type: [{result/type}] Value: [{result/value}] Period: [{result/period}] Mod: [{result/mode}]"
    }
  ]          
}

Other entities

The NLP Entities node can be used to extract entities that are not LUIS built-ins

The code example on the right assumes a LUIS entitiy which will match an utterance starting “I want a pint of lager”. The required entities are drink (which matches to lager) and size (which matches to pint). The results will be

Utterance drink size
i want a pint of lager lager pint

The LUIS response for the utterance in the table above is shown on the right

{
        "id": "NLP Node",
        "trigger": {
          "type": "intent",
          "intent": "Drink order",
          "output": "luisJson"
         },
         "nodes": [
         {
           "type": "nlpEntities",
           "input": "luisJson",
            "requiredEntities": "drink",
            "outputs": {
              "matchedEntities": "results"
            }
         },
         {
           "type": "message",
           "message": "Drink order intent"
         },
         
            "type": "sequenceDialogue",
            "dialogueId": "NLP Output",
            "inputItem": "result",
            "listName": "results"
         }
       ],
       "model": {
         "dateTimes": [
            "drink",                 
            "size"			
         ]
      }
}
{
  "id": "NLP Output",
  "trigger": {
    "type": "nestedDialogue"
  },
  "nodes": [
    {
      "type": "message",
      "message": "Type: [{result/type}] Value: [{result/value}] Period: [{result/period}] Mod: [{result/mode}]"
    }
  ]          
}
{
  "query": "i want a pint of lager",
  "topScoringIntent": {
    "intent": "Drink order",
    "score": 0.9613707
  },
  "entities": [
    {
      "entity": "lager",
      "type": "drink",
      "startIndex": 17,
      "endIndex": 21,
      "score": 0.9462079
    },
    {
      "entity": "pint",
      "type": "size",
      "startIndex": 9,
      "endIndex": 12,
      "score": 0.929178238
    }
  ],
  "sentimentAnalysis": {
    "label": "negative",
    "score": 0.0957952142
  }
}

Grouping entities

The NLP Entities node can be used to extract groups of entities. If an item in the requiredEntities list is a comma-separated list of entities, enclosed by brackets, these entities will be grouped. talksuite will look for instances of “ and” in between recognised entities and treat this as a separator. When an “and” separator is found, the matching process will move onto the next group.

The code example on the right assumes a LUIS entitiy which will match an utterance starting “I want to book a holiday”. There are two groups of required entities, both containing a date and duration

Utterance group 1 - date group 1 - duration group 2 - date group 2 - duration
I want to book a holiday for monday and for 3 hours on tuesday monday blank tuesday 3 hours
I want to book a holiday for 3 hours on monday and for 4 hours on tuesday monday 3 hours tuesday 4 hours
I want to book a holiday for monday for 3 hours and tuesday monday 3 hours tuesday blank
{
        "id": "NLP Node",
        "trigger": {
          "type": "intent",
          "intent": "Drink order",
          "output": "luisJson"
         },
         "nodes": [
         {
           "type": "nlpEntities",
           "input": "luisJson",
            "requiredEntities": "datesAndDurations",
            "outputs": {
              "matchedEntities": "results"
            }
         },
         {
           "type": "message",
           "message": "Holiday booking intent"
         },
         {               
            "type": "sequenceDialogue",
            "dialogueId": "NLP Output",
            "inputItem": "result",
            "listName": "results"
         }
       ],
       "model": {
         "datesAndDurations": [
            "(date,duration)",                 
            "(date,duration)"			
         ]
      }
}
{
  "id": "NLP Output",
  "trigger": {
    "type": "nestedDialogue"
  },
  "nodes": [
    {
      "type": "message",
      "message": "Type: [{result/type}] Value: [{result/value}] Period: [{result/period}] Mod: [{result/mode}]"
    }
  ]          
}

Notification text

Default text of a push notification

Properties
notificationMessage

Default push notification text when there is no suitable text in the message (e.g an image)

The default value (“New Message”) may be changed for each language

{
  "systemTexts": {
    "notificationMessage": [
      "New message"
    ]
  }        
  "region": "en-GB"
}

Dialogue not found

Default response when no dialogue can be found to be triggered

Properties
dialogueNotFound

If no dialogue can be triggered on user input, this message is displayed.

The default value (“I’m sorry I don’t understand that”) may be changed for each language

If you want to do more than just output a message when the user input is not understood, you can write a No Trigger Match dialogue

{
  "systemTexts": {
    "dialogueNotFound": [
      "I'm sorry. I don't understand that"
    ]
  }        
  "region": "en-GB"
}

Brief and De-brief text

Messages and button labels for brief and de-brief

Properties
startMessage

Replaces the standard “Do you want to start your brief/debief message”

interruptMessage

Replaces the standard “Do you want to stop what you are doing and start your brief/debief message” displayed when interrupting a running dialogue

startButton

Button label for the option to start the brief/de-brief

cancelButton

Button label for the option to cancel the brief/de-brief

returnButton

Button label for the option to cancel the brief/de-brief and return to a running dialogue

The default value (“New Message”) may be changed for each language

{
  "systemTexts": {
    "processes": [
    {
      "brief": {
        "startMessage": [
          "Do you want to start your daily brief"
        ],
        "interruptMessage": [
          "Do you want to stop what you are doing and start your daily brief"
        ],
        "startButton": [
          "Yes"
        ],
        "cancelButton": [
          "No"
        ],
        "returnButton": [
          "No. Take me back to what I was doing"
        ]
      }
    }
  ]
}

Creating a Bot

When you create a bot using the Add Bot link at the top of the left hand menu, you will be asked to give the bot a name

The bot must be registered with several Microsoft services before it can be used. This may take a few moments, so a progress indicator shows each step of the process

Once the registation is complete, press the View JSON button to see the configuration

Registration and Localisation

Bot configuration mandatory fields defining the registration of the bot with microsoft and timezone and language defaults

Properties
msAppId

App Id for the application registered with the Microsoft Application Registration Portal. This will be automatically populated when you create your bot

msAppSecret

Password for the application registered with the Microsoft Application Registration Portal. This will be automatically populated when you create your bot

bcrName

Bot channel registration name

region

Region for the bot. Defaults to en-GB

timeZone

Time zone for the bot. Defaults to Europe/London

name

Name of the bot. Must be unique within the organisation

{
  "data": {
    "attributes": {   
      "msAppId": "place you app id here",
      "msAppSecret": "place your app secret here",
      "region": "en-GB",
      "timeZone": "Europe/London",
      "name": "Basic bot"
    }
  }
}      

Talksuite App

Configure the bot to work with the talksuite app and other apps that use the directline channel

Properties
directLineSecret

Secret required to work with the directline API. This is required to connect to the talksuite desktop app or the iOS and Android people first and iTrent native apps. This will be automatically populated when you create your bot

discoveryUrl

Read only property containing the discovery URL for for the talksuite desktop app and MHR’s people first mobile apps.

chatViewTitle

The value of a constant with this name will appear as the title of the app

primaryColor

This constant value sets the background colour of the user input speech bubble

botAvatarImage

This constant value is a link to an image file. Sets the avatar image

Authentication Providers

Details of authentication providers supported by the bot

Properties
type

OpenIdConnect for people first or MSGraph or Slack

authParameters

List of application-specific parameters

azureADResource

Azure active directory

clientId

OpenId Connect Client Id

clientSecret

OpenId Connect Client secret

isPrimary

If true, authentication will always be required. If false, authentication will only be required when an attempt is made to access an API. If you are using the Android or iOS talksuite apps, you can have multiple primary authentication providers. The apps will ask the user to choose one of the primary providers when logging in

name

Application name shown when the user signs in

scope

Application-specific parameters to control the access level requested

serviceDomains

List of application domains. Used to determine which application is being access from the url in the API call

tokenIssuer

Token issuing end point for OpenIdConnect

authorizationUrl

End point to get an initial authorisation code for oauth2

tokenUrl

Token issuing end point for oauth2

maxAuthPrompts

Number of attempts the user has to authenticate, before the authentication process is stopped

singleSignOn

iTrent only. Single sign on request path

A bot can support multiple authenticaion providers. Each provider is configured in a record in the authentication providers list. OpenIdConnect and oauth2 (for Slack) authentication policies are supported. Bespoke support is also provided for MHR’s iTrent product.

If a provider is marked as primary, then authentication will be performed before any dialogues can be run.Authentication for secondary providers will occur at the start of a dialogue that performs an API call for that provider. Authentication is not mandatory, so you can create an unauthenticated (i.e. public) bot.

{
  "data": {
    "attributes": { 
      "authenticationProviders": [   
        {
          "authorizationUrl": "place you oauth2 authorisation URL here",
          "authParameters": [
            {
              "name": "place your provider-specific parameter names here",
              "value": "place your provider-specific parameter values here"
            }
          ],
          "type": "OpenIdConnect",
          "clientId": "place your client ID here",
          "clientSecret": "place your client secret here",
          "isPrimary": false,
          "maxAuthPrompts": 3,
          "name": "Name of the appliction being connected to",
          "serviceDomains": [
            "place the base url of the providers APIs here"
          ],
          "tokenIssuer": "place you OpenIdConnect token issuer here"
        },        
       ],     
      "msAppId": "place you app id here",
      "msAppSecret": "place your app secret here",
      "region": "en-GB",
      "timeZone": "Europe/London",
      "name": "Authenticated bot"
    }
  }
}      

Constants

Use bot constants to hold bot-specific values to be used by dialogues

Properties
name

Name of a constant

value

Value of a constant

publish

Include in the discovery API. Defaults to false

Bot constants are used to hold values that are common accross multiple dialogues, but may differ between bots. For example if a bot is being used by several different clients, the environment name and tehcnical support details may differ for each client. They can also be used to make authentication information from the bot available to the dialogues. For example the service domain could be made available and used as a base URL for API calls

The discovery API is used by the people first Android and iOS apps. Setting a constant to published will include the value of the constant in the returned data for the API. This API is public, so do not publish and constants with sensitive or secret data

{
  "data": {
    "attributes": {  
      "constants": [				
        {
          "name": "environmentName",
          "value": "Production"
        },
        {
          "name" : "technicalSupport",
          "value" : "Please ring the IT team on 555-123-456"
        }
      ],      
      "msAppId": "place you app id here",
      "msAppSecret": "place your app secret here",
      "region": "en-GB",
      "timeZone": "Europe/London",
      "name": "Basic bot"
    }
  }
}      
{
  "type": "message",
  "message" : "An error has occurred. {Bot/technicalSupport}"
}            

Natural Language

Speicfy which LUIS model to use

Properties
appID

LUIS application ID. in LUIS, select the MANAGE menu option. The application ID is displayed at the top of the screen

appKey

LUIS key. From the MANAGE menu select the Keys and Endpoint soption from the Application Setting menu on the left. The key can be copied to the clipboard from the list of resouces at the bottom of the screen

intentThreshold

Each matched intent is given a score between 1 (very good match) and 0 (no match). This value prevents intents with a score below the threshold from firing

staging

If you have a staging version of the model as well as production, set this property to true to use the staging model.

location

LUIS hosting location. Defaults to westus

These properties control which LUIS application utterances will be sent to

{
  "data": {
    "attributes": {   
     "naturalLanguageProcessors": [
       {
         "appId": "a321fdd9-bc3f-4dd8-9b17-b6694cfa15ce",
          "appKey": "4e9d9d05953e483ebf20ee882f9276f9",
          "intentThreshold": 0.7,
          "staging": false,
          "location" : "westeurope"
       }
      ],              
      "msAppId": "place you app id here",
      "msAppSecret": "place your app secret here",
      "region": "en-GB",
      "timeZone": "Europe/London",
      "name": "Bot with NLP"
    }
  }
}      

Brief and Debrief

Specifies when the Brief and Debrief will run each day

Properties
processes

A list of all the processes that can be run for the bot (currently brief and de-brief only)

processes/name

The name of the process (must be brief or debrief)

processes/recurrence

Parameters that define when the process will run

processes/recurrence/hours

Hour of the day when the process will start (0-23). Syntactically this is a list, but only one value will be accepted.

processes/recurrence/minutes

Minutes within the hour when the process will start. Syntactically this is a list, but only one value will be accepted.

processes/recurrence/daysOfWeek

Days of the week to run the process. If not specified, the process will run every day

{
  "data": {
    "attributes": {   
      "msAppId": "place you app id here",
      "msAppSecret": "place your app secret here",
      "region": "en-GB",
      "timeZone": "Europe/London",
      "name": "Bot with brief start time",
      "processes": [
         {
           "name": "brief",
            "recurrence": {
              "hours": [
                9
              ],
              "minutes": [
                 30
              ],
              "daysOfWeek" : [
                "monday",
                "tuesday",
                "wednesday"
              ]
            }
         }
      ]
    }
  }
}      

Projects and Project Groups

Specifies the projects that are active within a bot

Properties
projectIds

A list of the Ids of the projects that you want to be avaialble to the bot. Only dialogues in projects in this list can fire in the bot (in addition to global dialogues)

projectGroups

A list project groups. Each group has a name and a list of projectIds. Each group can be turned on from within a dialogue by setting the conversation.settings/projectGroup build-in variable.

projectGroups/name

The name of the project group

projectGroups/projectIds

A list of project Ids for the project group

Right click on a project in the right hand menu to copy the Id of the project to the clipboard

Use project groups when you have mutually exlusive sets of triggers, based on a user’s status or choice. For example you may have dialogues designed for an applicant to an organisation and dialogues designed for employees. When someone applies to the organisation you can turn on the applicant projects. If they join the organisation, you can turn off the applicant project and turn on the employee projects. It can also be used to provide basi

{
  "data": {
    "attributes": {   
      "msAppId": "place you app id here",
      "msAppSecret": "place your app secret here",
      "region": "en-GB",
      "timeZone": "Europe/London",
      "name": "Bot with projects",
      "projectIds": [
         "Enter you project Id here",
         "Enter another project Id here"
     ],
   }
  }
}      
{
  "projectGroups": [
   {
     "name": "Product 1",
     "projectIds": [
        "Enter a project Id for a project in Product 1",
        "Enter a project Id for a project in Product 1"
      ]
    },
    {
      "name": "Product 2",
      "projectIds": [
         "Enter a project Id for a project in Product 1",
         "Enter a project Id for a project in Product 1"
       ]
    }
  ],
}
{      
  "id": "select product",
  "trigger": {
    "type": "message",
    "values": [
      "select product"
    ]
  },
  "model": {
    "products": [
      "Product 1",
      "Product 2"
    ]
  },
  "nodes": [
    {
      "type": "choicePrompt",
      "message": "Select the product",
      "retryMessage": "Select one of the buttons",
      "listName": "products",
      "output": "product"
    },
    {
      "type": "message",
      "message": "You have selected {product}."
    },
    {
      "type": "operation",
      "operation": {
        "var": "product"
      },
      "output": "conversation.settings/projectGroup"
    }
  ]
}

Other properties

Other optional bot config properties

Properties
channels

Whitelist of Microsoft Bot Framework channels that can be used by the bot. If none are specified, all channels configured in Azure can be used

authenticationTimout

Number of seconds of inactivity after which the user must re-authenticate

webHookSecretKey

AWS-style key. If this is specifed for the bot, AWS headers generated from this key must be used to initiate dialogues externally. The app-level AWS key cannot be used if a key is supplied at bot level

disableTypingActivity

If set to true, the channel typing activity indicator will be disabled when the bot is processing

dialogueTimeout

Number of seconds of inactivity after which a running dialogue is cancelled

deploymentState

Read-only property recording the state of the bot deployment. Should be manual for a successfully created bot

isDeleting

Read-only property recording the start of the deletion of a bot. Should be false

conversationTimeToLive

Number of days of inactivity on the conversation after which it will be deleted. Default value is 14

The following items can be placed in the channels whitelist property: facebook skype msteams telegram kik email slack groupme sms emulator directline webchat console cortana

{
  "data": {
    "attributes": {  
      "authenticationTimeout": 0,
      "dialogueTimeout" : 3600,
      "channels": ["skype", "slack"],			
      "disableTypingActivity": false,
      "deploymentState" : "manual",
      "isDeleting" : false,
      "conversationTimeToLive" : 99,
      "webHookSecretKey": "password123",
      "msAppId": "place you app id here",
      "msAppSecret": "place your app secret here",
      "region": "en-GB",
      "timeZone": "Europe/London",
      "name": "Basic bot"
    }
  }
}      

Schedules

Specifies a recurring schedule of dates/times that a dialogue can be confiried to fire on

Properties
schedules

A list of all the schedules available to the dialogues in the bot

schedules/name

The name of the schedule to be used in the dialogue trigger

schedules/description

A description of the recurrance schedule

schedules/recurrence

Recurrance rules (e.g. run every 15 minutes)

schedules/active

If set to false, dialogues will not fire for this schedule

Specifying Scheduler Recurrence rules

The recurrence property consists of 5 mandatory space separated elements

MINUTES HOURS DAYS MONTHS DAYS-OF-WEEK

The fields specify at what

  • minutes within an hour
  • hours within a day
  • days within a month
  • months within a year
  • days of the week the schedule is to fire. T

The schedule wil only fire when the values in ALL the fields match (e.g run at 10:15 every Sunday 1st June)

Here are the basic values allowed for each field:

Field Allowed values Example Meaning
MINUTES 0-59 2 2 minutes past the hour
HOURS 0-23 2 2 am
DAYS 1-31 2 2nd of the month
MONTHS 1-12 2 February
DAYS-OF-WEEK 0-6 2 Tuesday

Although all fields are mandatory, you won’t normally want to specify a value for all the fields in a schedule. If you place an askterisk in a field, the scheduler will ignore it. For exanple, to run at 10:30am on every Tuesday use ` 30 10 * * 2`

You can specify ranges using a pair of numbers separated by a dash. For example to run at 10:30am, 11:30 am and 12:30am every Tuesday, Wednesday and Thursday use 30 10-12 * * 2-4

You can specifiy sets of values by separating them with commas. For example to run at 10:30am : 10:40am and 10:55an every day use 30,40,55 10 * * *

You can specify a set of values separated by an interval. This is a set of values taking every Nth value from the possible set by adding /N. For example a minute value of 0/15 is equivalent to 0,15,30,45. This can also be applied to ranges. For example to run every 10 minutes use `0/10 * * * *’

An english month name can also be used in full or abrieviateed form (e.g. January, Dec)

An english day name can also be used (e.g. Monday, Tue)

The schedule will run in the timezone defined in the bot config

The schedule cannot currently be overridded at conversation level

{
  "data": {
    "attributes": {   
      "msAppId": "place you app id here",
      "msAppSecret": "place your app secret here",
      "region": "en-GB",
      "timeZone": "Europe/London",
      "name": "Bot with schedule",
      "schedules": [ {
         "name": "quarter hourly",
         "description" : "Run every 15 minutes",
         "recurrence" : "0,15,30,45 * * * *",
         "active" : true
        }
      ]
    }
  }
}      

Data stores

Make a data store available to a bot

Properties
dataStores

List of IDs of datastores. The ID can be obtained by right-clicking on the data store in the left-hand menu

Microsoft Teams

Enable a bot to be used in Microsoft Teams

Properties
msTeamsBotInviteUrl

Paste this link into your browser and will add the bot to your Teams contact list

Slack intergration

Enable a bot to be used in Slack

Properties
state

Will be set to “Deployed” when the bot is available in Slack. Set to “Awaiting Properties” when the bot is not set up. You can set a deployed bot to “NotDeploayed” to disable the bot

clientId

Populate this from the App Credentials form on Slack

clientSecret

Populate this from the App Credentials form on Slack

signingSecret

Populate this from the App Credentials form on Slack

redirectUrl

Generated when the bot is saved with the clientId, clientSecret and signingSecret are populated. Used to authorise the use of the app

slackBotInviteUrl

Invite url used to add the app to a slack workplaced

All the properties above can be found in channelSettings/slack

Go to the slack API page and follow theses instructionsto connect a bot to slack. When asked for a bot handle, use the bcrName property in your bot.

In order to get buttons working in Slack, you need to turn on interactivity (which is an optional step in the instructions)

Obtain the client ID, client secret and signing secret from the Basic Infomation page and populate the bot proerties.

Save the bot. A redirect Url and slack bot invite Url will be generated. Paste the redirect Url into your browser and select Allow.

Paste the invite Url into your browser and select Allow

Go to the Apps section within Slack and search for and add your app to the workspace

Push Notifications

Configure the sending of push notifications to mobile clients. An Azure notification hub service must exist

Properties
notificationHub/connectionString

Connection string for the notification hub service

notificationHub/hubPath

Path of the notification hub service.

Managing data stores

Multiple data stores can be created, allowng you to store different sorts of data in seperate stores.

Select Data stores from the main studio menu and click on Add new data store in the left hand menu

Data stores can be deleted and renamed by right clicking on the data store name in the left hand menu

In order to use a data store, right click on the data store name in the list of data stores and select copy data store ID, which will copy the ID to the clipboard

Edit your bot and paste the ID in the dataStores property (in double quotes). Dialogues linked to the bot can now write to that data store

You can write to a data store in a dialohue using a data node

To view a data record, click on the data store. A list of records will be displayed. Clicking on a record will display its contents

You can delete a record. Individual records can be copied to the clipboard or the whole data store can be exported to a spreadsheet

There is a search field which can be used to filter records

Importing data

Users with an author or data analyst role can import data into a data store via a spreadsheet. The spreadsheet should have a header row which gives the field name of each column. Columns with Label or User heading will be stored in the label and user fields. Other columns will be stored in the data section

Click on the data store and select import. You can then click on an individual record to view into

Exporting data

All record in a data store can be exported to a spreadsheet. The exported spreadsheet is in the same format as the import, so it can be edited and re-imported

Access via dialogue

Using a data node you can add a record to a data store or read all the records in a data store into a list object

The universal client

talksuite comes with applications that enable you to run your bots without having to use a third party messaging channel, such as Teams. The talksuite app is available as:

  • A web application
  • A windows or mac desktop application
  • An iOS app
  • An Android app

The functionality of the web and desktop versions differs from the iOS and Android version, with a few features only available on the mobile apps

Desktop app

To sign-in, enter the discovery URL, for you bot.

The app is branded as people first, but branded versions can be created for your organisation.

Mobile apps

Mobile apps are available on the Apple app store and Gogle play (called talksuite).

Connect to the bot by scanning a QR code of the discovery URL

Events

All versions of the universal client wirll send an introduction event to talksuite when the bot is first accessed for each user. This allows you to write a dialogue with a welcome message. The dialogue should have a Custom Event trigger

The mobile apps will also send events to allow you to write dialogues to synchronise the phone’s language and timezone with the bot. See Custom Events

You can write a dialogue to request the users location from the mobile and web versions of the univeral client

Bespoke Cards

In addition to the usual channel features, the client supports bespoke cards.

As well as the standard Hero card, the following cards are available:

The Bespoke cards are displayed by specifying the card content in a customContent property. Within this property, the cardType specifies the type of card and the content property contains the card data.

Custom content can be added to the following node types:

If the card needs buttons, there are three types of button available:

Type Description
postBack Don’t display the button value
imBack Display the button value
openUrl Open the url in the value property

Cards can work in two ways. If there is an output property, the card will wait for input for imBack and postBack buttons and the button value will be returned as output. Without an output property, the node does not wait.Whenever the an imBack or postBack button is pressed, the value of the button is output in the channel as if the user had typed it (although not echoed for postBacks).In order to catch and button value, you will need to have a dialogue which is triggered on the button value property. For carousels of cards, you may need to include details of which card has been selected. As the button value will contain variable data, you will need to use an intent or pattern trigger to initiate the dialogue

If you are developing your own directline client, you can create your own bespoke card types.

A card with buttons but no text or image will be displayed as a set of quick reply buttons

If you are using Teams as a channel, rather than a postBack button, use a messageBack button with the following properties

Property Value
type messageBack
title Button title
displayText Message displayed after the button click
text Message posted to the channel (but not displayed) when the button is pressed

Examples of each card type are shown on the right

{
  "type": "message",
  "message": "Message to display if the card is not supported in the channel",
  "customContent": {
    "contentType": "insert card type here",
    "content": {
      "insertBespokePropertyNamesHere": "insert card content here"
    }
  }
}  
{
  "type": "stringPrompt",
  "message": "Message to display if the card is not supported in the channel",
  "retryMessage": "Message to display on invalid input",
  "customContent": {
    "contentType": "card Type",
    "content": {
      "insertBespokePropertyNamesHere": "insert card content here",
      "buttons": [
        {
          "type": "postBack",
          "title": "Button label",
          "value": "message text to place in the channel"
        },
        {
          "type": "imBack",
          "title": "Button label",
          "value": "Value to return as output"
        },
        {
          "type": "openUrl",
          "title": "Button label",
          "value": "url"
        }
      ]
    }
  },
  "output": "buttonSelection"
}  
{
  "type": "customCardCollection",
  "listName": "listVariableName",
  "contentItem": "individualRecordName",
  "customContent": {
    "contentType": "card Type",
    "content": {
      "insertBespokePropertyNamesHere": "insert card content here"
    }
  }
}     

Person Card

Display personal details on a card

Properties
contentType

application/vnd.peoplefirst.card.person

content

A list of people

content/name

Person’s name

content/email

Person’s email

content/image

Person’s image

content/event

Additional text

content/reminders

A list of reminders

content/reminders/subject

Reminder details

content/buttons

List of buttons

buttons/type

Type of button

buttons/title

Button label

buttons/value

Value to be returned when the button is pressed

Custom cards are only available using the talksuite or MHR apps.

The person card displays personal details and a list of reminders.

Use in a message node to display a single card with no buttons

Use in a string prompt node to display a single card with buttons

Use in a custom card collection node to display a carousel of cards

{
  "type": "message",
   "message": "This card is not available on this channel",
   "customContent": {
     "contentType": "application/vnd.peoplefirst.card.person",
      "content": {
        "name": "John Smith",
        "email": "john.smith@ruddington-software.com",
        "image": "https://media.giphy.com/media/3owzWmHgH4Mbh0Omre/giphy.gif",
        "event": "Next event with John is tomorrow"
       }
    }
} 
{
  "id": "advanced person card",
  "trigger": {
    "type": "message",
    "values": [
      "person card"
    ]
  },
  "nodes": [
    {
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "reminders"
          },
          "addItem",
          [
            {
              "var": "reminderA"
            }
          ]
        ]
      },
      "output": "reminders"
    },
    {
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "reminders"
          },
          "addItem",
          [
            {
              "var": "reminderB"
            }
          ]
        ]
      },
      "output": "reminders"
    },
    {
      "type": "operation",
      "operation": {
        "var": "reminders"
      },
      "output": "personA/reminders"
    },
    {
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "personCollection"
          },
          "addItem",
          [
            {
              "var": "personA"
            }
          ]
        ]
      },
      "output": "personCollection"
    },
    {
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "personCollection"
          },
          "addItem",
          [
            {
              "var": "personB"
            }
          ]
        ]
      },
      "output": "personCollection"
    },
    {
      "id": "displayEventCard",
      "type": "customCardCollection",
      "listName": "personCollection",
      "contentItem": "person",
      "customContent": {
        "contentType": "application/vnd.peoplefirst.card.person",
        "content": {
          "name": "{person/name}",
          "email": "{person/email}",
          "image": "{person/image}",
          "event": "Next event with {person/name} is {person/dueDate}",               
          "reminders": "{person/reminders}",
          "buttons": [
            {
              "type": "imBack",
              "title": "Clear reminders",
              "value": "clear reminders"
            }
          ]
        }
      },
      "nextNode": null
    }
  ],
  "model": {
    "personA/name": "John Smith",
    "personA/email": "john.smith@ruddinston-software.com",
    "personA/image": "https://media.giphy.com/media/3owzWmHgH4Mbh0Omre/giphy.gif",
    "personA/dueDate": "1/1/2020",
    "reminderA/subject/title": "Approve John's expenses",
    "personB/name": "Jane Williams",
    "personB/email": "j.williams@ruddinston-software.com",
    "personB/image": "https://media.giphy.com/media/3owzWmHgH4Mbh0Omre/giphy.gif",
    "personB/dueDate": "2/2/2020",
    "reminderB/subject/title": "Arrange a meeting with John"
  }
}

Email Card

Display details of an email on a card

Properties
contentType

application/vnd.peoplefirst.message.email

content

Card contents

content/person

Personal details

content/person/name

Person’s name

content/person/email

Person’s email

content/person/imageUrl

Person’s image

content/emailContent

Properties containing details of the email

content/emailContent/id

email Id

content/emailContent/subject

Email subject

content/emailContent/body

Email body

content/buttons

List of buttons

content/buttons/type

Type of button

content/buttons/title

Button label

content/buttons/value

Value to be returned when the button is pressed

Custom cards are only available using the talksuite or MHR apps.

The email card displays personal details plus the email subject and body

Use in a message node to display a single card with no buttons

Use in a string prompt node to display a single card with buttons

Use in a custom card collection node to display a carousel of cards

{    
  "type": "message",
  "customContent": {
    "contentType": "application/vnd.peoplefirst.card.message.email",
    "content": {
      "person": {
        "name": "John Smith",
        "email": "j.smith@jumpcrash.co.uk",
        "imageUrl": "https://media.giphy.com/media/3owzWmHgH4Mbh0Omre/giphy.gif"
      },
      "emailContent": {
        "id": "123",
        "subject": "This is the email subject",
        "body": "This is the email body"
      },
      "buttons": [
        {
          "type": "postBack",
          "title": "Reply",
          "value": "replying"
        }
      ]
    }
  },
  "message": "This is a custom email card that is not supported on this channel."
}

Clock-in Card

Display details of clock-in and out times

Properties
contentType

application/vnd.peoplefirst.card.timeandattendance.clockIn

content

Card contents

content/title

Card title

clockins

List of clock-in and out times

content/images/url

map image (iOS and Android only)

clockins/id

Record id

content/clockins/startLabel

Label to display at the start of the time-pair graphic (iOS and Android only)

content/clockins/startDate

Date and time of the clock-in

content/clockins/startTimeZone

Time zone of the start date-time

content/clockins/startAutomatic

true if the clock-in time was calculated, false if the user manually entered a time

content/clockins/endLabel

Label to display at the end of the time-pair graphic (iOS and Android only)

content/clockins/endDate

Date and time of the clock-out

content/clockins/endTimeZone

Time zone of the end date-time

content/clockins/endAutomatic

true if the clock-out time was calculated, false if the user manually entered a time

content/clockins/colour (iOS and Android only)

Hex colour for the time-pair graphic (preceded by

content/buttons

List of buttons

content/buttons/type

Type of button

content/buttons/title

Button label

content/buttons/value

Value to be returned when the button is pressed

Custom cards are only available using the talksuite or MHR apps. Label, map and colours are only available on the mobile apps

The clock-in card displays one or more clock-in/out time pairs.

If the automatic flag is set to false, “manual” will be displayed against the time.

If the time zone differs from the device time zone, the time zone of the clock in or out time will be displayed.

Use in a message node to display a single card with no buttons

Use in a string prompt node to display a single card with buttons

Use in a custom card collection node to display a carousel of cards

{
  "id": "clockin",
  "trigger": {
    "type": "message",
    "values": [
      "clockin"
    ]
  },
  "nodes": [
    {
      "type": "downloadAction",
      "service": {
        "url": "https://i.stack.imgur.com/KOICW.png"
      },
      "outputs": {
        "content": "map"
      }
    },
    {
      "type": "message",
      "message": "{unableToDisplayCardMessage}",
      "customContent": {
        "contentType": "application/vnd.peoplefirst.card.timeandattendance.clockIn",
        "content": {
          "title": "Clock-in",
          "images": [
            {
              "url": "{map/url}",
              "alt": "Clock in map"
            }
          ],
          "text": "Totally arbitrary text",
          "clockIns": [
            {
              "id": "1",
              "colour": "#9E5BB9",
              "startLabel": "A",                   
              "startDate": "2019-01-01T10:11:12Z",
              "startTimeZone": "Europe/London",
              "startAutomatic": true,
              "endLabel": "B",                   
              "endDate": "2019-01-01T12:11:12Z",
              "endTimeZone": "Europe/London",
              "endAutomatic": true
            },
            {
              "id": "2",
              "colour": "#9E5BB9",
              "startLabel": "C",                   
              "startDate": "2019-01-02T09:01:12Z",
              "startTimeZone": "Europe/London",
              "startAutomatic": true,
              "endLabel": "D",
              "endCoords": "52.886098,-1.145899",
              "endDate": "2019-01-02T15:22:12Z",
              "endTimeZone": "Europe/London",
              "endAutomatic": true
            }
          ],
          "buttons": [
            {
              "type": "imBack",
              "title": "Amend",
              "value": "Amend"
            },
            {
              "type": "imBack",
              "title": "Cancel",
              "value": "Cancel"
            }
          ]
        }
      }
    }
  ]
}     

Clock-in Approval Card

Display details of people and clock-in and out times

Properties
contentType

application/vnd.peoplefirst.card.timeandattendance.clockInApproval

content

Card contents

content/title

Card title

content/person

Person details

content/person/name

Person name

content/person/imageUrl

Person photo

content/images/url

map image (iOS and Android only)

clockins

List of clock-in and out times

clockins/id

Record id

content/clockins/startLabel

Label to display at the start of the time-pair graphic (iOS and Android only)

content/clockins/startDate

Date and time of the clock-in

content/clockins/startTimeZone

Time zone of the start date-time

content/clockins/startAutomatic

true if the clock-in time was calculated, false if the user manually entered a time

content/clockins/endLabel

Label to display at the end of the time-pair graphic (iOS and Android only)

content/clockins/endDate

Date and time of the clock-out

content/clockins/endTimeZone

Time zone of the end date-time

content/clockins/endAutomatic

true if the clock-out time was calculated, false if the user manually entered a time

content/clockins/colour

Hex colour for the time-pair graphic (preceded by

content/buttons

List of buttons

content/buttons/type

Type of button

content/buttons/title

Button label

content/buttons/value

Value to be returned when the button is pressed

Custom cards are only available using the talksuite or MHR apps. Label, map and colours are only available on the mobile apps

The clock-in approval card differs from the clock-in card in that it displays details of the person. This allows a carousel of cards from mutlple people to be displayed, allowing the manager to make multiple approval/rejection decisions

The data can be set-up so that a separate card is displayed for each person and for each day for a person.

If the automatic flag is set to false, “manual” will be displayed against the time.

If the time zone differs from the device time zone, the time zone of the clock in or out time will be displayed.

Use in a message node to display a single card with no buttons

Use in a string prompt node to display a single card with buttons

Use in a custom card collection node to display a carousel of cards

{
  "id": "clockinApproval",
  "trigger": {
    "type": "message",
    "values": [
      "clockinApproval"
    ]
  },
  "nodes": [
    {
      "type": "downloadAction",
      "service": {
        "url": "https://i.stack.imgur.com/KOICW.png"
      },
      "outputs": {
        "content": "map"
      }
    },
    {
      "type": "downloadAction",
      "service": {
        "url": "https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500"
      },
      "outputs": {
        "content": "photo"
      }
    },
    {
      "type": "message",
      "message": "{unableToDisplayCardMessage}",
      "customContent": {
        "contentType": "application/vnd.peoplefirst.card.timeandattendance.clockInApproval",
        "content": {
          "title": "Clock-in approval",
          "person": {
            "name": "Alan Young",
            "imageUrl": "{photo/Url}"
          },
          "images": [
            {
              "url": "{map/Url}",
              "alt": "Clock in map"
            }
          ],
          "text": "Totally arbitrary text",
          "clockIns": [
            {
              "id": "1",
              "colour": "#9E5BB9",
              "startLabel": "A",
              "startDate": "2019-01-01T10:11:12Z",
              "startTimeZone": "Europe/London",
              "startAutomatic": true,
              "endLabel": "B",
              "endCoords": "{cfg/m2/coords}",
              "endDate": "2019-01-01T12:11:12Z",
              "endTimeZone": "Europe/London",
              "endAutomatic": true
            },
            {
              "id": "2",
              "colour": "#9E5BB9",
              "startLabel": "C",
              "startDate": "2019-01-02T09:01:12Z",
              "startTimeZone": "Europe/London",
              "startAutomatic": true,
              "endLabel": "D",
              "endDate": "2019-01-02T15:22:12Z",
              "endTimeZone": "Europe/London",
              "endAutomatic": true
            }
          ],
          "buttons": [
            {
              "type": "imBack",
              "title": "Amend",
              "value": "Amend"
            },
            {
              "type": "imBack",
              "title": "Cancel",
              "value": "Cancel"
            }
          ]
        }
      }
    }
  ]
}    

Date Period Card

Display details of a date or date period

Properties
contentType

application/vnd.talksuite.card.dateperiod

content

Card contents

content/title

Card title

content/subtitle

Card subtitle

content/person

Person details

content/person/name

Person name

content/person/thumbnailUrl

Person photo

datePeriod

Date details

datePeriod/start

Start date details

datePeriod/start/date

Start date or date-time

datePeriod/start/caption

Caption for start date

datePeriod/end

Start date details

datePeriod/end/date

Start date or date-time

datePeriod/end/caption

Caption for start date

content/buttons

List of buttons

content/buttons/type

Type of button

content/buttons/title

Button label

content/buttons/value

Value to be returned when the button is pressed

This card is only available on iOS and Android apps

This card can be used to display details of time-related events. If the event is for a single date, then omit the end section.Therefore this can be used to display details of a single event or a period. The caption allows a date to be annotated (e.g. to show a location for the event)

{
  "type": "message",
  "message": "Not available in this channel",
  "customContent": {
    "contentType": "application/vnd.talksuite.card.dateperiod",
    "content": {
      "title": "1 day off",
      "subtitle": "Leave",
      "person": {
        "name": "Akeem Larkin",
        "ImageUrl": "https://media.giphy.com/media/3owzWmHgH4Mbh0Omre/giphy.gif"
      },
      "datePeriod": {
        "start": {
          "date": "2019-01-01T00:00:00Z",
          "caption": "2:30 hours"
        },
        "end": {
          "date": "2019-02-01T00:00:00Z",
          "caption": "4:30 hours"
        }
      },
      "buttons": [
        {
          "type": "openUrl",
          "title": "Edit absence",
          "value": "http://bbc.co.uk/news"
        }
      ]
    }
  }
}

Introduction

Adaptive cards allow rich cards to be displayed including:

  • Mutltiple columns
  • Text formatting
  • Input fields
  • Action buttons
  • Embed cards witin cards
  • Show and hide fields and embedded cards
  • Images
  • Videos
  • Background image

Adaptive cards can be displayed using a message node with custom content or a custom card collection for carousels

Input field data can be accessed using a dialogue with a Form data triggere

The following sections include basic examples, but these are not exhaustive. Full documentation is available at adaptivecards.io. This site contains documentation on the schema and a graphical card designer, which generates JSON that can be pasted into the content property in the example nodes on the right.

{
  "type": "message",
  "message": "Not available",
  "customContent": {
    "contentType": "application/vnd.microsoft.card.adaptive",
    "content": {} 
  }      
}
{
  "type": "message",
  "message": "Not available",
  "listName" : "listOfCards",
  "contentItem" : "card",
  "customContent": {
    "contentType": "application/vnd.microsoft.card.adaptive",
    "content": {} 
  }      
}

Text input

Display a text input field

Properties
type

Input.Text

placeholder

Placeholder text displayed when the field is empty

id

Unique name of the field. This is used to fire the form data dialogue which processed the data when the form is saved

The example on the right shows a TextBlock (field label) a Text input field and an OK button to save the form

More properties (e.g. default values, text wrapping) are available - see the text input schema documentation

{
  "id": "text input",
  "trigger": {
    "type": "message",
    "values": [
      "text input"
    ]
  },
  "nodes": [
    {
      "type": "buildObject",
      "object": {
        "type": "AdaptiveCard",
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "version": "1.2",
        "body": [
          {
            "type": "TextBlock",
            "text": "Name"
          },
          {
            "type": "Input.Text",
            "placeholder": "Please enter your name",
            "id": "name"
          }
        ],
        "actions": [
          {
            "type": "Action.Submit",
            "title": "OK"
          }
        ]
      },
      "output": "content"
    },
    {
      "type": "message",
      "message": "Not available",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.adaptive",
        "content": "{content}"
      }
    }
  ]
}
{
  "id": "text input trigger",
  "trigger": {
    "type": "formData",
    "fieldName": "name",
     "values": [],
     "output": "data"
   },
   "nodes": [
     {
       "type": "message",
        "message": "Hello {data/name}"
     }
   ]
}

Number input

Display a number input field

Properties
type

Input.Number

placeholder

Placeholder text displayed when the field is empty

id

Unique name of the field. This is used to fire the form data dialogue which processed the data when the form is saved

The example on the right shows a TextBlock (field label) a Number input field and an OK button to save the form

More properties (e.g. default values, text wrapping) are available - see the number input schema documentation

Up and down buttons are displayed in the field, which will add or subtract 1 from the field value

{
  "id": "number input",
  "trigger": {
    "type": "message",
    "values": [
      "number input"
    ]
  },
  "nodes": [
    {
      "type": "buildObject",
      "object": {
        "type": "AdaptiveCard",
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "version": "1.2",
        "body": [
          {
            "type": "TextBlock",
            "text": "Quantity"
          },
          {
            "type": "Input.Number",
            "placeholder": "Please enter the quantity",
            "id": "quantity"
          }
        ],
        "actions": [
          {
            "type": "Action.Submit",
            "title": "OK"
          }
        ]
      },
      "output": "content"
    },
    {
      "type": "message",
      "message": "Not available",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.adaptive",
        "content": "{content}"
      }
    }
  ]
}
{
  "id": "number input trigger",
  "trigger": {
    "type": "formData",
    "fieldName": "quantity",
     "values": [],
     "output": "data"
   },
   "nodes": [
     {
       "type": "message",
        "message": "Quantity is {data/quantity}"
     }
   ]
}

Date input

Display a date input field

Properties
type

Input.Date

placeholder

Placeholder text displayed when the field is empty

id

Unique name of the field. This is used to fire the form data dialogue which processed the data when the form is saved

The example on the right shows a TextBlock (field label) a Date input field and an OK button to save the form

More properties (e.g. default values, text wrapping) are available - see the date input schema documentation

The field contains a date picker that will display a calendar

{
  "id": "date input",
  "trigger": {
    "type": "message",
    "values": [
      "number input"
    ]
  },
  "nodes": [
    {
      "type": "buildObject",
      "object": {
        "type": "AdaptiveCard",
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "version": "1.2",
        "body": [
          {
            "type": "TextBlock",
            "text": "Date"
          },
          {
            "type": "Input.Number",
            "placeholder": "Please enter the date",
            "id": "date"
          }
        ],
        "actions": [
          {
            "type": "Action.Submit",
            "title": "OK"
          }
        ]
      },
      "output": "content"
    },
    {
      "type": "message",
      "message": "Not available",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.adaptive",
        "content": "{content}"
      }
    }
  ]
}
{
  "id": "date input trigger",
  "trigger": {
    "type": "formData",
    "fieldName": "date",
     "values": [],
     "output": "data"
   },
   "nodes": [
     {
       "type": "message",
        "message": "Date is {data/date}"
     }
   ]
}

Time input

Display a time input field

Properties
type

Input.Time

placeholder

Placeholder text displayed when the field is empty

id

Unique name of the field. This is used to fire the form data dialogue which processed the data when the form is saved

The example on the right shows a TextBlock (field label) a Time input field and an OK button to save the form

More properties (e.g. default values, text wrapping) are available - see the time input schema documentation

The field contains a time picker

{
  "id": "time input",
  "trigger": {
    "type": "message",
    "values": [
      "time input"
    ]
  },
  "nodes": [
    {
      "type": "buildObject",
      "object": {
        "type": "AdaptiveCard",
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "version": "1.2",
        "body": [
          {
            "type": "TextBlock",
            "text": "Time"
          },
          {
            "type": "Input.Number",
            "placeholder": "Please enter the time",
            "id": "time"
          }
        ],
        "actions": [
          {
            "type": "Action.Submit",
            "title": "OK"
          }
        ]
      },
      "output": "content"
    },
    {
      "type": "message",
      "message": "Not available",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.adaptive",
        "content": "{content}"
      }
    }
  ]
}
{
  "id": "time input trigger",
  "trigger": {
    "type": "formData",
    "fieldName": "time",
     "values": [],
     "output": "data"
   },
   "nodes": [
     {
       "type": "message",
        "message": "Time is {data/time}"
     }
   ]
}

Select from a list

Drop down lists or lists of check boxes or radio buttons

Properties
type

Input.ChoiceSet

choices

List containing the titles and values of list

style

compact for drop down lists expanded for radio buttons and checkboxes

isMultiSelect

When the style is expanded set to true for multi-select checkboxes

choices/title

Item to display in the list

choices/value

Value to return when a list item is selected

id

Unique name of the field. This is used to fire the form data dialogue which processed the data when the form is saved

The example on the right shows a TextBlock (field label) a drop down list field and an OK button to save the form

More properties (e.g. default values, multi-select) are available - see the choice set input schema documentation

{
  "id": "list input",
  "trigger": {
    "type": "message",
    "values": [
      "list input"
    ]
  },
  "nodes": [
    {
      "type": "buildObject",
      "object": {
        "type": "AdaptiveCard",
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "version": "1.2",
        "body": [
          {
            "type": "TextBlock",
            "text": "Drop down list"
          },
          {
            "type": "Input.ChoiceSet",
            "style": "compact",
            "isMultiSelect": false,
            "choices": [
              {
                "title": "Choice 1",
                "value": "Choice 1"
              },
              {
                "title": "Choice 2",
                "value": "Choice 2"
              }
            ],
            "placeholder": "Please select from the list",
            "id": "list"
          },
          {
            "type": "TextBlock",
            "text": "Radio buttons"
          },
          {
            "type": "Input.ChoiceSet",
            "style": "expanded",
            "isMultiSelect": false,
            "choices": [
              {
                "title": "Choice 1",
                "value": "Choice 1"
              },
              {
                "title": "Choice 2",
                "value": "Choice 2"
              }
            ],
            "placeholder": "Please select from the list",
            "id": "radio"
          },
          {
            "type": "TextBlock",
            "text": "Check boxes"
          },
          {
            "type": "Input.ChoiceSet",
            "style": "expanded",
            "isMultiSelect": true,
            "choices": [
              {
                "title": "Choice 1",
                "value": "Choice 1"
              },
              {
                "title": "Choice 2",
                "value": "Choice 2"
              }
            ],
            "placeholder": "Please select from the list",
            "id": "checkboxlist"
          }
        ],
        "actions": [
          {
            "type": "Action.Submit",
            "title": "OK"
          }
        ]
      },
      "output": "content"
    },
    {
      "type": "message",
      "message": "Not available",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.adaptive",
        "content": "{content}"
      }
    }
  ]
}
{
  "id": "list input trigger",
  "trigger": {
    "type": "formData",
    "fieldName": "list",
    "values": [],
    "output": "data"
  },
  "nodes": [
    {
      "type": "message",
      "message": "List selection : {data/list}"
    },
    {
      "type": "message",
      "message": "Radio button selection : {data/radio}"
    },
    {
      "type": "message",
      "message": "Checkbox selections : {data/checkboxlist}"
    }
  ]
}

Buttons

Display a button

Properties
type

Action.Submit

title

Button label

data

JSON returned when the button is pressed. Use a Form Data trigger to catch the data

The example on the right shows a card with a text field and OK and Cancel buttons

More properties (e.g. default values, text wrapping) are available - see the action submit schema documentation

If you have a card with buttons but no input fields, you will need to add a dummy field, make it invisible (isVisible property of false) and a default value (value property)

{
  "id": "buttons",
  "trigger": {
    "type": "message",
    "values": [
      "buttons"
    ]
  },
  "nodes": [
    {
      "type": "buildObject",
      "object": {
        "type": "AdaptiveCard",
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "version": "1.2",
        "body": [
          {
            "type": "TextBlock",
            "text": "Name"
          },
          {
            "type": "Input.Text",
            "placeholder": "Please enter your name",
            "id": "OkCancel"
          }
        ],
        "actions": [
          {
            "type": "Action.Submit",
            "title": "OK",
            "data": {
              "button": "OK"
            }
          },
          {
            "type": "Action.Submit",
            "title": "Cancel",
            "data": {
              "button": "Cancel"
            }
          }
        ]
      },
      "output": "content"
    },
    {
      "type": "message",
      "message": "Not available",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.adaptive",
        "content": "{content}"
      }
    }
  ]
}    
{
  "id": "text input trigger",
  "trigger": {
    "type": "formData",
    "fieldName": "OkCancel",
     "values": [],
     "output": "data"
   },
   "nodes": [
     {
       "type": "message",
        "message": "You pressed {data/button}"
     }
   ]
}

Fact sets

Display read only title/value pairs

Properties
type

Input.FactSet

facts

Display read only field titles and descriptions in two columns

facts/title

Fact title

facts/value

Fact value

More properties are available - see the fact set input schema documentation

{
  "id": "fact set",
  "trigger": {
    "type": "message",
    "values": [
      "fact set"
    ]
  },
  "nodes": [
    {
      "type": "buildObject",
      "object": {
        "type": "AdaptiveCard",
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "version": "1.2",
        "body": [
          {
            "FactSet",
            "facts": [
              {
                "title": "Fact 1",
                "value": "Value 1"
              },
              {
                "title": "Fact 2",
                "value": "Value 2"
              }
            ],
            "placeholder": "Please select from the list",
            "id": "list"
          }
        ]
      },
      "output": "content"
    },
    {
      "type": "message",
      "message": "Not available",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.adaptive",
        "content": "{content}"
      }
    }
  ]
}

Rich text

Display rea

Properties
type

RichTextBlock

inlines

List of inline text items, which are displayed together as one rich text field

inlines/type

TextRun

text

unformatted text. Formatting properties should follow (e.g. size)

The formatting properties and their values - see the rich text block input schema documentation

{
  "id": "rich text",
  "trigger": {
    "type": "message",
    "values": [
      "rich text"
    ]
  },
  "nodes": [
    {
      "type": "buildObject",
      "object": {
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "type": "AdaptiveCard",
        "version": "1.2",
        "body": [
          {
            "type": "RichTextBlock",
            "inlines": [
              {
                "type": "TextRun",
                "text": "Text ",
                "size": "small"
              },
              {
                "type": "TextRun",
                "text": "of ",
                "size": "medium"
              },
              {
                "type": "TextRun",
                "text": "all ",
                "size": "large"
              },
              {
                "type": "TextRun",
                "text": "sizes! ",
                "size": "extraLarge"
              },
              {
                "type": "TextRun",
                "text": "Light weight text. ",
                "weight": "lighter"
              },
              {
                "type": "TextRun",
                "text": "Bold weight text. ",
                "weight": "bolder"
              },
              {
                "type": "TextRun",
                "text": "Highlights. ",
                "highlight": true
              },
              {
                "type": "TextRun",
                "text": "Italics. ",
                "italic": true
              }
            ]
          }
        ]
      },
      "output": "content"
    },
    {
      "type": "message",
      "message": "Not available",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.adaptive",
        "content": "{content}"
      }
    }
  ]
} 

Columns

Display fields in columns

Properties
type

ColumnSet

columns

List of columns

columns/type

ColumnSet

columns/items

Field definitions for the columns

More properties are available - see the column set and column schema documentation

{
  "id": "columns",
  "trigger": {
    "type": "message",
    "values": [
      "columns"
    ]
  },
  "nodes": [
    {
      "type": "buildObject",
      "object": {
        "type": "AdaptiveCard",
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "version": "1.2",
        "body": [
          {
            "type": "ColumnSet",
            "columns": [
              {
                "type": "Column",
                "items": [
                  {
                    "type": "TextBlock",
                    "text": "Start time"
                  },
                  {
                    "type": "Input.Time",
                    "id": "start"
                  }
                ]
              },
              {
                "type": "Column",
                "items": [
                  {
                    "type": "TextBlock",
                    "text": "End time"
                  },
                  {
                    "type": "Input.Time",
                    "id": "end"
                  }
                ]
              }
            ]
          }
        ]
      },
      "output": "content"
    },
    {
      "type": "message",
      "message": "Not available",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.adaptive",
        "content": "{content}"
      }
    }
  ]
}

Images

Display images

Properties
type

ImageSet

images

List of images

images/type

Image

images/url

Image url

More properties are available - see the image set and image schema documentation

{
  "id": "images",
  "trigger": {
    "type": "message",
    "values": [
      "images"
    ]
  },
  "nodes": [
    {
      "type": "buildObject",
      "object": {
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "type": "AdaptiveCard",
        "version": "1.2",
        "body": [
          {
            "type": "ImageSet",
            "imageSize": "medium",
            "images": [
              {
                "type": "Image",
                "url": "https://bbimagestorage.blob.core.windows.net/weather-images/0.jpg"
              },
              {
                "type": "Image",
                "url": "https://bbimagestorage.blob.core.windows.net/weather-images/1.jpg"
              },
              {
                "type": "Image",
                "url": "https://bbimagestorage.blob.core.windows.net/weather-images/2.jpg"
              }
            ]
          }
        ]
      },
      "output": "content"
    },
    {
      "type": "message",
      "message": "Not available",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.adaptive",
        "content": "{content}"
      }
    }
  ]
}

Video

Play video

Properties
type

Media

poster

Image to display when the video is not playing

sources

List of media files

sources/mimeType

video/mp3

sources/url

Video url

More properties are available - see the media schema documentation

{
  "id": "media",
  "trigger": {
    "type": "message",
    "values": [
      "media"
    ]
  },
  "nodes": [
    {
      "type": "buildObject",
      "object": {
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "type": "AdaptiveCard",
        "version": "1.1",
        "body": [
          {
            "type": "Media",
            "poster": "https://adaptivecards.io/content/poster-video.png",
            "sources": [
              {
                "mimeType": "video/mp4",
                "url": "https://adaptivecardsblob.blob.core.windows.net/assets/AdaptiveCardsOverviewVideo.mp4"
              }
            ]
          }
        ]
      },
      "output": "content"
    },
    {
      "type": "message",
      "message": "Not available",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.adaptive",
        "content": "{content}"
      }
    }
  ]
}  

Show sub-cards

Display a sub-card on a button press

Properties
type

Action.ShowCard

title

Button label

card

Definition of the sub-card

card/type

AdaptiveCard

More properties are available - see the show card schema documentation

{
  "id": "show card",
  "trigger": {
    "type": "message",
    "values": [
      "show card"
    ]
  },
  "nodes": [
    {
      "type": "buildObject",
      "object": {
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "type": "AdaptiveCard",
        "version": "1.2",
        "actions": [
          {
            "type": "Action.ShowCard",
            "title": "Weather",
            "card": {
              "type": "AdaptiveCard",
              "body": [
                {
                  "type": "TextBlock",
                  "text": "It will be sunny todau"
                }
              ]
            }
          },
          {
            "type": "Action.ShowCard",
            "title": "Traffic",
            "card": {
              "type": "AdaptiveCard",
              "body": [
                {
                  "type": "TextBlock",
                  "text": "There are no traffic problems"
                }
              ]
            }
          }
        ]
      },
      "output": "content"
    },
    {
      "type": "message",
      "message": "Not available",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.adaptive",
        "content": "{content}"
      }
    }
  ]
}

Background image

Display a background image on a card

Properties
backgroundImage

url of an image

{
  "id": "background",
  "trigger": {
    "type": "message",
    "values": [
      "background"
    ]
  },
  "nodes": [
    {
      "type": "buildObject",
      "object": {
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "type": "AdaptiveCard",
        "version": "1.2",
        "backgroundImage": "https://download-ssl.msgamestudios.com/content/mgs/ce/production/SolitaireWin10/dev/adapative_card_assets/v1/card_background.png",
        "body": [
          {
            "type": "Image",
            "url": "https://bbimagestorage.blob.core.windows.net/weather-images/0.jpg"
          }
        ]
      },
      "output": "content"
    },
    {
      "type": "message",
      "message": "Not available",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.adaptive",
        "content": "{content}"
      }
    }
  ]
}  

Put a bot on your website

You can run your bot on a chat window embedded in your web site, accessed from a button displayed on your page

When the button is pressed your bot appears, overlaying the right of your site

You can customise introduction text, icons, text colours and button styles.

HTML changes

The following changes need to be made to your web page html

Add a style sheet in the head section

<link rel="stylesheet" type="text/css" href="https://bbqchatproduksstor.blob.core.windows.net/static/ts-style-v2.css">

Append the following lines after the body section

<script src="https://unpkg.com/markdown-it@8.4.2/dist/markdown-it.min.js"></script>

<script src="https://cdn.botframework.com/botframework-webchat/latest/webchat.js"></script>

<script id="ts-quick-start" src="https://bbqchatproduksstor.blob.core.windows.net/static/quickstart-alt-v2.js"></script>

<script>

(function () {

let discoveryUrl = "https://bb-prod-uks-app.azurewebsites.net/api/organisations/570c0a23-86a7-4a98-aa25-dc1bb8ec5021/bots/109eb3b0-c358-4b2d-9caf-0b2c3d0ec78e/discover";

let styleOptions = {
  bubbleFromUserTextColor: 'White',
  bubbleFromUserBackground: '#ec7f1d',
  bubbleFromUserBorderRadius: 5,
  bubbleBorderRadius: 5,
  botAvatarImage: 'https://pbs.twimg.com/profile_images/1038057408931803136/5gEYoOHp_400x400.jpg',
};

let botName = "talksuite FAQ bot";
let botMessage = "All your questions about the talksuite platform answered";
let startChatButtonColour = "#ec7f1d";
let botIcon = "https://bbimagestorage.blob.core.windows.net/faq-images/botbuilder.JPG";
let isInputDisplayed = true

tsModule.talksuite(
        discoveryUrl,
        styleOptions,
        botName,
        botMessage,
        startChatButtonColour,
        botIcon,
		      allowTextInput
);   })();

</script>>

You can configure the following items:

  • discoveryUrl The bot you want to run
  • bubbleFromUserTextColour The text colour for user-typed text in the coonversation history
  • bubbleFromUserBackground The background colour for user-typed text in the coonversation history
  • bubbleFromUserBorderRadius The border radius for user-typed text in the coonversation history
  • borderRadius The border radius for cards
  • botAvitarImage The icon to be displayed in the channel
  • botName The title of the windows
  • botMessage A description of the bot
  • botIcon An icon that appear next to the start chat button
  • isInputDIsplayed Set to false to turn off typing

The configuration in the code above is branded as talksuite and connects to a bot that can gives information on the talksuite platform

Calculate Overtime 💰

Write a dialogue to perform the following steps, when the user types “Claim overtime”

  1. Ask the user to enter their standard hourly rate of pay

  2. Ask the user to enter the date on which the overtime was earned

  3. Ask the user to enter the number of hours overtime worked on that day

  4. Calculate the overtime for the day at the standard rate for a week day, time and a half for a Saturday and double time for a Sunday

  5. Display the hours worked, date and total overtime for the day

  6. Ask the user if they want to add another day’s overtime or submit their Claim

  7. If the user selects to add another day, repeat the process from step 2

  8. If the user selects to submit their claim, display the total overtime pay for all days entered and stop the dialogue

For hints and tips and a solution go to the Overtime solution

Photo Album 📷

Write 3 dialogues to add a photo to an album, view the photo album and remove a photo from the album

  • The add photo dialogue asks the user to upload a photo, then asks for a caption for the photo. The photos are stored in the user’s conversation

  • The photo album dialogue shows the photo album as a set (carousel) of cards, with the caption shown for each card

  • The remove album dialogue asks for a caption, then removes the photo with that caption

For hints and tips and a solution go to the Photo Album solution

Flight Calculator ✈️

Write a dialogue to calculate the arrival time in local time for a flight. The steps are:

  1. Select the departure airport (choose from Los Angeles, New York, London or Paris)

  2. Enter the departure time in the departure time zone

  3. Select the arrival airport (from the same list of locations)

  4. Calculate the arrival time in the timezone of the arrival airport

  5. Display the details of the flight, including the local arrival time

For hints and tips and a solution go to the Flight calculator solution

Flags of the world 🎌

Write a set of dialogues to implement a flags of the world bot:

  1. Create a project and a bot which references the project

  2. When first connecting to the bot display an introduction message “Welcome to flags of the world!”.

  3. Display “Please select a continent” then a list of buttons with labels “Europe”, “The Americas”, “Asia” and “Africa”

  4. Choose a few countries from each continent. When a continent button is pressed, display a list of buttons showing your selected countries for that continent

  5. When a country button is pressed. display a card with the flag of the country, a title of the country name, a subtitle with the capital of the country and show the population of the country in the card text

  6. All buttons should continue to be clickable in the message history and will display the appropriate submenu or card

  7. When the user types any sentence containing “help” or “continent” (in any case) display the continents menu

  8. When the user types anything (in any case) that containins the name of one of the countries, display the card for that country (e.g What’s the flag of france)

  9. If the user types anything else display “I’m sorry I didn’t understand that”. Then display the continent message and menu from point 3 above

For hints and tips and a solution go to the Flags of the world solution

UK Public Holidays Example Dialogue

Example dialogues that use a public UK government API to display UK public holidays

{
  "id": "UK Public Holidays",
  "description": "Gets bank holiday for England and Wales, Scotland and Northern Ireland for a date range ",
  "trigger": {
    "type": "message",
    "values": [
      "uk public holidays",
      "bank holidays"           
    ]
  },
  "nodes": [
    {
      "description": "Display a menu of the areas of the UK that have separate public holidays",
      "type": "choicePrompt",
      "message": "Please select the part of the UK that you want to see the Bank Holidays for",
      "retryMessage": "Please select one of the options below",
      "listName": "areas",
      "output": "area"
    },
    {
      "description": "Prompt for a start date",
      "type": "datePrompt",
      "message": "I can show bank holidays for a year. Enter the start date of the year",
      "retryMessage": "Please enter a valid date",
      "output": "startDate"
    },
    {
      "description": "Format the date as, for example, 1st December 2018",
      "type": "operation",
      "operation": {
        "Date.format": [
          {
            "var": "startDate"
          },
          "do MMMM yyyy"
        ]
      },
      "output": "formattedStartDate"
    },
    {
      "description": "The end date of the range of holidays to display is one year after the start date",
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "startDate"
          },
          "addYears",
          [
            1
          ]
        ]
      },
      "output": "endDate"
    },
    {
      "type": "message",
      "message": "Looking up Bank Holidays for the year starting on {formattedStartDate} for {area}"
    },
    {
      "description": "Read all holiday records from the UK government API",
      "type": "action",
      "service": {
        "url": "https://www.gov.uk/bank-holidays.json",
        "method": "GET",
        "headers": {
          "Accept": "application/json"
        }
      },
      "outputs": {
        "body": {
          "englandAndWales": "/england-and-wales/events",
          "scotland": "/scotland/events",
          "northerIreland": "/northern-ireland/events"
        }
      }
    },
    {
      "description": "A separate variable has been populated for each UK area. Populate the chosenArea variable with data from the selected area",
      "type": "operation",
      "output": "chosenArea",
      "operation": {
        "if": [
          {
            "===": [
              {
                "var": "area"
              },
              "England and Wales"
            ]
          },
          {
            "var": "englandAndWales"
          },
          {
            "===": [
              {
                "var": "area"
              },
              "Scotland"
            ]
          },
          {
            "var": "scotland"
          },
          {
            "===": [
              {
                "var": "area"
              },
              "Northern Ireland"
            ]
          },
          {
            "var": "northernIreland"
          },
          {
            "var": "englandAndWales"
          }
        ]
      }
    },
    {
      "description": "Discard any dates outside the required range",
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "chosenArea"
          },
          "filter",
          [
            {
              "and": [
                {
                  ">=": [
                    {
                      "current": "date"
                    },
                    {
                      "var": "startDate"
                    }
                  ]
                },
                {
                  "<": [
                    {
                      "current": "date"
                    },
                    {
                      "var": "endDate"
                    }
                  ]
                }
              ]
            }
          ]
        ]
      },
      "output": "filteredDates"
    },
    {
      "description": "Count the number of records in the range",
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "filteredDates"
          },
          "getCount"
        ]
      },
      "output": "numberOfItems"
    },
    {
      "description": "If there is no data in the range, skip to a no data message",
      "type": "decision",
      "rule": {
        "===": [
          {
            "var": "numberOfItems"
          },
          0
        ]
      },
      "passNode": "no data"
    },
    {
      "description": "For each date in the range, build the text to output",
      "type": "repeatDialogue",
      "dialogueId": "Format UK Public Holidays",
      "inputs": {
        "index": {
          "var": "index"
        },
        "filteredDates": {
          "var": "filteredDates"
        },
        "display": {
          "var": "display"
        }
      },
      "outputs": {
        "index": {
          "var": "index"
        },
        "filteredDates": {
          "var": "filteredDates"
        },
        "display": {
          "var": "display"
        }
      },
      "repeatUntil": {
        ">=": [
          {
            "var": "index"
          },
          {
            "var": "numberOfItems"
          }
        ]
      }
    },
    {
      "description": "Output the formatted data for the dates in the range",
      "type": "message",
      "message": "{display}",
      "nextNode": "Dialogue.stop"
    },
    {
      "id": "no data",
      "type": "message",
      "message": "There are no bank holidays for that period"
    }
  ],
  "model": {
    "index": "0",
    "areas": [
      "England and Wales",
      "Scotland",
      "Northern Ireland"
    ]
  }
}        
{      
  "id": "Format UK Public Holidays",
  "description": "Format output for a UK public holiday and append  it to a new-line separated block of text",
  "trigger": {
    "type": "nestedDialogue"
  },
  "nodes": [
    {
      "description": "Using the index loop control variable, get the current holiday record",
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "filteredDates"
          },
          "getItem",
          [
            {
              "var": "index"
            }
          ]
        ]
      },
      "output": "bankHoliday"
    },
    {
      "description": "Format the date as, for example, Saturday 1st December 2018",
      "type": "operation",
      "operation": {
        "Date.format": [
          {
            "var": "bankHoliday/date"
          },
          "dddd do MMMM yyyy"
        ]
      },
      "output": "formattedDate"
    },
    {
      "description": "Construct a line of output and append to the text block",
      "type": "operation",
      "operation": {
        "cat": [
          {
            "var": "display"
          },
          {
            "var": "bankHoliday/title"
          },
          " - ",
          {
            "var": "formattedDate"
          },
          "\n\r"
        ]
      },
      "output": "display"
    },
    {
      "description": "Increment the loop control variable",
      "type": "operation",
      "output": "index",
      "operation": {
        "+": [
          {
            "var": "index"
          },
          1
        ]
      }
    }
  ]
}

Admin and Studio

If you have been invited to talksuite as an administrator, you can invite other people to use talksuite for your organisation(s). As an administrator, you have access to the Admin area. If you have been given an author role, you will also have access to the studio. You can switch between the two areas.

The admin area looks like this:

Click on the Studio link to go to the studio.

The studio looks like this:

Click on the Admin link to go to the admin area

Invite people

Once you have selected an organisation in the admin area, you can then invite others to join you.

There is an Invite link in the header of the pending invites list. Click on this link to create an invitation.

You will need to specify an email address for the person you want to invite. This must be unique within the organisation

You must also select the person’s roles. One or both of these roles must be selected:

  • An Administrator to allow the invitee to invite people themselves

  • An Author who can create bots, projects, dialogues and languages

When you save the form, a link will be displayed. Email this link to the invitee. They will then need to sign in or sign up to use talksuite. The link will expire in 72 hours.

If you have invited the wrong person by mistake, you can revoke the invite. The record will disappear from the pending invites list and if the invitee has not already signed up, the link will no longer work.

Managing Members

Once someone has accepted an invitation and signed up (or in) to talksuite, they will be removed from the invite list and will appear on the members list in the organisation screen within the admin area. The user will supply their name as part of the sign-up process.

Click on the manage link to amend a member’s details. You can change their name and roles and remove them from the organisation, which will prevent them using the studio for that organisation.

Usage Statistics

Your organisation page will contain a measure of the amount of activity by bot users within the organisation. An activity count is given for each calendar month. This includes

  • All messages sent by the bots in the organisation to users. If typing indicators are in use, this will count as a separate activity

  • All messages typed by users and sent to a bot in the organisation

  • Any activity when the client app reconnects, which will not be visible to the app user

Audit Log

A log of invites and changes to the membership list for your organisation can be exported to a file. This is downloaded as a CSV file. There is no date filter, so all activity will be output. The output will look something like this

Who What Detail When
michael@jumpcrash.co.uk Invited dan@jumpcrash.co.uk to be an author 2019-01-11 09:00
michael@jumpcrash.co.uk Amended Member dan@jumpcrash.co.uk: Daniel Smith to Danny Smith 2019-01-11 11:00

Super Administrators

The super administrator looks after a whole deployment. Their primary role is to create organisations and invite an initial Administrator to manage their own organisation.

A super administrator has the same access as an organisation administrators to an organisation. That is :

In addition they can:

  • Create organisations

  • Rename, suspend, re-active and remove organisations

  • Enable data stores for an organisation

  • Set the maximum number of Instant FAQ sets for an organisation

  • See the super admin activity log

Organisation Management

To create an organisation, select the “Add new organisation” option from the top of the left hand organisation menu in the admin area.

Then enter the organisation name

Organisation name must be unique

An organisation can be active (in which case authors and bot users have access to it) or suspended (in which case nobody can use it)

Once an organisation is created, the action menu on the right can be used to change the name of the organisation, suspend and activate it and delete it. The organisation creation date will be also be displayed, which allows you to keep track of the expiry of time-limited free trials

If you delete an organisation, all the members, projects, languages and dialogues in the organisation will be removed

Enable data stores

The data store feature is not enabled by default when an organisation is created. To allow data stores to be created in an organisation a super admin must select the Enable data store option on the organisations action menu

Set Instant FAQ limit

The instant FAQ feature is not enabled by default for a new organisation. To enable the feature select the Instant FAQ option from the organisation action menu and press the allow additional set button to set the number of FAQ sets that are allowed in the organisation

Other Super Admins

As a super admin, it is a good idea to create at least one additional super admin, so that organisations can be created and managed in your absence.

The super admin menu item at the bottom of the left hand organisation menu takes you to the super admin users management screen.

There is an Invite link in the header of the pending invites list. Click on this link to create an invitation for additional super admins.

You will need to specify an email address for the person you want to invite. This must be unique within the organisation

When you save the form, a link will be displayed. Email this link to the invitee. They will then need to sign in or sign up to use talksuite.

If you have inivited the wrong person by mistake, you can revoke the invite. The record will disappear from the pending invites list and if the invitee has no already signed up, the link will not longer work. The link will expire in 72 hours.

Once the new super admin has signed up, they will disappear from the invites list and appear on the members list. You can change the name of a member, suspend and re-activate then or remove them.

You can’t remove or deactivate your own super admin record

Audit Log

A log of super admin invites and changes to the super admin membership list can be exported to a file.

talksuite CLI

talksuite CLI is a comand-line interface for talksuite. Its features include:

  • Copy dialogues to file
  • Upload dialogues from file
  • Upgrade bots

The app is run in a powershell window. To install type the following command from within powershell

npm install -g @talksuite/talksuite-cli

You will need to authenticate using your normal talksuite studio login.

Once installed, type ts to run the application

A menu of commands is displayed

The help command takes a command name as a second argument and gives details of the paramters available for each command. Place organisation and project name parameters in single quotes. For example, this comand back up “My Project” in “My Organisation” to c:\backup is:

ts export-content c:\backup 'My Organisation' 'My Project'

Data Protection Requirements

If you are using talksuite to hold personal data, it’s a good practice to explain to users what data you hold and why, and to have procedures in place that allow users to ensure there data in up to date and only held for a valid reason

Some regions will have legisltative requirements in this area (GDPR for the EU)

The general areas that you need to consider are:

  • Explain to the user what types of personal data are held and why

  • Allow the user to see what their personal data is

  • Allow the user to delete any non-essential personal data

  • Have a retention policy in place to remove any old data that is no longer required.

Implementating Data Protection

Managing Conversation Data

talksuite uses conversation variables to permanently store personal information, and this section concentrates on how to write dialogues to implmement data protection requirements for conversation data. If you are storing data in spreadsheets or using APIs in systems that do not have their own data protection features, you will need to cover this data as well

Firstly, design the data to make its management easy:

  • Divide personal data into related groups (e.g data required for payroll and data required for diversity analysis). A user may want to remove their diversity data, but their payroll data cannot practically be removed. Keeping data held for different reasons separately will allow you to delete non-essential data while allowing an employee to continue to use essential features.

  • Avoid storing data in simple conversation variables. Hold data in lists of objects (even if there is only one item in the list). This allows data to be explicitly deleted (using the removeitem function), rather than justed blanked out.

  • Store the date of change against each personal data object. This allows you to show the user how current the data is and to implement a retention policy, where data over a certain age can be removed. This also gives you the option of creating a new record every time a change is made.

Next, write dialogues that allow the user to manage their own data. This will include dialogues to:

  • Show the users their own data

  • Allow the user (where appropriate) to keep the data up to date

  • Allow the user to remove any non-essential personal data

  • Allow the user to delete the conversation

Administrator Access

If you deal with requests for data disclosure or removal from users who no longer have access to the system, you will need to build administrative functions outside of talskuite to allow administrators to access or remove data. Use the External Event functionality to trigger a dialogue for a user. These dialogues can then perform the same access and removal functions as described above

Privacy Policies

The MHR custom app contains a privacy policy. The app will send an event "requestCurrentDataPrivacyPolicy to get the policy. The app expects an object with a property of text to be returned.

Welcome!

Hello and welcome to talksuite!

This section is for people who have received an invite to talksuite. It takes you through the steps required to create your first simple bot.

The steps are

  1. Signing in
  2. Creating a bot
  3. Creating a project
  4. Linking the bot and project
  5. Writing a welcome dialogue
  6. Run the dialogue using the talksuite app
  7. Writing a question and answer dialogue

Signing in

When you click on your invite link, you will be taken to the talksuite studio and a message like this is displayed

When you click on the sign up link you will be taken to the Microsoft Azure sign in page. If you are already a member of a talksuite organisation, you can enter your credentials. However, if this is your first invite you must click on the sign up link

Enter your email address (it must exactly match the email address on the welcome message). Click the Send verification code and a code will be sent to your email address. Enter the code and complete the rest of the form

You will now be logged into the talksuite studio. The name of your organisation will be displayed in the top right.

Creating a bot

Now your are signed in, the first thing we need to do is to create a bot. You will have a bot for each application you create in talksuite. Click on the Bots menu option

Click on Add bot

Enter a name for your bot

Creation of a bot will take a couple of minutes or so.

Creating a project

While you are waiting for the bot to be created, we will create a project. A project is a folder that will hold the logic for your bot. Folders are used to help you organise your work.

Click on the Projects menu at the top of the screen. Click on Add new project and enter a project name

Your project name should now be shown on the left of the screen.

Link project and bot

We need to link the bot and project, so the bot knows which dialogues it can run. Right click on your project in the right-hand menu and select the Copy project id menu option. This will copy the ID of the project into the clipboard

Go back to the Bots menu. Your new bot should now be displayed on the right (if it is still building the icon against the bot name will be animated).

Click on the bot name and the bot config details will be opened

Place the cursor in between the square brackets on line 16. Type double quotes, then paste the project ID inside the quotes. Select the update button on the top right. Your project and bot are now linked

Introduction dialogue

Go back to the project menu. Click on Add new under your project name and select Dialogue from the pop-up menu

A template for a new dialogue will be displayed on the right. The red blocks indicate missing components of the dialogue that must be completed.

Please the cursor inside the empty quotes on line 2. Type the name you want to give your dialogue (the bot user won’t see this). Now place the cursor on line 4 and type the word custom. A pop-up menu will appear. Select Custom event trigger from the menu

Replace eventname on line 5 with introduction. This tells talksuite that this dialogue should be run when a user first connects to the bot.

Place the cursor on line 8 and type the word message. Select message trigger from the pop-up list. You have added a message node, which is an instruction to the bot to output a message to the user. Change the highlighted Message text on line 10 with the message you want to give to the user when they sign in to the bot. Press the Create button on the top right. If the dialogue has been successfully created the “New dialogue” title will be replaced with the ID you have given your dialogue

Using the app

Use the talksuite app to test your bot. This can be downloaded from the App store or Google play (search for talksuite).

To connect to the bot you need to generate a QR code containing the bot details. Go back to the bot config and select the URL from line 25 (excluding the quotes). Use an on-line QR generator to generate th QR code. Select the Scan QR Code button and scan the QR code.

The welcome message from your dialogue should be displayed

Q and A dialogue

Now we will create a dialogue that answers a user’s question.

Go back to the studio, select Projects from the top menu, click on your project on the left hand menu and select Add new and Dialogue.

Enter a name for this dialogue in the id field on line 2.

Place the cursor on line 4 and type Message and select Message Trigger from the pop-up menu (the third option)

A message trigger starts a dialogue when a user enters a specific phrase. Replace the highlighted message text with your question.

Place the cursor on line 10 and type Message again. This time select Message Node from the menu (the first option). A message node is an instruction to the bot to output a message to the user. Replace the highlighted message text with the answer to your question.

Press the create button to create the dialogue

Now go back to the app and type the question in the text box at the bottom of the app. Press the button to the right of the text bot to send the question to the bot. The bot should respond with your answer

Find out more

Now it’s time to find out what talksuite can do and start writing more complex dialogues. This web site contains everthing you need to learn about bot building. Click here to get started

Introduction to the APIs

All talksuite functions are available via APIs.

List of APIs

The APIs controlling administration are:

API Function
superadmins Manage superadmins
organisations Create and manage organisations
invites Create and manage invitations to join talksuite
members Manage members of an organisation
usage Get message activity statistics
audit Get details of organisation and user changes
profile Get organisations to which you have access

The APIs controlling the studio are:

API Function
bots Create and manage bots
projects Create and manage projects
dialogues Create and manage dialogues
language Create and manage language stores
search Search across bots, language and dialogues
schema Obtain the dialogue JSON schema
snippets List of code snippets

The APIs used by the engine are:

API Function
discovery Connect to a bot
webhook Initiate a dialogue in a conversation

URL structure

The URL structure is:

https://deployment/api/apiname

Headers

All APIs except discovery must have an authorization header containing “Bearer token”.

The studio APIs must have an organisation-id header containing the organisation ID.


Audit

Available to super admins and admins. Admins can only access their own organisations (list obtained via the whoami API)

GET. Use /audit to get the audit activity in CSV format

Super Admins

GET. Use /superadmins to get all superadmins

GET. Use /superadmins/{superadminId} to get a superadmin

PUT. Use /superadmins/{superadminId} to update a superadmin

DELETE. Use /superadmins/{superadminId} to remove a superadmin

{
  "data": {
      "attributes" : {               
         "name" : "Alan Jones"
      }
  }   
}

Organisations

Organisation details

POST, PUT and DELETE to super admins only.

GET. Use /organisations to get all organisations

POST. Use /organisations to create an organistion

GET. Use /organisatons{orgId} to get an individual organisation

PUT. Use /organisations{orgId} to update an organisation

DELETE. Use /organisations{OrgId} to delete an organisation

Initiate a dialogue

POST. Use /organisations/{OrgId}/bots/{botId}/dialogue to initiate a dialogue in a bot. AWS authorisation is required. See External events

Discovery URL

This API does not require authentication. Used by the People First native app to connect to a bot

GET. Use /organisations/{orgId}bots/{botId}/discover to obtain bot discovery information

{
  "data": {
    "active" : true,
    "name" : "Example organisation" 
}

Invites

Invites to organisations

Available to super admins and admins. Admins can only access their own organisations (list obtained via the whoami API)

GET. Use /organisations/{orgId}/invites to get all pending invites for an organisation

POST. Use /organisations/{orgId}/invites to create an invite

DELETE. Use /organisations/{orgId}/invites/{inviteId} to revoke an invite

Invites to super admins

Available to super admins only

GET. Use /invites to get all pending invites for super admins

POST. Use /invites to create an invite for a super admin

POST. Use /invites/{inviteId} to revoke a super admin invite

{
  "data": {
      "attributes" : {
         "admin" : true,
         "author" : true,
         "email" : "andy.mcford@mhr.co.uk"
      }
  }   
}
{
  "data": [
     {
       "attributes": {
       "admin": true,
        "author": true,
        "email": "andy.mcford@mhr.co.uk"
     },
     "links": {
        "self": "invitation url will be shown here"
     }
    }
  ]
}

Members

Available to super admins and admins. Admins can only access their own organisations (list obtained via the whoami API)

GET. Use /{orgId}/members to get all members of an organisation

GET. Use /{orgId}/members/{memberId} to get a member of an organisation

PUT. Use /{orgId}/members/{memberId} to update a member

DELETE. Use /{orgId}/members/{memberId} to remove a member

{
  "data": {
      "attributes" : {
         "admin" : true,
         "author" : true,
         "name" : "Alan Jones"
      }
  }   
}

Usage

Available to super admins and admins. Admins can only access their own organisations (list obtained via the whoami API)

POST. Use /usage to get statistics on the number of messages used each calendar month

The post consists of one or more UTC date ranges. The message is returned for each of the ranges

{
  "dateTimeRanges": [
    {
      "start": "2019-01-01T00:00:00.000Z",
      "end": "2019-02-01T00:00:00.000Z"
    }
  ]
}

Profile

GET. Use /profile to get a list of the organisations to which the user has access

Bots

Available to authors only. Authors can only access their own organisations (list obtained via the whoami API)

See Bot config for details of the body of the API

GET. Use /bots to get all bots

POST. Use /bots to create a bot

GET. Use /bots{botId} to get an individual bot

PUT. Use /bots{botId} to update a bot

DELETE. Use /bots{botId} to delete a bot

GET. Use /bots/search&q=criteria to search for bots with contents matching criteria

{
  "name": "hmactest",
  "address": "john.smith",
  "value": {
          "message":  "Hello John"
  }
}    

Projects

Available to authors only. Authors can only access their own organisations (list obtained via the whoami API)

GET. Use /projects to get all projects

POST. Use /projects to create a project

GET. Use /projects{id} to get an individual project

PUT. Use /projects{id} to update a project

DELETE. Use /projects{id} to delete a project

GET. Use /projects/{id}/dialogues to get all dialogues for a project

GET. Use /projects/{id}/language to get all language records for a project

{
  "data": {
    "name": "My new project"
  }
}

Dialogues

See the Dialogue Schema for the full definition of the json section of the body

Available to authors only. Authors can only access their own organisations (list obtained via the whoami API)

GET. Use /dialogues to get all dialogues

POST. Use /dialogues to create a dialogue

GET. Use /dialogues{dialogueId} to get an individual dialogue

PUT. Use /dialogues{dialogueId} to update a dialogue

DELETE. Use /dialogues{dialogueId} to delete a dialogue

{
  "data": {
    "projectId" : "ad325aee-4b69-4cef-ab8d-c1dcdb37ffca",
    "attributes": {
      "json": {
        "id": "Example Date Input",
        "trigger": {
          "type": "message",
          "values": [
            "example date input"
          ]
        },
        "nodes": [
          {
            "type": "datePrompt",
            "message": "When is your next birthday",
            "retryMessage": "That's not a valid date. Plese try again",
            "output": "birthday"
          },
          {
            "type": "message",
            "message": "OK. I've made a note of that. Next birthday on {birthday}"
          }
        ]
      }
    }
  }
}

Language

Available to authors only. Authors can only access their own organisations (list obtained via the whoami API)

GET. Use /language to get all language record

POST. Use /language to create a language record

GET. Use /language{id} to get an individual language record

PUT. Use /language{id} to update a language record

DELETE. Use /language{id} to delete a language record

{
  "data": {
    "projectId": "ad325aee-4b69-4cef-ab8d-c1dcdb37ffca",
    "attributes": {
      "systemTexts": {
        "notificationMessage": [
          "New message"
        ]
      },
      "texts": [
        {
          "name": "helloMessage",
          "values": [
            "hello",
            "hi"
          ]
        }
      ],
      "region": "en-GB"
    }
  }
}      

Search

Available to authors only. Authors can only access their own organisations (list obtained via the whoami API)

Dialogues and Projects

GET. Use /search/aggregated?q=criteria to get all dialogues and language records matching the criteria

Bots

GET. Use /search/bots?q=criteria to get all bots records matching the criteria

Schema

GET. Use /schema to get the json schema used to validate dialogues

Snippets

GET. Use /schema?context=dialogue to get a list of the node snippets

Discovery

This API does not require authentication. Used by the People First native app to connect to a bot. It contains information on the primary authentication providers and the values of any bot constants marked as public

GET. Use /organisations/{orgId}bots/{botId}/discover to obtain bot discovery information

As this end point is not authenticated, don’t publish any bot constants that contain sensitive information

Web hook

POST. Use /organisations/{OrgId}/bots/{botId}/dialogue to initiate a dialogue in a bot. AWS authorisation is required. See External events

Dialogue Schema

JSON Schema used to validate dialogue syntax

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "talksuite Dialogue Schema",
  "description": "This is a schema that defines the format for bot dialogue templates",
  "type": "object",
  "properties": {
    "trigger": {
      "oneOf": [
        {
          "$ref": "#/definitions/triggers/attachmentTrigger"
        },
        {
          "$ref": "#/definitions/triggers/customEventTrigger"
        },
        {
          "$ref": "#/definitions/triggers/dialogueTrigger"
        },
        {
          "$ref": "#/definitions/triggers/eventTrigger"
        },
        {
          "$ref": "#/definitions/triggers/intentTrigger"
        },
        {
          "$ref": "#/definitions/triggers/messageTrigger"
        },
        {
          "$ref": "#/definitions/triggers/patternTrigger"
        },
        {
          "$ref": "#/definitions/triggers/processTrigger"
        }
      ]
    },
    "nodes": {
      "type": "array",
      "minItems": 1,
      "items": {
        "anyOf": [
          {
            "$ref": "#/definitions/nodes/actionNode"
          },
          {
            "$ref": "#/definitions/nodes/attachmentPromptNode"
          },
          {
            "$ref": "#/definitions/nodes/buildObjectNode"
          },
          {
            "$ref": "#/definitions/nodes/cardCollectionNode"
          },
          {
            "$ref": "#/definitions/nodes/cardNode"
          },
          {
            "$ref": "#/definitions/nodes/choicePromptNode"
          },
          {
            "$ref": "#/definitions/nodes/confirmationPromptNode"
          },
          {
            "$ref": "#/definitions/nodes/customCardCollectionNode"
          },
          {
            "$ref": "#/definitions/nodes/customEventNode"
          },
          {
            "$ref": "#/definitions/nodes/datePromptNode"
          },
          {
            "$ref": "#/definitions/nodes/decisionNode"
          },
          {
            "$ref": "#/definitions/nodes/dialogueNode"
          },
          {
            "$ref": "#/definitions/nodes/downloadActionNode"
          },
          {
            "$ref": "#/definitions/nodes/eventNode"
          },
          {
            "$ref": "#/definitions/nodes/messageNode"
          },
          {
            "$ref": "#/definitions/nodes/nlpDatePromptNode"
          },
          {
            "$ref": "#/definitions/nodes/nlpEntitiesNode"
          },
          {
            "$ref": "#/definitions/nodes/operationNode"
          },
          {
            "$ref": "#/definitions/nodes/repeatDialogueNode"
          },
          {
            "$ref": "#/definitions/nodes/sequenceDialogueNode"
          },
          {
            "$ref": "#/definitions/nodes/simplePromptNode"
          }
        ]
      }
    },
    "model": {
      "$ref": "#/definitions/patterns/propertiesObject"
    },
    "entities": {
      "$ref": "#/definitions/patterns/propertiesObject"
    },
    "id": {
      "$ref": "#/definitions/dialogueId"
    },
    "priority": {
      "type": "string",
      "enum": [ "none", "interrupt", "background", "supersede" ]
    },
    "description": {
      "type": "string"
    }
  },
  "additionalProperties": false,
  "required": [
    "trigger",
    "nodes",
    "id"
  ],
  "definitions": {
    "cardContent": {
      "type": "object",
      "properties": {
        "url": {
          "type": "string"
        },
        "title": {
          "$ref": "#/definitions/outputShort"
        },
        "subtitle": {
          "$ref": "#/definitions/outputShort"
        },
        "text": {
          "$ref": "#/definitions/outputLong"
        },
        "image": {
          "$ref": "#/definitions/outputLong"
        },
        "isThumbnail": {
          "$ref": "#/definitions/typeBoolean"
        },
        "retryMessage": {
          "$ref": "#/definitions/outputShort"
        },
        "tapOptions": {
          "type": "object",
          "properties": {
            "displayName": {
              "$ref": "#/definitions/patterns/propertyReference"
            },
            "output": {
              "$ref": "#/definitions/patterns/propertyReference"
            },
            "value": {
              "$ref": "#/definitions/outputShort"
            }
          },
          "additionalProperties": false,
          "required": [
            "displayName",
            "output",
            "value"
          ]
        },
        "buttonOptions": {
          "type": "object",
          "properties": {
            "listName": {
              "$ref": "#/definitions/patterns/propertyReference"
            },
            "displayName": {
              "$ref": "#/definitions/patterns/propertyReference"
            },
            "output": {
              "$ref": "#/definitions/patterns/propertyReference"
            }
          },
          "additionalProperties": false,
          "required": [
            "listName"
          ]
        }
      },
      "anyOf": [
        { "required": [ "title" ] },
        { "required": [ "subtitle" ] },
        { "required": [ "text" ] },
        { "required": [ "image" ] },
        { "required": [ "tapOptions" ] },
        { "required": [ "buttonOptions" ] }
      ],
      "additionalProperties": false
    },
    "contextDataProperty": {
      "anyOf": [
        {
          "$ref": "#/definitions/patterns/propertyReference"
        },
        {
          "type": "string",
          "pattern": "^(?i)\\/*Bot\\/[a-zA-Z0-9]{1,128}$"
        },
        {
          "type": "string",
          "pattern": "^(?i)\\/*Dialogue\\/LastApiStatusCode$"
        },
        {
          "type": "string",
          "pattern": "^(?i)\\/*Dialogue\\/triggerUtterance"
        },
        {
          "type": "string",
          "pattern": "^(?i)\\/*Language\\/[a-zA-Z0-9_]{1,128}$"
        },
        {
          "type": "string",
          "pattern": "^(?i)\\/*User\\/[a-zA-Z0-9_]{1,128}$"
        }
      ]
    },
    "dialogueId": {
      "type": "string",
      "minLength": 1
    },
    "json": {
      "type": [ "object", "string" ]
    },
    "mimeTypes": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "minItems": 1,
      "uniqueItems": true
    },
    "outputShort": {
      "type": "string",
      "minLength": 1,
      "maxLength": 320
    },
    "outputLong": {
      "type": "string",
      "minLength": 1
    },
    "languageReference": {
      "type": "object",
      "properties": {
        "var": {
          "type": "string",
          "pattern": "^(?i)\\/*Language\\/[a-zA-Z0-9_]{1,128}$"
        }
      },
      "additionalProperties": false,
      "required": [
        "var"
      ]
    },
    "nodeCustomContent": {
      "type": "object",
      "properties": {
        "contentType": {
          "type": "string"
        },
        "content": {
          "$ref": "#/definitions/json"
        }
      },
      "additionalProperties": false,
      "required": [
        "contentType",
        "content"
      ]
    },
    "patterns": {
      "alphanumeric": {
        "type": "string",
        "pattern": "^[a-zA-Z0-9]{1,128}$"
      },
      "dialogueNodeInputPropertiesObject": {
        "type": "object",
        "patternProperties": {
          "^[a-zA-Z0-9]{1,128}$": {
            "$ref": "#/definitions/jsonLogic"
          }
        },
        "additionalProperties": false
      },
      "dialogueNodeOutputPropertiesObject": {
        "type": "object",
        "patternProperties": {
          "(?!(?i)\\/*(bot\\/+|dialogue\\/+|language\\/+|user\\/+))^.*$": {
            "$ref": "#/definitions/logicData"
          }
        },
        "additionalProperties": false
      },
      "propertyReference": {
        "type": "string",
        "pattern": "(?!(?i)\\/*(bot\\/+|dialogue\\/+|language\\/+|user\\/+))^.*$"
      },
      "propertyReferenceBlob": {
        "type": "string",
        "pattern": "^(?i)(\\/*conversation\\/+)?[^\\/]+$"
      },
      "propertiesObject": {
        "type": "object",
        "patternProperties": {
          "(?!(?i)\\/*(bot\\/+|dialogue\\/+|language\\/+|user\\/+))^.*$": {
            "$ref": "#/definitions/propertiesObjectValue"
          }
        },
        "additionalProperties": false
      }
    },
    "propertiesObjectValue": {
      "oneOf": [
        {
          "type": "string"
        },
        {
          "type": "array",
          "items": {
            "type": "string"
          },
          "minItems": 1
        }
      ]
    },
    "triggers": {
      "attachmentTrigger": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "attachment" ]
          },
          "contentTypes": {
            "$ref": "#/definitions/mimeTypes"
          },
          "output": {
            "$ref": "#/definitions/patterns/propertyReferenceBlob"
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "contentTypes",
          "output"
        ]
      },
      "customEventTrigger": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "customEvent" ]
          },
          "broadcast": {
            "type": "boolean"
          },
          "name": {
            "type": "string"
          },
          "output": {
            "$ref": "#/definitions/patterns/propertyReference"
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "name"
        ]
      },
      "dialogueTrigger": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "nestedDialogue" ]
          }
        },
        "additionalProperties": false,
        "required": [
          "type"
        ]
      },
      "eventTrigger": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "event" ]
          },
          "event": {
            "type": "string",
            "enum": [
              "conversationStart",
              "noTriggerMatch"
            ]
          },
          "output": {
            "$ref": "#/definitions/patterns/propertyReference"
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "event"
        ]
      },
      "intentTrigger": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "intent" ]
          },
          "intent": {
            "type": "string"
          },
          "context": {
            "type": "string",
            "enum": [ "earliest", "latest" ]
          },
          "output": {
            "$ref": "#/definitions/patterns/propertyReference"
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "intent"
        ]
      },
      "messageTrigger": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "message" ]
          },
          "values": {
            "type": "array",
            "items": {
              "anyOf": [
                {
                  "$ref": "#/definitions/outputShort"
                },
                {
                  "$ref": "#/definitions/languageReference"
                }
              ]
            }
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "values"
        ]
      },
      "patternTrigger": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "pattern" ]
          },
          "values": {
            "type": "array",
            "items": {
              "anyOf": [
                {
                  "$ref": "#/definitions/outputShort"
                },
                {
                  "$ref": "#/definitions/languageReference"
                }
              ]
            }
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "values"
        ]
      },
      "processTrigger": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "process" ]
          },
          "name": {
            "type": "string",
            "enum": [ "brief", "debrief" ]
          },
          "precedence": {
            "type": "string",
            "enum": [ "start", "urgent", "standard", "additional", "finish" ]
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "name",
          "precedence"
        ]
      }
    },
    "nodes": {
      "actionNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "action" ]
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "service": {
            "type": "object",
            "properties": {
              "url": {
                "type": "string"
              },
              "headers": {
                "type": "object",
                "patternProperties": {
                  ".+": {
                    "type": "string"
                  }
                },
                "additionalProperties": false
              },
              "method": {
                "type": "string",
                "enum": [ "GET", "PUT", "POST", "DELETE", "PATCH" ]
              },
              "body": {
                "$ref": "#/definitions/json"
              },
              "authorised": {
                "type": "boolean"
              }
            },
            "additionalProperties": false,
            "required": [
              "url",
              "method"
            ]
          },
          "outputs": {
            "type": "object",
            "properties": {
              "body": {
                "$ref": "#/definitions/patterns/propertiesObject"
              },
              "header": {
                "$ref": "#/definitions/patterns/propertiesObject"
              }
            },
            "additionalProperties": false
          },
          "nextNodeIndex": {
            "type": [ "number", "null" ]
          },
          "nextNode": {
            "type": [ "string", "null" ]
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "service"
        ]
      },
      "attachmentPromptNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "attachmentPrompt" ]
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "contentTypes": {
            "$ref": "#/definitions/mimeTypes"
          },
          "message": {
            "$ref": "#/definitions/outputShort"
          },
          "maxRetries": {
            "type": "number"
          },
          "continueOnFail": {
            "type": "boolean"
          },
          "retryMessage": {
            "$ref": "#/definitions/outputShort"
          },
          "customContent": {
            "$ref": "#/definitions/nodeCustomContent"
          },
          "output": {
            "$ref": "#/definitions/patterns/propertyReferenceBlob"
          },
          "nextNodeIndex": {
            "type": [ "number", "null" ]
          },
          "nextNode": {
            "type": [ "string", "null" ]
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "message",
          "retryMessage",
          "output"
        ]
      },
      "cardCollectionNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "cardCollection" ]
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "listName": {
            "$ref": "#/definitions/patterns/propertyReference"
          },
          "contentItem": {
            "$ref": "#/definitions/patterns/alphanumeric"
          },
          "content": {
            "$ref": "#/definitions/cardContent"
          },
          "messageText": {
            "$ref": "#/definitions/outputShort"
          },
          "finishMessage": {
            "$ref": "#/definitions/outputShort"
          },
          "output": {
            "$ref": "#/definitions/patterns/propertyReference"
          },
          "maxRetries": {
            "type": "number"
          },
          "continueOnFail": {
            "type": "boolean"
          },
          "nextNodeIndex": {
            "type": [ "number", "null" ]
          },
          "nextNode": {
            "type": [ "string", "null" ]
          },
          "pageSize": {
            "type": "number"
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "listName",
          "contentItem",
          "content"
        ]
      },
      "cardNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "card" ]
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "content": {
            "$ref": "#/definitions/cardContent"
          },
          "maxRetries": {
            "type": "number"
          },
          "continueOnFail": {
            "type": "boolean"
          },
          "nextNodeIndex": {
            "type": [ "number", "null" ]
          },
          "nextNode": {
            "type": [ "string", "null" ]
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "content"
        ]
      },
      "choicePromptNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "choicePrompt" ]
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "message": {
            "$ref": "#/definitions/outputShort"
          },
          "retryMessage": {
            "$ref": "#/definitions/outputShort"
          },
          "listName": {
            "$ref": "#/definitions/patterns/propertyReference"
          },
          "displayName": {
            "$ref": "#/definitions/patterns/propertyReference"
          },
          "output": {
            "$ref": "#/definitions/patterns/propertyReference"
          },
          "maxRetries": {
            "type": "number"
          },
          "continueOnFail": {
            "type": "boolean"
          },
          "nextNodeIndex": {
            "type": [ "number", "null" ]
          },
          "nextNode": {
            "type": [ "string", "null" ]
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "message",
          "retryMessage",
          "listName",
          "output"
        ]
      },
      "confirmationPromptNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "confirmationPrompt" ]
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "message": {
            "$ref": "#/definitions/outputShort"
          },
          "retryMessage": {
            "$ref": "#/definitions/outputShort"
          },
          "positiveMessage": {
            "$ref": "#/definitions/outputShort"
          },
          "negativeMessage": {
            "$ref": "#/definitions/outputShort"
          },
          "output": {
            "$ref": "#/definitions/patterns/propertyReference"
          },
          "maxRetries": {
            "type": "number"
          },
          "continueOnFail": {
            "type": "boolean"
          },
          "nextNodeIndex": {
            "type": [ "number", "null" ]
          },
          "nextNode": {
            "type": [ "string", "null" ]
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "message",
          "retryMessage",
          "output"
        ]
      },
      "customCardCollectionNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "customCardCollection" ]
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "listName": {
            "$ref": "#/definitions/patterns/propertyReference"
          },
          "contentItem": {
            "$ref": "#/definitions/patterns/alphanumeric"
          },
          "customContent": {
            "$ref": "#/definitions/nodeCustomContent"
          },
          "messageText": {
            "$ref": "#/definitions/outputShort"
          },
          "outputOperation": {
            "type": "object",
            "properties": {
              "operation": {
                "$ref": "#/definitions/jsonLogic"
              },
              "output": {
                "$ref": "#/definitions/patterns/propertyReference"
              }
            },
            "additionalProperties": false,
            "required": [
              "operation",
              "output"
            ]
          },
          "finishMessage": {
            "$ref": "#/definitions/outputShort"
          },
          "output": {
            "$ref": "#/definitions/patterns/propertyReference"
          },
          "selected": {
            "$ref": "#/definitions/patterns/propertyReference"
          },
          "nextNodeIndex": {
            "type": [ "number", "null" ]
          },
          "nextNode": {
            "type": [ "string", "null" ]
          },
          "pageSize": {
            "type": "number"
          },
          "validation": {
            "$ref": "#/definitions/jsonLogic"
          }
        },
        "additionalProperties": false,
        "OneOf": [
          { "required": [ "type", "listName", "contentItem", "customContent" ] },
          { "required": [ "type", "listName", "contentItem", "customContent", "finishMessage", "selected", "output" ] }
        ]
      },
      "customEventNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "customEvent" ]
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "name": {
            "type": "string"
          },
          "data": {
            "$ref": "#/definitions/logicData"
          },
          "nextNode": {
            "type": [ "string", "null" ]
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "name"
        ]
      },
      "datePromptNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "datePrompt", "dateTimePrompt" ]
          },
          "id": {
            "type": "string"
          },
          "context": {
            "type": "string",
            "enum": [ "earliest", "latest" ]
          },
          "description": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "message": {
            "$ref": "#/definitions/outputShort"
          },
          "retryMessage": {
            "$ref": "#/definitions/outputShort"
          },
          "customContent": {
            "$ref": "#/definitions/nodeCustomContent"
          },
          "output": {
            "$ref": "#/definitions/patterns/propertyReference"
          },
          "maxRetries": {
            "type": "number"
          },
          "continueOnFail": {
            "type": "boolean"
          },
          "nextNodeIndex": {
            "type": [ "number", "null" ]
          },
          "nextNode": {
            "type": [ "string", "null" ]
          },
          "validation": {
            "$ref": "#/definitions/jsonLogic"
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "message",
          "retryMessage",
          "output"
        ]
      },
      "decisionNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "decision" ]
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "rule": {
            "$ref": "#/definitions/jsonLogic"
          },
          "passNodeIndex": {
            "type": [ "number", "null" ]
          },
          "passNode": {
            "type": [ "string", "null" ]
          },
          "failNodeIndex": {
            "type": [ "number", "null" ]
          },
          "failNode": {
            "type": [ "string", "null" ]
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "rule"
        ]
      },
      "dialogueNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "dialogue" ]
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "dialogueId": {
            "$ref": "#/definitions/dialogueId"
          },
          "inputs": {
            "$ref": "#/definitions/patterns/dialogueNodeInputPropertiesObject"
          },
          "outputs": {
            "$ref": "#/definitions/patterns/dialogueNodeOutputPropertiesObject"
          },
          "nextNodeIndex": {
            "type": [ "number", "null" ]
          },
          "nextNode": {
            "type": [ "string", "null" ]
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "dialogueId"
        ]
      },
      "downloadActionNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "downloadAction" ]
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "service": {
            "type": "object",
            "properties": {
              "url": {
                "type": "string"
              },
              "headers": {
                "type": "object",
                "patternProperties": {
                  ".+": {
                    "type": "string"
                  }
                },
                "additionalProperties": false
              },
              "authorised": {
                "type": "boolean"
              }
            },
            "additionalProperties": false,
            "required": [
              "url"
            ]
          },
          "outputs": {
            "type": "object",
            "properties": {
              "content": {
                "$ref": "#/definitions/patterns/propertyReferenceBlob"
              },
              "header": {
                "$ref": "#/definitions/patterns/propertiesObject"
              }
            },
            "additionalProperties": false
          },
          "nextNodeIndex": {
            "type": [ "number", "null" ]
          },
          "nextNode": {
            "type": [ "string", "null" ]
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "service"
        ]
      },
      "nlpEntitiesNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "nlpEntities" ]
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "input": {
            "$ref": "#/definitions/patterns/propertyReference"
          },
          "requiredEntities": {
            "$ref": "#/definitions/patterns/propertyReference"
          },
          "outputs": {
            "type": "object",
            "properties": {
              "matchedEntities": {
                "$ref": "#/definitions/patterns/propertyReference"
              }
            },
            "additionalProperties": false,
            "required": [
              "matchedEntities"
            ]
          },
          "rules": {
            "type": "object",
            "properties": {
              "dateContext": {
                "type": "string",
                "enum": [ "earliest", "latest" ]
              },
              "likelyStartTime": {
                "type": [ "number" ]
              }
            },
            "additionalProperties": false,
            "required": [
              "dateContext"
            ]
          },
          "nextNodeIndex": {
            "type": [ "number", "null" ]
          },
          "nextNode": {
            "type": [ "string", "null" ]
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "input",
          "outputs",
          "requiredEntities"
        ]
      },
      "eventNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "event" ]
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "event": {
            "type": "string",
            "enum": [ "leaveConversation", "resetDialogue", "endDialogue", "resetAuthentication" ]
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "event"
        ]
      },
      "messageNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "message" ]
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "message": {
            "$ref": "#/definitions/outputShort"
          },
          "customContent": {
            "$ref": "#/definitions/nodeCustomContent"
          },
          "nextNodeIndex": {
            "type": [ "number", "null" ]
          },
          "nextNode": {
            "type": [ "string", "null" ]
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "message"
        ]
      },
      "nlpDatePromptNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "nlpDatePrompt", "nlpTimePrompt", "nlpDateTimePrompt" ]
          },
          "id": {
            "type": "string"
          },
          "context": {
            "type": "string",
            "enum": [ "earliest", "latest" ]
          },
          "description": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "message": {
            "$ref": "#/definitions/outputShort"
          },
          "retryMessage": {
            "$ref": "#/definitions/outputShort"
          },
          "customContent": {
            "$ref": "#/definitions/nodeCustomContent"
          },
          "output": {
            "$ref": "#/definitions/patterns/propertyReference"
          },
          "maxRetries": {
            "type": "number"
          },
          "continueOnFail": {
            "type": "boolean"
          },
          "nextNodeIndex": {
            "type": [ "number", "null" ]
          },
          "nextNode": {
            "type": [ "string", "null" ]
          },
          "validation": {
            "$ref": "#/definitions/jsonLogic"
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "message",
          "retryMessage",
          "output"
        ]
      },
      "buildObjectNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "buildObject" ]
          },
          "object": {
            "type": "object",
            "properties": {}
          },
          "output": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "nextNodeIndex": {
            "type": [ "number", "null" ]
          },
          "nextNode": {
            "type": [ "string", "null" ]
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "object",
          "output"
        ]
      },
      "operationNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "operation" ]
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "operation": {
            "$ref": "#/definitions/jsonLogic"
          },
          "output": {
            "$ref": "#/definitions/patterns/propertyReference"
          },
          "nextNodeIndex": {
            "type": [ "number", "null" ]
          },
          "nextNode": {
            "type": [ "string", "null" ]
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "operation",
          "output"
        ]
      },
      "repeatDialogueNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "repeatDialogue" ]
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "dialogueId": {
            "$ref": "#/definitions/dialogueId"
          },
          "inputs": {
            "$ref": "#/definitions/patterns/dialogueNodeInputPropertiesObject"
          },
          "maximumRepetitions": {
            "type": [ "number" ]
          },
          "nextNode": {
            "type": [ "string", "null" ]
          },
          "nextNodeIndex": {
            "type": [ "number", "null" ]
          },
          "outputs": {
            "$ref": "#/definitions/patterns/dialogueNodeOutputPropertiesObject"
          },
          "repeatUntil": {
            "$ref": "#/definitions/jsonLogic"
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "dialogueId",
          "inputs",
          "outputs",
          "repeatUntil"
        ]
      },
      "sequenceDialogueNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "sequenceDialogue" ]
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "dialogueId": {
            "$ref": "#/definitions/dialogueId"
          },
          "inputItem": {
            "$ref": "#/definitions/patterns/alphanumeric"
          },
          "inputs": {
            "$ref": "#/definitions/patterns/dialogueNodeInputPropertiesObject"
          },
          "listName": {
            "$ref": "#/definitions/patterns/propertyReference"
          },
          "nextNodeIndex": {
            "type": [ "number", "null" ]
          },
          "nextNode": {
            "type": [ "string", "null" ]
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "dialogueId",
          "inputItem",
          "listName"
        ]
      },
      "simplePromptNode": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [ "numberPrompt", "stringPrompt", "timePrompt" ]
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "skip": {
            "type": "boolean"
          },
          "maxRetries": {
            "type": "number"
          },
          "continueOnFail": {
            "type": "boolean"
          },
          "message": {
            "$ref": "#/definitions/outputShort"
          },
          "retryMessage": {
            "$ref": "#/definitions/outputShort"
          },
          "customContent": {
            "$ref": "#/definitions/nodeCustomContent"
          },
          "output": {
            "$ref": "#/definitions/patterns/propertyReference"
          },
          "nextNodeIndex": {
            "type": [ "number", "null" ]
          },
          "nextNode": {
            "type": [ "string", "null" ]
          },
          "validation": {
            "$ref": "#/definitions/jsonLogic"
          }
        },
        "additionalProperties": false,
        "required": [
          "type",
          "message",
          "retryMessage",
          "output"
        ]
      }
    },
    "logicData": {
      "type": "object",
      "properties": {
        "var": {
          "$ref": "#/definitions/contextDataProperty"
        }
      },
      "additionalProperties": false,
      "required": [
        "var"
      ]
    },
    "logicAnyUnary": {
      "oneOf": [
        {
          "type": "array",
          "maxItems": 1,
          "minItems": 1,
          "items": {
            "$ref": "#/definitions/jsonLogic"
          }
        },
        {
          "$ref": "#/definitions/jsonLogic"
        }
      ]
    },
    "logicAny": {
      "type": "array",
      "maxItems": 2,
      "minItems": 2,
      "items": {
        "$ref": "#/definitions/jsonLogic"
      }
    },
    "logicDate": {
      "type": "array",
      "maxItems": 2,
      "minItems": 2,
      "items": {
        "$ref": "#/definitions/typeDate"
      }
    },
    "logicDateTime": {
      "type": "array",
      "maxItems": 2,
      "minItems": 2,
      "items": {
        "$ref": "#/definitions/typeDateTime"
      }
    },
    "logicNumeric": {
      "type": "array",
      "maxItems": 2,
      "minItems": 2,
      "items": {
        "$ref": "#/definitions/typeNumeric"
      }
    },
    "logicSize": {
      "anyOf": [
        { "$ref": "#/definitions/logicDate" },
        { "$ref": "#/definitions/logicDateTime" },
        { "$ref": "#/definitions/logicNumeric" },
        { "$ref": "#/definitions/logicTime" }
      ]
    },
    "logicTime": {
      "type": "array",
      "maxItems": 2,
      "minItems": 2,
      "items": {
        "$ref": "#/definitions/typeTime"
      }
    },
    "jsonLogic": {
      "anyOf": [
        {
          "$ref": "#/definitions/typeAny"
        },
        {
          "$ref": "#/definitions/typeBoolean"
        },
        {
          "$ref": "#/definitions/typeComplex"
        },
        {
          "$ref": "#/definitions/typeDate"
        },
        {
          "$ref": "#/definitions/typeDateTime"
        },
        {
          "$ref": "#/definitions/typeList"
        },
        {
          "$ref": "#/definitions/typeNumeric"
        },
        {
          "$ref": "#/definitions/typeString"
        },
        {
          "$ref": "#/definitions/typeTime"
        },
        {
          "type": "null"
        }
      ]
    },
    "typeAny": {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "method": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeList"
                },
                {
                  "type": "string",
                  "enum": [ "getItem" ]
                },
                {
                  "type": "array",
                  "items": [
                    {
                      "$ref": "#/definitions/typeNumeric"
                    }
                  ],
                  "minItems": 1,
                  "maxItems": 1
                }
              ],
              "minItems": 3,
              "maxItems": 3
            }
          },
          "additionalProperties": false,
          "required": [
            "method"
          ]
        },
        {
          "type": "object",
          "properties": {
            "if": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/jsonLogic"
              },
              "minItems": 3
            }
          },
          "additionalProperties": false,
          "required": [
            "if"
          ]
        },
        {
          "type": "object",
          "properties": {
            "current": {
              "type": "string"
            }
          },
          "additionalProperties": false,
          "required": [
            "current"
          ]
        },
        {
          "$ref": "#/definitions/logicData"
        }
      ]
    },
    "typeBoolean": {
      "oneOf": [
        {
          "type": "boolean"
        },
        {
          "type": "object",
          "properties": {
            "method": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeString"
                },
                {
                  "type": "string",
                  "enum": [ "matchesPattern" ]
                },
                {
                  "type": "array",
                  "items": {
                    "$ref": "#/definitions/typeString"
                  },
                  "minItems": 1,
                  "maxItems": 1
                }
              ],
              "minItems": 3,
              "maxItems": 3
            }
          },
          "additionalProperties": false,
          "required": [
            "method"
          ]
        },
        {
          "type": "object",
          "properties": {
            "===": {
              "$ref": "#/definitions/logicAny"
            }
          },
          "additionalProperties": false,
          "required": [
            "==="
          ]
        },
        {
          "type": "object",
          "properties": {
            "==": {
              "$ref": "#/definitions/logicAny"
            }
          },
          "additionalProperties": false,
          "required": [
            "=="
          ]
        },
        {
          "type": "object",
          "properties": {
            "!==": {
              "$ref": "#/definitions/logicAny"
            }
          },
          "additionalProperties": false,
          "required": [
            "!=="
          ]
        },
        {
          "type": "object",
          "properties": {
            "!=": {
              "$ref": "#/definitions/logicAny"
            }
          },
          "additionalProperties": false,
          "required": [
            "!="
          ]
        },
        {
          "type": "object",
          "properties": {
            "and": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/jsonLogic"
              },
              "minItems": 2
            }
          },
          "additionalProperties": false,
          "required": [
            "and"
          ]
        },
        {
          "type": "object",
          "properties": {
            "or": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/jsonLogic"
              },
              "minItems": 2
            }
          },
          "additionalProperties": false,
          "required": [
            "or"
          ]
        },
        {
          "type": "object",
          "properties": {
            "!": {
              "$ref": "#/definitions/logicAnyUnary"
            }
          },
          "additionalProperties": false,
          "required": [
            "!"
          ]
        },
        {
          "type": "object",
          "properties": {
            "!!": {
              "$ref": "#/definitions/logicAnyUnary"
            }
          },
          "additionalProperties": false,
          "required": [
            "!!"
          ]
        },
        {
          "type": "object",
          "properties": {
            ">": {
              "$ref": "#/definitions/logicSize"
            }
          },
          "additionalProperties": false,
          "required": [
            ">"
          ]
        },
        {
          "type": "object",
          "properties": {
            ">=": {
              "$ref": "#/definitions/logicSize"
            }
          },
          "additionalProperties": false,
          "required": [
            ">="
          ]
        },
        {
          "type": "object",
          "properties": {
            "<": {
              "$ref": "#/definitions/logicSize"
            }
          },
          "additionalProperties": false,
          "required": [
            "<"
          ]
        },
        {
          "type": "object",
          "properties": {
            "<=": {
              "$ref": "#/definitions/logicSize"
            }
          },
          "additionalProperties": false,
          "required": [
            "<="
          ]
        },
        {
          "$ref": "#/definitions/typeAny"
        }
      ]
    },
    "typeComplex": {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "Date.difference": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeDate"
                },
                {
                  "$ref": "#/definitions/typeDate"
                }
              ],
              "minItems": 2,
              "maxItems": 2
            }
          },
          "additionalProperties": false,
          "required": [
            "Date.difference"
          ]
        },
        {
          "type": "object",
          "properties": {
            "DateTime.difference": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeDateTime"
                },
                {
                  "$ref": "#/definitions/typeDateTime"
                }
              ],
              "minItems": 2,
              "maxItems": 2
            }
          },
          "additionalProperties": false,
          "required": [
            "DateTime.difference"
          ]
        },
        {
          "$ref": "#/definitions/typeAny"
        }
      ]
    },
    "typeDate": {
      "oneOf": [
        {
          "type": "string"
        },
        {
          "type": "object",
          "properties": {
            "method": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeDate"
                },
                {
                  "type": "string",
                  "pattern": "^(add|subtract)(Days|Weeks|Months|Years)$"
                },
                {
                  "type": "array",
                  "items": {
                    "$ref": "#/definitions/typeNumeric"
                  },
                  "minItems": 1,
                  "maxItems": 1
                }
              ],
              "minItems": 3,
              "maxItems": 3
            }
          },
          "additionalProperties": false,
          "required": [
            "method"
          ]
        },
        {
          "type": "object",
          "properties": {
            "Date.currentDate": {
              "type": "array",
              "maxItems": 0
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "Date.fromUTC": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeString"
                },
                {
                  "$ref": "#/definitions/typeString"
                }
              ],
              "minItems": 2,
              "maxItems": 2
            }
          },
          "additionalProperties": false
        },
        {
          "$ref": "#/definitions/typeAny"
        }
      ]
    },
    "typeDateTime": {
      "oneOf": [
        {
          "type": "string"
        },
        {
          "type": "object",
          "properties": {
            "method": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeDateTime"
                },
                {
                  "type": "string",
                  "pattern": "^(add|subtract)(Seconds|Minutes|Hours|Days|Weeks|Months|Years)$"
                },
                {
                  "type": "array",
                  "items": {
                    "$ref": "#/definitions/typeNumeric"
                  },
                  "minItems": 1,
                  "maxItems": 1
                }
              ],
              "minItems": 3,
              "maxItems": 3
            }
          },
          "additionalProperties": false,
          "required": [
            "method"
          ]
        },
        {
          "type": "object",
          "properties": {
            "DateTime.currentDateTime": {
              "type": "array",
              "maxItems": 0
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "DateTime.fromUTC": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeString"
                },
                {
                  "$ref": "#/definitions/typeString"
                }
              ],
              "minItems": 2,
              "maxItems": 2
            }
          },
          "additionalProperties": false
        },
        {
          "$ref": "#/definitions/typeAny"
        }
      ]
    },
    "typeList": {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "method": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeList"
                },
                {
                  "type": "string",
                  "enum": [ "addItem" ]
                },
                {
                  "type": "array",
                  "items": {
                    "$ref": "#/definitions/jsonLogic"
                  },
                  "minItems": 1
                }
              ],
              "minItems": 3,
              "maxItems": 3
            }
          },
          "additionalProperties": false,
          "required": [
            "method"
          ]
        },
        {
          "type": "object",
          "properties": {
            "method": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeList"
                },
                {
                  "type": "string",
                  "enum": [ "filter" ]
                },
                {
                  "oneOf": [
                    {
                      "type": "array",
                      "items": [
                        {
                          "type": "string"
                        },
                        {
                          "$ref": "#/definitions/jsonLogic"
                        }
                      ],
                      "minItems": 2,
                      "maxItems": 2
                    },
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/definitions/jsonLogic"
                      },
                      "minItems": 1,
                      "maxItems": 1
                    }
                  ]
                }
              ],
              "minItems": 3,
              "maxItems": 3
            }
          },
          "additionalProperties": false,
          "required": [
            "method"
          ]
        },
        {
          "type": "object",
          "properties": {
            "method": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeString"
                },
                {
                  "type": "string",
                  "enum": [ "split" ]
                },
                {
                  "type": "array",
                  "items": [
                    {
                      "$ref": "#/definitions/typeString"
                    }
                  ],
                  "minItems": 1,
                  "maxItems": 1
                }
              ],
              "minItems": 3,
              "maxItems": 3
            }
          },
          "additionalProperties": false,
          "required": [
            "method"
          ]
        },
        {
          "type": "object",
          "properties": {
            "method": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeList"
                },
                {
                  "type": "string",
                  "enum": [ "sort" ]
                },
                {
                  "type": "array",
                  "items": {
                    "$ref": "#/definitions/jsonLogic"
                  }
                }
              ],
              "minItems": 3,
              "maxItems": 3
            }
          },
          "additionalProperties": false,
          "required": [
            "method"
          ]
        },
        {
          "type": "object",
          "properties": {
            "method": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeList"
                },
                {
                  "type": "string",
                  "enum": [ "removeItem" ]
                },
                {
                  "type": "array",
                  "items": [
                    {
                      "$ref": "#/definitions/typeNumeric"
                    }
                  ],
                  "minItems": 0,
                  "maxItems": 1
                }
              ],
              "minItems": 3,
              "maxItems": 3
            }
          },
          "additionalProperties": false,
          "required": [
            "method"
          ]
        },
        {
          "type": "object",
          "properties": {
            "method": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeList"
                },
                {
                  "type": "string",
                  "enum": [ "updateItem" ]
                },
                {
                  "type": "array",
                  "items": [
                    {
                      "$ref": "#/definitions/typeNumeric"
                    },
                    {
                      "$ref": "#/definitions/jsonLogic"
                    }
                  ],
                  "minItems": 2,
                  "maxItems": 2
                }
              ],
              "minItems": 3,
              "maxItems": 3
            }
          },
          "additionalProperties": false,
          "required": [
            "method"
          ]
        },
        {
          "$ref": "#/definitions/typeAny"
        }
      ]
    },
    "typeNumeric": {
      "oneOf": [
        {
          "type": "number"
        },
        {
          "type": "object",
          "patternProperties": {
            "^[\\+\\-]$": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeNumeric"
                }
              ],
              "minItems": 1
            }
          },
          "additionalProperties": false,
          "minProperties": 1,
          "maxProperties": 1
        },
        {
          "type": "object",
          "properties": {
            "*": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeNumeric"
                }
              ],
              "minItems": 2
            }
          },
          "additionalProperties": false,
          "required": [
            "*"
          ]
        },
        {
          "type": "object",
          "patternProperties": {
            "^[\\/\\%]$": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeNumeric"
                }
              ],
              "minItems": 2,
              "maxItems": 2
            }
          },
          "additionalProperties": false,
          "minProperties": 1,
          "maxProperties": 1
        },
        {
          "type": "object",
          "properties": {
            "method": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/logicData"
                },
                {
                  "type": "string",
                  "enum": [ "getCount" ]
                }
              ],
              "minItems": 2,
              "maxItems": 2
            }
          },
          "additionalProperties": false,
          "required": [
            "method"
          ]
        },
        {
          "type": "object",
          "properties": {
            "method": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeString"
                },
                {
                  "type": "string",
                  "enum": [ "indexOf" ]
                },
                {
                  "type": "array",
                  "items": [
                    {
                      "$ref": "#/definitions/typeString"
                    },
                    {
                      "$ref": "#/definitions/typeNumeric"
                    }
                  ],
                  "minItems": 1,
                  "maxItems": 2
                }
              ],
              "minItems": 3,
              "maxItems": 3
            }
          },
          "additionalProperties": false,
          "required": [
            "method"
          ]
        },
        {
          "type": "object",
          "properties": {
            "method": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeString"
                },
                {
                  "type": "string",
                  "enum": [ "length" ]
                }
              ],
              "minItems": 2,
              "maxItems": 2
            }
          },
          "additionalProperties": false,
          "required": [
            "method"
          ]
        },
        {
          "type": "object",
          "patternProperties": {
            "^Date[.]current(Year|Month|Day)$": {
              "type": "array",
              "maxItems": 0
            }
          },
          "additionalProperties": false,
          "minProperties": 1,
          "maxProperties": 1
        },
        {
          "type": "object",
          "properties": {
            "String.toLower": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeString"
                }
              ],
              "minItems": 1,
              "maxItems": 1
            }
          },
          "additionalProperties": false,
          "required": [
            "String.toLower"
          ]
        },
        {
          "type": "object",
          "properties": {
            "String.toUpper": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeString"
                }
              ],
              "minItems": 1,
              "maxItems": 1
            }
          },
          "additionalProperties": false,
          "required": [
            "String.toUpper"
          ]
        },
        {
          "type": "object",
          "properties": {
            "String.replace": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeString"
                }
              ],
              "minItems": 3,
              "maxItems": 4
            }
          },
          "additionalProperties": false,
          "required": [
            "String.replace"
          ]
        },
        {
          "type": "object",
          "properties": {
            "String.xmlToJson": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeString"
                }
              ],
              "minItems": 1,
              "maxItems": 1
            }
          },
          "additionalProperties": false,
          "required": [
            "String.xmlToJson"
          ]
        },
        {
          "type": "object",
          "properties": {
            "String.escapedXmlToJson": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeString"
                }
              ],
              "minItems": 1,
              "maxItems": 1
            }
          },
          "additionalProperties": false,
          "required": [
            "String.escapedXmlToJson"
          ]
        },
        {
          "type": "object",
          "properties": {
            "String.toTitleCase": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeString"
                }
              ],
              "minItems": 1,
              "maxItems": 1
            }
          },
          "additionalProperties": false,
          "required": [
            "String.toTitleCase"
          ]
        },
        {
          "type": "object",
          "properties": {
            "Date.totalDifference": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeDate"
                },
                {
                  "$ref": "#/definitions/typeDate"
                },
                {
                  "type": "string",
                  "enum": [ "Days", "Hours", "Minutes", "Seconds" ]
                }
              ],
              "minItems": 3,
              "maxItems": 3
            }
          },
          "additionalProperties": false,
          "required": [
            "Date.totalDifference"
          ]
        },
        {
          "type": "object",
          "properties": {
            "DateTime.totalDifference": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeDateTime"
                },
                {
                  "$ref": "#/definitions/typeDateTime"
                },
                {
                  "type": "string",
                  "enum": [ "Days", "Hours", "Minutes", "Seconds" ]
                }
              ],
              "minItems": 3,
              "maxItems": 3
            }
          },
          "additionalProperties": false,
          "required": [
            "DateTime.totalDifference"
          ]
        },
        {
          "type": "object",
          "patternProperties": {
            "^Time[.]current(Hour|Minute|Second)$": {
              "type": "array",
              "maxItems": 0
            }
          },
          "additionalProperties": false,
          "minProperties": 1,
          "maxProperties": 1
        },
        {
          "type": "object",
          "properties": {
            "method": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeNumeric"
                },
                {
                  "type": "string",
                  "enum": [ "round" ]
                },
                {
                  "type": "array",
                  "items": {
                    "$ref": "#/definitions/typeNumeric"
                  },
                  "minItems": 1,
                  "maxItems": 1
                }
              ],
              "minItems": 3,
              "maxItems": 3
            }
          },
          "additionalProperties": false,
          "required": [
            "method"
          ]
        },
        {
          "type": "object",
          "properties": {
            "method": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeNumeric"
                },
                {
                  "type": "string",
                  "enum": [ "toInteger" ]
                }
              ],
              "minItems": 2,
              "maxItems": 2
            }
          },
          "additionalProperties": false,
          "required": [
            "method"
          ]
        },
        {
          "type": "object",
          "properties": {
            "method": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeNumeric"
                },
                {
                  "type": "string",
                  "enum": [ "toFloat" ]
                }
              ],
              "minItems": 2,
              "maxItems": 2
            }
          },
          "additionalProperties": false,
          "required": [
            "method"
          ]
        },
        {
          "$ref": "#/definitions/typeAny"
        }
      ]
    },
    "typeString": {
      "oneOf": [
        {
          "type": "string"
        },
        {
          "type": "object",
          "properties": {
            "cat": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/jsonLogic"
              },
              "minItems": 2
            }
          },
          "additionalProperties": false,
          "required": [
            "cat"
          ]
        },
        {
          "type": "object",
          "properties": {
            "substr": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeString"
                },
                {
                  "$ref": "#/definitions/typeNumeric"
                }
              ],
              "minItems": 2,
              "maxItems": 3
            }
          },
          "additionalProperties": false,
          "required": [
            "substr"
          ]
        },
        {
          "type": "object",
          "properties": {
            "Date.format": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeDate"
                },
                {
                  "type": "string",
                  "pattern": "^[yMdotDfFgG\\s-/]+$"
                }
              ],
              "minItems": 2,
              "maxItems": 2
            }
          },
          "additionalProperties": false,
          "required": [
            "Date.format"
          ]
        },
        {
          "type": "object",
          "properties": {
            "Number.format": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeNumeric"
                },
                {
                  "type": "string",
                  "pattern": "^[eEfFnNpP]$"
                },
                {
                  "$ref": "#/definitions/typeNumeric"
                }
              ],
              "minItems": 3,
              "maxItems": 3
            }
          },
          "additionalProperties": false,
          "required": [
            "Number.format"
          ]
        },

        {
          "type": "object",
          "properties": {
            "DateTime.toUTC": {
              "oneOf": [
                {
                  "type": "array",
                  "items": [
                    {
                      "$ref": "#/definitions/typeDate"
                    },
                    {
                      "$ref": "#/definitions/typeTime"
                    },
                    {
                      "$ref": "#/definitions/typeString"
                    }
                  ],
                  "minItems": 3,
                  "maxItems": 3
                },
                {
                  "type": "array",
                  "items": [
                    {
                      "$ref": "#/definitions/typeDateTime"
                    },
                    {
                      "$ref": "#/definitions/typeString"
                    }
                  ],
                  "minItems": 2,
                  "maxItems": 2
                }
              ]
            }
          },
          "additionalProperties": false,
          "required": [
            "DateTime.toUTC"
          ]
        },
        {
          "type": "object",
          "properties": {
            "DateTime.format": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeDateTime"
                },
                {
                  "type": "string",
                  "pattern": "^[yMdDfFgGotHhms\\s-/:]+$"
                }
              ],
              "minItems": 2,
              "maxItems": 2
            }
          },
          "additionalProperties": false,
          "required": [
            "DateTime.format"
          ]
        },
        {
          "$ref": "#/definitions/typeAny"
        }
      ]
    },
    "typeTime": {
      "oneOf": [
        {
          "type": "string"
        },
        {
          "type": "object",
          "properties": {
            "method": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeTime"
                },
                {
                  "type": "string",
                  "pattern": "^(add|subtract)(Seconds|Minutes|Hours)$"
                },
                {
                  "type": "array",
                  "items": {
                    "$ref": "#/definitions/typeNumeric"
                  },
                  "minItems": 1,
                  "maxItems": 1
                }
              ],
              "minItems": 3,
              "maxItems": 3
            }
          },
          "additionalProperties": false,
          "required": [
            "method"
          ]
        },
        {
          "type": "object",
          "properties": {
            "Time.currentTime": {
              "type": "array",
              "maxItems": 0
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "Time.fromUTC": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/typeString"
                },
                {
                  "$ref": "#/definitions/typeString"
                }
              ],
              "minItems": 2,
              "maxItems": 2
            }
          },
          "additionalProperties": false
        },
        {
          "$ref": "#/definitions/typeAny"
        }
      ]
    }
  }
}

Overtime Calculator

Hints and tips

Put the repeated steps in a Nested dialogue and use a Repeat dialogue node to call it repeatedly. Use a repeatUntil clause to control when the dialogue ends. This should be when the user opts to submit the claim

Get the rate of pay in the calling dialogue and pass it in to the nestedDialogue

In order to work out whether it is a Saturday or Sunday, use Date formatting to extract the day of the week from the date

Whenever you are writing a dialogue with a loop, make sure you have a dialogue that can cancel the current dialogue, so you don’t get stuck in an infinite loop. See the End Dialogue snippet in the Event node

Make sure you intialise the cumulative pay variable and the variable controlling the end of the loop in the model section of the calling dialogue

Use a Confirmation prompt to ask the user to add another item or submit the claim

Prompts will not execute if the output variable is already set, so you need to store the output of the prompt in a temporary variable, then passing it to the variable used to control the loop

Click on Overtime or Overtime Day to see the solution        
{
  "id": "Overtime",
  "trigger": {
    "type": "message",
    "values": [
      "claim overtime"
    ]
  },
  "nodes": [
    {
      "type": "message",
      "message": "OK. lets get your overtime for this month submitted"
    },
    {
      "type": "numberPrompt",
      "message": "Please enter your standard hourly rate",
      "retryMessage": "That's not a valid number",
      "output": "rate"
    },
    {
      "description": "Call the Overtime Day dialogue (which captures the overtime for each day and calculates the pay) until the user asks to submit the claim",
      "type": "repeatDialogue",
      "dialogueId": "Overtime Day",
      "inputs": {
        "pay": {
          "var": "pay"
        },
        "addAnother": {
          "var": "addAnother"
        },
        "rate": {
          "var": "rate"
        }
      },
      "outputs": {
        "pay": {
          "var": "pay"
        },
        "addAnother": {
          "var": "addAnother"
        },
        "rate": {
          "var": "rate"
        }
      },
      "repeatUntil": {
        "===": [
          {
            "var": "addAnother"
          },
          false
        ]
      }
    },
    {
      "description": "The variable pay is the total pay for all days in the submission",
      "type": "message",
      "message": "I've submitted your claim for £{pay}"
    }
  ],
  "model": {
    "pay": "0",
    "addAnother": "true"
  }
}      
{
  "id": "Overtime Day",
  "description": "Collect hours worked for each day in the month, calculates the day's pay and keeps a running total. The variables pay, addanother are rate passed from the calling dialogue and are passed back and passed back in again for each iteration",
  "trigger": {
    "type": "nestedDialogue"
  },
  "nodes": [
    {
      "type": "datePrompt",
      "message": "Enter overtime date",
      "retryMessage": "That's not a valid date",
      "output": "date"
    },
    {
      "type": "numberPrompt",
      "message": "Enter the number of hours worked on {date}",
      "retryMessage": "That's not a valid date",
      "output": "hours"
    },
    {
      "description": "Find the day of the week from the date",
      "type": "operation",
      "operation": {
        "Date.format": [
          {
            "var": "date"
          },
          "dddd"
        ]
      },
      "output": "dayOfWeek"
    },
    {
      "description": "Calculate the overtime rate for the day: 1.5 for a Saturday, 2 for a Sunday and 1 for any other day",
      "type": "operation",
      "output": "overtimeRate",
      "operation": {
        "if": [
          {
            "===": [
              {
                "var": "dayOfWeek"
              },
              "Saturday"
            ]
          },
          1.5,
          {
            "===": [
              {
                "var": "dayOfWeek"
              },
              "Sunday"
            ]
          },
          2,
          1
        ]
      }
    },
    {
      "description": "calculate the days overtime pay as overtime rate x hours",
      "type": "operation",
      "output": "dayPayHours",
      "operation": {
        "*": [
          {
            "var": "overtimeRate"
          },
          {
            "var": "hours"
          }
        ]
      }
    },
    {
      "description": "Keep a running total of the total months overtime",
      "type": "operation",
      "output": "dayPay",
      "operation": {
        "*": [
          {
            "var": "dayPayHours"
          },
          {
            "var": "rate"
          }
        ]
      }
    },
    {
      "type": "operation",
      "output": "pay",
      "operation": {
        "+": [
          {
            "var": "pay"
          },
          {
            "var": "dayPay"
          }
        ]
      }
    },
    {
      "type": "message",
      "message": "Added overtime of {hours} at a rate of {overTimeRate} x £{rate} per hour for {date}, totalling £{dayPay}"
    },
    {
      "description": "Show a card with Add Another and Submit buttons. Prompts are skipped if the output variable is set. As the addAnother variable is passed in, we need to use a temporary output variable",
      "type": "confirmationPrompt",
      "message": "Do you want to  add another day's overtime, or submit your claim?",
      "retryMessage": "Please Add Aother or Submit",
      "positiveMessage": "Add Another",
      "negativeMessage": "Submit",
      "output": "addAnotherResponse"
    },
    {
      "description": "Assign the temporary variable to the addAnother variable which controls the loop. When set to false (when the user selects submit), this will end the loop",
      "type": "operation",
      "operation": {
        "var": "addAnotherResponse"
      },
      "output": "addAnother"
    }
  ]
}      

Photo Album

Hints and tips

Use an attachment prompt to upload the photos

Store the photos in a list of objects. Each object should have a caption and url for the photo from the attachment prompt. Also store the index of the list as a property in the list. This can be used to delete a photo

Use a card collection node to display the list of cards and captions. You don’t need any buttons.

To delete a photo, filter the album by caption. Check that you have matched only one photo. Delete the photo from the list using the index property of the list

Click on Add Photo, Photo Album or Remove Photo to see the solution        
{
  "id": "Add Photo",
  "trigger": {
    "type": "message",
    "values": [
      "Add Photo"
    ]
  },
  "nodes": [
    {
      "type": "attachmentPrompt",
      "message": "Upload a photo",
      "retryMessage": "Invalid file type - please supply a jpg, jpeg, png or gif file type",
      "contentTypes": [
        "image/jpeg",
        "image/png",
        "image/gif"
      ],
      "output": "image"
    },
    {
      "type": "stringPrompt",
      "message": "Enter a caption for your photo",
      "retryMessage": "?",
      "output": "caption"
    },
    {
      "type": "operation",
      "operation": {
        "var": "image/Url"
      },
      "output": "item/Url"
    },
    {
      "type": "operation",
      "operation": {
        "var": "caption"
      },
      "output": "item/caption"
    },
    {
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "conversation/album"
          },
          "getCount"
        ]
      },
      "output": "albumCount"
    },
    {
      "type": "operation",
      "output": "albumCount",
      "operation": {
        "if": [
          {
            ">": [
              {
                "var": "albumCount"
              },
              0
            ]
          },
          {
            "var": "albumCount"
          },
          0
        ]
      }
    },
    {
      "type": "operation",
      "operation": {
        "var": "albumCount"
      },
      "output": "item/index"
    },
    {
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "conversation/album"
          },
          "addItem",
          [
            {
              "var": "item"
            }
          ]
        ]
      },
      "output": "conversation/album"
    },
    {
      "type": "message",
      "message": "I've added this to your album"
    }
  ]
}      
{
  "id": "Photo album",
  "trigger": {
    "type": "message",
    "values": [
      "photo album"
    ]
  },
  "nodes": [
    {
      "type": "message",
      "message": "hello"
    },
    {
      "type": "cardCollection",
      "listName": "conversation/album",
      "contentItem": "item",
      "content": {
        "title": "{item/caption}",
        "image": "{item/Url}"
      }
    }
  ]
}
{
  "id": "Remove photo",
  "trigger": {
    "type": "message",
    "values": [
      "Remove photo"
    ]
  },
  "nodes": [
    {
      "type": "stringPrompt",
      "message": "Enter the caption of the photo you want to remove",
      "retryMessage": "?",
      "output": "caption"
    },
    {
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "conversation/album"
          },
          "filter",
          [
            {
              "===": [
                {
                  "current": "caption"
                },
                {
                  "var": "caption"
                }
              ]
            }
          ]
        ]
      },
      "output": "selectedPhoto"
    },
    {
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "selectedPhoto"
          },
          "getCount"
        ]
      },
      "output": "numberSelected"
    },
    {
      "type": "decision",
      "rule": {
        "===": [
          {
            "var": "numberSelected"
          },
          1
        ]
      },
      "passNode": "remove"
    },
    {
      "type": "message",
      "message": "I'm sorry, I can't find a photo with caption {caption}",
      "nextNode": "dialogue.stop"
    },
    {
      "id": "remove",
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "conversation/album"
          },
          "removeItem",
          [
            {
              "var": "selectedPhoto/0/index"
            }
          ]
        ]
      },
      "output": "conversation/album"
    },
    {
      "type": "message",
      "message": "I've removed  {selectedPhoto/0/caption} for you"
    }
  ]
}    

Flight Calculator

Hints and tips

Create a list of airports and use a choice prompt to allow the user to select the departure and arrival locations

Use a date-time prompt to capture the departure time and a time prompt to capture the duration of the flight

You need to convert the departure time to UTC using the IANA location for the departure location (America/Los_Angeles, America/New_York, Europe/London and Europe/Paris)

Calculate the destination time by working out the number of minutes in the flight duration (extract the hours and minutes using date formatting) and using the “addMinutes” function to add the duration to the departure time

Convert the UTC arrival time to local time using the IANA location of the arrival location

Click on Flight Calculator to see the solution        
{
  "id": "Flight calculator",
  "trigger": {
    "type": "message",
    "values": [
      "Flight calculator"
    ]
  },
  "nodes": [
    {
      "type": "choicePrompt",
      "message": "Where is  you flight leaving from?",
      "retryMessage": "Please select from the list",
      "listName": "Airports",
      "output": "departureLocation"
    },
    {
      "type": "dateTimePrompt",
      "message": "Enter the departure date and time",
      "retryMessage": "That's not a valid date/time",
      "output": "departureTime"
    },
    {
      "type": "choicePrompt",
      "message": "Where is  you flight going to?",
      "retryMessage": "Please select from the list",
      "listName": "Airports",
      "output": "arrivalLocation"
    },
    {
      "type": "timePrompt",
      "message": "How long is the flight?",
      "retryMessage": "That's not a valid time",
      "output": "flightTime"
    },
    {
      "type": "operation",
      "output": "departureTimeZone",
      "operation": {
        "if": [
          {
            "===": [
              {
                "var": "departureLocation"
              },
              "Los Angeles"
            ]
          },
          "America/Los Angeles",
          {
            "===": [
              {
                "var": "departureLocation"
              },
              "New York"
            ]
          },
          "America/New_York",
          {
            "===": [
              {
                "var": "departureLocation"
              },
              "London"
            ]
          },
          "Europe/London",
          {
            "===": [
              {
                "var": "departureLocation"
              },
              "Paris"
            ]
          },
          "Europe/Paris",
          "Europe/London"
        ]
      }
    },
    {
      "type": "operation",
      "output": "arrivalTimeZone",
      "operation": {
        "if": [
          {
            "===": [
              {
                "var": "arrivalLocation"
              },
              "Los Angeles"
            ]
          },
          "America/Los Angeles",
          {
            "===": [
              {
                "var": "arrivalLocation"
              },
              "New York"
            ]
          },
          "America/New_York",
          {
            "===": [
              {
                "var": "arrivalLocation"
              },
              "London"
            ]
          },
          "Europe/London",
          {
            "===": [
              {
                "var": "arrivalLocation"
              },
              "Paris"
            ]
          },
          "Europe/Paris",
          "Europe/London"
        ]
      }
    },
    {
      "type": "operation",
      "operation": {
        "DateTime.toUTC": [
          {
            "var": "departureTime"
          },
          {
            "var": "departureTimeZone"
          }
        ]
      },
      "output": "departureUTC"
    },
    {
      "type": "operation",
      "operation": {
        "DateTime.format": [
          {
            "var": "flightTime"
          },
          "HH"
        ]
      },
      "output": "hours"
    },
    {
      "type": "operation",
      "operation": {
        "DateTime.format": [
          {
            "var": "flightTime"
          },
          "mm"
        ]
      },
      "output": "minutes"
    },
    {
      "type": "operation",
      "output": "durationInMinutes",
      "operation": {
        "+": [
          {
            "var": "minutes"
          },
          {
            "*": [
              {
                "var": "hours"
              },
              60
            ]
          }
        ]
      }
    },
    {
      "type": "operation",
      "operation": {
        "method": [
          {
            "var": "departureUTC"
          },
          "addMinutes",
          [
            {
              "var": "durationInMinutes"
            }
          ]
        ]
      },
      "output": "arrivalUTC"
    },
    {
      "type": "operation",
      "operation": {
        "DateTime.fromUTC": [
          {
            "var": "arrivalUTC"
          },
          {
            "var": "arrivalTimeZone"
          }
        ]
      },
      "output": "localArrivalTime"
    },
    {
      "type": "operation",
      "operation": {
        "DateTime.format": [
          {
            "var": "localArrivalTime"
          },
          "dddd do MMMM yyyy HH:mm:ss"
        ]
      },
      "output": "formattedArrivalTime"
    },
    {
      "type": "message",
      "message": "Your flight leaving from {departureLocation} at {departureTime} will arrive in {arrivalLocation} at {formattedArrivalTime} local time"
    }
  ],
  "model": {
    "Airports": [
      "Los Angeles",
      "New York",
      "London",
      "Paris"
    ]
  }
}

Flags of the world

Hints and tips

Use a custom event trigger of introduction to display the welcome message.

The continents menu should be in a nested dialogue, called from the intro dialogus. Display the continent buttons menu using a message node with custom content and hero card content that contains only buttons. The type of each button should be postBack to ensure that button can be re-clicked. The value of each button should by the continent name.

The each menu of countries should be in dialogue triggered by a pattern firing when any input in any case contains the continent nameshould be in a nested dialogue, called from the intro dialogus. Display the continent buttons menu using a message node with custom content and hero card content that contains only buttons. The value of each button should by the continent name

Create a dialoge for each country. The dialogue trigger should be a pattern trigger which is case insensitive and fires on any input containing the country name

Each card should be a message node with custom content and a hero card content type. Obtain an image address from the internet for each country.

Create a dialogue trigged on No Trigger Match which displays the not understood message and calls the continents menu nested dialogue

The solution only shows the Europe menu and the France card. Other content and country dialogues will be similar

Click on the items to the right to see the solution        
{
  "id": "intro",
  "trigger": {
    "type": "customEvent",
    "name": "introduction"
  },
  "nodes": [
    {
      "type": "message",
      "message": "Welcome to flags of the world!"
    },
    {
      "type": "dialogue",
      "dialogueId": "continent menu"
    }
  ]
}  
{
  "id": "continent menu",
  "trigger": {
    "type": "nestedDialogue"
  },
  "nodes": [
    {
      "type": "message",
      "message": "Please select a contintent"
    },
    {
      "type": "message",
      "message": "This channel is not suitable for this bot",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.hero",
        "content": {
          "buttons": [
            {
              "type": "postBack",
              "title": "Europe",
              "value": "Europe"
            },
            {
              "type": "postBack",
              "title": "The Americas",
              "value": "The Americas"
            },
            {
              "type": "postBack",
              "title": "Africa",
              "value": "Africa"
            },
            {
              "type": "postBack",
              "title": "Asia",
              "value": "Asia"
            }
          ]
        }
      }
    }
  ]
}
{
  "id": "europe menu",
  "trigger": {
    "type": "pattern",
    "values": [
      "(?i)europe"
    ]
  },
  "nodes": [
    {
      "type": "message",
      "message": "Please select a country"
    },
    {
      "type": "message",
      "message": "This channel is not suitable for this bot",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.hero",
        "content": {
          "buttons": [
            {
              "type": "postBack",
              "title": "France",
              "value": "France"
            },
            {
              "type": "postBack",
              "title": "UK",
              "value": "UK"
            },
            {
              "type": "postBack",
              "title": "Germany",
              "value": "Germany"
            },
            {
              "type": "postBack",
              "title": "Spain",
              "value": "Spain"
            }
          ]
        }
      }
    }
  ]
} 
{
  "id": "france",
  "trigger": {
    "type": "pattern",
    "values": [
      "(?i)france"
    ]
  },
  "nodes": [
    {
      "type": "message",
      "message": "Message displayed if the card is not available in the channel",
      "customContent": {
        "contentType": "application/vnd.microsoft.card.hero",
        "content": {
          "title": "France",
          "subtitle": "Capital : Paris",
          "text": "Population 67 million",
          "images": [
            {
              "url": "https://www.countries-ofthe-world.com/flags-normal/flag-of-France.png"
            }
          ]
        }
      }
    }
  ]
}        
{
  "id": "not understood",
  "trigger": {
    "type": "event",
    "event": "noTriggerMatch"
  },
  "nodes": [
    {
      "type": "message",
      "message": "I'm sorry I don't understand that"
    },
    {
      "type": "dialogue",
      "dialogueId": "continent menu"
    }
  ]
}