├── .eslintignore ├── Procfile ├── .gitattributes ├── .cfignore ├── public ├── favicon.png ├── img │ ├── map-bank.png │ ├── Chat Button.png │ ├── Code Button.png │ └── marker_image.png ├── fonts │ └── roman │ │ ├── h-n-roman.woff │ │ └── h-n-roman.woff2 ├── js │ ├── global.js │ ├── common.js │ ├── api.js │ ├── payload.js │ └── conversation.js ├── conversation.svg ├── index.html └── css │ └── app.css ├── .gitignore ├── readme_images ├── demo.gif ├── copy_icon.png └── assistant-simple.gif ├── manifest.yml ├── health └── health.js ├── .env.example ├── .eslintrc.yml ├── .travis.yml ├── server.js ├── test ├── unit │ └── express.test.js └── integration │ └── test.mainpage.js ├── package.json ├── casper-runner.js ├── CONTRIBUTING.md ├── app.js ├── README.md ├── cloud_pak_for_data_quickstart ├── README.md └── openshift │ └── templates │ └── watson-assistant-quickstart.json └── LICENSE /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm start -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.cfignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | coverage 4 | npm-debug.log 5 | readme_images 6 | test 7 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/assistant-simple/HEAD/public/favicon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | temp/ 4 | .idea/ 5 | coverage 6 | .env 7 | npm-debug.log 8 | .jshintrc 9 | .DS_Store -------------------------------------------------------------------------------- /public/img/map-bank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/assistant-simple/HEAD/public/img/map-bank.png -------------------------------------------------------------------------------- /readme_images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/assistant-simple/HEAD/readme_images/demo.gif -------------------------------------------------------------------------------- /public/img/Chat Button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/assistant-simple/HEAD/public/img/Chat Button.png -------------------------------------------------------------------------------- /public/img/Code Button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/assistant-simple/HEAD/public/img/Code Button.png -------------------------------------------------------------------------------- /public/img/marker_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/assistant-simple/HEAD/public/img/marker_image.png -------------------------------------------------------------------------------- /readme_images/copy_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/assistant-simple/HEAD/readme_images/copy_icon.png -------------------------------------------------------------------------------- /manifest.yml: -------------------------------------------------------------------------------- 1 | applications: 2 | - name: watson-assistant-simple 3 | command: npm start 4 | path: . 5 | memory: 256M 6 | instances: 1 7 | -------------------------------------------------------------------------------- /public/fonts/roman/h-n-roman.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/assistant-simple/HEAD/public/fonts/roman/h-n-roman.woff -------------------------------------------------------------------------------- /public/fonts/roman/h-n-roman.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/assistant-simple/HEAD/public/fonts/roman/h-n-roman.woff2 -------------------------------------------------------------------------------- /readme_images/assistant-simple.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/assistant-simple/HEAD/readme_images/assistant-simple.gif -------------------------------------------------------------------------------- /health/health.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | module.exports = function (app) { 4 | const router = express.Router(); 5 | 6 | router.get('/', function (req, res) { 7 | res.json({ 8 | status: 'UP' 9 | }); 10 | }); 11 | 12 | app.use('/health', router); 13 | }; 14 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Environment variables 2 | ASSISTANT_URL=https://gateway.watsonplatform.net/assistant/api 3 | ASSISTANT_ID= 4 | 5 | # IBM Cloud connection parameters 6 | ASSISTANT_IAM_APIKEY= 7 | ASSISTANT_IAM_URL= 8 | 9 | # Cloud Pak for Data connection parameters 10 | BEARER_TOKEN= 11 | DISABLE_SSL_VERIFICATION=false -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | node: true 4 | mocha: true 5 | es6: true 6 | extends: 'eslint:recommended' 7 | rules: 8 | indent: 9 | - error 10 | - 2 11 | linebreak-style: 12 | - error 13 | - unix 14 | quotes: 15 | - error 16 | - single 17 | semi: 18 | - error 19 | - always 20 | -------------------------------------------------------------------------------- /public/js/global.js: -------------------------------------------------------------------------------- 1 | 2 | /* global ConversationPanel: true, PayloadPanel: true*/ 3 | /* eslint no-unused-vars: "off" */ 4 | 5 | // Other JS files required to be loaded first: apis.js, conversation.js, payload.js 6 | (function() { 7 | // Initialize all modules 8 | ConversationPanel.init(); 9 | PayloadPanel.init(); 10 | })(); 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | dist: trusty 3 | sudo: required 4 | node_js: 12 5 | script: 6 | - npm run test 7 | 8 | cache: 9 | directories: 10 | - node_modules 11 | env: 12 | global: 13 | - BX_APP=watson-assistant-simple 14 | - BX_API=https://api.ng.bluemix.net 15 | - BX_ORGANIZATION=WatsonPlatformServices 16 | - BX_SPACE=demos 17 | before_deploy: npm install -g bx-blue-green 18 | deploy: 19 | - provider: script 20 | skip_cleanup: true 21 | script: bx-blue-green-travis 22 | on: 23 | branch: master 24 | repo: watson-developer-cloud/assistant-simple 25 | - provider: script 26 | skip_cleanup: true 27 | script: npx semantic-release 28 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | /** 3 | * Copyright 2015 IBM Corp. All Rights Reserved. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | require('dotenv').config({silent: true}); 21 | 22 | var server = require('./app'); 23 | var port = process.env.PORT || 3000; 24 | 25 | server.listen(port, function() { 26 | // eslint-disable-next-line 27 | console.log('Server running on port: %d', port); 28 | }); 29 | -------------------------------------------------------------------------------- /test/unit/express.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 IBM Corp. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var path = require('path'); 18 | // load default variables for testing 19 | require('dotenv').config({ path: path.join(__dirname, '../../.env.example') }); 20 | 21 | var app = require('../../app'); 22 | var request = require('supertest'); 23 | 24 | describe('express', function() { 25 | it('load home page when GET /', function() { 26 | return request(app).get('/').expect(200); 27 | }); 28 | 29 | it('404 when page not found', function() { 30 | return request(app).get('/foo/bar').expect(404); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ibm-watson/assistant-simple", 3 | "description": "A simple Node.js based web app which shows how to use the Watson Assistant API to recognize user intents.", 4 | "version": "0.1.1", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "test-integration": "casperjs test ./test/integration/test.*.js", 9 | "test-integration-runner": "NODE_ENV=test node casper-runner.js", 10 | "test": "npm run lint && npm run test-integration-runner", 11 | "test-unit": "jest test/unit --coverage", 12 | "lint": "eslint .", 13 | "autofix": "eslint --fix .", 14 | "codecov": "npm run test && (codecov || true)" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/watson-developer-cloud/assistant-simple.git" 19 | }, 20 | "license": "Apache-2.0", 21 | "dependencies": { 22 | "body-parser": "^1.19.0", 23 | "dotenv": "^6.2.0", 24 | "express": "^4.17.1", 25 | "ibm-watson": "^5.1.0" 26 | }, 27 | "publishConfig": { 28 | "registry": "https://registry.npmjs.org/", 29 | "access": "public" 30 | }, 31 | "engines": { 32 | "node": ">=12" 33 | }, 34 | "engineStrict": true, 35 | "devDependencies": { 36 | "babel-eslint": "^10.0.3", 37 | "casperjs": "^1.1.4", 38 | "codecov": "^3.6.1", 39 | "eslint": "^5.14.0", 40 | "jest": "^24.9.0", 41 | "phantomjs-prebuilt": "^2.1.16", 42 | "supertest": "^4.0.2" 43 | }, 44 | "bugs": { 45 | "url": "https://github.com/watson-developer-cloud/assistant-simple/issues" 46 | }, 47 | "homepage": "https://github.com/watson-developer-cloud/assistant-simple#readme", 48 | "directories": { 49 | "test": "test" 50 | }, 51 | "author": "" 52 | } 53 | -------------------------------------------------------------------------------- /casper-runner.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 IBM Corp. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | require('dotenv').config({ silent: true }); 18 | 19 | if (!process.env.ASSISTANT_ID) { 20 | // eslint-disable-next-line 21 | console.warn('Skipping casper tests because ASSISTANT_ID is null'); 22 | return; 23 | } 24 | 25 | var spawn = require('child_process').spawn; 26 | var app = require('./app'); 27 | var port = 3000; 28 | 29 | var server = app.listen(port, function() { 30 | // eslint-disable-next-line no-console 31 | console.log('Server running on port: %d', port); 32 | 33 | function kill(code) { 34 | server.close(function() { 35 | // eslint-disable-next-line no-process-exit 36 | process.exit(code); 37 | }); 38 | } 39 | 40 | function runTests() { 41 | var casper = spawn('npm', ['run', 'test-integration']); 42 | casper.stdout.pipe(process.stdout); 43 | 44 | casper.on('error', function(error) { 45 | // eslint-disable-next-line 46 | console.error(error); 47 | server.close(function() { 48 | process.exit(1); 49 | }); 50 | }); 51 | 52 | casper.on('close', kill); 53 | } 54 | 55 | runTests(); 56 | }); 57 | -------------------------------------------------------------------------------- /test/integration/test.mainpage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 IBM Corp. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /* eslint no-undef: 0 */ 17 | 18 | casper.test.begin('Watson Assistant simple Demo', 5, function suite(test) { 19 | var baseHost = 'http://localhost:3000'; 20 | 21 | function testWelcomeMessageExists() { 22 | casper.waitForSelector('.from-watson', function () { 23 | test.assertExists('.message-inner', 'Welcome message received'); 24 | }); 25 | } 26 | 27 | function testEnterMessageClick() { 28 | casper.then(function () { 29 | this.sendKeys('#textInput', 'I want to make a credit card payment'); 30 | this.sendKeys('#textInput', casper.page.event.key.Enter); 31 | }); 32 | casper.waitForSelector('.from-user', function () { 33 | test.assertExists('.message-inner', 'Message sent'); 34 | test.assertTextExists('I want to make a credit card payment', 'Message in bubble'); 35 | casper.waitForText('I can help you with credit card payments'); 36 | }); 37 | } 38 | 39 | casper.start(baseHost, function () { 40 | casper.test.comment('Starting Testing'); 41 | test.assertHttpStatus(200, 'assistant-simple is up'); 42 | test.assertTitle('Watson Assistant Chat App', 'Title is correct'); 43 | 44 | testWelcomeMessageExists(); 45 | testEnterMessageClick(); 46 | }); 47 | 48 | casper.run(function () { 49 | test.done(); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /public/conversation.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Watson Assistant Chat App 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 |
19 |
20 |
21 |
22 |
23 |

