├── .eslintignore ├── .cfignore ├── .gitignore ├── public ├── favicon.ico ├── fonts │ ├── ibm-glyphs.eot │ ├── ibm-glyphs.ttf │ ├── ibm-glyphs.woff │ ├── ibm-icons.eot │ ├── ibm-icons.ttf │ ├── ibm-icons.woff │ ├── bold │ │ ├── h-n-bold.eot │ │ ├── h-n-bold.ttf │ │ ├── h-n-bold.woff │ │ └── h-n-bold.woff2 │ ├── light │ │ ├── h-n-light.eot │ │ ├── h-n-light.ttf │ │ ├── h-n-light.woff │ │ └── h-n-light.woff2 │ ├── roman │ │ ├── h-n-roman.eot │ │ ├── h-n-roman.ttf │ │ ├── h-n-roman.woff │ │ └── h-n-roman.woff2 │ ├── medium │ │ ├── h-n-medium.eot │ │ ├── h-n-medium.ttf │ │ ├── h-n-medium.woff │ │ └── h-n-medium.woff2 │ ├── bold-italic │ │ ├── h-n-bold-italic.eot │ │ ├── h-n-bold-italic.ttf │ │ ├── h-n-bold-italic.woff │ │ └── h-n-bold-italic.woff2 │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ ├── glyphicons-halflings-regular.woff2 │ ├── light-italic │ │ ├── h-n-light-italic.eot │ │ ├── h-n-light-italic.ttf │ │ ├── h-n-light-italic.woff │ │ └── h-n-light-italic.woff2 │ ├── medium-italic │ │ ├── h-n-medium-italic.eot │ │ ├── h-n-medium-italic.ttf │ │ ├── h-n-medium-italic.woff │ │ └── h-n-medium-italic.woff2 │ ├── roman-italic │ │ ├── h-n-roman-italic.eot │ │ ├── h-n-roman-italic.ttf │ │ ├── h-n-roman-italic.woff │ │ └── h-n-roman-italic.woff2 │ ├── ibm-glyph.svg │ └── ibm-icons.svg ├── img │ ├── Chat Button.png │ ├── Code Button.png │ └── marker_image.png ├── js │ ├── analytics.js │ ├── global.js │ ├── api.js │ ├── common.js │ ├── payload.js │ └── conversation.js ├── conversation.svg ├── index.html └── css │ └── app.css ├── readme_images ├── copy.png ├── rule.png ├── ce-demo.gif ├── examples.jpg ├── tone_context.jpg ├── WA_ImportSkill.png ├── WA_LaunchTool.png ├── WA_WorkspaceID.png ├── WA_CreateNewSkill.png └── WA_ImportSkillFinish.png ├── .eslintrc ├── server.js ├── manifest.yml ├── .env.example ├── .travis.yml ├── test ├── unit │ └── test.express.js └── test.webui.js ├── casper-runner.js ├── package.json ├── CONTRIBUTING.md ├── addons └── tone_detection.js ├── README.md ├── LICENSE ├── app.js └── training └── ce-workspace.json /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage -------------------------------------------------------------------------------- /.cfignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | coverage 4 | npm-debug.log -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | temp/ 4 | .idea/ 5 | coverage 6 | .env 7 | npm-debug.log -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /readme_images/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/readme_images/copy.png -------------------------------------------------------------------------------- /readme_images/rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/readme_images/rule.png -------------------------------------------------------------------------------- /readme_images/ce-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/readme_images/ce-demo.gif -------------------------------------------------------------------------------- /public/fonts/ibm-glyphs.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/ibm-glyphs.eot -------------------------------------------------------------------------------- /public/fonts/ibm-glyphs.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/ibm-glyphs.ttf -------------------------------------------------------------------------------- /public/fonts/ibm-glyphs.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/ibm-glyphs.woff -------------------------------------------------------------------------------- /public/fonts/ibm-icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/ibm-icons.eot -------------------------------------------------------------------------------- /public/fonts/ibm-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/ibm-icons.ttf -------------------------------------------------------------------------------- /public/fonts/ibm-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/ibm-icons.woff -------------------------------------------------------------------------------- /public/img/Chat Button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/img/Chat Button.png -------------------------------------------------------------------------------- /public/img/Code Button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/img/Code Button.png -------------------------------------------------------------------------------- /public/img/marker_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/img/marker_image.png -------------------------------------------------------------------------------- /readme_images/examples.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/readme_images/examples.jpg -------------------------------------------------------------------------------- /public/fonts/bold/h-n-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/bold/h-n-bold.eot -------------------------------------------------------------------------------- /public/fonts/bold/h-n-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/bold/h-n-bold.ttf -------------------------------------------------------------------------------- /readme_images/tone_context.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/readme_images/tone_context.jpg -------------------------------------------------------------------------------- /public/fonts/bold/h-n-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/bold/h-n-bold.woff -------------------------------------------------------------------------------- /public/fonts/bold/h-n-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/bold/h-n-bold.woff2 -------------------------------------------------------------------------------- /public/fonts/light/h-n-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/light/h-n-light.eot -------------------------------------------------------------------------------- /public/fonts/light/h-n-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/light/h-n-light.ttf -------------------------------------------------------------------------------- /public/fonts/light/h-n-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/light/h-n-light.woff -------------------------------------------------------------------------------- /public/fonts/roman/h-n-roman.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/roman/h-n-roman.eot -------------------------------------------------------------------------------- /public/fonts/roman/h-n-roman.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/roman/h-n-roman.ttf -------------------------------------------------------------------------------- /public/fonts/roman/h-n-roman.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/roman/h-n-roman.woff -------------------------------------------------------------------------------- /readme_images/WA_ImportSkill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/readme_images/WA_ImportSkill.png -------------------------------------------------------------------------------- /readme_images/WA_LaunchTool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/readme_images/WA_LaunchTool.png -------------------------------------------------------------------------------- /readme_images/WA_WorkspaceID.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/readme_images/WA_WorkspaceID.png -------------------------------------------------------------------------------- /public/fonts/light/h-n-light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/light/h-n-light.woff2 -------------------------------------------------------------------------------- /public/fonts/medium/h-n-medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/medium/h-n-medium.eot -------------------------------------------------------------------------------- /public/fonts/medium/h-n-medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/medium/h-n-medium.ttf -------------------------------------------------------------------------------- /public/fonts/medium/h-n-medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/medium/h-n-medium.woff -------------------------------------------------------------------------------- /public/fonts/roman/h-n-roman.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/roman/h-n-roman.woff2 -------------------------------------------------------------------------------- /readme_images/WA_CreateNewSkill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/readme_images/WA_CreateNewSkill.png -------------------------------------------------------------------------------- /public/fonts/medium/h-n-medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/medium/h-n-medium.woff2 -------------------------------------------------------------------------------- /readme_images/WA_ImportSkillFinish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/readme_images/WA_ImportSkillFinish.png -------------------------------------------------------------------------------- /public/fonts/bold-italic/h-n-bold-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/bold-italic/h-n-bold-italic.eot -------------------------------------------------------------------------------- /public/fonts/bold-italic/h-n-bold-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/bold-italic/h-n-bold-italic.ttf -------------------------------------------------------------------------------- /public/fonts/bold-italic/h-n-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/bold-italic/h-n-bold-italic.woff -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/fonts/bold-italic/h-n-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/bold-italic/h-n-bold-italic.woff2 -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /public/fonts/light-italic/h-n-light-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/light-italic/h-n-light-italic.eot -------------------------------------------------------------------------------- /public/fonts/light-italic/h-n-light-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/light-italic/h-n-light-italic.ttf -------------------------------------------------------------------------------- /public/fonts/light-italic/h-n-light-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/light-italic/h-n-light-italic.woff -------------------------------------------------------------------------------- /public/fonts/light-italic/h-n-light-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/light-italic/h-n-light-italic.woff2 -------------------------------------------------------------------------------- /public/fonts/medium-italic/h-n-medium-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/medium-italic/h-n-medium-italic.eot -------------------------------------------------------------------------------- /public/fonts/medium-italic/h-n-medium-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/medium-italic/h-n-medium-italic.ttf -------------------------------------------------------------------------------- /public/fonts/roman-italic/h-n-roman-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/roman-italic/h-n-roman-italic.eot -------------------------------------------------------------------------------- /public/fonts/roman-italic/h-n-roman-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/roman-italic/h-n-roman-italic.ttf -------------------------------------------------------------------------------- /public/fonts/roman-italic/h-n-roman-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/roman-italic/h-n-roman-italic.woff -------------------------------------------------------------------------------- /public/fonts/roman-italic/h-n-roman-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/roman-italic/h-n-roman-italic.woff2 -------------------------------------------------------------------------------- /public/fonts/medium-italic/h-n-medium-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/medium-italic/h-n-medium-italic.woff -------------------------------------------------------------------------------- /public/fonts/medium-italic/h-n-medium-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/customer-engagement-bot/HEAD/public/fonts/medium-italic/h-n-medium-italic.woff2 -------------------------------------------------------------------------------- /public/js/analytics.js: -------------------------------------------------------------------------------- 1 | function loadAnalytics() { 2 | var idaScript = document.createElement('script'); 3 | idaScript.src = '//www.ibm.com/common/stats/ida_stats.js'; 4 | document.head.appendChild(idaScript); 5 | } 6 | 7 | window.addEventListener('load', loadAnalytics); 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": 0, 4 | "func-names": 0, 5 | "vars-on-top": 0, 6 | "consistent-return": 0 7 | }, 8 | "globals": { 9 | "describe": true, 10 | "it": true, 11 | "casper": false 12 | }, 13 | "env": { 14 | "node": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /public/js/global.js: -------------------------------------------------------------------------------- 1 | /* global ConversationPanel: true, PayloadPanel: true*/ 2 | /* eslint no-unused-vars: "off" */ 3 | 4 | // Other JS files required to be loaded first: apis.js, conversation.js, payload.js 5 | (function() { 6 | // Initialize all modules 7 | ConversationPanel.init(); 8 | PayloadPanel.init(); 9 | })(); 10 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | require('dotenv').config({silent: true}); 5 | 6 | var server = require('./app'); 7 | var port = process.env.PORT || 3000; 8 | 9 | var startServer = server.listen(port, function() { 10 | console.log('Server running on port: %d', port); 11 | }); 12 | 13 | function close() { 14 | startServer.close(); 15 | } 16 | 17 | module.exports = { 18 | close: close 19 | }; 20 | -------------------------------------------------------------------------------- /manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | declared-services: 3 | watson-assistant-ce-tone: 4 | label: conversation 5 | plan: standard 6 | tone-analyzer-ce-tone: 7 | label: tone_analyzer 8 | plan: standard 9 | applications: 10 | - name: customer-engagement-bot.ng.bluemix.net 11 | command: npm start 12 | path: . 13 | instances: 1 14 | services: 15 | - watson-assistant-ce-tone 16 | - tone-analyzer-ce-tone 17 | memory: 256MB 18 | env: 19 | NPM_CONFIG_PRODUCTION: false 20 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Environment variables 2 | ASSISTANT_IAM_APIKEY= 3 | ASSISTANT_URL=https://gateway.watsonplatform.net/assistant/api 4 | WORKSPACE_ID= 5 | 6 | TONE_ANALYZER_IAM_APIKEY= 7 | TONE_ANALYZER_URL=https://gateway.watsonplatform.net/tone-analyzer/api 8 | 9 | #Optional cloudant URL for login 10 | #CLOUDANT_URL= 11 | #If cloudant url is specified a user name and password must be specified to secure the logging endpoints 12 | #LOG_USER= 13 | #LOG_PASS= 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: true 3 | cache: 4 | directories: 5 | - node_modules 6 | node_js: 8 7 | script: 8 | - npm run lint 9 | - npm run codecov 10 | env: 11 | global: 12 | - BX_APP=customer-engagement-bot 13 | - BX_API=https://api.ng.bluemix.net 14 | - BX_ORGANIZATION=WatsonPlatformServices 15 | - BX_SPACE=demos 16 | before_deploy: npm install -g bx-blue-green 17 | deploy: 18 | - provider: script 19 | skip_cleanup: true 20 | script: 21 | - bx-blue-green-travis 22 | on: 23 | branch: master 24 | repo: watson-developer-cloud/customer-engagement-bot 25 | - provider: script 26 | skip_cleanup: true 27 | script: npx semantic-release 28 | on: 29 | node: 8 30 | 31 | -------------------------------------------------------------------------------- /test/unit/test.express.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 IBM Corp. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | if (!process.env.CLOUDANT_URL) { 20 | console.log('Skipping unit test because CLOUDANT_URL is null'); 21 | return; 22 | } 23 | 24 | var app = require('../../app'); 25 | var bodyParser = require('body-parser'); 26 | var request = require('supertest'); 27 | 28 | app.use(bodyParser.json()); 29 | 30 | describe('Basic API tests', function() { 31 | it('GET to / should load the home page', function(done) { 32 | request(app).get('/').expect(200, done); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /casper-runner.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 IBM Corp. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var spawn = require('child_process').spawn; 20 | var fs = require('fs'); 21 | 22 | require('dotenv').config({silent: true}); 23 | 24 | if (!fs.existsSync('./.env')) { 25 | console.log('Skip integration tests since .env does not exist'); 26 | return; 27 | } 28 | 29 | var server = require('./server'); 30 | 31 | var runTests = function() { 32 | var casper = spawn('npm', [ 'run', 'test-casper' ]); 33 | casper.stdout.pipe(process.stdout); 34 | 35 | casper.on('error', function() { 36 | server.close(); 37 | }); 38 | 39 | casper.on('close', function() { 40 | server.close(); 41 | }); 42 | }; 43 | 44 | setTimeout(runTests, 10000); 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ibm-watson/customer-engagement-bot", 3 | "description": "A simple Node.js based web app which shows how to use the Assistant API to recognize user intents.", 4 | "version": "0.2.1", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "test-unit": "mocha ./test/unit --exit", 9 | "test-casper": "casperjs test ./test/test.webui.js", 10 | "test-casper-runner": "NODE_ENV=test node casper-runner.js", 11 | "test": "npm run lint && npm run test-unit && npm run test-casper-runner", 12 | "lint": "eslint .", 13 | "codecov": "npm run test && (codecov || true)", 14 | "autofix": "eslint --fix .", 15 | "validate": "npm ls" 16 | }, 17 | "license": "Apache-2.0", 18 | "dependencies": { 19 | "basic-auth-connect": "^1.0.0", 20 | "bluebird": "^3.5.1", 21 | "body-parser": "^1.18.3", 22 | "cloudant": "^1.10.0-NOTICE", 23 | "dotenv": "^6.0.0", 24 | "express": "^4.16.3", 25 | "nano": "^6.4.4", 26 | "uuid": "^3.3.2", 27 | "vcap_services": "^0.4.0", 28 | "watson-developer-cloud": "^3.7.0" 29 | }, 30 | "devDependencies": { 31 | "casperjs": "^1.1.4", 32 | "eslint": "^5.2.0", 33 | "mocha": "^5.2.0", 34 | "phantomjs-prebuilt": "^2.1.16", 35 | "supertest": "^3.1.0" 36 | }, 37 | "engine": { 38 | "node": ">= 8.4.2" 39 | }, 40 | "directories": { 41 | "test": "test" 42 | }, 43 | "repository": { 44 | "type": "git", 45 | "url": "git+https://github.com/watson-developer-cloud/customer-engagement-bot.git" 46 | }, 47 | "contributors": [ 48 | { 49 | "name": "Vibha Sinha", 50 | "email": "vibha.sinha@us.ibm.com" 51 | }, 52 | { 53 | "name": "April Webster", 54 | "email": "awebster@us.ibm.com" 55 | } 56 | ], 57 | "publishConfig": { 58 | "registry": "https://registry.npmjs.org/", 59 | "access": "public" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /public/conversation.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/js/api.js: -------------------------------------------------------------------------------- 1 | // The Api module is designed to handle all interactions with the server 2 | 3 | var Api = (function() { 4 | var requestPayload; 5 | var responsePayload; 6 | var messageEndpoint = '/api/message'; 7 | 8 | // Publicly accessible methods defined 9 | return { 10 | sendRequest: sendRequest, 11 | 12 | // The request/response getters/setters are defined here to prevent internal 13 | // methods 14 | // from calling the methods without any of the callbacks that are added 15 | // elsewhere. 16 | getRequestPayload: function() { 17 | return requestPayload; 18 | }, 19 | setRequestPayload: function(newPayloadStr) { 20 | requestPayload = JSON.parse(newPayloadStr); 21 | }, 22 | getResponsePayload: function() { 23 | return responsePayload; 24 | }, 25 | setResponsePayload: function(newPayloadStr) { 26 | responsePayload = JSON.parse(newPayloadStr); 27 | } 28 | }; 29 | 30 | // Send a message request to the server 31 | function sendRequest(text, context) { 32 | // Build request payload 33 | var payloadToWatson = {}; 34 | if (text) { 35 | payloadToWatson.input = { 36 | text: text 37 | }; 38 | } 39 | if (context) { 40 | payloadToWatson.context = context; 41 | } 42 | 43 | // Built http request 44 | var http = new XMLHttpRequest(); 45 | http.open('POST', messageEndpoint, true); 46 | http.setRequestHeader('Content-type', 'application/json'); 47 | http.onreadystatechange = function() { 48 | if (http.readyState === 4 && http.status === 200 && http.responseText) { 49 | Api.setResponsePayload(http.responseText); 50 | } 51 | }; 52 | 53 | var params = JSON.stringify(payloadToWatson); 54 | // Stored in variable (publicly visible through Api.getRequestPayload) 55 | // to be used throughout the application 56 | if (Object.getOwnPropertyNames(payloadToWatson).length !== 0) { 57 | Api.setRequestPayload(params); 58 | } 59 | 60 | // Send request 61 | http.send(params); 62 | } 63 | }()); 64 | -------------------------------------------------------------------------------- /public/js/common.js: -------------------------------------------------------------------------------- 1 | // The Common module is designed as an auxiliary module 2 | // to hold functions that are used in multiple other modules 3 | /* eslint no-unused-vars: "off" */ 4 | 5 | var Common = (function() { 6 | // Publicly accessible methods defined 7 | return { 8 | buildDomElement: buildDomElementFromJson, 9 | fireEvent: fireEvent, 10 | listForEach: listForEach 11 | }; 12 | 13 | function buildDomElementFromJson(domJson) { 14 | // Create a DOM element with the given tag name 15 | var element = document.createElement(domJson.tagName); 16 | 17 | // Fill the "content" of the element 18 | if (domJson.text) { 19 | element.innerHTML = domJson.text; 20 | } else if (domJson.html) { 21 | element.insertAdjacentHTML('beforeend', domJson.html); 22 | } 23 | 24 | // Add classes to the element 25 | if (domJson.classNames) { 26 | for (var i = 0; i < domJson.classNames.length; i++) { 27 | element.classList.add(domJson.classNames[i]); 28 | } 29 | } 30 | // Add attributes to the element 31 | if (domJson.attributes) { 32 | for (var j = 0; j < domJson.attributes.length; j++) { 33 | var currentAttribute = domJson.attributes[j]; 34 | element.setAttribute(currentAttribute.name, currentAttribute.value); 35 | } 36 | } 37 | // Add children elements to the element 38 | if (domJson.children) { 39 | for (var k = 0; k < domJson.children.length; k++) { 40 | var currentChild = domJson.children[k]; 41 | element.appendChild(buildDomElementFromJson(currentChild)); 42 | } 43 | } 44 | return element; 45 | } 46 | 47 | // Trigger an event to fire 48 | function fireEvent(element, event) { 49 | var evt; 50 | if (document.createEventObject) { 51 | // dispatch for IE 52 | evt = document.createEventObject(); 53 | return element.fireEvent('on' + event, evt); 54 | } 55 | // otherwise, dispatch for Firefox, Chrome + others 56 | evt = document.createEvent('HTMLEvents'); 57 | evt.initEvent(event, true, true); // event type,bubbling,cancelable 58 | return !element.dispatchEvent(evt); 59 | } 60 | 61 | // A function that runs a for each loop on a List, running the callback 62 | // function for each one 63 | function listForEach(list, callback) { 64 | for (var i = 0; i < list.length; i++) { 65 | callback.call(null, list[i]); 66 | } 67 | } 68 | }()); 69 | -------------------------------------------------------------------------------- /test/test.webui.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 IBM Corp. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | casper.options.waitTimeout = 360000; 20 | 21 | casper.start(); 22 | 23 | casper 24 | .thenOpen( 25 | 'http://localhost:3000', 26 | function (result) { 27 | this.echo(result.status); 28 | casper.test.assert(result.status === 200, 'Front page opens'); 29 | 30 | casper.then(function () { 31 | casper.waitForSelector('#scrollingChat > div:nth-child(1)', 32 | function () {}); 33 | }); 34 | 35 | // Assert - Initial Dialog message 36 | casper.then(function () { 37 | var text = this.evaluate(function () { 38 | return document.querySelector('p').textContent; 39 | }); 40 | 41 | casper.test.assertMatch(text, /^Hi, I see you purchased a new laptop two weeks ago*/i); 42 | 43 | casper.sendKeys('#textInput', 'Good'); 44 | this.sendKeys('#textInput', casper.page.event.key.Enter, { 45 | keepFocus: true 46 | }); 47 | }); 48 | 49 | // Process response 50 | casper.then(function () { 51 | casper.waitForSelector('#scrollingChat > div:nth-child(3)', 52 | function () {}); 53 | }); 54 | 55 | casper 56 | .then(function () { 57 | var text2 = this 58 | .evaluate(function () { 59 | return document 60 | .querySelector('#scrollingChat > div:nth-child(3) > div > div > p').textContent; 61 | }); 62 | 63 | casper.test.assertMatch(text2, /^.*Got it. Is anything not meeting your expectations.*/i); 64 | 65 | casper.sendKeys('#textInput', 'No'); 66 | this.sendKeys('#textInput', casper.page.event.key.Enter, { 67 | keepFocus: true 68 | }); 69 | }); 70 | 71 | // Process response 72 | casper.then(function () { 73 | casper.waitForSelector('#scrollingChat > div:nth-child(5)'); 74 | }); 75 | 76 | // Check for Response 77 | casper 78 | .then(function () { 79 | var text3 = this 80 | .evaluate(function () { 81 | return document 82 | .querySelector('#scrollingChat > div:nth-child(5) > div > div > p').textContent; 83 | }); 84 | 85 | casper.test.assertMatch(text3, /^Great to hear*/i); 86 | }); 87 | }, null, 6 * 60 * 1000); 88 | 89 | casper.run(function () { 90 | this.test.done(); 91 | }); -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Questions 2 | 3 | If you are having problems using the APIs or have a question about the IBM 4 | Watson Services, please ask a question on 5 | [dW Answers](https://developer.ibm.com/answers/questions/ask/?topics=watson) 6 | or [Stack Overflow](http://stackoverflow.com/questions/ask?tags=ibm-watson). 7 | 8 | # Code 9 | 10 | * Our style guide is based on [Google's](https://google.github.io/styleguide/jsguide.html), most of it is automaticaly enforced (and can be automatically applied with `npm run autofix`) 11 | * Commits should follow the [Angular commit message guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-guidelines). This is because our release tool uses this format for determining release versions and generating changelogs. To make this easier, we recommend using the [Commitizen CLI](https://github.com/commitizen/cz-cli) with the `cz-conventional-changelog` adapter. 12 | 13 | # Issues 14 | 15 | If you encounter an issue with the Node.js library, you are welcome to submit 16 | a [bug report](https://github.com/watson-developer-cloud/customer-engagement-bot/issues). 17 | Before that, please search for similar issues. It's possible somebody has 18 | already encountered this issue. 19 | 20 | # Pull Requests 21 | 22 | If you want to contribute to the repository, follow these steps: 23 | 24 | 1. Fork the repo. 25 | 2. Develop and test your code changes: `npm install -d && npm test`. 26 | 3. Travis-CI will run the tests for all services once your changes are merged. 27 | 4. Add a test for your changes. Only refactoring and documentation changes require no new tests. 28 | 5. Make the test pass. 29 | 6. Commit your changes. 30 | 7. Push to your fork and submit a pull request. 31 | 32 | # Developer's Certificate of Origin 1.1 33 | 34 | By making a contribution to this project, I certify that: 35 | 36 | (a) The contribution was created in whole or in part by me and I 37 | have the right to submit it under the open source license 38 | indicated in the file; or 39 | 40 | (b) The contribution is based upon previous work that, to the best 41 | of my knowledge, is covered under an appropriate open source 42 | license and I have the right under that license to submit that 43 | work with modifications, whether created in whole or in part 44 | by me, under the same open source license (unless I am 45 | permitted to submit under a different license), as indicated 46 | in the file; or 47 | 48 | (c) The contribution was provided directly to me by some other 49 | person who certified (a), (b) or (c) and I have not modified 50 | it. 51 | 52 | (d) I understand and agree that this project and the contribution 53 | are public and that a record of the contribution (including all 54 | personal information I submit with it, including my sign-off) is 55 | maintained indefinitely and may be redistributed consistent with 56 | this project or the open source license(s) involved. 57 | 58 | ## Tests 59 | 60 | Ideally, we'd like to see both unit and innervation tests on each method. 61 | (Unit tests do not actually connect to the Watson service, integration tests do.) 62 | 63 | Out of the box, `npm test` runs linting and unit tests, but skips the integration tests, 64 | because they require credentials. -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Customer Care Bot 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 15 | 16 | 17 |
19 | 21 |
22 |
23 |
25 |
26 | 30 |
31 | 37 |
38 | * This system is for demonstration purposes only and is not intended to process Personal Data. No Personal Data is to be entered 39 | into this system as it may not have the necessary controls in place to meet the requirements of the General Data Protection 40 | Regulation (EU) 2016/679. 41 |
42 |
43 | By using this application, you agree to the  44 | 46 | Terms of Use 47 | 48 |
49 |
50 |
51 |
52 |
Type something to see the 53 | output
54 |
55 |
56 |
57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /addons/tone_detection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 IBM Corp. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | 'use strict'; 18 | /* eslint-env es6 */ 19 | 20 | var Promise = require('bluebird'); 21 | 22 | /** 23 | * Public functions for this module 24 | */ 25 | module.exports = { 26 | updateUserTone: updateUserTone, 27 | invokeToneAsync: invokeToneAsync, 28 | initUser: initUser 29 | }; 30 | 31 | /** 32 | * invokeToneAsync is an asynchronous function that calls the Tone Analyzer 33 | * service and returns a Promise 34 | * 35 | * @param {Json} 36 | * conversationPayload json object returned by the Watson 37 | * Conversation Service 38 | * @param {Object} 39 | * toneAnalyzer an instance of the Watson Tone Analyzer service 40 | * @returns {Promise} a Promise for the result of calling the toneAnalyzer with 41 | * the conversationPayload (which contains the user's input text) 42 | */ 43 | function invokeToneAsync(conversationPayload, toneAnalyzer) { 44 | if (!conversationPayload.input || !conversationPayload.input.text || conversationPayload.input.text.trim() == '') 45 | conversationPayload.input.text = ''; 46 | return new Promise(function(resolve, reject) { 47 | toneAnalyzer.toneChat({ 48 | utterances: [ 49 | { text: conversationPayload.input.text, user: 'customer'} 50 | ]}, (error, data) => { 51 | if (error) { 52 | reject(error); 53 | } else { 54 | resolve(data); 55 | } 56 | }); 57 | }); 58 | } 59 | 60 | /** 61 | * updateUserTone processes the Tone Analyzer payload to pull out the meaningful 62 | * tones. The conversationPayload json object is updated to include these tones. 63 | * 64 | * @param {Json} 65 | * conversationPayload json object returned by the Watson 66 | * Conversation Service 67 | * @param {Json} 68 | * toneAnalyzerPayload json object returned by the Watson Tone 69 | * Analyzer Service 70 | * @returns {void} 71 | */ 72 | function updateUserTone(conversationPayload, toneAnalyzerPayload) { 73 | if (!conversationPayload.context) { 74 | conversationPayload.context = {}; 75 | } 76 | 77 | if (!conversationPayload.context.user) { 78 | conversationPayload.context.user = initUser(); 79 | } 80 | 81 | // For convenience sake, define a variable for the user object 82 | var user = conversationPayload.context.user; 83 | var detectedTones = toneAnalyzerPayload.utterances_tone[0].tones; 84 | var userTone = null; 85 | 86 | if (detectedTones.length == 0) { 87 | userTone = 'none'; 88 | } else { 89 | userTone = detectedTones[0].tone_id; 90 | } 91 | 92 | user.tone.emotion.current = userTone; 93 | conversationPayload.context.user = user; 94 | return conversationPayload; 95 | } 96 | 97 | /** 98 | * initToneContext initializes a user object containing tone data (from the 99 | * Watson Tone Analyzer) 100 | * 101 | * @returns {Json} user json object with the emotion, language and social tones. 102 | * The current tone identifies the tone for a specific conversation 103 | * turn, and the history provides the conversation for all tones up to 104 | * the current tone for a conversation instance with a user. 105 | */ 106 | function initUser() { 107 | return { 108 | 'tone': { 109 | 'emotion': { 110 | 'current': null 111 | }, 112 | } 113 | }; 114 | } 115 | -------------------------------------------------------------------------------- /public/js/payload.js: -------------------------------------------------------------------------------- 1 | // The PayloadPanel module is designed to handle 2 | // all display and behaviors of the payload column of the app. 3 | /* eslint no-unused-vars: "off" */ 4 | /* global Api: true, Common: true, PayloadPanel: true*/ 5 | 6 | var PayloadPanel = (function() { 7 | var settings = { 8 | selectors: { 9 | payloadColumn: '#payload-column', 10 | payloadInitial: '#payload-initial-message', 11 | payloadRequest: '#payload-request', 12 | payloadResponse: '#payload-response' 13 | }, 14 | payloadTypes: { 15 | request: 'request', 16 | response: 'response' 17 | } 18 | }; 19 | 20 | // Publicly accessible methods defined 21 | return { 22 | init: init, 23 | togglePanel: togglePanel 24 | }; 25 | 26 | // Initialize the module 27 | function init() { 28 | payloadUpdateSetup(); 29 | } 30 | 31 | // Toggle panel between being: 32 | // reduced width (default for large resolution apps) 33 | // hidden (default for small/mobile resolution apps) 34 | // full width (regardless of screen size) 35 | function togglePanel(event, element) { 36 | var payloadColumn = document 37 | .querySelector(settings.selectors.payloadColumn); 38 | if (element.classList.contains('full')) { 39 | element.classList.remove('full'); 40 | payloadColumn.classList.remove('full'); 41 | } else { 42 | element.classList.add('full'); 43 | payloadColumn.classList.add('full'); 44 | } 45 | } 46 | 47 | // Set up callbacks on payload setters in Api module 48 | // This causes the displayPayload function to be called when messages are sent 49 | // / received 50 | function payloadUpdateSetup() { 51 | var currentRequestPayloadSetter = Api.setRequestPayload; 52 | Api.setRequestPayload = function(newPayloadStr) { 53 | currentRequestPayloadSetter.call(Api, newPayloadStr); 54 | // displayPayload(settings.payloadTypes.request); 55 | }; 56 | 57 | var currentResponsePayloadSetter = Api.setResponsePayload; 58 | Api.setResponsePayload = function(newPayload) { 59 | currentResponsePayloadSetter.call(Api, newPayload); 60 | displayPayload(settings.payloadTypes.response); 61 | }; 62 | } 63 | 64 | // Display a request or response payload that has just been sent/received 65 | function displayPayload(typeValue) { 66 | var isRequest = checkRequestType(typeValue); 67 | if (isRequest !== null) { 68 | // Create new payload DOM element 69 | var payloadDiv = buildPayloadDomElement(isRequest); 70 | var payloadElement = document 71 | .querySelector(isRequest ? settings.selectors.payloadRequest 72 | : settings.selectors.payloadResponse); 73 | // Clear out payload holder element 74 | while (payloadElement.lastChild) { 75 | payloadElement.removeChild(payloadElement.lastChild); 76 | } 77 | // Add new payload element 78 | payloadElement.appendChild(payloadDiv); 79 | // Set the horizontal rule to show (if request and response payloads both 80 | // exist) 81 | // or to hide (otherwise) 82 | var payloadInitial = document 83 | .querySelector(settings.selectors.payloadInitial); 84 | if (Api.getRequestPayload() || Api.getResponsePayload()) { 85 | payloadInitial.classList.add('hide'); 86 | } 87 | } 88 | } 89 | 90 | // Checks if the given typeValue matches with the request "name", the response 91 | // "name", or neither 92 | // Returns true if request, false if response, and null if neither 93 | // Used to keep track of what type of payload we're currently working with 94 | function checkRequestType(typeValue) { 95 | if (typeValue === settings.payloadTypes.request) { 96 | return true; 97 | } else if (typeValue === settings.payloadTypes.response) { 98 | return false; 99 | } 100 | return null; 101 | } 102 | 103 | // Constructs new DOM element to use in displaying the payload 104 | function buildPayloadDomElement(isRequest) { 105 | var payloadPrettyString = jsonPrettyPrint(isRequest ? Api 106 | .getRequestPayload() : Api.getResponsePayload()); 107 | 108 | var payloadJson = { 109 | 'tagName': 'div', 110 | 'children': [ 111 | { 112 | //
113 | 'tagName': 'div', 114 | 'text': isRequest ? 'User input (request payload)' 115 | : 'Response from Watson Conversation (response payload)', 116 | 'classNames': [ 'header-text' ] 117 | }, 118 | { 119 | //
120 | 'tagName': 'div', 121 | 'classNames': [ 'code-line', 'responsive-columns-wrapper' ], 122 | 'children': [ 123 | { 124 | //
125 | 'tagName': 'pre', 126 | 'text': createLineNumberString((payloadPrettyString 127 | .match(/\n/g) || []).length + 1), 128 | 'classNames': [ 'line-numbers' ] 129 | }, { 130 | //
131 | 'tagName': 'pre', 132 | 'classNames': [ 'payload-text', 'responsive-column' ], 133 | 'html': payloadPrettyString 134 | } ] 135 | } ] 136 | }; 137 | 138 | return Common.buildDomElement(payloadJson); 139 | } 140 | 141 | // Format (payload) JSON to make it more readable 142 | function jsonPrettyPrint(json) { 143 | if (json === null) { 144 | return ''; 145 | } 146 | var convert = JSON.stringify(json, null, 2); 147 | 148 | convert = convert.replace(/&/g, '&').replace(//g, '>'); 150 | convert = convert 151 | .replace( 152 | /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, 153 | function(match) { 154 | var cls = 'number'; 155 | if (/^"/.test(match)) { 156 | if (/:$/.test(match)) { 157 | cls = 'key'; 158 | } else { 159 | cls = 'string'; 160 | } 161 | } else if (/true|false/.test(match)) { 162 | cls = 'boolean'; 163 | } else if (/null/.test(match)) { 164 | cls = 'null'; 165 | } 166 | return '' + match + ''; 167 | }); 168 | return convert; 169 | } 170 | 171 | // Used to generate a string of consecutive numbers separated by new lines 172 | // - used as line numbers for displayed JSON 173 | function createLineNumberString(numberOfLines) { 174 | var lineString = ''; 175 | var prefix = ''; 176 | for (var i = 1; i <= numberOfLines; i++) { 177 | lineString += prefix; 178 | lineString += i; 179 | prefix = '\n'; 180 | } 181 | return lineString; 182 | } 183 | }()); 184 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

