From 4bc138798d9d7d7784cdaafb8ef2b5e0dee0e1e6 Mon Sep 17 00:00:00 2001 From: Antonio Pintus Date: Fri, 16 Feb 2018 15:41:52 +0100 Subject: [PATCH] Add Wit.ai Bot sample along with HTTP calls --- examples/http-requests/sample-wit.http | 117 ++++++++++++++++ examples/sample-wit.js | 180 ++++++++++++++++++++++++ examples/sample-wit.ts | 185 +++++++++++++++++++++++++ package.json | 2 +- 4 files changed, 483 insertions(+), 1 deletion(-) create mode 100644 examples/http-requests/sample-wit.http create mode 100644 examples/sample-wit.js create mode 100644 examples/sample-wit.ts diff --git a/examples/http-requests/sample-wit.http b/examples/http-requests/sample-wit.http new file mode 100644 index 0000000..8a55759 --- /dev/null +++ b/examples/http-requests/sample-wit.http @@ -0,0 +1,117 @@ +// Examples of HTTP calls to Vivocha Bot API +// related to the Simple Wit.ai Bot +// implemented in file: examples/sample-wit + +// N.B. Replace the token property in the following +// requests body with the real token of your Wit.ai App. + + +// Send start event +POST http://localhost:8888/bot/message +Content-Type: application/json + +{ + "language": "en", + "event": "start", + "settings": { + "engine": { + "type": "WitAi", + "settings": { + "token": "" + } + } + }, + "context": { + } +} + +### +// Send name +POST http://localhost:8888/bot/message +Content-Type: application/json + +{ + "language": "en", + "event": "continue", + "message": { + "code": "message", + "type": "text", + "body": "Antonio Watson" + }, + "settings": { + "engine": { + "type": "WitAi", + "settings": { + "token": "" + } + } + }, + "context": { + "contexts": [ + "ask_for_name" + ] + } +} + +### +// Send "I prefer by phone" +POST http://localhost:8888/bot/message +Content-Type: application/json + +{ + "language": "en", + "event": "continue", + "message": { + "code": "message", + "type": "text", + "body": "meglio per telefono, grazie" + }, + "settings": { + "engine": { + "type": "WitAi", + "settings": { + "token": "" + } + } + }, + "data": { + "name": "Antonio Watson" + }, + "context": { + "contexts": [ + "recontact_by_email_or_phone" + ] + } +} + +### +// Send phone number +POST http://localhost:8888/bot/message +Content-Type: application/json + +{ + "language": "en", + "event": "continue", + "message": { + "code": "message", + "type": "text", + "body": "3391111111" + }, + "settings": { + "engine": { + "type": "WitAi", + "settings": { + "token": "" + } + } + }, + "data": { + "name": "Antonio Watson" + }, + "context": { + "contexts": [ + "recontact_by_email_or_phone", + "ask_for_phone" + ] + } +} \ No newline at end of file diff --git a/examples/sample-wit.js b/examples/sample-wit.js new file mode 100644 index 0000000..20d1ddb --- /dev/null +++ b/examples/sample-wit.js @@ -0,0 +1,180 @@ +"use strict"; +/** + * This is a sample bot built using Wit.ai NLP. + * In order to use this bot you need to create and train + * a Wit.ai app able to collect: names, phone numbers and email addresses + * and to understand users preferring to be contacted by email or phone. + * The Wit.ai App should be trained to detect the intents mapped in the following + * intents property in SimpleWitBot class. + * See comments in the code below. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const index_1 = require("../dist/index"); +const _ = require("lodash"); +// WitAiBot is an abstract class, so we need to: +// - set the intents property +// - implement the getStartMessage() method +class SimpleWitBot extends index_1.WitAiBot { + constructor() { + super(...arguments); + // intents mappings bound to specific Wit.ai Bot, the intent names MUST match + // the intents as defined in Wit.ai Console. + this.intents = { + provide_name: (data, request) => this.askEmailorPhone(data.entities, request), + by_email: (data, request) => this.contactMeByEmail(data, request), + by_phone: (data, request) => this.contactMeByPhone(data, request), + provide_phone: (data, request) => this.providePhoneNumber(data.entities, request), + provide_email: (data, request) => this.provideEmailAddress(data.entities, request), + unknown: (data, request) => this.unknown(data, request) + }; + } + // The following method is invoked only for a start event, sent by Vivocha at the very beginning + // of the conversation (in other words it wakes-up the bot) + async getStartMessage(request) { + const res = { + event: 'continue', + messages: [prepareBotMessage("Hello, I'm a Vivocha Wit Bot, what's your name?")], + data: request.data, + settings: request.settings, + //Please note how contexts are set (and checked in each intent handler) + context: _.merge(request.context, { contexts: ['ask_for_name'] }) + }; + return res; + } + // user provided her name, then the Wit.ai 'provide_name' was detected + async askEmailorPhone(entities, request) { + let name = ''; + if (entities.contact) { + name = entities.contact.map(v => v.value).join(' '); + } + const pre = `Thank you ${name}, `; + let response = { + event: 'continue', + messages: [prepareBotMessage(`${pre}do you prefer to be contacted by email or by phone?`)], + data: { name }, + contexts: ['recontact_by_email_or_phone'] + }; + return response; + } + ; + // user said she prefers to be contacted by email... + async contactMeByEmail(data, request) { + let response = {}; + if (this.inContext(request.context.contexts, ['recontact_by_email_or_phone'])) { + response = { + messages: [prepareBotMessage('Good, send me your email address, please')], + contexts: [...request.context.contexts, 'ask_for_email'], + event: 'continue', + data: request.data + }; + } + else { + response = { + messages: [prepareBotMessage('ERROR')], + contexts: ['end'], + data: request.data || {}, + event: 'end' + }; + } + return response; + } + // user chose to be contacted by phone + async contactMeByPhone(data, request) { + let response = {}; + if (this.inContext(request.context.contexts, ['recontact_by_email_or_phone'])) { + response = { + messages: [prepareBotMessage(`OK ${request.data.name}, text me your phone number, please`)], + contexts: [...request.context.contexts, 'ask_for_phone'], + event: 'continue', + data: request.data + }; + } + else { + response = { + messages: [prepareBotMessage('ERROR')], + contexts: ['end'], + data: request.data || {}, + event: 'end' + }; + } + return response; + } + // user sent her phone number and the correponding intent was detected by the Wit.ai NLP engine + async providePhoneNumber(data, request) { + let response = {}; + if (this.inContext(request.context.contexts, ['ask_for_phone', 'recontact_by_email_or_phone'])) { + response = { + messages: [prepareBotMessage(`Perfect, ${request.data.name}. We will call you as soon as possible. Thank you and bye! :)`)], + contexts: ['end'], + // collect phone number entity + data: { phone: data.phone_number[0].value }, + // Bot sends the 'end' event to communicate to Vivocha to close the contact, conversation finished. + event: 'end' + }; + } + else { + response = { + messages: [prepareBotMessage('ERROR')], + contexts: ['end'], + data: request.data || {}, + event: 'end' + }; + } + return response; + } + // user chose to be recontacted by email and provided the related address + async provideEmailAddress(data, request) { + let response = {}; + if (this.inContext(request.context.contexts, ['ask_for_email', 'recontact_by_email_or_phone'])) { + response = { + messages: [prepareBotMessage(`Perfect, ${request.data.name}. We will send soon an email to the provided address. Thank you and goodbye! :)`)], + contexts: ['end'], + // collect email address + data: { email: data.email[0].value }, + event: 'end' + }; + } + else { + response = { + messages: [prepareBotMessage('ERROR')], + contexts: ['end'], + data: request.data || {}, + event: 'end' + }; + } + return response; + } + // when Wit.ai is not able to detect an intent, WitAiBot class maps an 'unknown' intent, here's the handler + async unknown(data, request) { + let response = { + event: 'continue', + messages: [prepareBotMessage("Sorry, I didn't get that. Can you say it again?")], + contexts: request.context.contexts, + data: request.data || {} + }; + return response; + } +} +exports.SimpleWitBot = SimpleWitBot; +// just prepares a complete Vivocha TextMessage +function prepareBotMessage(body) { + return { + code: 'message', + type: 'text', + body + }; +} +// Create a BotManager and register the Wit.ai Bot Agent +const manager = new index_1.BotAgentManager(); +manager.registerAgent('WitAi', async (req) => { + const bot = new SimpleWitBot(req.settings.engine.settings.token); + return bot.sendMessage(req); +}); +const port = process.env.PORT ? parseInt(process.env.PORT || '') : 8888; +// run the Bot +manager.listen(port); +console.log(`SimpleWitBot listening at port: ${port}`); +process.on('SIGTERM', () => process.exit(0)); +process.on('SIGINT', () => process.exit(0)); +// RUN this with: node sample-wit +// If you need to change the default 8888 port, run: PORT= node sample-wit diff --git a/examples/sample-wit.ts b/examples/sample-wit.ts new file mode 100644 index 0000000..0dd2fb9 --- /dev/null +++ b/examples/sample-wit.ts @@ -0,0 +1,185 @@ +/** + * This is a sample bot built using Wit.ai NLP. + * In order to use this bot you need to create and train + * a Wit.ai app able to collect: names, phone numbers and email addresses + * and to understand users preferring to be contacted by email or phone. + * The Wit.ai App should be trained to detect the intents mapped in the following + * intents property in SimpleWitBot class. + * See comments in the code below. + */ + +import { BotRequest, BotResponse, TextMessage, BotAgentManager, WitAiBot, IntentsMap, NextMessage } from '../dist/index'; +import { Wit, log, MessageResponse } from 'node-wit'; +import * as _ from 'lodash'; + +// WitAiBot is an abstract class, so we need to: +// - set the intents property +// - implement the getStartMessage() method +export class SimpleWitBot extends WitAiBot { + // intents mappings bound to specific Wit.ai Bot, the intent names MUST match + // the intents as defined in Wit.ai Console. + protected intents: IntentsMap = { + provide_name: (data, request) => this.askEmailorPhone(data.entities, request), + by_email: (data, request) => this.contactMeByEmail(data, request), + by_phone: (data, request) => this.contactMeByPhone(data, request), + provide_phone: (data, request) => this.providePhoneNumber(data.entities, request), + provide_email: (data, request) => this.provideEmailAddress(data.entities, request), + unknown: (data, request) => this.unknown(data, request) + }; + + // The following method is invoked only for a start event, sent by Vivocha at the very beginning + // of the conversation (in other words it wakes-up the bot) + async getStartMessage(request: BotRequest): Promise { + const res: BotResponse = { + event: 'continue', + messages: [prepareBotMessage("Hello, I'm a Vivocha Wit Bot, what's your name?")], + data: request.data, + settings: request.settings, + //Please note how contexts are set (and checked in each intent handler) + context: _.merge(request.context, { contexts: ['ask_for_name'] }) + }; + return res; + } + + // user provided her name, then the Wit.ai 'provide_name' was detected + private async askEmailorPhone(entities, request): Promise { + let name: string = ''; + if (entities.contact) { + name = entities.contact.map(v => v.value).join(' '); + } + const pre = `Thank you ${name}, `; + let response: NextMessage = { + event: 'continue', + messages: [prepareBotMessage(`${pre}do you prefer to be contacted by email or by phone?`)], + data: { name }, + contexts: ['recontact_by_email_or_phone'] + }; + return response; + }; + + // user said she prefers to be contacted by email... + private async contactMeByEmail(data, request): Promise { + let response = {} as NextMessage; + if (this.inContext(request.context.contexts, ['recontact_by_email_or_phone'])) { + response = { + messages: [prepareBotMessage('Good, send me your email address, please')], + contexts: [...request.context.contexts, 'ask_for_email'], + event: 'continue', + data: request.data + } + } else { + response = { + messages: [prepareBotMessage('ERROR')], + contexts: ['end'], + data: request.data || {}, + event: 'end' + }; + } + return response; + } + + // user chose to be contacted by phone + private async contactMeByPhone(data, request): Promise { + let response = {} as NextMessage; + if (this.inContext(request.context.contexts, ['recontact_by_email_or_phone'])) { + response = { + messages: [prepareBotMessage(`OK ${request.data.name}, text me your phone number, please`)], + contexts: [...request.context.contexts, 'ask_for_phone'], + event: 'continue', + data: request.data + } + } else { + response = { + messages: [prepareBotMessage('ERROR')], + contexts: ['end'], + data: request.data || {}, + event: 'end' + }; + } + return response; + } + + // user sent her phone number and the correponding intent was detected by the Wit.ai NLP engine + private async providePhoneNumber(data, request): Promise { + let response = {} as NextMessage; + if (this.inContext(request.context.contexts, ['ask_for_phone', 'recontact_by_email_or_phone'])) { + response = { + messages: [prepareBotMessage(`Perfect, ${request.data.name}. We will call you as soon as possible. Thank you and bye! :)`)], + contexts: ['end'], + // collect phone number entity + data: { phone: data.phone_number[0].value }, + // Bot sends the 'end' event to communicate to Vivocha to close the contact, conversation finished. + event: 'end' + } + } else { + response = { + messages: [prepareBotMessage('ERROR')], + contexts: ['end'], + data: request.data || {}, + event: 'end' + }; + } + return response; + } + + // user chose to be recontacted by email and provided the related address + private async provideEmailAddress(data, request): Promise { + let response = {} as NextMessage; + if (this.inContext(request.context.contexts, ['ask_for_email', 'recontact_by_email_or_phone'])) { + response = { + messages: [prepareBotMessage(`Perfect, ${request.data.name}. We will send soon an email to the provided address. Thank you and goodbye! :)`)], + contexts: ['end'], + // collect email address + data: { email: data.email[0].value }, + event: 'end' + } + } else { + response = { + messages: [prepareBotMessage('ERROR')], + contexts: ['end'], + data: request.data || {}, + event: 'end' + }; + } + return response; + } + + // when Wit.ai is not able to detect an intent, WitAiBot class maps an 'unknown' intent, here's the handler + private async unknown(data, request): Promise { + let response: NextMessage = { + event: 'continue', + messages: [prepareBotMessage("Sorry, I didn't get that. Can you say it again?")], + contexts: request.context.contexts, + data: request.data || {} + }; + return response; + } +} + +// just prepares a complete Vivocha TextMessage +function prepareBotMessage(body: string): TextMessage { + return { + code: 'message', + type: 'text', + body + }; +} + +// Create a BotManager and register the Wit.ai Bot Agent +const manager: BotAgentManager = new BotAgentManager(); +manager.registerAgent('WitAi', async (req: BotRequest): Promise => { + const bot = new SimpleWitBot(req.settings.engine.settings.token); + return bot.sendMessage(req); +}); + +const port = process.env.PORT ? parseInt(process.env.PORT || '') : 8888; +// run the Bot +manager.listen(port); +console.log(`SimpleWitBot listening at port: ${port}`); + +process.on('SIGTERM', () => process.exit(0)); +process.on('SIGINT', () => process.exit(0)); + + +// RUN this with: node sample-wit +// If you need to change the default 8888 port, run: PORT= node sample-wit \ No newline at end of file diff --git a/package.json b/package.json index 1b4d589..6e54f66 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vivocha/bot-sdk", - "version": "2.1.5", + "version": "2.1.6", "description": "Typescript / JavaScript SDK to create Vivocha Bot Agents and Filters", "main": "dist/index.js", "typings": "dist/index.d.ts",