24 | 28 |
29 | * This system is for demonstration purposes only and is not intended to process Personal Data. No Personal 30 | Data is to be entered 31 | into this system as it may not have the necessary controls in place to meet the requirements of the General 32 | Data Protection 33 | Regulation (EU) 2016/679. 34 |
35 |
36 | By using this application, you agree to the  37 | 38 | Terms of Use 39 | 40 |
41 |
42 |
43 |
44 |
45 | Type something to see the output 46 |
47 |
48 |
49 |
50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /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 | // Take in JSON object and build a DOM element out of it 14 | // (Limited in scope, cannot necessarily create arbitrary DOM elements) 15 | // JSON Example: 16 | // { 17 | // "tagName": "div", 18 | // "text": "Hello World!", 19 | // "className": ["aClass", "bClass"], 20 | // "attributes": [{ 21 | // "name": "onclick", 22 | // "value": "alert("Hi there!")" 23 | // }], 24 | // "children: [{other similarly structured JSON objects...}, {...}] 25 | // } 26 | function buildDomElementFromJson(domJson) { 27 | // Create a DOM element with the given tag name 28 | var element = document.createElement(domJson.tagName); 29 | 30 | // Fill the "content" of the element 31 | if (domJson.text) { 32 | element.innerHTML = domJson.text; 33 | } else if (domJson.html) { 34 | element.insertAdjacentHTML('beforeend', domJson.html); 35 | } 36 | 37 | // Add classes to the element 38 | if (domJson.classNames) { 39 | for (var i = 0; i < domJson.classNames.length; i++) { 40 | element.classList.add(domJson.classNames[i]); 41 | } 42 | } 43 | // Add attributes to the element 44 | if (domJson.attributes) { 45 | for (var j = 0; j < domJson.attributes.length; j++) { 46 | var currentAttribute = domJson.attributes[j]; 47 | element.setAttribute(currentAttribute.name, currentAttribute.value); 48 | } 49 | } 50 | // Add children elements to the element 51 | if (domJson.children) { 52 | for (var k = 0; k < domJson.children.length; k++) { 53 | var currentChild = domJson.children[k]; 54 | element.appendChild(buildDomElementFromJson(currentChild)); 55 | } 56 | } 57 | return element; 58 | } 59 | 60 | // Trigger an event to fire 61 | function fireEvent(element, event) { 62 | var evt; 63 | if (document.createEventObject) { 64 | // dispatch for IE 65 | evt = document.createEventObject(); 66 | return element.fireEvent('on' + event, evt); 67 | } 68 | // otherwise, dispatch for Firefox, Chrome + others 69 | evt = document.createEvent('HTMLEvents'); 70 | evt.initEvent(event, true, true); // event type,bubbling,cancelable 71 | return !element.dispatchEvent(evt); 72 | } 73 | 74 | // A function that runs a for each loop on a List, running the callback function for each one 75 | function listForEach(list, callback) { 76 | for (var i = 0; i < list.length; i++) { 77 | callback.call(null, list[i]); 78 | } 79 | } 80 | }()); -------------------------------------------------------------------------------- /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 | var sessionEndpoint = '/api/session'; 9 | 10 | var sessionId = null; 11 | 12 | // Publicly accessible methods defined 13 | return { 14 | sendRequest: sendRequest, 15 | getSessionId: getSessionId, 16 | 17 | // The request/response getters/setters are defined here to prevent internal methods 18 | // from calling the methods without any of the callbacks that are added elsewhere. 19 | getRequestPayload: function() { 20 | return requestPayload; 21 | }, 22 | setRequestPayload: function(newPayloadStr) { 23 | requestPayload = JSON.parse(newPayloadStr); 24 | }, 25 | getResponsePayload: function() { 26 | return responsePayload; 27 | }, 28 | setResponsePayload: function(newPayloadStr) { 29 | responsePayload = JSON.parse(newPayloadStr).result; 30 | }, 31 | setErrorPayload: function() { 32 | } 33 | }; 34 | 35 | function getSessionId(callback) { 36 | var http = new XMLHttpRequest(); 37 | http.open('GET', sessionEndpoint, true); 38 | http.setRequestHeader('Content-type', 'application/json'); 39 | http.onreadystatechange = function () { 40 | if (http.readyState === XMLHttpRequest.DONE) { 41 | let res = JSON.parse(http.response); 42 | sessionId = res.result.session_id; 43 | callback(); 44 | } 45 | }; 46 | http.send(); 47 | } 48 | 49 | 50 | // Send a message request to the server 51 | function sendRequest(text) { 52 | // Build request payload 53 | var payloadToWatson = { 54 | session_id: sessionId 55 | }; 56 | 57 | payloadToWatson.input = { 58 | message_type: 'text', 59 | text: text, 60 | }; 61 | 62 | 63 | // Built http request 64 | var http = new XMLHttpRequest(); 65 | http.open('POST', messageEndpoint, true); 66 | http.setRequestHeader('Content-type', 'application/json'); 67 | http.onreadystatechange = function() { 68 | if (http.readyState === XMLHttpRequest.DONE && http.status === 200 && http.responseText) { 69 | Api.setResponsePayload(http.responseText); 70 | } else if (http.readyState === XMLHttpRequest.DONE && http.status !== 200) { 71 | Api.setErrorPayload({ 72 | 'output': { 73 | 'generic': [ 74 | { 75 | 'response_type': 'text', 76 | 'text': 'I\'m having trouble connecting to the server, please refresh the page' 77 | } 78 | ], 79 | } 80 | }); 81 | } 82 | }; 83 | 84 | var params = JSON.stringify(payloadToWatson); 85 | // Stored in variable (publicly visible through Api.getRequestPayload) 86 | // to be used throughout the application 87 | if (Object.getOwnPropertyNames(payloadToWatson).length !== 0) { 88 | Api.setRequestPayload(params); 89 | } 90 | 91 | // Send request 92 | http.send(params); 93 | } 94 | }()); 95 | -------------------------------------------------------------------------------- /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/assistant-simple/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. -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2015 IBM Corp. All Rights Reserved. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | var express = require('express'); // app server 21 | var bodyParser = require('body-parser'); // parser for post requests 22 | var AssistantV2 = require('ibm-watson/assistant/v2'); // watson sdk 23 | const { IamAuthenticator, BearerTokenAuthenticator } = require('ibm-watson/auth'); 24 | 25 | var app = express(); 26 | require('./health/health')(app); 27 | 28 | // Bootstrap application settings 29 | app.use(express.static('./public')); // load UI from public folder 30 | app.use(bodyParser.json()); 31 | 32 | // Create the service wrapper 33 | 34 | let authenticator; 35 | if (process.env.ASSISTANT_IAM_APIKEY) { 36 | authenticator = new IamAuthenticator({ 37 | apikey: process.env.ASSISTANT_IAM_APIKEY 38 | }); 39 | } else if (process.env.BEARER_TOKEN) { 40 | authenticator = new BearerTokenAuthenticator({ 41 | bearerToken: process.env.BEARER_TOKEN 42 | }); 43 | } 44 | 45 | var assistant = new AssistantV2({ 46 | version: '2019-02-28', 47 | authenticator: authenticator, 48 | url: process.env.ASSISTANT_URL, 49 | disableSslVerification: process.env.DISABLE_SSL_VERIFICATION === 'true' ? true : false 50 | }); 51 | 52 | // Endpoint to be call from the client side 53 | app.post('/api/message', function(req, res) { 54 | let assistantId = process.env.ASSISTANT_ID || ''; 55 | if (!assistantId || assistantId === '') { 56 | return res.json({ 57 | output: { 58 | text: 59 | 'The app has not been configured with a ASSISTANT_ID environment variable. Please refer to the ' + 60 | 'README documentation on how to set this variable.
' + 61 | 'Once a workspace has been defined the intents may be imported from ' + 62 | 'here in order to get a working application.', 63 | }, 64 | }); 65 | } 66 | 67 | var textIn = ''; 68 | 69 | if (req.body.input) { 70 | textIn = req.body.input.text; 71 | } 72 | 73 | var payload = { 74 | assistantId: assistantId, 75 | sessionId: req.body.session_id, 76 | input: { 77 | message_type: 'text', 78 | text: textIn, 79 | }, 80 | }; 81 | 82 | // Send the input to the assistant service 83 | assistant.message(payload, function(err, data) { 84 | if (err) { 85 | const status = err.code !== undefined && err.code > 0 ? err.code : 500; 86 | return res.status(status).json(err); 87 | } 88 | 89 | return res.json(data); 90 | }); 91 | }); 92 | 93 | app.get('/api/session', function(req, res) { 94 | assistant.createSession( 95 | { 96 | assistantId: process.env.ASSISTANT_ID || '{assistant_id}', 97 | }, 98 | function(error, response) { 99 | if (error) { 100 | return res.send(error); 101 | } else { 102 | return res.send(response); 103 | } 104 | } 105 | ); 106 | }); 107 | 108 | module.exports = app; 109 | -------------------------------------------------------------------------------- /public/js/payload.js: -------------------------------------------------------------------------------- 1 | // The PayloadPanel 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, 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.querySelector(settings.selectors.payloadColumn); 37 | if (element.classList.contains('full')) { 38 | element.classList.remove('full'); 39 | payloadColumn.classList.remove('full'); 40 | } else { 41 | element.classList.add('full'); 42 | payloadColumn.classList.add('full'); 43 | } 44 | } 45 | 46 | // Set up callbacks on payload setters in Api module 47 | // This causes the displayPayload function to be called when messages are sent / received 48 | function payloadUpdateSetup() { 49 | var currentRequestPayloadSetter = Api.setRequestPayload; 50 | Api.setRequestPayload = function(newPayloadStr) { 51 | currentRequestPayloadSetter.call(Api, newPayloadStr); 52 | displayPayload(settings.payloadTypes.request); 53 | }; 54 | 55 | var currentResponsePayloadSetter = Api.setResponsePayload; 56 | Api.setResponsePayload = function(newPayload) { 57 | currentResponsePayloadSetter.call(Api, newPayload); 58 | displayPayload(settings.payloadTypes.response); 59 | }; 60 | } 61 | 62 | // Display a request or response payload that has just been sent/received 63 | function displayPayload(typeValue) { 64 | var isRequest = checkRequestType(typeValue); 65 | if (isRequest !== null) { 66 | // Create new payload DOM element 67 | var payloadDiv = buildPayloadDomElement(isRequest); 68 | var payloadElement = document.querySelector(isRequest 69 | ? settings.selectors.payloadRequest : settings.selectors.payloadResponse); 70 | // Clear out payload holder element 71 | while (payloadElement.lastChild) { 72 | payloadElement.removeChild(payloadElement.lastChild); 73 | } 74 | // Add new payload element 75 | payloadElement.appendChild(payloadDiv); 76 | // Set the horizontal rule to show (if request and response payloads both exist) 77 | // or to hide (otherwise) 78 | var payloadInitial = document.querySelector(settings.selectors.payloadInitial); 79 | if (Api.getRequestPayload() || Api.getResponsePayload()) { 80 | payloadInitial.classList.add('hide'); 81 | } 82 | } 83 | } 84 | 85 | // Checks if the given typeValue matches with the request "name", the response "name", or neither 86 | // Returns true if request, false if response, and null if neither 87 | // Used to keep track of what type of payload we're currently working with 88 | function checkRequestType(typeValue) { 89 | if (typeValue === settings.payloadTypes.request) { 90 | return true; 91 | } else if (typeValue === settings.payloadTypes.response) { 92 | return false; 93 | } 94 | return null; 95 | } 96 | 97 | // Constructs new DOM element to use in displaying the payload 98 | function buildPayloadDomElement(isRequest) { 99 | var payloadPrettyString = jsonPrettyPrint(isRequest 100 | ? Api.getRequestPayload() : Api.getResponsePayload()); 101 | 102 | var payloadJson = { 103 | 'tagName': 'div', 104 | 'children': [{ 105 | //
106 | 'tagName': 'div', 107 | 'text': isRequest ? 'User input' : 'Watson understands', 108 | 'classNames': ['header-text'] 109 | }, { 110 | //
111 | 'tagName': 'div', 112 | 'classNames': ['code-line', 'responsive-columns-wrapper'], 113 | 'children': [{ 114 | //
115 | 'tagName': 'pre', 116 | 'text': createLineNumberString((payloadPrettyString.match(/\n/g) || []).length + 1), 117 | 'classNames': ['line-numbers'] 118 | }, { 119 | //
120 | 'tagName': 'pre', 121 | 'classNames': ['payload-text', 'responsive-column'], 122 | 'html': payloadPrettyString 123 | }] 124 | }] 125 | }; 126 | 127 | return Common.buildDomElement(payloadJson); 128 | } 129 | 130 | // Format (payload) JSON to make it more readable 131 | function jsonPrettyPrint(json) { 132 | if (json === null) { 133 | return ''; 134 | } 135 | var convert = JSON.stringify(json, null, 2); 136 | 137 | convert = convert.replace(/&/g, '&').replace(//g, '>'); 139 | convert = convert 140 | .replace( 141 | /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)/g, 142 | function(match) { 143 | var cls = 'number'; 144 | if (/^"/.test(match)) { 145 | if (/:$/.test(match)) { 146 | cls = 'key'; 147 | } else { 148 | cls = 'string'; 149 | } 150 | } else if (/true|false/.test(match)) { 151 | cls = 'boolean'; 152 | } else if (/null/.test(match)) { 153 | cls = 'null'; 154 | } 155 | return '' + match + ''; 156 | }); 157 | return convert; 158 | } 159 | 160 | // Used to generate a string of consecutive numbers separated by new lines 161 | // - used as line numbers for displayed JSON 162 | function createLineNumberString(numberOfLines) { 163 | var lineString = ''; 164 | var prefix = ''; 165 | for (var i = 1; i <= numberOfLines; i++) { 166 | lineString += prefix; 167 | lineString += i; 168 | prefix = '\n'; 169 | } 170 | return lineString; 171 | } 172 | }()); 173 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