🚀 Customer Engagement Bot

2 |

DEPRECATED: this repo is no longer actively maintained. It can still be used as reference, but may contain outdated or unpatched code.

3 |

4 | 5 | Travis 6 | 7 | 8 | semantic-release 9 | 10 |

11 |

12 | 13 | 14 | ![Demo GIF](readme_images/ce-demo.gif?raw=true) 15 | 16 | For more information on the Watson Assistant (Conversation) service, see the [detailed documentation](https://console.bluemix.net/docs/services/conversation/index.html#about). 17 | For more information on the Tone Analyzer Service, see the [detailed documentation](https://console.bluemix.net/docs/services/tone-analyzer/index.html#about). 18 | 19 | ## Deploying the application 20 | 21 | If you want to experiment with the application or use it as a basis for building your own application, you need to deploy it in your own environment. You can then explore the files, make changes, and see how those changes affect the running application. After making modifications, you can deploy your modified version of the application to IBM Cloud. 22 | 23 | ## Prerequisites 24 | 25 | 1. Sign up for an [IBM Cloud account](https://console.bluemix.net/registration/). 26 | 1. Download the [IBM Cloud CLI](https://console.bluemix.net/docs/cli/index.html#overview). 27 | 1. Create an instance of the Watson Assistant service and get your credentials: 28 | - Go to the [Watson Assistant](https://console.bluemix.net/catalog/services/conversation) page in the IBM Cloud Catalog. 29 | - Log in to your IBM Cloud account. 30 | - Click **Create**. 31 | - Click **Show** to view the service credentials. 32 | - Copy the `apikey` value 33 | - Copy the `url` value. 34 | 1. Create an instance of the Tone Analyzer service and get your credentials: 35 | - Go to the [Tone Analyzer](https://console.bluemix.net/catalog/services/tone-analyzer) page in the IBM Cloud Catalog. 36 | - Log in to your IBM Cloud account. 37 | - Click **Create**. 38 | - Click **Show** to view the service credentials. 39 | - Copy the `apikey` value 40 | - Copy the `url` value. 41 | 42 | ## Configuring the application 43 | 44 | 1. In your IBM Cloud console, open the Watson Assistant service instance 45 | 46 | 1. Click on the **Launch tool** button to launch into the Watson Assistant tooling. 47 | 48 | ![Watson Assistant Launch Tool](readme_images/WA_LaunchTool.png) 49 | 50 | 51 | 1. This is the Watson Assistant tooling where you can create assistants, skills and and setup different chatbots applications. We'll be importing a pre-built skill. **Click on 'Skills'** on the top left, and then on the **Create new** button. 52 | 53 | ![Watson Assistant New Skill](readme_images/WA_CreateNewSkill.png) 54 | 55 | 1. **Click on 'Import Skill'** and then on the **Choose JSON File** button. 56 | 57 | ![Watson Assistant Import Skill](readme_images/WA_ImportSkill.png) 58 | 59 | 1. Find the workspace JSON file `training/ce-workspace.json` from this repository on your local machine and **Click the 'Import'** button (make sure the **Everything** radio button is selected to import intents, entities and dialog). 60 | 61 | ![Watson Assistant Import Skill Complete](readme_images/WA_ImportSkillFinish.png) 62 | 63 | 1. You will be redirected into a page with four tabs, Intents, Entities, Dialog, and Content Catalog. For the purposes of this lab, the skill is fairly complete. 64 | 65 | 66 | 1. To interact with the correct skill, you will need the unique identifier for your skill. You can find the workspace ID from the Watson Assistant tooling. From the main Skills page, **Click on the three stacked dots** on the top right of the skill you created/imported. Then **click on the 'View API Details'** option in the menu. 67 | 68 | ![Workspace ID](readme_images/WA_WorkspaceID.png) 69 | 70 | Copy the **Workspace ID** value from this page. 71 | 72 | 1. In the application folder, copy the *.env.example* file and create a file called *.env* 73 | 74 | ``` 75 | cp .env.example .env 76 | ``` 77 | 78 | 1. Open the *.env* file and add the service credentials that you obtained for both Watson Assistant and Tone Analyzer in the previous step. 79 | 80 | 1. Add the `WORKSPACE_ID` to the previous properties 81 | 82 | 1. Your `.env` file should looks like: 83 | 84 | ``` 85 | # Environment variables 86 | WORKSPACE_ID=1c464fa0-2b2f-4464-b2fb-af0ffebc3aab 87 | ASSISTANT_IAM_APIKEY=_5iLGHasd86t9NddddrbJPOFDdxrixnOJYvAATKi1 88 | ASSISTANT_URL=https://gateway-syd.watsonplatform.net/assistant/api 89 | 90 | TONE_ANALYZER_IAM_APIKEY=UdHqOFLzoOCFD2M50AbsasdYhOnLV6sd_C3ua5zah 91 | TONE_ANALYZER_URL=https://gateway-syd.watsonplatform.net/tone-analyzer/api 92 | ``` 93 | 94 | ## Running locally 95 | 96 | 1. Install the dependencies 97 | 98 | ``` 99 | npm install 100 | ``` 101 | 102 | 1. Run the application 103 | 104 | ``` 105 | npm start 106 | ``` 107 | 108 | 1. View the application in a browser at `localhost:3000` 109 | 110 | ## Deploying to IBM Cloud as a Cloud Foundry Application 111 | 112 | 1. Login to IBM Cloud with the [IBM Cloud CLI](https://console.bluemix.net/docs/cli/index.html#overview) 113 | 114 | ``` 115 | ibmcloud login 116 | ``` 117 | 118 | 1. Target a Cloud Foundry organization and space. 119 | 120 | ``` 121 | ibmcloud target --cf 122 | ``` 123 | 124 | 1. Edit the *manifest.yml* file. Change the **name** field to something unique. 125 | For example, `- name: my-app-name`. 126 | 1. Deploy the application 127 | 128 | ``` 129 | ibmcloud app push 130 | ``` 131 | 132 | 1. View the application online at the app URL. 133 | For example: https://my-app-name.mybluemix.net 134 | 135 | # What to do next 136 | 137 | After you have the application installed and running, experiment with it to see how it responds to your input. 138 | 139 | ## Modifying the application 140 | 141 | After you have the application deployed and running, you can explore the source files and make changes. Try the following: 142 | 143 | * Modify the `.js` files to change the application logic. 144 | 145 | * Modify the `.html` file to change the appearance of the application page. 146 | 147 | * Use the Watson Assistant tool to train the service for new intents, or to modify the dialog flow. For more information, see the [Watson Assistant service documentation](https://www.ibm.com/watson/services/conversation/). 148 | 149 | # What does the Customer Engagement Bot application do? 150 | 151 | The application interface is designed for chatting with a customer engagement bot. Based on a previous laptop purchase, the bot asks how the experience has been and responds accordingly if given a negative or positive response. 152 | 153 | The chat interface is in the left panel of the UI, and the JSON response object returned by the Watson Assistant service in the right panel. Your input is run against a small set of sample data trained with the following intents: 154 | 155 | yes 156 | no 157 | refund: get a refund on current laptop 158 | restart: restart the conversation at any point 159 | tradeIn: replace current laptop with another one 160 | thanks 161 | greeting 162 | 163 | The dialog is also trained on two types of entities: 164 | 165 | design 166 | size 167 | weight 168 | 169 | These intents and entities help the bot understand variations your input. 170 | 171 | Below you can find some sample interactions: 172 | 173 | ![Alt text](readme_images/examples.jpg?raw=true) 174 | 175 | In order to integrate the Tone Analyzer with the Watson Assistant service (formerly Conversation), the following approach was taken: 176 | * Intercept the user's message. Before sending it to the Watson Assistant service, invoke the Tone Analyzer Service. See the call to `toneDetection.invokeToneAsync` in the `invokeToneConversation` function in [app.js](./app.js). 177 | * Parse the JSON response object from the Tone Analyzer Service, and add appropriate variables to the context object of the JSON payload to be sent to the Watson Assistant service. See the `updateUserTone` function in [tone_detection.js](./addons/tone_detection.js). 178 | * Send the user input, along with the updated context object in the payload to the Watson Assistant service. See the call to `conversation.message` in the `invokeToneConversation` function in [app.js](./app.js). 179 | 180 | 181 | You can see the JSON response object from the Watson Assistant service in the right hand panel. 182 | 183 | ![Alt text](readme_images/tone_context.jpg?raw=true) 184 | 185 | In the conversation template, alternative bot responses were encoded based on the user's emotional tone. For example: 186 | 187 | ![Alt text](readme_images/rule.png?raw=true) 188 | 189 | 190 | ## License 191 | 192 | This sample code is licensed under Apache 2.0. 193 | Full license text is available in [LICENSE](LICENSE). 194 | 195 | ## Contributing 196 | 197 | See [CONTRIBUTING](CONTRIBUTING.md). 198 | 199 | ## Open Source @ IBM 200 | 201 | Find more open source projects on the 202 | [IBM Github Page](http://ibm.github.io/). 203 | -------------------------------------------------------------------------------- /public/js/conversation.js: -------------------------------------------------------------------------------- 1 | // The ConversationPanel module is designed to handle 2 | // all display and behaviors of the conversation column of the app. 3 | /* eslint no-unused-vars: "off" */ 4 | /* global Api: true, Common: true*/ 5 | 6 | var ConversationPanel = (function() { 7 | var settings = { 8 | selectors: { 9 | chatBox: '#scrollingChat', 10 | fromUser: '.from-user', 11 | fromWatson: '.from-watson', 12 | latest: '.latest' 13 | }, 14 | authorTypes: { 15 | user: 'user', 16 | watson: 'watson' 17 | } 18 | }; 19 | 20 | // Publicly accessible methods defined 21 | return { 22 | init: init, 23 | inputKeyDown: inputKeyDown 24 | }; 25 | 26 | /** 27 | * getMealType determines what meal a user of the app might have eaten most 28 | * recently. It uses the client's browser time. 29 | * 30 | * @returns {string} a string indicating the meal the user most likely ate 31 | * recently - breakfast, lunch, dinner 32 | */ 33 | function getMealType() { 34 | var date = new Date(); 35 | var hrs = date.getHours(); 36 | 37 | if (hrs >= 5 && hrs < 11) 38 | return 'breakfast'; 39 | else if (hrs >= 11 && hrs < 17) 40 | return 'lunch'; 41 | else if ((hrs >= 17 && hrs <= 23) || hrs < 5) 42 | return 'dinner'; 43 | return 'meal'; 44 | } 45 | 46 | // Initialize the module 47 | function init() { 48 | chatUpdateSetup(); 49 | // Add the time context variable to indicate what meal the user may be 50 | // eating 51 | var context = { 52 | 53 | }; 54 | Api.sendRequest(' ', context); 55 | setupInputBox(); 56 | } 57 | 58 | // Set up callbacks on payload setters in Api module 59 | // This causes the displayMessage function to be called when messages are sent 60 | // / received 61 | function chatUpdateSetup() { 62 | var currentRequestPayloadSetter = Api.setRequestPayload; 63 | Api.setRequestPayload = function(newPayloadStr) { 64 | currentRequestPayloadSetter.call(Api, newPayloadStr); 65 | displayMessage(JSON.parse(newPayloadStr), settings.authorTypes.user); 66 | }; 67 | 68 | var currentResponsePayloadSetter = Api.setResponsePayload; 69 | Api.setResponsePayload = function(newPayloadStr) { 70 | currentResponsePayloadSetter.call(Api, newPayloadStr); 71 | displayMessage(JSON.parse(newPayloadStr), settings.authorTypes.watson); 72 | }; 73 | } 74 | 75 | // Set up the input box to underline text as it is typed 76 | // This is done by creating a hidden dummy version of the input box that 77 | // is used to determine what the width of the input text should be. 78 | // This value is then used to set the new width of the visible input box. 79 | function setupInputBox() { 80 | var input = document.getElementById('textInput'); 81 | var dummy = document.getElementById('textInputDummy'); 82 | var minFontSize = 14; 83 | var maxFontSize = 16; 84 | var minPadding = 4; 85 | var maxPadding = 6; 86 | 87 | // If no dummy input box exists, create one 88 | if (dummy === null) { 89 | var dummyJson = { 90 | 'tagName': 'div', 91 | 'attributes': [ { 92 | 'name': 'id', 93 | 'value': 'textInputDummy' 94 | } ] 95 | }; 96 | 97 | dummy = Common.buildDomElement(dummyJson); 98 | document.body.appendChild(dummy); 99 | } 100 | 101 | function adjustInput() { 102 | if (input.value === '') { 103 | // If the input box is empty, remove the underline 104 | input.classList.remove('underline'); 105 | input.setAttribute('style', 'width:' + '100%'); 106 | input.style.width = '100%'; 107 | } else { 108 | // otherwise, adjust the dummy text to match, and then set the width of 109 | // the visible input box to match it (thus extending the underline) 110 | input.classList.add('underline'); 111 | var txtNode = document.createTextNode(input.value); 112 | [ 'font-size', 'font-style', 'font-weight', 'font-family', 113 | 'line-height', 'text-transform', 'letter-spacing' ] 114 | .forEach(function(index) { 115 | dummy.style[index] = window.getComputedStyle(input, null) 116 | .getPropertyValue(index); 117 | }); 118 | dummy.textContent = txtNode.textContent; 119 | 120 | var padding = 0; 121 | var htmlElem = document.getElementsByTagName('html')[0]; 122 | var currentFontSize = parseInt(window.getComputedStyle(htmlElem, null) 123 | .getPropertyValue('font-size'), 10); 124 | if (currentFontSize) { 125 | padding = Math.floor((currentFontSize - minFontSize) 126 | / (maxFontSize - minFontSize) * (maxPadding - minPadding) 127 | + minPadding); 128 | } else { 129 | padding = maxPadding; 130 | } 131 | 132 | var widthValue = (dummy.offsetWidth + padding) + 'px'; 133 | input.setAttribute('style', 'width:' + widthValue); 134 | input.style.width = widthValue; 135 | } 136 | } 137 | 138 | // Any time the input changes, or the window resizes, adjust the size of the 139 | // input box 140 | input.addEventListener('input', adjustInput); 141 | window.addEventListener('resize', adjustInput); 142 | 143 | // Trigger the input event once to set up the input box and dummy element 144 | Common.fireEvent(input, 'input'); 145 | } 146 | 147 | // Display a user or Watson message that has just been sent/received 148 | function displayMessage(newPayload, typeValue) { 149 | var isUser = isUserMessage(typeValue); 150 | var textExists = (newPayload.input && newPayload.input.text) 151 | || (newPayload.output && newPayload.output.text); 152 | if (isUser !== null && textExists) { 153 | // Create new message DOM element 154 | var messageDivs = buildMessageDomElements(newPayload, isUser); 155 | var chatBoxElement = document.querySelector(settings.selectors.chatBox); 156 | var previousLatest = chatBoxElement 157 | .querySelectorAll((isUser ? settings.selectors.fromUser 158 | : settings.selectors.fromWatson) 159 | + settings.selectors.latest); 160 | // Previous "latest" message is no longer the most recent 161 | if (previousLatest) { 162 | Common.listForEach(previousLatest, function(element) { 163 | element.classList.remove('latest'); 164 | }); 165 | } 166 | 167 | messageDivs.forEach(function(currentDiv) { 168 | chatBoxElement.appendChild(currentDiv); 169 | // Class to start fade in animation 170 | currentDiv.classList.add('load'); 171 | }); 172 | // Move chat to the most recent messages when new messages are added 173 | scrollToChatBottom(); 174 | var input = document.getElementById('textInput'); 175 | if (newPayload.context && newPayload.context.conversationEnd === true) { 176 | input.disabled = 'disabled'; 177 | input.value = ' '; 178 | } 179 | } 180 | } 181 | 182 | // Checks if the given typeValue matches with the user "name", the Watson 183 | // "name", or neither 184 | // Returns true if user, false if Watson, and null if neither 185 | // Used to keep track of whether a message was from the user or Watson 186 | function isUserMessage(typeValue) { 187 | if (typeValue === settings.authorTypes.user) { 188 | return true; 189 | } else if (typeValue === settings.authorTypes.watson) { 190 | return false; 191 | } 192 | return null; 193 | } 194 | 195 | // Constructs new DOM element from a message payload 196 | function buildMessageDomElements(newPayload, isUser) { 197 | var textArray = isUser ? newPayload.input.text : newPayload.output.text; 198 | var emotionClass = 'top'; 199 | 200 | if (Object.prototype.toString.call(textArray) !== '[object Array]') { 201 | textArray = [ textArray ]; 202 | } 203 | var messageArray = []; 204 | 205 | textArray.forEach(function(currentText) { 206 | if (currentText && currentText.trim() !== '') { 207 | var messageJson = { 208 | //
209 | 'tagName': 'div', 210 | 'classNames': [ 'segments' ], 211 | 'children': [ { 212 | //
213 | 'tagName': 'div', 214 | // AW - change colour of watson tag 215 | 'classNames': [ (isUser ? 'from-user' : 'from-watson'), 'latest', 216 | ((messageArray.length === 0) ? emotionClass : 'sub') ], 217 | 'children': [ { 218 | //
219 | 'tagName': 'div', 220 | 'classNames': [ 'message-inner' ], 221 | 'children': [ { 222 | //

{messageText}

223 | 'tagName': 'p', 224 | 'text': currentText 225 | } ] 226 | } ] 227 | } ] 228 | }; 229 | messageArray.push(Common.buildDomElement(messageJson)); 230 | } 231 | }); 232 | 233 | return messageArray; 234 | } 235 | 236 | // Scroll to the bottom of the chat window (to the most recent messages) 237 | // Note: this method will bring the most recent user message into view, 238 | // even if the most recent message is from Watson. 239 | // This is done so that the "context" of the conversation is maintained in the 240 | // view, 241 | // even if the Watson message is long. 242 | function scrollToChatBottom() { 243 | var scrollingChat = document.querySelector('#scrollingChat'); 244 | 245 | // Scroll to the latest message sent by the user 246 | var scrollEl = scrollingChat.querySelector(settings.selectors.fromUser 247 | + settings.selectors.latest); 248 | if (scrollEl) { 249 | scrollingChat.scrollTop = scrollEl.offsetTop; 250 | } 251 | } 252 | 253 | // Handles the submission of input 254 | function inputKeyDown(event, inputBox) { 255 | // Submit on enter key, dis-allowing blank messages 256 | if (event.keyCode === 13 && inputBox.value) { 257 | // Retrieve the context from the previous server response 258 | // var context = {}; 259 | // context.test = "TEST"; 260 | var context; 261 | var latestResponse = Api.getResponsePayload(); 262 | if (latestResponse) { 263 | context = latestResponse.context; 264 | } 265 | 266 | // Send the user message 267 | Api.sendRequest(inputBox.value, context); 268 | 269 | // Clear input box for further messages 270 | inputBox.value = ''; 271 | Common.fireEvent(inputBox, 'input'); 272 | } 273 | } 274 | }()); 275 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /public/css/app.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-box-sizing: border-box; 3 | -moz-box-sizing: border-box; 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | font-family: Helvetica Neue for IBM, Helvetica Neue, Helvetica, Arial, sans-serif; 9 | margin: 0; 10 | padding: 0; 11 | } 12 | 13 | div { 14 | word-wrap: break-word; 15 | line-height: 1.25rem; 16 | } 17 | 18 | .disclaimer-message .base-h6 { 19 | padding: 5px; 20 | } 21 | 22 | #view-change-button { 23 | display: inline-block; 24 | position: absolute; 25 | width: 3.125rem; 26 | height: 3.125rem; 27 | border-radius: 1.5625rem; 28 | background: #AB72F8; 29 | top: 0.3125rem; 30 | right: 0.3125rem; 31 | line-height: 3.125rem; 32 | vertical-align: middle; 33 | } 34 | 35 | #view-change-button img { 36 | display: none; 37 | width: 100%; 38 | height: 100%; 39 | vertical-align: middle; 40 | } 41 | 42 | #view-change-button:not(.full) .not-full { 43 | display: inline-block; 44 | } 45 | 46 | #view-change-button.full .full { 47 | display: inline-block; 48 | } 49 | 50 | #contentParent { 51 | height: 100%; 52 | } 53 | 54 | .responsive-columns-wrapper { 55 | display: -ms-flexbox; 56 | display: -webkit-flex; 57 | display: flex; 58 | flex-direction: row; 59 | -ms-display: flex; 60 | -ms-flex-direction: row; 61 | } 62 | 63 | .responsive-column { 64 | -webkit-flex: 1; 65 | -ms-flex: 1; 66 | flex: 1; 67 | overflow: auto; 68 | } 69 | 70 | #chat-column-holder { 71 | text-align: center; 72 | } 73 | 74 | .chat-column { 75 | height: 74%; 76 | padding: 0.9375rem 0 0.625rem 0; 77 | margin: auto; 78 | text-align: left; 79 | max-width: 25rem; 80 | min-width: 9.375rem; 81 | } 82 | 83 | #scrollingChat { 84 | margin: 0.75rem; 85 | overflow-y: auto; 86 | overflow-x: hidden; 87 | height: calc(100% - 4rem); 88 | } 89 | 90 | .message-inner { 91 | opacity: 0; 92 | margin-top: 0.9375rem; 93 | -webkit-transition-property: opacity, margin-top; 94 | -webkit-transition-duration: 0.75s; 95 | -webkit-transition-timing-function: ease-in; 96 | -moz-transition-property: opacity, margin-top; 97 | -moz-transition-duration: 0.75s; 98 | -moz-transition-timing-function: ease-in; 99 | -o-transition-property: opacity, margin-top; 100 | -o-transition-duration: 0.75s; 101 | -o-transition-timing-function: ease-in; 102 | -ms-transition-property: opacity, margin-top; 103 | -ms-transition-duration: 0.75s; 104 | -ms-transition-timing-function: ease-in; 105 | transition-property: opacity, margin-top; 106 | transition-duration: 0.75s; 107 | transition-timing-function: ease-in; 108 | } 109 | 110 | .load .message-inner { 111 | opacity: 1; 112 | margin-top: 0.3125rem; 113 | } 114 | 115 | .from-user { 116 | text-align: right; 117 | } 118 | 119 | .from-user .message-inner { 120 | position: relative; 121 | font-size: 1rem; 122 | color: #fff; 123 | letter-spacing: 0.015rem; 124 | line-height: 1.3125rem; 125 | background: #00B4A0; 126 | border-radius: 1.25rem; 127 | border-bottom-right-radius: 0; 128 | text-align: left; 129 | display: inline-block; 130 | margin-left: 2.5rem; 131 | min-width: 2.5rem; 132 | } 133 | 134 | .from-user .message-inner p { 135 | margin: 0.3125rem; 136 | padding: 0 0.9375rem; 137 | } 138 | 139 | .from-user .message-inner:before, .from-user .message-inner:after { 140 | content: ""; 141 | position: absolute; 142 | } 143 | 144 | 145 | .from-user .message-inner:before { 146 | z-index: -2; 147 | bottom: -0.375rem; 148 | right: 0; 149 | height: 0.375rem; 150 | width: 0.5rem; 151 | background: #1cb3a0; 152 | } 153 | 154 | .from-user .message-inner:after { 155 | z-index: -1; 156 | bottom: -0.5rem; 157 | right: 0; 158 | height: 0.5rem; 159 | width: 0.5rem; 160 | background: #fff; 161 | border-top-right-radius: 1.25rem; 162 | } 163 | 164 | .from-watson .message-inner { 165 | position: relative; 166 | border-radius: 1.5625rem; 167 | font-size: 1rem; 168 | color: #B5B5B5; 169 | letter-spacing: 0.015rem; 170 | line-height: 1.3125rem; 171 | } 172 | 173 | .from-watson.latest .message-inner { 174 | color: #323232; 175 | } 176 | 177 | .from-watson p { 178 | margin: 0.3125rem; 179 | padding: 0 1.25rem; 180 | } 181 | 182 | .from-watson.latest.top p:before { 183 | content: "."; 184 | color: #9855D4; 185 | background-image: url("../img/marker_image.png"); 186 | background-size: 0.3125rem 1.3125rem; 187 | position: absolute; 188 | z-index: 2; 189 | left: 0.4375rem; 190 | width: 0.3125rem; 191 | height: 1.3125rem; 192 | line-height: 1.3125rem; 193 | } 194 | 195 | #textInput { 196 | border: none; 197 | outline: none; 198 | background: transparent; 199 | font-size: 1rem; 200 | color: #323232; 201 | letter-spacing: 0.015rem; 202 | line-height: 1.3125rem; 203 | height: 2.5rem; 204 | max-width: 100%; 205 | padding-left: 0.125rem; 206 | margin-bottom: -0.125rem; 207 | font-family: Helvetica Neue for IBM, Helvetica Neue, Helvetica, Arial, sans-serif; 208 | 209 | } 210 | 211 | #textInput.underline { 212 | border-bottom: 2px solid #00B4A0; 213 | } 214 | 215 | ::-webkit-input-placeholder { 216 | color: #B5B5B5; 217 | } 218 | 219 | ::-moz-placeholder { 220 | color: #B5B5B5; 221 | opacity: 1; 222 | } 223 | 224 | input:-moz-placeholder { 225 | color: #B5B5B5; 226 | opacity: 1; 227 | } 228 | 229 | :-ms-input-placeholder { 230 | color: #B5B5B5; 231 | } 232 | 233 | ::-ms-clear { 234 | display: none; 235 | } 236 | 237 | .inputOutline { 238 | display: block; 239 | border-bottom: 0.0625rem solid #aeaeae; 240 | margin-left: 0.5rem; 241 | margin-right: 0.5rem; 242 | } 243 | 244 | #textInputDummy { 245 | position:absolute; 246 | white-space:pre; 247 | top: 0; 248 | left: -1000%; 249 | opacity: 0; 250 | } 251 | 252 | #payload-column { 253 | font-family: Monaco, monospace; 254 | font-size: 0.75rem; 255 | letter-spacing: 0; 256 | line-height: 1.125rem; 257 | background-color: #23292A; 258 | color: #fff; 259 | overflow-x: auto; 260 | 261 | width: 45%; 262 | max-width: 32.0625rem; 263 | min-width: 29.6875rem; 264 | } 265 | 266 | #payload-column.full { 267 | width: 100%; 268 | max-width: none; 269 | min-width: initial; 270 | } 271 | 272 | #payload-column .header-text, #payload-column #payload-initial-message { 273 | font-family: Helvetica Neue for IBM, Helvetica Neue, Helvetica, Arial, sans-serif; 274 | font-size: 1.125rem; 275 | color: #9E9E9E; 276 | letter-spacing: 0.01875rem; 277 | padding: 0.5rem; 278 | padding-left: 2.5rem; 279 | background: #383D3E; 280 | } 281 | 282 | .hide { 283 | display: none; 284 | } 285 | 286 | .payload .line-numbers, .payload .payload-text { 287 | padding: 0.5rem; 288 | } 289 | 290 | .line-numbers { 291 | width: 2rem; 292 | color: #898989; 293 | text-align: right; 294 | } 295 | 296 | pre { 297 | margin: 0; 298 | word-wrap: normal; 299 | } 300 | 301 | .string { 302 | color: #54EED0; 303 | } 304 | 305 | .boolean, .null, .number { 306 | color: #CE8EFF; 307 | } 308 | 309 | .key { 310 | color: #66B7FF; 311 | } 312 | 313 | html{ 314 | font-size: 16px; 315 | } 316 | 317 | @media only screen and (max-width: 1000px) { 318 | html { 319 | font-size: 15px; 320 | } 321 | } 322 | 323 | @media only screen and (max-width: 600px) { 324 | html { 325 | font-size: 14px; 326 | } 327 | 328 | .chat-column { 329 | padding-top: 4rem; 330 | } 331 | 332 | #payload-column { 333 | width: 0; 334 | max-width: none; 335 | min-width: initial; 336 | } 337 | } 338 | 339 | /* IBM Design fonts https://github.ibm.com/Design/fonts */ 340 | @font-face { 341 | font-family: 'Helvetica Neue for IBM'; 342 | src: url('../fonts/light/h-n-light.eot?') format('eot'), 343 | url('../fonts/light/h-n-light.woff2') format('woff2'), 344 | url('../fonts/light/h-n-light.woff') format('woff'), 345 | url('../fonts/light/h-n-light.ttf') format('truetype'); 346 | font-weight: 300; 347 | font-style: normal; 348 | } 349 | @font-face { 350 | font-family: 'Helvetica Neue for IBM'; 351 | src: url('../fonts/light-italic/h-n-light-italic.eot?') format('eot'), 352 | url('../fonts/light-italic/h-n-light-italic.woff2') format('woff2'), 353 | url('../fonts/light-italic/h-n-light-italic.woff') format('woff'), 354 | url('../fonts/light-italic/h-n-light-italic.ttf') format('truetype'); 355 | font-weight: 300; 356 | font-style: italic; 357 | } 358 | @font-face { 359 | font-family: 'Helvetica Neue for IBM'; 360 | src: url('../fonts/roman/h-n-roman.eot?') format('eot'), 361 | url('../fonts/roman/h-n-roman.woff2') format('woff2'), 362 | url('../fonts/roman/h-n-roman.woff') format('woff'), 363 | url('../fonts/roman/h-n-roman.ttf') format('truetype'); 364 | font-weight: 400; 365 | font-style: normal; 366 | } 367 | @font-face { 368 | font-family: 'Helvetica Neue for IBM'; 369 | src: url('../fonts/roman-italic/h-n-roman-italic.eot?') format('eot'), 370 | url('../fonts/roman-italic/h-n-roman-italic.woff2') format('woff2'), 371 | url('../fonts/roman-italic/h-n-roman-italic.woff') format('woff'), 372 | url('../fonts/roman-italic/h-n-roman-italic.ttf') format('truetype'); 373 | font-weight: 400; 374 | font-style: italic; 375 | } 376 | @font-face { 377 | font-family: 'Helvetica Neue for IBM'; 378 | src: url('../fonts/medium/h-n-medium.eot?') format('eot'), 379 | url('../fonts/medium/h-n-medium.woff2') format('woff2'), 380 | url('../fonts/medium/h-n-medium.woff') format('woff'), 381 | url('../fonts/medium/h-n-medium.ttf') format('truetype'); 382 | font-weight: 500; 383 | font-style: normal; 384 | } 385 | @font-face { 386 | font-family: 'Helvetica Neue for IBM'; 387 | src: url('../fonts/medium-italic/h-n-medium-italic.eot?') format('eot'), 388 | url('../fonts/medium-italic/h-n-medium-italic.woff2') format('woff2'), 389 | url('../fonts/medium-italic/h-n-medium-italic.woff') format('woff'), 390 | url('../fonts/medium-italic/h-n-medium-italic.ttf') format('truetype'); 391 | font-weight: 500; 392 | font-style: italic; 393 | } 394 | @font-face { 395 | font-family: 'Helvetica Neue for IBM'; 396 | src: url('../fonts/bold/h-n-bold.eot?') format('eot'), 397 | url('../fonts/bold/h-n-bold.woff2') format('woff2'), 398 | url('../fonts/bold/h-n-bold.woff') format('woff'), 399 | url('../fonts/bold/h-n-bold.ttf') format('truetype'); 400 | font-weight: 700; 401 | font-style: normal; 402 | } 403 | @font-face { 404 | font-family: 'Helvetica Neue for IBM'; 405 | src: url('../fonts/bold-italic/h-n-bold-italic.eot?') format('eot'), 406 | url('../fonts/bold-italic/h-n-bold-italic.woff2') format('woff2'), 407 | url('../fonts/bold-italic/h-n-bold-italic.woff') format('woff'), 408 | url('../fonts/bold-italic/h-n-bold-italic.ttf') format('truetype'); 409 | font-weight: 700; 410 | font-style: italic; 411 | } 412 | 413 | /* IBM Icons */ 414 | @font-face { 415 | font-family: 'ibm-icons'; 416 | src:url('../fonts/ibm-icons.eot?ytcz1z') format('eot'), 417 | url('../fonts/ibm-icons.eot?ytcz1z#iefix') format('embedded-opentype'), 418 | url('../fonts/ibm-icons.ttf?ytcz1z') format('truetype'), 419 | url('../fonts/ibm-icons.woff?ytcz1z') format('woff'), 420 | url('../fonts/ibm-icons.svg?ytcz1z#ibm-icons') format('svg'); 421 | font-weight: normal; 422 | font-style: normal; 423 | } 424 | 425 | /* IBM glyphs */ 426 | @font-face { 427 | font-family: 'ibm-glyph'; 428 | src:url('../fonts/ibm-glyphs.eot?1b8643') format('eot'), 429 | url('../fonts/ibm-glyphs.eot?1b8643#iefix') format('embedded-opentype'), 430 | url('../fonts/ibm-glyphs.ttf?1b8643') format('truetype'), 431 | url('../fonts/ibm-glyphs.woff?1b8643') format('woff'), 432 | url('../fonts/ibm-glyphs.svg?1b8643#ibm-glyph') format('svg'); 433 | font-weight: normal; 434 | font-style: normal; 435 | } 436 | 437 | .message-disclaimer { 438 | margin-bottom: 0; 439 | } -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 IBM Corp. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var express = require('express'); // app server 20 | var bodyParser = require('body-parser'); // parser for post requests 21 | var AssistantV1 = require('watson-developer-cloud/assistant/v1'); 22 | var ToneAnalyzerV3 = require('watson-developer-cloud/tone-analyzer/v3'); 23 | 24 | var toneDetection = require('./addons/tone_detection.js'); // required for tone 25 | // detection 26 | var maintainToneHistory = false; 27 | 28 | // The following requires are needed for logging purposes 29 | var uuid = require('uuid'); 30 | var vcapServices = require('vcap_services'); 31 | var basicAuth = require('basic-auth-connect'); 32 | 33 | // The app owner may optionally configure a cloudand db to track user input. 34 | // This cloudand db is not required, the app will operate without it. 35 | // If logging is enabled the app must also enable basic auth to secure logging 36 | // endpoints 37 | var cloudantCredentials = vcapServices.getCredentials('cloudantNoSQLDB'); 38 | var cloudantUrl = null; 39 | if (cloudantCredentials) { 40 | cloudantUrl = cloudantCredentials.url; 41 | } 42 | cloudantUrl = cloudantUrl || process.env.CLOUDANT_URL; // || ''; 43 | var logs = null; 44 | var app = express(); 45 | 46 | // Bootstrap application settings 47 | app.use(express.static('./public')); // load UI from public folder 48 | app.use(bodyParser.json()); 49 | 50 | 51 | // Instantiate the Watson AssistantV1 Service as per WDC 2.2.0 52 | var assistant = new AssistantV1({ 53 | version: '2017-05-26' 54 | }); 55 | 56 | // Instantiate the Watson Tone Analyzer Service as per WDC 2.2.0 57 | var toneAnalyzer = new ToneAnalyzerV3({ 58 | version: '2017-09-21' 59 | }); 60 | 61 | // Endpoint to be called from the client side 62 | app.post('/api/message', function(req, res) { 63 | var workspace = process.env.WORKSPACE_ID || ''; 64 | if (!workspace || workspace === '') { 65 | return res.json({ 66 | 'output': { 67 | 'text': 'The app has not been configured with a WORKSPACE_ID environment variable. Please refer to the ' + 'README documentation on how to set this variable.
' + 'Once a workspace has been defined the intents may be imported from ' + 'here in order to get a working application.' 68 | } 69 | }); 70 | } 71 | var payload = { 72 | workspace_id: workspace, 73 | context: {}, 74 | input: {} 75 | }; 76 | 77 | if (req.body) { 78 | if (req.body.input) { 79 | payload.input = req.body.input; 80 | } 81 | if (req.body.context) { 82 | payload.context = req.body.context; 83 | } else { 84 | 85 | // Add the user object (containing tone) to the context object for 86 | // Assistant 87 | payload.context = toneDetection.initUser(); 88 | } 89 | 90 | 91 | // Invoke the tone-aware call to the Assistant Service 92 | invokeToneConversation(payload, res); 93 | } 94 | }); 95 | 96 | /** 97 | * Updates the response text using the intent confidence 98 | * 99 | * @param {Object} 100 | * input The request to the Assistant service 101 | * @param {Object} 102 | * response The response from the Assistant service 103 | * @return {Object} The response with the updated message 104 | */ 105 | function updateMessage(input, response) { 106 | var responseText = null; 107 | var id = null; 108 | 109 | if (!response.output) { 110 | response.output = {}; 111 | } else { 112 | if (logs) { 113 | // If the logs db is set, then we want to record all input and responses 114 | id = uuid.v4(); 115 | logs.insert({'_id': id, 'request': input, 'response': response, 'time': new Date()}); 116 | } 117 | return response; 118 | } 119 | 120 | if (response.intents && response.intents[0]) { 121 | var intent = response.intents[0]; 122 | // Depending on the confidence of the response the app can return different 123 | // messages. 124 | // The confidence will vary depending on how well the system is trained. The 125 | // service will always try to assign 126 | // a class/intent to the input. If the confidence is low, then it suggests 127 | // the service is unsure of the 128 | // user's intent . In these cases it is usually best to return a 129 | // disambiguation message 130 | // ('I did not understand your intent, please rephrase your question', 131 | // etc..) 132 | if (intent.confidence >= 0.75) { 133 | responseText = 'I understood your intent was ' + intent.intent; 134 | } else if (intent.confidence >= 0.5) { 135 | responseText = 'I think your intent was ' + intent.intent; 136 | } else { 137 | responseText = 'I did not understand your intent'; 138 | } 139 | } 140 | response.output.text = responseText; 141 | if (logs) { 142 | // If the logs db is set, then we want to record all input and responses 143 | id = uuid.v4(); 144 | logs.insert({'_id': id, 'request': input, 'response': response, 'time': new Date()}); 145 | } 146 | return response; 147 | } 148 | 149 | /** 150 | * @author April Webster 151 | * @returns {Object} return response from Assistant service 152 | * invokeToneConversation calls the invokeToneAsync function to get the 153 | * tone information for the user's input text (input.text in the 154 | * payload json object), adds/updates the user's tone in the payload's 155 | * context, and sends the payload to the Assistant service to get a 156 | * response which is printed to screen. 157 | * @param {Json} 158 | * payload a json object containing the basic information needed 159 | * to converse with the Assistant Service's message endpoint. 160 | * @param {Object} 161 | * res response object 162 | * 163 | */ 164 | function invokeToneConversation(payload, res) { 165 | toneDetection.invokeToneAsync(payload, toneAnalyzer).then(function(tone) { 166 | toneDetection.updateUserTone(payload, tone, maintainToneHistory); 167 | assistant.message(payload, function(err, data) { 168 | var returnObject = null; 169 | if (err) { 170 | console.error(JSON.stringify(err, null, 2)); 171 | returnObject = res.status(err.code || 500).json(err); 172 | } else { 173 | returnObject = res.json(updateMessage(payload, data)); 174 | } 175 | return returnObject; 176 | }); 177 | }).catch(function(err) { 178 | console.log(JSON.stringify(err, null, 2)); 179 | }); 180 | } 181 | 182 | /** 183 | * Enable logging Must add an instance of the Cloudant NoSQL DB to the 184 | * application in BlueMix and add the Cloudant credentials to the application's 185 | * user-defined Environment Variables. 186 | */ 187 | if (cloudantUrl) { 188 | // If logging has been enabled (as signalled by the presence of the 189 | // cloudantUrl) then the 190 | // app developer must also specify a LOG_USER and LOG_PASS env vars. 191 | if (!process.env.LOG_USER || !process.env.LOG_PASS) { 192 | throw new Error('LOG_USER OR LOG_PASS not defined, both required to enable logging!'); 193 | } 194 | // add basic auth to the endpoints to retrieve the logs! 195 | var auth = basicAuth(process.env.LOG_USER, process.env.LOG_PASS); 196 | // If the cloudantUrl has been configured then we will want to set up a nano 197 | // client 198 | var nano = require('nano')(cloudantUrl); 199 | // add a new API which allows us to retrieve the logs (note this is not 200 | // secure) 201 | nano.db.get('food_coach', function(err) { 202 | if (err) { 203 | console.error(err); 204 | nano.db.create('food_coach', function(errCreate) { 205 | console.error(errCreate); 206 | logs = nano.db.use('food_coach'); 207 | }); 208 | } else { 209 | logs = nano.db.use('food_coach'); 210 | } 211 | }); 212 | 213 | // Endpoint which allows deletion of db 214 | app.post('/clearDb', auth, function(req, res) { 215 | nano.db.destroy('food_coach', function() { 216 | nano.db.create('food_coach', function() { 217 | logs = nano.db.use('food_coach'); 218 | }); 219 | }); 220 | return res.json({'message': 'Clearing db'}); 221 | }); 222 | 223 | // Endpoint which allows conversation logs to be fetched 224 | // csv - user input, conversation_id, timestamp 225 | 226 | app.get('/chats', auth, function(req, res) { 227 | logs.list({ 228 | include_docs: true, 229 | 'descending': true 230 | }, function(err, body) { 231 | console.error(err); 232 | // download as CSV 233 | var csv = []; 234 | csv.push([ 235 | 'Id', 236 | 'Question', 237 | 'Intent', 238 | 'Confidence', 239 | 'Entity', 240 | 'Emotion', 241 | 'Output', 242 | 'Time' 243 | ]); 244 | body.rows.sort(function(a, b) { 245 | if (a && b && a.doc && b.doc) { 246 | var date1 = new Date(a.doc.time); 247 | var date2 = new Date(b.doc.time); 248 | var t1 = date1.getTime(); 249 | var t2 = date2.getTime(); 250 | var aGreaterThanB = t1 > t2; 251 | var equal = t1 === t2; 252 | if (aGreaterThanB) { 253 | return 1; 254 | } 255 | return equal 256 | ? 0 257 | : -1; 258 | } 259 | }); 260 | body.rows.forEach(function(row) { 261 | var question = ''; 262 | var intent = ''; 263 | var confidence = 0; 264 | var time = ''; 265 | var entity = ''; 266 | var outputText = ''; 267 | var emotion = ''; 268 | var id = ''; 269 | 270 | if (row.doc) { 271 | var doc = row.doc; 272 | if (doc.response.context) { 273 | id = doc.response.context.conversation_id; 274 | } 275 | 276 | if (doc.response.context && doc.response.context.user) { 277 | emotion = doc.response.context.user.tone.emotion.current; 278 | } 279 | 280 | if (doc.request && doc.request.input) { 281 | question = doc.request.input.text; 282 | } 283 | if (doc.response) { 284 | intent = ''; 285 | if (doc.response.intents && doc.response.intents.length > 0) { 286 | intent = doc.response.intents[0].intent; 287 | confidence = doc.response.intents[0].confidence; 288 | } 289 | entity = ''; 290 | if (doc.response.entities && doc.response.entities.length > 0) { 291 | entity = doc.response.entities[0].entity + ' : ' + doc.response.entities[0].value; 292 | } 293 | outputText = ''; 294 | if (doc.response.output && doc.response.output.text) { 295 | outputText = doc.response.output.text.join(' '); 296 | } 297 | } 298 | time = new Date(doc.time).toLocaleString(); 299 | } 300 | csv.push([ 301 | id, 302 | question, 303 | intent, 304 | confidence, 305 | entity, 306 | emotion, 307 | outputText, 308 | time 309 | ]); 310 | }); 311 | res.json(csv); 312 | }); 313 | }); 314 | } 315 | 316 | module.exports = app; 317 | -------------------------------------------------------------------------------- /public/fonts/ibm-glyph.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /training/ce-workspace.json: -------------------------------------------------------------------------------- 1 | {"name":"Tone CE Conversation Demo","created":"2017-08-16T19:37:59.347Z","intents":[{"intent":"refund","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","examples":[{"text":"I would like my money back","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"return it","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"refund","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"Id like to get my money returned to me","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"Give me a refund","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"}],"description":null},{"intent":"no","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","examples":[{"text":"no","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"nope","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"no thanks","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"no way","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"i dont want that","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"}],"description":null},{"intent":"greeting","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","examples":[{"text":"hi","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"hello","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"hey","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"hiya","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"hola","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"bonjour","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"}],"description":null},{"intent":"restart","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","examples":[{"text":"restart","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"can we restart","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"redo","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"do this again","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"replay","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"}],"description":null},{"intent":"thanks","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","examples":[{"text":"thanks","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"thank you","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"gracias","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"i appreciate it","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"thanks a lot","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"}],"description":null},{"intent":"yes","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","examples":[{"text":"ok","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"yes","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"yep","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"yeah","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"yup","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"ya","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"confirm","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"i do","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"}],"description":null},{"intent":"tradeIn","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","examples":[{"text":"trade","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"trade in","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"trade it in","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"I want a different laptop","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"I'd like to get a different model instead","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"I want to replace it","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"},{"text":"I want to switch it for a new one","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"}],"description":null}],"updated":"2017-08-17T18:18:32.929Z","entities":[{"entity":"size","values":[{"value":"size","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"synonyms":[]}],"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"description":null,"fuzzy_match":true},{"entity":"design","values":[{"value":"design","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"synonyms":[]}],"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"description":null,"fuzzy_match":true},{"entity":"weight","values":[{"value":"weight","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"synonyms":[]}],"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"description":null,"fuzzy_match":true}],"language":"en","metadata":{"api_version":{"major_version":"v1","minor_version":"2017-05-26"}},"description":"Discuss your recent laptop purchase with this tone-aware conversation bot.","dialog_nodes":[{"title":null,"output":{},"parent":null,"context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":{"behavior":"jump_to","selector":"body","dialog_node":"Hello"},"conditions":"#restart","description":null,"dialog_node":"Restart","previous_sibling":"Youre Welcome"},{"title":null,"output":{},"parent":"None Satisfied Polite or Sympathetic","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":{"behavior":"jump_to","selector":"body","dialog_node":"Excited"},"conditions":"#no","description":null,"dialog_node":"node_2_1500475857067","previous_sibling":"node_1_1500475762159"},{"title":null,"output":{},"parent":"None Satisfied Polite or Sympathetic","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":{"behavior":"jump_to","selector":"body","dialog_node":"Sad Frustrated or Impolite"},"conditions":"#yes","description":null,"dialog_node":"node_1_1500475762159","previous_sibling":null},{"title":null,"output":{},"parent":"Sad Frustrated or Impolite","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":null,"conditions":"$user.tone.emotion.current==\"frustrated\" || $user.tone.emotion.current==\"impolite\"","description":null,"dialog_node":"Frustrated or Impolite","previous_sibling":null},{"title":null,"output":{"text":{"values":["Alright. I am sorry that your purchase is not meeting your standards. Launch this form to initiate the refund process and we will work to get that processed for you immediately."],"selection_policy":"sequential"}},"parent":"Frustrated or Impolite","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":null,"conditions":"#refund","description":null,"dialog_node":"node_4_1500423448045","previous_sibling":"Yes"},{"title":null,"output":{"text":{"values":["Alright. I am sorry that your purchase is not meeting your standards. Launch this form to initiate the refund process and we will work to get that processed or you immediately."],"selection_policy":"sequential"}},"parent":"None or Sad","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":null,"conditions":"#yes","description":null,"dialog_node":"node_6_1500423608635","previous_sibling":null},{"title":null,"output":{"text":{"values":["Alright. If it is necessary, you can also submit a refund request. Would you like to move forward with a refund request?"],"selection_policy":"sequential"}},"parent":"None or Sad","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":null,"conditions":"#no","description":null,"dialog_node":"node_7_1500423648970","previous_sibling":"node_6_1500423608635"},{"title":null,"output":{"text":{"values":["Alright, noted. Please let us know if you need anything from us!"],"selection_policy":"sequential"}},"parent":"Sad Frustrated or Impolite","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":null,"conditions":"$user.tone.emotion.current==\"satisfied\" || $user.tone.emotion.current==\"sympathetic\" || $user.tone.emotion.current==\"polite\"","description":null,"dialog_node":"Excited Satisfied Polite or Sympathetic","previous_sibling":"None or Sad"},{"title":null,"output":{"text":{"values":["Alright, noted. Please let us know if you need anything from us! Enjoy your purchase!"],"selection_policy":"sequential"}},"parent":"Excited","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-17T18:00:40.285Z","metadata":null,"next_step":null,"conditions":"true","description":null,"dialog_node":"node_13_1500424150804","previous_sibling":"node_12_1500423828225"},{"title":null,"output":{"text":{"values":["Awesome. I'm glad you like the look of the laptop! If you still need a case for it, check out our recommended cases for this unit. You’ll have a discounted rate since you purchased your laptop from us."],"selection_policy":"sequential"}},"parent":"Excited","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-17T18:18:26.048Z","metadata":null,"next_step":null,"conditions":"@design","description":null,"dialog_node":"node_10_1500423776314","previous_sibling":null},{"title":null,"output":{"text":{"values":["Got it. Is anything not meeting your expectations?"],"selection_policy":"sequential"}},"parent":null,"context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-17T18:08:40.223Z","metadata":null,"next_step":null,"conditions":"$user.tone.emotion.current==\"none\" || $user.tone.emotion.current==\"polite\" || $user.tone.emotion.current==\"sympathetic\" || $user.tone.emotion.current==\"satisfied\"","description":null,"dialog_node":"None Satisfied Polite or Sympathetic","previous_sibling":"Sad Frustrated or Impolite"},{"title":null,"output":{"text":{"values":["Great. I understand the size of the laptop is important to you. If you’re using this laptop on the go, check out these portable wifi routers customers have often bought with it!"],"selection_policy":"sequential"}},"parent":"Excited","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-17T18:18:32.929Z","metadata":null,"next_step":null,"conditions":"@size","description":null,"dialog_node":"node_11_1500423813089","previous_sibling":"node_10_1500423776314"},{"title":null,"output":{"text":{"values":["Great to hear! Other customers have really liked the sleek design, compact size, and light weight of this laptop. What do you like most about it?"],"selection_policy":"sequential"}},"parent":null,"context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-17T17:59:40.019Z","metadata":null,"next_step":null,"conditions":"$user.tone.emotion.current==\"excited\"","description":null,"dialog_node":"Excited","previous_sibling":"Restart"},{"title":null,"output":{"text":{"values":["Hi, I see you purchased a new laptop two weeks ago. How has your experience been so far?"],"selection_policy":"sequential"}},"parent":null,"context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":null,"conditions":"conversation_start","description":null,"dialog_node":"Hello","previous_sibling":null},{"title":null,"output":{"text":{"values":["If you would like to start the conversation over, type \"restart.\""],"selection_policy":"sequential"}},"parent":"node_10_1500423776314","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":null,"conditions":null,"description":null,"dialog_node":"Restart Prompt","previous_sibling":null},{"title":null,"output":{"text":{"values":["If you would like to start the conversation over, type \"restart.\""],"selection_policy":"sequential"}},"parent":"node_11_1500423813089","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":null,"conditions":null,"description":null,"dialog_node":"node_2_1501181903899","previous_sibling":null},{"title":null,"output":{"text":{"values":["If you would like to start the conversation over, type \"restart.\""],"selection_policy":"sequential"}},"parent":"node_12_1500423828225","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":null,"conditions":null,"description":null,"dialog_node":"node_4_1501181940992","previous_sibling":null},{"title":null,"output":{"text":{"values":["If you would like to start the conversation over, type \"restart.\""],"selection_policy":"sequential"}},"parent":"node_13_1500424150804","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":null,"conditions":null,"description":null,"dialog_node":"node_5_1501181967798","previous_sibling":null},{"title":null,"output":{"text":{"values":["If you would like to start the conversation over, type \"restart.\""],"selection_policy":"sequential"}},"parent":"node_4_1500423448045","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":null,"conditions":null,"description":null,"dialog_node":"node_6_1501181993949","previous_sibling":null},{"title":null,"output":{"text":{"values":["If you would like to start the conversation over, type \"restart.\""],"selection_policy":"sequential"}},"parent":"node_5_1500423462327","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":null,"conditions":null,"description":null,"dialog_node":"node_7_1501182091085","previous_sibling":null},{"title":null,"output":{"text":{"values":["I’m sorry to hear that. Can you share any more detail about the experience?"],"selection_policy":"sequential"}},"parent":null,"context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-17T18:00:45.041Z","metadata":null,"next_step":null,"conditions":"$user.tone.emotion.current==\"sad\" || $user.tone.emotion.current==\"frustrated\" || $user.tone.emotion.current==\"impolite\"","description":null,"dialog_node":"Sad Frustrated or Impolite","previous_sibling":"Excited"},{"title":null,"output":{"text":{"values":["I see. I’m glad the weight of the laptop is working well for you. Plus, for sharing your feedback, we’re offering you 10% off of any wireless mouse. Choose your mouse here."],"selection_policy":"sequential"}},"parent":"Excited","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-17T18:00:35.225Z","metadata":null,"next_step":null,"conditions":"@weight","description":null,"dialog_node":"node_12_1500423828225","previous_sibling":"node_11_1500423813089"},{"title":null,"output":{"text":{"values":["Okay, and thank you for the feedback. If you'd like, visit our customer support page to explain your issue in detail so we can provide you with a suitable solution as soon as possible."],"selection_policy":"sequential"}},"parent":"Frustrated or Impolite","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":null,"conditions":"true","description":null,"dialog_node":"No","previous_sibling":"node_5_1500423462327"},{"title":null,"output":{"text":{"values":["Okay. Feel free to reach out via our r support page to explain your issue in detail, and we can make sure to get you a suitable solution as soon as possible"],"selection_policy":"sequential"}},"parent":"node_7_1500423648970","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":null,"conditions":"#no","description":null,"dialog_node":"node_9_1500423705533","previous_sibling":"node_8_1500423663681"},{"title":null,"output":{"text":{"values":["Okay, let’s get you a laptop that works better for you. View your options and submit a trade-in request, here."],"selection_policy":"sequential"}},"parent":"Frustrated or Impolite","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":null,"conditions":"#tradeIn","description":null,"dialog_node":"node_5_1500423462327","previous_sibling":"node_4_1500423448045"},{"title":null,"output":{"text":{"values":["Okay. Would you like to refund your laptop, or trade it in for a new one?"],"selection_policy":"sequential"}},"parent":"Frustrated or Impolite","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":{"behavior":"jump_to","selector":"user_input","dialog_node":"node_4_1500423448045"},"conditions":"#yes","description":null,"dialog_node":"Yes","previous_sibling":null},{"title":null,"output":{"text":{"values":["Understood. I am sorry that your purchase is not meeting your standards. Let’s get you a laptop that works better for you. Launch this form to initiate the refund process and we will work to get that processed or you immediately."],"selection_policy":"sequential"}},"parent":"node_7_1500423648970","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":null,"conditions":"#yes","description":null,"dialog_node":"node_8_1500423663681","previous_sibling":null},{"title":null,"output":{"text":{"values":["You're welcome!"],"selection_policy":"sequential"}},"parent":null,"context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-17T18:13:26.888Z","metadata":null,"next_step":null,"conditions":"#thanks","description":null,"dialog_node":"Youre Welcome","previous_sibling":"Hello"},{"title":null,"output":{"text":{"values":["Your happiness is really important to us, and it sounds like you are disappointed with your experience. Would you be willing to trade your laptop in for another model?"],"selection_policy":"sequential"}},"parent":"Sad Frustrated or Impolite","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":null,"conditions":"$user.tone.emotion.current==\"none\" || $user.tone.emotion.current==\"sad\"","description":null,"dialog_node":"None or Sad","previous_sibling":"Frustrated or Impolite"},{"title":null,"output":{"text":{"values":["If you want to start a new conversation, hit refresh.","If you'd like to start the conversation over, refresh the page."],"selection_policy":"sequential"}},"parent":null,"context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z","metadata":null,"next_step":null,"conditions":"anything_else","description":null,"dialog_node":"Anything else","previous_sibling":"None Satisfied Polite or Sympathetic"},{"type":"response_condition","title":null,"output":{"text":{"values":["It sounds like you are frustrated about this purchase. Would you like to refund or trade in your laptop?"],"selection_policy":"sequential"}},"parent":"Frustrated or Impolite","context":null,"created":"2017-08-16T19:37:59.347Z","updated":"2017-08-17T18:00:52.036Z","metadata":null,"next_step":null,"conditions":"","description":null,"dialog_node":"node_7_1498711464686","previous_sibling":"No"}],"workspace_id":"f419d126-2025-4e3f-a33a-8b42e60323fe","counterexamples":[{"text":"great!","created":"2017-08-16T19:37:59.347Z","updated":"2017-08-16T19:37:59.347Z"}],"learning_opt_out":false} -------------------------------------------------------------------------------- /public/fonts/ibm-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | --------------------------------------------------------------------------------