🚀 Watson Assistant (formerly Conversation) Sample Application

2 |

This Node.js app demonstrates the Watson Assistant service in a simple interface engaging in a series of simple simulated banking tasks.

3 |

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

11 |

12 | 13 | ![Demo](readme_images/demo.gif) 14 | 15 | You can view a [demo][demo_url] of this app. 16 | 17 | Please note this app uses the [Watson Assistant V2 API](https://cloud.ibm.com/apidocs/assistant-v2#introduction). To access a version of the V1 app, you can go to [v1.4.1](https://github.com/watson-developer-cloud/assistant-simple/releases/tag/v1.4.1). 18 | 19 | If you need more information about the V1 API, you can go to the [Watson Assistant V1 API page](https://cloud.ibm.com/apidocs/assistant#introduction). 20 | 21 | 22 | ## Prerequisites 23 | 24 | 1. Sign up for an [IBM Cloud account](https://cloud.ibm.com/registration/). 25 | 1. Download the [IBM Cloud CLI](https://cloud.ibm.com/docs/cli/index.html#overview). 26 | 1. Create an instance of the Watson Assistant service and get your credentials: 27 | - Go to the [Watson Assistant](https://cloud.ibm.com/catalog/services/conversation) page in the IBM Cloud Catalog. 28 | - Log in to your IBM Cloud account. 29 | - Click **Create**. 30 | - Click **Show** to view the service credentials. 31 | - Copy the `apikey` value, or copy the `username` and `password` values if your service instance doesn't provide an `apikey`. 32 | - Copy the `url` value. 33 | 34 | ## Configuring the application 35 | 36 | 1. In your IBM Cloud console, open the Watson Assistant service instance 37 | 38 | 2. Click the **Import workspace** icon in the Watson Assistant service tool. Specify the location of the workspace JSON file in your local copy of the app project: 39 | 40 | `/training/bank_simple_workspace.json` 41 | 42 | 3. Select **Everything (Intents, Entities, and Dialog)** and then click **Import**. The car dashboard workspace is created. 43 | 44 | 4. Click the menu icon in the upper-right corner of the workspace tile, and then select **View details**. 45 | 46 | 5. Click the ![Copy](readme_images/copy_icon.png) icon to copy the workspace ID to the clipboard. 47 | 48 | ![Steps to get credentials](readme_images/assistant-simple.gif) 49 | 50 | 6. In the application folder, copy the *.env.example* file and create a file called *.env* 51 | 52 | ``` 53 | cp .env.example .env 54 | ``` 55 | 56 | 7. Open the *.env* file and add the service credentials that you obtained in the previous step. The Watson SDK automatically locates the correct environmental variables for either `username`, `password`, and `url` or the `apikey` and `url` credentials found in the *.env* file. 57 | 58 | Example *.env* file that configures the `apikey` and `url` for a Watson Assistant service instance hosted in the US East region: 59 | 60 | ``` 61 | ASSISTANT_IAM_APIKEY=X4rbi8vwZmKpXfowaS3GAsA7vdy17Qhxxxxxxxx 62 | ASSISTANT_URL=https://gateway-wdc.watsonplatform.net/assistant/api 63 | ``` 64 | 65 | - If your service instance uses `username` and `password` credentials, add the `ASSISTANT_USERNAME` and `ASSISTANT_PASSWORD` variables to the *.env* file. 66 | 67 | Example *.env* file that configures the `username`, `password`, and `url` for a Watson Assistant service instance hosted in the US South region: 68 | 69 | ``` 70 | ASSISTANT_USERNAME=522be-7b41-ab44-dec3-xxxxxxxx 71 | ASSISTANT_PASSWORD=A4Z5BdGENxxxx 72 | ASSISTANT_URL=https://gateway.watsonplatform.net/assistant/api 73 | ``` 74 | However, if your credentials contain an IAM API key, copy the `apikey` and `url` to the relevant fields. 75 | ```JSON 76 | { 77 | "apikey": "ca2905e6-7b5d-4408-9192-xxxxxxxx", 78 | "iam_apikey_description": "Auto generated apikey during resource-key ...", 79 | "iam_apikey_name": "auto-generated-apikey-62b71334-3ae3-4609-xxxxxxxx", 80 | "iam_role_crn": "crn:v1:bluemix:public:iam::::serviceRole:Manager", 81 | "iam_serviceid_crn": "crn:v1:bluemix:public:iam...", 82 | "url": "https://gateway-syd.watsonplatform.net/assistant/api" 83 | } 84 | ``` 85 | ``` 86 | ASSISTANT_IAM_APIKEY=ca2905e6-7b5d-4408-9192-xxxxxxxx 87 | ``` 88 | 89 | 8. Add the `ASSISTANT_ID` to the previous properties 90 | 91 | ``` 92 | ASSISTANT_ID=522be-7b41-ab44-dec3-xxxxxxxx 93 | ``` 94 | 95 | ## Running locally 96 | 97 | 1. Install the dependencies 98 | 99 | ``` 100 | npm install 101 | ``` 102 | 103 | 1. Run the application 104 | 105 | ``` 106 | npm start 107 | ``` 108 | 109 | 1. View the application in a browser at `localhost:3000` 110 | 111 | ## Deploying to IBM Cloud as a Cloud Foundry Application 112 | 113 | 1. Login to IBM Cloud with the [IBM Cloud CLI](https://cloud.ibm.com/docs/cli/index.html#overview) 114 | 115 | ``` 116 | ibmcloud login 117 | ``` 118 | 119 | 1. Target a Cloud Foundry organization and space. 120 | 121 | ``` 122 | ibmcloud target --cf 123 | ``` 124 | 125 | 1. Edit the *manifest.yml* file. Change the **name** field to something unique. 126 | For example, `- name: my-app-name`. 127 | 1. Deploy the application 128 | 129 | ``` 130 | ibmcloud app push 131 | ``` 132 | 133 | 1. View the application online at the app URL. 134 | For example: https://my-app-name.mybluemix.net 135 | 136 | 137 | ## License 138 | 139 | This sample code is licensed under Apache 2.0. 140 | Full license text is available in [LICENSE](LICENSE). 141 | 142 | ## Contributing 143 | 144 | See [CONTRIBUTING](CONTRIBUTING.md). 145 | 146 | ## Open Source @ IBM 147 | 148 | Find more open source projects on the 149 | [IBM Github Page](http://ibm.github.io/). 150 | 151 | 152 | [demo_url]: https://assistant-simple.ng.bluemix.net/ 153 | [doc_intents]: https://cloud.ibm.com/docs/services/conversation/intents-entities.html#planning-your-entities 154 | [docs]: https://cloud.ibm.com/docs/services/assistant/index.html#index 155 | [docs_landing]: (https://cloud.ibm.com/docs/services/assistant/index.html#index) 156 | [node_link]: (http://nodejs.org/) 157 | [npm_link]: (https://www.npmjs.com/) 158 | [sign_up]: https://cloud.ibm.com/registration 159 | -------------------------------------------------------------------------------- /cloud_pak_for_data_quickstart/README.md: -------------------------------------------------------------------------------- 1 | # Watson Assistant Quickstart 2 | 3 | IBM Watson Assistant for IBM Cloud Pak for Data combines machine learning, natural language understanding, and an integrated dialog editor to create conversation flows between your apps and your users. 4 | 5 | This Node.js app demonstrates the Watson Assistant service in a simple interface engaging in a series of simple simulated banking tasks. 6 | 7 | This quickstart will get you up and running on any OpenShift cluster including a local [minishift](https://www.okd.io/minishift) running on your machine. 8 | 9 | ![Demo](../readme_images/demo.gif) 10 | 11 | You can view a [demo](http://conversation-simple.ng.bluemix.net/) of this app. 12 | 13 | ## Getting started 14 | 15 | ### Installing the Watson Assistant add-on on IBM Cloud Pak for Data 16 | 17 | Make your data ready for an AI and multicloud world. Cloud Pak for Data System is an all-in-one cloud-native Data and AI platform in a box, providing a pre-configured, governed, and secure environment to collect, organize and analyze data. [Learn more](https://docs-icpdata.mybluemix.net/docs/content/SSQNUZ_current/com.ibm.icpdata.doc/zen/overview/overview.html). 18 | 19 | Installing Cloud Pak for Data - [instructions](https://www.ibm.com/support/producthub/icpdata/docs/content/SSQNUZ_current/cpd/install/install.html) 20 | 21 | Installing the Watson Assistant add-on - [instructions](https://www.ibm.com/support/producthub/icpdata/docs/content/SSQNUZ_current/cpd/svc/watson/wavi.html) 22 | 23 | ### Create your own copy of this repo 24 | Fork a copy of this repo. 25 | 26 | ### Configuring the Watson Assistant instance 27 | 28 | 1. In your Cloud Pak for Data console, open the Watson Assistant service instance and click on **Open Watson Assistant**. 29 | 30 | 2. Create a new skill: under the **Skills** tab click on **Create skill** and select **Dialog skill**. Under **Import skill**, click the **Choose JSON FIle** button and specify the location of the workspace JSON file in your local copy of the app project: 31 | 32 | `/training/bank_simple_workspace.json` 33 | 34 | Select **Everything (Intents, Entities, and Dialog)** and then click **Import**. The skill is created. 35 | 36 | 3. Create a new assistant: under the **Assistants** tab click on **Create assistant**, give it a name such as **bank assistant** and click on **Create assistant**. Click on **Add dialog skill** and select the **Banking_Simple_DO_NOT_DELETE** skill. 37 | 38 | 4. Click on the menu icon in the upper-right corner, and then select **Settings**. Inside **API Details** you can find your **Assistant ID**. We will use it later so it's a good idea to copy and paste it somewhere. 39 | 40 | ### Creating a project 41 | 42 | After logging in with `oc login`, ensure that you have a project set up. If not, create one as follows: 43 | 44 | $ oc new-project watson-assistant-project --display-name="Watson Assistant Project" 45 | 46 | That's it, project has been created. Ensure that your current project is set: 47 | 48 | $ oc project watson-assistant-project 49 | 50 | ### Creating the app from a template 51 | 52 | The template for this example is located at [watson-assistant-quickstart.json](openshift/templates/watson-assistant-quickstart.json). 53 | 54 | First, list the available parameters: 55 | 56 | $ oc process --parameters -f https://raw.githubusercontent.com/watson-developer-cloud/assistant-simple/master/cloud_pak_for_data_quickstart/openshift/templates/watson-assistant-quickstart.json 57 | 58 | The following parameters are required, You can find them by clicking on your Watson Assistant instance in the Cloud Pak for Data console: 59 | 1. `BEARER_TOKEN` 60 | 2. `ASSISTANT_URL` 61 | * For security reasons, don't use this token in production. 62 | 63 | Create the app from the template. Specify the `SOURCE_REPOSITORY_URL` to be the url of your forked repo. If you are using a self-signed certificate then set DISABLE_SSL_VERIFICATION to true 64 | 65 | $ oc new-app -f \ 66 | https://raw.githubusercontent.com/watson-developer-cloud/assistant-simple/master/cloud_pak_for_data_quickstart/openshift/templates/watson-assistant-quickstart.json \ 67 | -p BEARER_TOKEN= \ 68 | -p ASSISTANT_URL= \ 69 | -p ASSISTANT_ID= \ 70 | -p SOURCE_REPOSITORY_URL= \ 71 | -p DISABLE_SSL_VERIFICATION=false 72 | 73 | `oc new-app` will kick off a build once all required dependencies are confirmed. 74 | 75 | 76 | #### Check the status 77 | 78 | 79 | Check the status of your new nodejs app with the command: 80 | 81 | $ oc status 82 | 83 | 84 | Which should return something like: 85 | 86 | In project Watson Assistant Project (watson-assistant-project) on server https://10.2.2.2:8443 87 | 88 | svc/watson-assistant-quickstart - 172.30.108.183:8080 89 | dc/watson-assistant-quickstart deploys istag/watson-assistant-quickstart:latest <- 90 | bc/watson-assistant-quickstart source builds https://github.com/watson-developer-cloud/assistant-simple on openshift/nodejs:10 91 | build #1 running for 7 seconds 92 | deployment #1 waiting on image or update 93 | 94 | 95 | #### Custom Routing 96 | 97 | An OpenShift route exposes a service at a host name, like www.example.com, so that external clients can reach it by name. 98 | 99 | DNS resolution for a host name is handled separately from routing; you may wish to configure a cloud domain that will always correctly resolve to the OpenShift router, or if using an unrelated host name you may need to modify its DNS records independently to resolve to the router. 100 | 101 | That aside, let's explore our new web app. `oc new-app` created a new route. To view your new route: 102 | 103 | $ oc get route 104 | 105 | In the result you can find all routes in your project and for each route you can find its hostname. 106 | Find the `watson-assistant-quickstart` route and use the hostname to navigate to the newly created Node.js web app. 107 | Notice that you can use the `APPLICATION_DOMAIN` template parameter to define a hostname for your app. 108 | 109 | To create a new route at a host name, like www.example.com: 110 | 111 | $ oc expose svc/watson-assistant-quickstart --hostname=www.example.com 112 | 113 | 114 | #### Optional diagnostics 115 | 116 | If the build is not yet started (you can check by running `oc get builds`), start one and stream the logs with: 117 | 118 | $ oc start-build watson-assistant-quickstart --follow 119 | 120 | Deployment happens automatically once the new application image is available. To monitor its status either watch the web console or execute `oc get pods` to see when the pod is up. Another helpful command is 121 | 122 | $ oc get svc 123 | 124 | 125 | This will help indicate what IP address the service is running, the default port for it to deploy at is 8080. Output should look like: 126 | 127 | NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE 128 | watson-assistant-quickstart 172.30.249.251 8080/TCP 7m 129 | 130 | 131 | ### Adding Webhooks and Making Code Changes 132 | Assuming you used the URL of your own forked repository, you can configure your github repository to make a webhook call whenever you push your code. Learn more about [Webhook Triggers](https://docs.openshift.com/container-platform/3.5/dev_guide/builds/triggering_builds.html#webhook-triggers). 133 | 134 | 1. From the OpenShift web console homepage, navigate to your project 135 | 2. Go to Builds 136 | 3. Click the link with your BuildConfig name 137 | 4. Click the Configuration tab 138 | 5. Click the "Copy to clipboard" icon to the right of the "GitHub webhook URL" field 139 | 6. Navigate to your repository on GitHub and click on repository settings > webhooks > Add webhook 140 | 7. Paste your webhook URL provided by OpenShift 141 | 8. Leave the defaults for the remaining fields - That's it! 142 | 9. After you save your webhook, refresh your Github settings page and check the status to verify connectivity. 143 | 144 | 145 | ### Testing the app 146 | 147 | After your app is installed and running, experiment with it to see how it responds. 148 | 149 | The chat interface is on the left, and the JSON that the JavaScript code receives from the Watson Assistant service is on the right. Your questions and commands are interpreted using a small set of sample data trained with the banking intents. 150 | 151 | * Visit the documentation to learn more about [intents](https://cloud.ibm.com/docs/services/assistant/intents.html#defining-intents) and [entities](https://cloud.ibm.com/docs/services/assistant/entities.html#defining-entities) 152 | 153 | 154 | ### Learn more about [OpenShift templates](https://docs.openshift.com/enterprise/3.0/dev_guide/templates.html#dev-guide-templates). 155 | 156 | 157 | ### Running locally 158 | 159 | Follow the steps below to run the app locally: 160 | 161 | 1. First install [Node.js](https://nodejs.org) ([LTS](https://github.com/nodejs/Release) supported versions). 162 | 163 | 2. In the application folder, copy the *.env.example* file and create a file called *.env* 164 | 165 | ```sh 166 | cp .env.example .env 167 | ``` 168 | 169 | 7. Open the *.env* file and add `BEARER_TOKEN`, `ASSISTANT_URL`, `ASSISTANT_ID` and `DISABLE_SSL_VERIFICATION=true` if you are using a self-signed certificate. 170 | 171 | 3. Install the dependencies: 172 | 173 | ```sh 174 | npm install 175 | ``` 176 | 177 | 4. Start the app: 178 | 179 | ```sh 180 | npm start 181 | ``` 182 | 183 | 5. Point your browser to [localhost:3000](http://localhost:3000). 184 | 185 | 186 | ## License 187 | 188 | This sample code is licensed under Apache 2.0. 189 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /cloud_pak_for_data_quickstart/openshift/templates/watson-assistant-quickstart.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "kind": "Template", 4 | "labels": { 5 | "app": "watson-assistant-quickstart", 6 | "template": "watson-assistant-quickstart" 7 | }, 8 | "message": "The ${NAME} service has been created in your project.\n\nFor more information about using this quickstart see https://github.com/watson-developer-cloud/assistant-simple/tree/master/cloud_pak_for_data_quickstart.", 9 | "metadata": { 10 | "annotations": { 11 | "description": "An example Node.js application demonstrating the Watson Assistant api. For more information about using this template see https://github.com/watson-developer-cloud/assistant-simple/tree/master/cloud_pak_for_data_quickstart.", 12 | "iconClass": "icon-nodejs", 13 | "openshift.io/display-name": "Watson Assistant Quick Start", 14 | "openshift.io/documentation-url": "https://github.com/watson-developer-cloud/assistant-simple/tree/master/cloud_pak_for_data_quickstart", 15 | "openshift.io/provider-display-name": "IBM", 16 | "tags": "quickstart,nodejs,watson,assistant" 17 | }, 18 | "name": "watson-assistant-quickstart" 19 | }, 20 | "objects": [ 21 | { 22 | "apiVersion": "v1", 23 | "kind": "Service", 24 | "metadata": { 25 | "annotations": { 26 | "description": "Exposes and load balances the application pods" 27 | }, 28 | "name": "${NAME}" 29 | }, 30 | "spec": { 31 | "ports": [ 32 | { 33 | "name": "web", 34 | "port": 8080, 35 | "targetPort": 3000 36 | } 37 | ], 38 | "selector": { 39 | "name": "${NAME}" 40 | } 41 | } 42 | }, 43 | { 44 | "apiVersion": "v1", 45 | "kind": "Route", 46 | "metadata": { 47 | "name": "${NAME}" 48 | }, 49 | "spec": { 50 | "host": "${APPLICATION_DOMAIN}", 51 | "to": { 52 | "kind": "Service", 53 | "name": "${NAME}" 54 | } 55 | } 56 | }, 57 | { 58 | "apiVersion": "v1", 59 | "kind": "ImageStream", 60 | "metadata": { 61 | "annotations": { 62 | "description": "Keeps track of changes in the application image" 63 | }, 64 | "name": "${NAME}" 65 | } 66 | }, 67 | { 68 | "apiVersion": "v1", 69 | "kind": "BuildConfig", 70 | "metadata": { 71 | "annotations": { 72 | "description": "Defines how to build the application", 73 | "template.alpha.openshift.io/wait-for-ready": "true" 74 | }, 75 | "name": "${NAME}" 76 | }, 77 | "spec": { 78 | "output": { 79 | "to": { 80 | "kind": "ImageStreamTag", 81 | "name": "${NAME}:latest" 82 | } 83 | }, 84 | "source": { 85 | "contextDir": "${CONTEXT_DIR}", 86 | "git": { 87 | "ref": "${SOURCE_REPOSITORY_REF}", 88 | "uri": "${SOURCE_REPOSITORY_URL}" 89 | }, 90 | "type": "Git" 91 | }, 92 | "strategy": { 93 | "sourceStrategy": { 94 | "env": [ 95 | { 96 | "name": "NPM_MIRROR", 97 | "value": "${NPM_MIRROR}" 98 | } 99 | ], 100 | "from": { 101 | "kind": "ImageStreamTag", 102 | "name": "nodejs:${NODEJS_VERSION}", 103 | "namespace": "${NAMESPACE}" 104 | } 105 | }, 106 | "type": "Source" 107 | }, 108 | "triggers": [ 109 | { 110 | "type": "ImageChange" 111 | }, 112 | { 113 | "type": "ConfigChange" 114 | }, 115 | { 116 | "github": { 117 | "secret": "${GITHUB_WEBHOOK_SECRET}" 118 | }, 119 | "type": "GitHub" 120 | }, 121 | { 122 | "generic": { 123 | "secret": "${GENERIC_WEBHOOK_SECRET}" 124 | }, 125 | "type": "Generic" 126 | } 127 | ] 128 | } 129 | }, 130 | { 131 | "apiVersion": "v1", 132 | "kind": "DeploymentConfig", 133 | "metadata": { 134 | "annotations": { 135 | "description": "Defines how to deploy the application server", 136 | "template.alpha.openshift.io/wait-for-ready": "true" 137 | }, 138 | "name": "${NAME}" 139 | }, 140 | "spec": { 141 | "replicas": 1, 142 | "selector": { 143 | "name": "${NAME}" 144 | }, 145 | "strategy": { 146 | "type": "Recreate" 147 | }, 148 | "template": { 149 | "metadata": { 150 | "labels": { 151 | "name": "${NAME}" 152 | }, 153 | "name": "${NAME}" 154 | }, 155 | "spec": { 156 | "containers": [ 157 | { 158 | "name": "watson-assistant-quickstart", 159 | "image": " ", 160 | "ports": [ 161 | { 162 | "containerPort": 8080 163 | } 164 | ], 165 | "env": [ 166 | { 167 | "name": "BEARER_TOKEN", 168 | "value": "${BEARER_TOKEN}" 169 | }, 170 | { 171 | "name": "ASSISTANT_URL", 172 | "value": "${ASSISTANT_URL}" 173 | }, 174 | { 175 | "name": "ASSISTANT_ID", 176 | "value": "${ASSISTANT_ID}" 177 | }, 178 | { 179 | "name": "DISABLE_SSL_VERIFICATION", 180 | "value": "${DISABLE_SSL_VERIFICATION}" 181 | } 182 | ], 183 | "livenessProbe": { 184 | "httpGet": { 185 | "path": "/health", 186 | "port": 3000 187 | }, 188 | "initialDelaySeconds": 30, 189 | "timeoutSeconds": 3 190 | }, 191 | "readinessProbe": { 192 | "httpGet": { 193 | "path": "/health", 194 | "port": 3000 195 | }, 196 | "initialDelaySeconds": 3, 197 | "timeoutSeconds": 3 198 | }, 199 | "resources": { 200 | "limits": { 201 | "memory": "${MEMORY_LIMIT}" 202 | } 203 | } 204 | } 205 | ] 206 | } 207 | }, 208 | "triggers": [ 209 | { 210 | "imageChangeParams": { 211 | "automatic": true, 212 | "containerNames": [ 213 | "watson-assistant-quickstart" 214 | ], 215 | "from": { 216 | "kind": "ImageStreamTag", 217 | "name": "${NAME}:latest" 218 | } 219 | }, 220 | "type": "ImageChange" 221 | }, 222 | { 223 | "type": "ConfigChange" 224 | } 225 | ] 226 | } 227 | } 228 | ], 229 | "parameters": [ 230 | { 231 | "description": "The name assigned to all of the frontend objects defined in this template.", 232 | "displayName": "Name", 233 | "name": "NAME", 234 | "required": true, 235 | "value": "watson-assistant-quickstart" 236 | }, 237 | { 238 | "displayName": "Cloud Pak for Data bearer token", 239 | "description": "Cloud Pak for Data bearer token.", 240 | "name": "BEARER_TOKEN", 241 | "required": true 242 | }, 243 | { 244 | "displayName": "Watson Assistant instance URL", 245 | "description": "Watson Assistant instance URL.", 246 | "name": "ASSISTANT_URL", 247 | "required": true 248 | }, 249 | { 250 | "displayName": "Watson Assistant ID", 251 | "description": "Watson Assistant ID.", 252 | "name": "ASSISTANT_ID", 253 | "required": true 254 | }, 255 | { 256 | "displayName": "Disable SSL certificate verification", 257 | "description": "Set this to 'true' if you are using a self-signed certificate.", 258 | "name": "DISABLE_SSL_VERIFICATION", 259 | "required": true, 260 | "value": "false" 261 | }, 262 | { 263 | "description": "The OpenShift Namespace where the ImageStream resides.", 264 | "displayName": "Namespace", 265 | "name": "NAMESPACE", 266 | "required": true, 267 | "value": "openshift" 268 | }, 269 | { 270 | "description": "Version of NodeJS image to be used (10 or latest).", 271 | "displayName": "Version of NodeJS Image", 272 | "name": "NODEJS_VERSION", 273 | "required": true, 274 | "value": "10" 275 | }, 276 | { 277 | "description": "Maximum amount of memory the Node.js container can use.", 278 | "displayName": "Memory Limit", 279 | "name": "MEMORY_LIMIT", 280 | "required": true, 281 | "value": "512Mi" 282 | }, 283 | { 284 | "description": "The URL of the repository with your application source code.", 285 | "displayName": "Git Repository URL", 286 | "name": "SOURCE_REPOSITORY_URL", 287 | "required": true, 288 | "value": "https://github.com/watson-developer-cloud/assistant-simple" 289 | }, 290 | { 291 | "description": "Set this to a branch name, tag or other ref of your repository if you are not using the default branch.", 292 | "displayName": "Git Reference", 293 | "name": "SOURCE_REPOSITORY_REF" 294 | }, 295 | { 296 | "description": "Set this to the relative path to your project if it is not in the root of your repository.", 297 | "displayName": "Context Directory", 298 | "name": "CONTEXT_DIR" 299 | }, 300 | { 301 | "description": "The exposed hostname that will route to the Node.js service, if left blank a value will be defaulted.", 302 | "displayName": "Application Hostname", 303 | "name": "APPLICATION_DOMAIN", 304 | "value": "" 305 | }, 306 | { 307 | "description": "Github trigger secret. A difficult to guess string encoded as part of the webhook URL. Not encrypted.", 308 | "displayName": "GitHub Webhook Secret", 309 | "from": "[a-zA-Z0-9]{40}", 310 | "generate": "expression", 311 | "name": "GITHUB_WEBHOOK_SECRET" 312 | }, 313 | { 314 | "description": "A secret string used to configure the Generic webhook.", 315 | "displayName": "Generic Webhook Secret", 316 | "from": "[a-zA-Z0-9]{40}", 317 | "generate": "expression", 318 | "name": "GENERIC_WEBHOOK_SECRET" 319 | }, 320 | { 321 | "description": "The custom NPM mirror URL", 322 | "displayName": "Custom NPM Mirror URL", 323 | "name": "NPM_MIRROR", 324 | "value": "" 325 | } 326 | ] 327 | } -------------------------------------------------------------------------------- /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 { 19 | font-size: 0.75rem; 20 | padding: 0.5rem; 21 | } 22 | 23 | #view-change-button { 24 | display: inline-block; 25 | position: absolute; 26 | width: 3.125rem; 27 | height: 3.125rem; 28 | border-radius: 1.5625rem; 29 | background: #AB72F8; 30 | top: 0.3125rem; 31 | right: 0.3125rem; 32 | line-height: 3.125rem; 33 | vertical-align: middle; 34 | } 35 | 36 | #view-change-button img { 37 | display: none; 38 | width: 100%; 39 | height: 100%; 40 | vertical-align: middle; 41 | } 42 | 43 | #view-change-button:not(.full) .not-full { 44 | display: inline-block; 45 | } 46 | 47 | #view-change-button.full .full { 48 | display: inline-block; 49 | } 50 | 51 | #contentParent { 52 | height: 100%; 53 | } 54 | 55 | .responsive-columns-wrapper { 56 | display: -ms-flexbox; 57 | display: -webkit-flex; 58 | display: flex; 59 | flex-direction: row; 60 | -ms-display: flex; 61 | -ms-flex-direction: row; 62 | } 63 | 64 | .responsive-column { 65 | -webkit-flex: 1; 66 | -ms-flex: 1; 67 | flex: 1; 68 | overflow: auto; 69 | } 70 | 71 | #chat-column-holder { 72 | text-align: center; 73 | } 74 | 75 | .chat-column { 76 | display: flex; 77 | flex-direction: column; 78 | height: 90%; 79 | padding: 0.9375rem 0 0.625rem 0; 80 | margin: auto; 81 | text-align: left; 82 | max-width: 25rem; 83 | min-width: 9.375rem; 84 | } 85 | 86 | .user-typing { 87 | flex: 0; 88 | border: none; 89 | color: #8d25e8; 90 | margin: 0.75rem; 91 | font-size: 15; 92 | font-family: Helvetica Neue for IBM, Helvetica Neue, Helvetica, Arial, sans-serif; 93 | } 94 | 95 | #scrollingChat { 96 | margin: 0.75rem; 97 | overflow-y: auto; 98 | overflow-x: hidden; 99 | flex: 1; 100 | } 101 | 102 | .message-inner { 103 | opacity: 0; 104 | margin-top: 0.9375rem; 105 | -webkit-transition-property: opacity, margin-top; 106 | -webkit-transition-duration: 0.75s; 107 | -webkit-transition-timing-function: ease-in; 108 | -moz-transition-property: opacity, margin-top; 109 | -moz-transition-duration: 0.75s; 110 | -moz-transition-timing-function: ease-in; 111 | -o-transition-property: opacity, margin-top; 112 | -o-transition-duration: 0.75s; 113 | -o-transition-timing-function: ease-in; 114 | -ms-transition-property: opacity, margin-top; 115 | -ms-transition-duration: 0.75s; 116 | -ms-transition-timing-function: ease-in; 117 | transition-property: opacity, margin-top; 118 | transition-duration: 0.75s; 119 | transition-timing-function: ease-in; 120 | } 121 | 122 | .load .message-inner { 123 | opacity: 1; 124 | margin-top: 0.3125rem; 125 | } 126 | 127 | .from-user { 128 | text-align: right; 129 | } 130 | 131 | .from-user .message-inner { 132 | position: relative; 133 | font-size: 1rem; 134 | color: #fff; 135 | letter-spacing: 0.015rem; 136 | line-height: 1.3125rem; 137 | background: #00B4A0; 138 | border-radius: 1.25rem; 139 | border-bottom-right-radius: 0; 140 | text-align: left; 141 | display: inline-block; 142 | margin-left: 2.5rem; 143 | min-width: 2.5rem; 144 | } 145 | 146 | .from-user .message-inner p { 147 | margin: 0.3125rem; 148 | padding: 0 0.9375rem; 149 | } 150 | 151 | .from-user .message-inner:before, .from-user .message-inner:after { 152 | content: ""; 153 | position: absolute; 154 | } 155 | 156 | 157 | .from-user .message-inner:before { 158 | z-index: -2; 159 | bottom: -0.375rem; 160 | right: 0; 161 | height: 0.375rem; 162 | width: 0.5rem; 163 | background: #1cb3a0; 164 | } 165 | 166 | .from-user .message-inner:after { 167 | z-index: -1; 168 | bottom: -0.5rem; 169 | right: 0; 170 | height: 0.5rem; 171 | width: 0.5rem; 172 | background: #fff; 173 | border-top-right-radius: 1.25rem; 174 | } 175 | 176 | .from-watson, 177 | .message-inner { 178 | position: relative; 179 | border-radius: 1.5625rem; 180 | font-size: 1rem; 181 | color: #B5B5B5; 182 | letter-spacing: 0.015rem; 183 | line-height: 1.3125rem; 184 | } 185 | 186 | .from-watson.latest .message-inner { 187 | color: #323232; 188 | } 189 | 190 | .from-watson p { 191 | margin: 0.3125rem; 192 | padding: 0 1.25rem; 193 | } 194 | 195 | .from-watson.latest.top p:before { 196 | content: "."; 197 | color: #9855D4; 198 | background-image: url("../img/marker_image.png"); 199 | background-size: 0.3125rem 1.3125rem; 200 | position: absolute; 201 | z-index: 2; 202 | left: 0.4375rem; 203 | width: 0.3125rem; 204 | height: 1.3125rem; 205 | line-height: 1.3125rem; 206 | } 207 | 208 | #textInput { 209 | border: none; 210 | outline: none; 211 | background: transparent; 212 | font-size: 1rem; 213 | color: #323232; 214 | letter-spacing: 0.015rem; 215 | line-height: 1.3125rem; 216 | height: 2.5rem; 217 | max-width: 100%; 218 | padding-left: 0.125rem; 219 | margin-bottom: -0.125rem; 220 | font-family: Helvetica Neue for IBM, Helvetica Neue, Helvetica, Arial, sans-serif; 221 | 222 | } 223 | 224 | #textInput.underline { 225 | border-bottom: 2px solid #00B4A0; 226 | } 227 | 228 | ::-webkit-input-placeholder { 229 | color: #B5B5B5; 230 | } 231 | 232 | ::-moz-placeholder { 233 | color: #B5B5B5; 234 | opacity: 1; 235 | } 236 | 237 | input:-moz-placeholder { 238 | color: #B5B5B5; 239 | opacity: 1; 240 | } 241 | 242 | :-ms-input-placeholder { 243 | color: #B5B5B5; 244 | } 245 | 246 | ::-ms-clear { 247 | display: none; 248 | } 249 | 250 | .inputOutline { 251 | display: block; 252 | border-bottom: 0.0625rem solid #aeaeae; 253 | margin-left: 0.5rem; 254 | margin-right: 0.5rem; 255 | } 256 | 257 | #textInputDummy { 258 | position:absolute; 259 | white-space:pre; 260 | top: 0; 261 | left: -1000%; 262 | opacity: 0; 263 | } 264 | 265 | #payload-column { 266 | font-family: Monaco, monospace; 267 | font-size: 0.75rem; 268 | letter-spacing: 0; 269 | line-height: 1.125rem; 270 | background-color: #23292A; 271 | color: #fff; 272 | overflow-x: auto; 273 | 274 | width: 45%; 275 | max-width: 32.0625rem; 276 | min-width: 29.6875rem; 277 | } 278 | 279 | #payload-column.full { 280 | width: 100%; 281 | max-width: none; 282 | min-width: initial; 283 | } 284 | 285 | #payload-column .header-text, #payload-column #payload-initial-message { 286 | font-family: Helvetica Neue for IBM, Helvetica Neue, Helvetica, Arial, sans-serif; 287 | font-size: 1.125rem; 288 | color: #9E9E9E; 289 | letter-spacing: 0.01875rem; 290 | padding: 0.5rem; 291 | padding-left: 2.5rem; 292 | background: #383D3E; 293 | } 294 | 295 | .hide { 296 | display: none; 297 | } 298 | 299 | .payload .line-numbers, .payload .payload-text { 300 | padding: 0.5rem; 301 | } 302 | 303 | .line-numbers { 304 | width: 2rem; 305 | color: #898989; 306 | text-align: right; 307 | } 308 | 309 | pre { 310 | margin: 0; 311 | word-wrap: normal; 312 | } 313 | 314 | .string { 315 | color: #54EED0; 316 | } 317 | 318 | .boolean, .null, .number { 319 | color: #CE8EFF; 320 | } 321 | 322 | .key { 323 | color: #66B7FF; 324 | } 325 | 326 | html{ 327 | font-size: 16px; 328 | } 329 | 330 | @media only screen and (max-width: 1000px) { 331 | html { 332 | font-size: 15px; 333 | } 334 | } 335 | 336 | @media only screen and (max-width: 600px) { 337 | html { 338 | font-size: 14px; 339 | } 340 | 341 | .chat-column { 342 | padding-top: 4rem; 343 | } 344 | 345 | #payload-column { 346 | width: 0; 347 | max-width: none; 348 | min-width: initial; 349 | } 350 | } 351 | 352 | /* IBM Design fonts https://github.ibm.com/Design/fonts */ 353 | @font-face { 354 | font-family: 'Helvetica Neue for IBM'; 355 | src: url('../fonts/light/h-n-light.eot?') format('eot'), 356 | url('../fonts/light/h-n-light.woff2') format('woff2'), 357 | url('../fonts/light/h-n-light.woff') format('woff'), 358 | url('../fonts/light/h-n-light.ttf') format('truetype'); 359 | font-weight: 300; 360 | font-style: normal; 361 | } 362 | @font-face { 363 | font-family: 'Helvetica Neue for IBM'; 364 | src: url('../fonts/light-italic/h-n-light-italic.eot?') format('eot'), 365 | url('../fonts/light-italic/h-n-light-italic.woff2') format('woff2'), 366 | url('../fonts/light-italic/h-n-light-italic.woff') format('woff'), 367 | url('../fonts/light-italic/h-n-light-italic.ttf') format('truetype'); 368 | font-weight: 300; 369 | font-style: italic; 370 | } 371 | @font-face { 372 | font-family: 'Helvetica Neue for IBM'; 373 | src: url('../fonts/roman/h-n-roman.eot?') format('eot'), 374 | url('../fonts/roman/h-n-roman.woff2') format('woff2'), 375 | url('../fonts/roman/h-n-roman.woff') format('woff'), 376 | url('../fonts/roman/h-n-roman.ttf') format('truetype'); 377 | font-weight: 400; 378 | font-style: normal; 379 | } 380 | @font-face { 381 | font-family: 'Helvetica Neue for IBM'; 382 | src: url('../fonts/roman-italic/h-n-roman-italic.eot?') format('eot'), 383 | url('../fonts/roman-italic/h-n-roman-italic.woff2') format('woff2'), 384 | url('../fonts/roman-italic/h-n-roman-italic.woff') format('woff'), 385 | url('../fonts/roman-italic/h-n-roman-italic.ttf') format('truetype'); 386 | font-weight: 400; 387 | font-style: italic; 388 | } 389 | @font-face { 390 | font-family: 'Helvetica Neue for IBM'; 391 | src: url('../fonts/medium/h-n-medium.eot?') format('eot'), 392 | url('../fonts/medium/h-n-medium.woff2') format('woff2'), 393 | url('../fonts/medium/h-n-medium.woff') format('woff'), 394 | url('../fonts/medium/h-n-medium.ttf') format('truetype'); 395 | font-weight: 500; 396 | font-style: normal; 397 | } 398 | @font-face { 399 | font-family: 'Helvetica Neue for IBM'; 400 | src: url('../fonts/medium-italic/h-n-medium-italic.eot?') format('eot'), 401 | url('../fonts/medium-italic/h-n-medium-italic.woff2') format('woff2'), 402 | url('../fonts/medium-italic/h-n-medium-italic.woff') format('woff'), 403 | url('../fonts/medium-italic/h-n-medium-italic.ttf') format('truetype'); 404 | font-weight: 500; 405 | font-style: italic; 406 | } 407 | @font-face { 408 | font-family: 'Helvetica Neue for IBM'; 409 | src: url('../fonts/bold/h-n-bold.eot?') format('eot'), 410 | url('../fonts/bold/h-n-bold.woff2') format('woff2'), 411 | url('../fonts/bold/h-n-bold.woff') format('woff'), 412 | url('../fonts/bold/h-n-bold.ttf') format('truetype'); 413 | font-weight: 700; 414 | font-style: normal; 415 | } 416 | @font-face { 417 | font-family: 'Helvetica Neue for IBM'; 418 | src: url('../fonts/bold-italic/h-n-bold-italic.eot?') format('eot'), 419 | url('../fonts/bold-italic/h-n-bold-italic.woff2') format('woff2'), 420 | url('../fonts/bold-italic/h-n-bold-italic.woff') format('woff'), 421 | url('../fonts/bold-italic/h-n-bold-italic.ttf') format('truetype'); 422 | font-weight: 700; 423 | font-style: italic; 424 | } 425 | 426 | /* IBM Icons */ 427 | @font-face { 428 | font-family: 'ibm-icons'; 429 | src:url('../fonts/ibm-icons.eot?ytcz1z') format('eot'), 430 | url('../fonts/ibm-icons.eot?ytcz1z#iefix') format('embedded-opentype'), 431 | url('../fonts/ibm-icons.ttf?ytcz1z') format('truetype'), 432 | url('../fonts/ibm-icons.woff?ytcz1z') format('woff'), 433 | url('../fonts/ibm-icons.svg?ytcz1z#ibm-icons') format('svg'); 434 | font-weight: normal; 435 | font-style: normal; 436 | } 437 | 438 | /* IBM glyphs */ 439 | @font-face { 440 | font-family: 'ibm-glyph'; 441 | src:url('../fonts/ibm-glyphs.eot?1b8643') format('eot'), 442 | url('../fonts/ibm-glyphs.eot?1b8643#iefix') format('embedded-opentype'), 443 | url('../fonts/ibm-glyphs.ttf?1b8643') format('truetype'), 444 | url('../fonts/ibm-glyphs.woff?1b8643') format('woff'), 445 | url('../fonts/ibm-glyphs.svg?1b8643#ibm-glyph') format('svg'); 446 | font-weight: normal; 447 | font-style: normal; 448 | } 449 | 450 | .options-list { 451 | color: #8d25e8; 452 | cursor: pointer; 453 | } 454 | 455 | .options-button { 456 | color: white; 457 | background-color: #8d25e8; 458 | border-radius: 6px; 459 | padding-bottom: 4px; 460 | padding-top: 4px; 461 | padding-left: 6px; 462 | padding-right: 6px; 463 | margin: 3px; 464 | cursor: pointer; 465 | display: inline-block; 466 | } 467 | -------------------------------------------------------------------------------- /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 | sendMessage: sendMessage 25 | }; 26 | 27 | // Initialize the module 28 | function init() { 29 | chatUpdateSetup(); 30 | Api.getSessionId(function() { 31 | Api.sendRequest('', null); 32 | }); 33 | setupInputBox(); 34 | } 35 | // Set up callbacks on payload setters in Api module 36 | // This causes the displayMessage function to be called when messages are sent / received 37 | function chatUpdateSetup() { 38 | var currentRequestPayloadSetter = Api.setRequestPayload; 39 | Api.setRequestPayload = function (newPayloadStr) { 40 | currentRequestPayloadSetter.call(Api, newPayloadStr); 41 | displayMessage(JSON.parse(newPayloadStr), settings.authorTypes.user); 42 | }; 43 | 44 | var currentResponsePayloadSetter = Api.setResponsePayload; 45 | Api.setResponsePayload = function (newPayloadStr) { 46 | currentResponsePayloadSetter.call(Api, newPayloadStr); 47 | displayMessage(JSON.parse(newPayloadStr).result, settings.authorTypes.watson); 48 | }; 49 | 50 | Api.setErrorPayload = function (newPayload) { 51 | displayMessage(newPayload, settings.authorTypes.watson); 52 | }; 53 | } 54 | 55 | // Set up the input box to underline text as it is typed 56 | // This is done by creating a hidden dummy version of the input box that 57 | // is used to determine what the width of the input text should be. 58 | // This value is then used to set the new width of the visible input box. 59 | function setupInputBox() { 60 | var input = document.getElementById('textInput'); 61 | var dummy = document.getElementById('textInputDummy'); 62 | var minFontSize = 14; 63 | var maxFontSize = 16; 64 | var minPadding = 4; 65 | var maxPadding = 6; 66 | 67 | // If no dummy input box exists, create one 68 | if (dummy === null) { 69 | var dummyJson = { 70 | 'tagName': 'div', 71 | 'attributes': [{ 72 | 'name': 'id', 73 | 'value': 'textInputDummy' 74 | }] 75 | }; 76 | 77 | dummy = Common.buildDomElement(dummyJson); 78 | document.body.appendChild(dummy); 79 | } 80 | 81 | function adjustInput() { 82 | if (input.value === '') { 83 | // If the input box is empty, remove the underline 84 | input.classList.remove('underline'); 85 | input.setAttribute('style', 'width:' + '100%'); 86 | input.style.width = '100%'; 87 | } else { 88 | // otherwise, adjust the dummy text to match, and then set the width of 89 | // the visible input box to match it (thus extending the underline) 90 | input.classList.add('underline'); 91 | var txtNode = document.createTextNode(input.value); 92 | ['font-size', 'font-style', 'font-weight', 'font-family', 'line-height', 93 | 'text-transform', 'letter-spacing' 94 | ].forEach(function (index) { 95 | dummy.style[index] = window.getComputedStyle(input, null).getPropertyValue(index); 96 | }); 97 | dummy.textContent = txtNode.textContent; 98 | 99 | var padding = 0; 100 | var htmlElem = document.getElementsByTagName('html')[0]; 101 | var currentFontSize = parseInt(window.getComputedStyle(htmlElem, null).getPropertyValue('font-size'), 10); 102 | if (currentFontSize) { 103 | padding = Math.floor((currentFontSize - minFontSize) / (maxFontSize - minFontSize) * 104 | (maxPadding - minPadding) + minPadding); 105 | } else { 106 | padding = maxPadding; 107 | } 108 | 109 | var widthValue = (dummy.offsetWidth + padding) + 'px'; 110 | input.setAttribute('style', 'width:' + widthValue); 111 | input.style.width = widthValue; 112 | } 113 | } 114 | 115 | // Any time the input changes, or the window resizes, adjust the size of the input box 116 | input.addEventListener('input', adjustInput); 117 | window.addEventListener('resize', adjustInput); 118 | 119 | // Trigger the input event once to set up the input box and dummy element 120 | Common.fireEvent(input, 'input'); 121 | } 122 | 123 | // Display a user or Watson message that has just been sent/received 124 | function displayMessage(newPayload, typeValue) { 125 | var isUser = isUserMessage(typeValue); 126 | //var textExists = newPayload.generic; 127 | if ((newPayload.output && newPayload.output.generic) || newPayload.input){ 128 | // Create new message generic elements 129 | var responses = buildMessageDomElements(newPayload, isUser); 130 | var chatBoxElement = document.querySelector(settings.selectors.chatBox); 131 | var previousLatest = chatBoxElement.querySelectorAll((isUser ? settings.selectors.fromUser : settings.selectors.fromWatson) + 132 | settings.selectors.latest); 133 | // Previous "latest" message is no longer the most recent 134 | if (previousLatest) { 135 | Common.listForEach(previousLatest, function (element) { 136 | element.classList.remove('latest'); 137 | }); 138 | } 139 | setResponse(responses, isUser, chatBoxElement, 0, true); 140 | } 141 | } 142 | 143 | // Recurisive function to add responses to the chat area 144 | function setResponse(responses, isUser, chatBoxElement, index, isTop) { 145 | if (index < responses.length) { 146 | var res = responses[index]; 147 | if (res.type !== 'pause') { 148 | var currentDiv = getDivObject(res, isUser, isTop); 149 | chatBoxElement.appendChild(currentDiv); 150 | // Class to start fade in animation 151 | currentDiv.classList.add('load'); 152 | // Move chat to the most recent messages when new messages are added 153 | setTimeout(function () { 154 | // wait a sec before scrolling 155 | scrollToChatBottom(); 156 | }, 1000); 157 | setResponse(responses, isUser, chatBoxElement, index + 1, false); 158 | } else { 159 | var userTypringField = document.getElementById('user-typing-field'); 160 | if (res.typing) { 161 | userTypringField.innerHTML = 'Watson Assistant Typing...'; 162 | } 163 | setTimeout(function () { 164 | userTypringField.innerHTML = ''; 165 | setResponse(responses, isUser, chatBoxElement, index + 1, isTop); 166 | }, res.time); 167 | } 168 | } 169 | } 170 | 171 | // Constructs new DOM element from a message 172 | function getDivObject(res, isUser, isTop) { 173 | var classes = [(isUser ? 'from-user' : 'from-watson'), 'latest', (isTop ? 'top' : 'sub')]; 174 | var messageJson = { 175 | //
176 | 'tagName': 'div', 177 | 'classNames': ['segments'], 178 | 'children': [{ 179 | //
180 | 'tagName': 'div', 181 | 'classNames': classes, 182 | 'children': [{ 183 | //
184 | 'tagName': 'div', 185 | 'classNames': ['message-inner'], 186 | 'children': [{ 187 | //

{messageText}

188 | 'tagName': 'p', 189 | 'text': res.innerhtml 190 | }] 191 | }] 192 | }] 193 | }; 194 | return Common.buildDomElement(messageJson); 195 | } 196 | 197 | // Checks if the given typeValue matches with the user "name", the Watson "name", or neither 198 | // Returns true if user, false if Watson, and null if neither 199 | // Used to keep track of whether a message was from the user or Watson 200 | function isUserMessage(typeValue) { 201 | if (typeValue === settings.authorTypes.user) { 202 | return true; 203 | } else if (typeValue === settings.authorTypes.watson) { 204 | return false; 205 | } 206 | return null; 207 | } 208 | 209 | function getOptions(optionsList, preference) { 210 | var list = ''; 211 | var i = 0; 212 | if (optionsList !== null) { 213 | if (preference === 'text') { 214 | list = '
    '; 215 | for (i = 0; i < optionsList.length; i++) { 216 | if (optionsList[i].value) { 217 | list += '
  • ' + optionsList[i].label + '
  • '; 219 | } 220 | } 221 | list += '
'; 222 | } else if (preference === 'button') { 223 | list = '
'; 224 | for (i = 0; i < optionsList.length; i++) { 225 | if (optionsList[i].value) { 226 | var item = '
' + optionsList[i].label + '
'; 228 | list += item; 229 | } 230 | } 231 | } 232 | } 233 | return list; 234 | } 235 | 236 | function getResponse(responses, gen) { 237 | var title = '', description = ''; 238 | if (gen.hasOwnProperty('title')) { 239 | title = gen.title; 240 | } 241 | if (gen.hasOwnProperty('description')) { 242 | description = '
' + gen.description + '
'; 243 | } 244 | if (gen.response_type === 'image') { 245 | var img = '
'; 246 | responses.push({ 247 | type: gen.response_type, 248 | innerhtml: title + description + img 249 | }); 250 | } else if (gen.response_type === 'text') { 251 | responses.push({ 252 | type: gen.response_type, 253 | innerhtml: gen.text 254 | }); 255 | } else if (gen.response_type === 'pause') { 256 | responses.push({ 257 | type: gen.response_type, 258 | time: gen.time, 259 | typing: gen.typing 260 | }); 261 | } else if (gen.response_type === 'option') { 262 | var preference = 'text'; 263 | if (gen.hasOwnProperty('preference')) { 264 | preference = gen.preference; 265 | } 266 | 267 | var list = getOptions(gen.options, preference); 268 | responses.push({ 269 | type: gen.response_type, 270 | innerhtml: title + description + list 271 | }); 272 | } 273 | } 274 | 275 | // Constructs new generic elements from a message payload 276 | function buildMessageDomElements(newPayload, isUser) { 277 | var textArray = isUser ? newPayload.input.text : newPayload.output.text; 278 | if (Object.prototype.toString.call(textArray) !== '[object Array]') { 279 | textArray = [textArray]; 280 | } 281 | 282 | var responses = []; 283 | 284 | if (newPayload.hasOwnProperty('output')) { 285 | if (newPayload.output.hasOwnProperty('generic')) { 286 | 287 | var generic = newPayload.output.generic; 288 | 289 | generic.forEach(function (gen) { 290 | getResponse(responses, gen); 291 | }); 292 | } 293 | } else if (newPayload.hasOwnProperty('input')) { 294 | var input = ''; 295 | textArray.forEach(function (msg) { 296 | input += msg + ' '; 297 | }); 298 | input = input.trim() 299 | .replace(/&/g, '&') 300 | .replace(//g, '>'); 302 | 303 | if (input.length !== 0) { 304 | responses.push({ 305 | type: 'text', 306 | innerhtml: input 307 | }); 308 | } 309 | } 310 | return responses; 311 | } 312 | 313 | // Scroll to the bottom of the chat window 314 | function scrollToChatBottom() { 315 | var scrollingChat = document.querySelector('#scrollingChat'); 316 | scrollingChat.scrollTop = scrollingChat.scrollHeight; 317 | } 318 | 319 | function sendMessage(text) { 320 | // Send the user message 321 | Api.sendRequest(text); 322 | } 323 | 324 | // Handles the submission of input 325 | function inputKeyDown(event, inputBox) { 326 | // Submit on enter key, dis-allowing blank messages 327 | if (event.keyCode === 13 && inputBox.value) { 328 | sendMessage(inputBox.value); 329 | // Clear input box for further messages 330 | inputBox.value = ''; 331 | Common.fireEvent(inputBox, 'input'); 332 | } 333 | } 334 | }()); 335 | --------------------------------------------------------------------------------