├── .cfignore ├── .env.example ├── .eslintignore ├── .eslintrc.yml ├── .gitattributes ├── .gitignore ├── ACKNOWLEDGEMENTS.md ├── CONTRIBUTING.md ├── Debugging.md ├── LICENSE ├── Procfile ├── README.md ├── WA ├── agent-bot.json ├── travel-bot.json └── weather-bot.json ├── app.js ├── images ├── CopyAPIKeyAndUrl.png ├── ImportSkill.png ├── SkillsTab.png ├── app_home_page.png ├── app_logs.png ├── app_logs_1.png ├── app_logs_2.png ├── app_logs_3.png ├── app_logs_4.png ├── app_logs_backup.png ├── architecture.png ├── bot_actions.png ├── chat_interface_full.png ├── import_json_file.png ├── import_workspace.png ├── launch_tool.png ├── manifest.png ├── query_1.png ├── service_credentials_link.png ├── view_credentials.png ├── visit_app_url.png └── workspaces.png ├── manifest.yml ├── package.json ├── public ├── conversation.svg ├── css │ └── app.css ├── favicon.png ├── fonts │ └── roman │ │ ├── h-n-roman.woff │ │ └── h-n-roman.woff2 ├── index.html └── js │ ├── api.js │ ├── common.js │ ├── conversation.js │ ├── global.js │ └── payload.js └── server.js /.cfignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | coverage 4 | npm-debug.log 5 | readme_images 6 | test 7 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Environment variables 2 | WORKSPACE_ID= 3 | # You need to provide either username and password 4 | ASSISTANT_USERNAME= 5 | ASSISTANT_PASSWORD= 6 | # OR IAM API key and URL 7 | ASSISTANT_IAM_APIKEY= 8 | ASSISTANT_IAM_URL= 9 | ASSISTANT_URL= 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | node: true 4 | mocha: true 5 | extends: 'eslint:recommended' 6 | rules: 7 | indent: 8 | - error 9 | - 2 10 | linebreak-style: 11 | - error 12 | - unix 13 | quotes: 14 | - error 15 | - single 16 | semi: 17 | - error 18 | - always 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | temp/ 4 | .idea/ 5 | coverage 6 | .env 7 | npm-debug.log 8 | .jshintrc 9 | .DS_Store 10 | .vscode 11 | .travis.yml 12 | -------------------------------------------------------------------------------- /ACKNOWLEDGEMENTS.md: -------------------------------------------------------------------------------- 1 | The interface of this code pattern is taken from the project https://github.com/watson-developer-cloud/assistant-simple. Credit goes to authors who developed the interface. 2 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /Debugging.md: -------------------------------------------------------------------------------- 1 | app log files 2 | workspace id is incorrect 3 | Assistant username and/or password is/are incorrect 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm start -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Compose domain specific bots using an agent bot 2 | A domain specific bot addresses queries related to a specific domain or topic. Some examples are - Travel bot(Travel related conversations), Weather bot (Weather related conversations). 3 | 4 | If a user wants to have a cross-domain conversation, then the user will have to switch between the different bots. 5 | 6 | There are scenarios where an user wants to have a conversation involving multiple domains. 7 | For example - When I want to travel to a place, I want to query for the weather and then book a cab or flight. I might have to end up switching between two bots, weather bot and travel bot. What if I could just have one interface bot which will redirect my messages to a specific bot and get answers to me? 8 | 9 | Well, this code pattern showcases an implementation of this approach. 10 | 11 | The solution here is to have an agent bot (or an interface bot) and a few other bots which can handle conversations for a specific domain - let's call these specific bots. The agent bot knows about the specific bots and also about which domain each of them can handle. When user initiates conversation with agent bot, the agent bot will understand the intent of user query and it will redirect the user query to a specific bot. Subsequent requests from user are redirected to specific bot. When the conversation with the specific bot is over or when the specific bot is not able to handle the request, the control is given back to agent bot which will then redirect the messages to appropriate bot. 12 | 13 | This approach provides seamless experience for users. It can be used by organisations which provide a host of services to its customers like financial services, tours and travel agencies, news agencies etc.. 14 | 15 | Advantages with this approach are: 16 | - Plug and play the bots 17 | - Modular approach facilitates Bots composition 18 | - Come up with new services by composing two or more Bots 19 | - Easy to maintain, make changes, add/remove functionalities 20 | - Easy to troubleshoot issues 21 | - Transparent to user 22 | 23 | In this code pattern we will use Watson assistant Bot for building Bots and Node.js application as orchestration layer. 24 | 25 | When the reader has completed this Code Pattern, they will understand how to: 26 | 27 | * How to configure a bot to make it Agent Bot 28 | * How to configure a specific bot to return control to Agent Bot 29 | * How to build an orchestration layer to stitch Agent Bot and specific Bots 30 | 31 | ## Flow 32 | 33 | ![Architecture](images/architecture.png) 34 | 35 | 1. User accesses web application and types in a message. Nodejs application, an orchestration later, sends user message to agent bot 36 | 2. Agent bot determines the intent of the message and responds with the specific bot details, to which the message needs to be redirected. 37 | 3. Node.js application sends message to the specific bot (Weather Bot, in this case). Specific bot responds. Conversation continues between user and specific bot. 38 | 4. When the conversation with specific bot is over, user message is then sent to agent bot to determine the intent. 39 | 5. Node.js application sends message to the specific bot (Travel Bot, in this case). Specific bot responds. Conversation continues between user and specific bot. 40 | 41 | 42 | # Watch the Video 43 | [![](https://img.youtube.com/vi/godEYin0IYA/0.jpg)](https://youtu.be/godEYin0IYA) 44 | 45 | # Steps 46 | 1. Clone git repo. 47 | 2. Create bots. 48 | 3. Configure application with bots details. 49 | 4. Deploy application to IBM Cloud. 50 | 5. Run application. 51 | 52 | ## 1. Clone git repo 53 | 54 | - On command prompt run the below command to clone the git repo. 55 | ``` 56 | git clone git@github.com:IBM/watson-assistant-multi-bot-agent.git 57 | ``` 58 | or 59 | ``` 60 | git clone https://github.com/IBM/watson-assistant-multi-bot-agent.git 61 | ``` 62 | run `cd watson-assistant-multi-bot-agent` to change directory to project parent folder 63 | 64 | 65 | ## 2. Create bots 66 | 67 | ### 2.1 Create Watson Assistant service instance 68 | - Click this [link](https://cloud.ibm.com/catalog/services/watson-assistant) to create Watson assistant service. 69 | - Enter the service name as `Watson Assistant-bots`. You can choose to enter any name you like. 70 | - Ensure you select the right region, organisation and space. 71 | - Under `Pricing Plans`, select `Lite` plan. 72 | - Click `Create`. 73 | - Watson Asistant service instance should get created. 74 | 75 | ### 2.2 Import bots 76 | - Go to IBM Cloud Resource list and click on the Watson Assistant service instance created in above steps. 77 | - On the Watson Assistant Resource list page, click `Launch Watson Assistant`. 78 | 79 | ![LaunchTool](images/launch_tool.png) 80 | 81 | - Click `Skills` tab in the side bar. 82 | 83 | ![SkillsTab](images/SkillsTab.png) 84 | 85 | - Click the `Create skill` button. 86 | - Select the `Dialog skill` box 87 | - Click the `Next` button. 88 | - Select the `Import skill` tab. 89 | 90 | ![ImportSkill](images/ImportSkill.png) 91 | 92 | - Click on `Choose JSON file`. 93 | - Browse to the cloned repository parent folder -> WA. 94 | - Select `agent-bot.json` and click `Open`. 95 | 96 | ![ImportAWorkspace](images/import_json_file.png) 97 | 98 | - Click `Import` button. 99 | - Repeat above steps in section [Import bots](#22-import-bots) to import `travel_bot.json` and `weather_bot.json`. 100 | 101 | ## 3. Configure application with bots details 102 | 103 | ### 3.1 Gather required details 104 | 105 | - Go to IBM Cloud Resource list and click on the Watson Assistant service instance. 106 | - On the Watson Assistant Resource list page, click `Launch Watson Assistant`. 107 | - Click `Skills` tab in the side bar. 108 | 109 | ![SkillsTab](images/SkillsTab.png) 110 | 111 | - On `agentBot` click `actions`, the three vertical dots on the top right corner. 112 | 113 | ![BotActions](images/bot_actions.png) 114 | 115 | - Click `View API Details`. 116 | - Copy and save `Skill ID` for later use. It can be interchangeably used as Workspace ID. 117 | - Repeat above steps in section [Configure application with bots details](#3-configure-application-with-bots-details) for all the other bots also. 118 | 119 | - Go to IBM Cloud Resource list and click on the Watson Assistant service instance. 120 | - Copy `API Key` and `Url` using the copy buttons as shown in below image or using the `Show Credentials` and copying the field contents. Save them in a text file for later use. 121 | ![CopyAPIKeyAndUrl](images/CopyAPIKeyAndUrl.png) 122 | 123 | 124 | ### 3.2 Update manifest.yml file with the details gathered 125 | 126 | - Under project parent folder, open `manifest.yml` file for editing. 127 | - Update `ASSISTANT_IAM_API_KEY` with Watson Assistant service instance's API Key as noted in section [Gather required details](#31-gather-required-details) 128 | - Update `ASSISTANT_IAM_URL` with Watson Assistant service instance's Url as noted in section [Gather required details](#31-gather-required-details) 129 | - Update `WORKSPACE_ID_AGENT`, `WORKSPACE_ID_TRAVEL`, `WORKSPACE_ID_WEATHER` with Workspace IDs of respective bots as noted in section [Gather required details](#31-gather-required-details) 130 | 131 | Updated `manifest.yml` file looks as below 132 | 133 | ![Manifest](images/manifest.png) 134 | 135 | 136 | ## 4. Deploy application to IBM Cloud 137 | - On command prompt, navigate to project parent folder 138 | - On command prompt, login to IBM Cloud using `ibmcloud login` or `ibmcloud login --sso` (for federated login). 139 | - Ensure that you are in the right organisation, space and region using the below command. 140 | ``` 141 | ibmcloud target 142 | ``` 143 | - Run the below command to deploy the application to IBM Cloud. 144 | ``` 145 | ibmcloud cf push 146 | ``` 147 | - Check the logs of the application using the command `ibmcloud cf logs --recent`. 148 | - Ensure that the application is deployed to IBM Cloud successfully. If you see any errors in logs, fix them and redeploy the application. 149 | 150 | 151 | ## 6. Run the application 152 | - [Login](https://cloud.ibm.com/) to IBM Cloud and from the dashboard. 153 | - Click on `Cloud Foundry apps`. The application you deployed should be listed and it should be in running state. 154 | - Click on the application and again click on `Visit App URL`. 155 | 156 | ![VisitAppURL](images/visit_app_url.png) 157 | 158 | - The application home page opens. 159 | 160 | ![AppHomePage](images/app_home_page.png) 161 | 162 | - On command prompt monitor logs using the command 163 | ``` 164 | ibmcloud cf logs watson-assistant-multi-bot-agent 165 | ``` 166 | 167 | - On the application home page type a weather related query `What does the weather look like tomorrow?`. 168 | ![Query1](images/query_1.png) 169 | - Check the log files and notice that the message first goes to Agent Bot and then it is redirected to the Weather Bot. 170 | ![AppLogs1](images/app_logs_1.png) 171 | - In the interface, you are asked to enter the location. Enter a location, e.g. `Bengaluru`. 172 | - Check the logs. Because the conversation with the Weather Bot has not ended, subsequent messages are sent to the Weather Bot itself, without the intervention of the Agent Bot. 173 | ![AppLogs2](images/app_logs_2.png) 174 | - The Weather Bot responds with an answer to user query and hence conversation with Weather Bot is treated as ended. 175 | - Next user enters a travel related query `Book a cab`. 176 | - In the logs, notice that the message is sent to Agent Bot and then it is redirected to Travel Bot. 177 | ![AppLogs3](images/app_logs_3.png) 178 | - In the interface you are asked to enter a date for cab booking. Enter a date or you can just say `Today`. 179 | - Check the logs. Because the conversation with the Travel Bot has not ended, subsequent messages are sent to the Weather Bot itself, without the intervention of the Agent Bot. 180 | ![AppLogs4](images/app_logs_4.png) 181 | - In the interface you are asked to enter time for the cab to arrive. Say `12 PM`. 182 | - Check the logs. Travel Bot responds with an answer. The conversation with Travel Bot has ended. 183 | - The above conversation flow can continue and the Agent Bot will redirect the messages to specific bots based on the intent of user query 184 | - Some of the basic messages as greetings and bye can be handled by Agent Bot itself. 185 | - In the interface type `Thank you. Bye` 186 | 187 | ![ChatInterfaceFull](images/chat_interface_full.png) 188 | 189 | - Overall Flow of messages between bots is as shown in the below diagram 190 | 191 | ![AppLogs](images/app_logs.png) 192 | 193 | Legend for above image 194 | 1. Message sent to Agent Bot. 195 | 2. Message redirected to Weather Bot. 196 | 3. Response from Weather Bot. 197 | 4. Message sent to Weather Bot. 198 | 5. Response from Weather Bot (end of conversation with Weather Bot). 199 | 6. Message sent to Agent Bot. 200 | 7. Message redirected to Travel Bot. 201 | 8. Response from Travel Bot. 202 | 9. Message sent to Travel Bot. 203 | 10. Response from Travel Bot. 204 | 11. Message sent to Travel Bot. 205 | 12. Response from Travel Bot (end of conversation with Travel Bot). 206 | 13. Message sent to Agent Bot. 207 | 14. Response from Agent Bot (end of conversation). 208 | 209 | 210 | # Plug and play process for a new bot 211 | 212 | 1. To add a new bot, create a new bot in the Watson Assistant service instance created earlier for this code pattern or you can use an existing bot that you want to use. Let's say you added a bot for `Restaurant Booking` and named it as `RESTAURANT_BOOKING`. 213 | 2. In `manifest.yml` file, add an entry for new bot as shown below 214 | ``` 215 | WORKSPACE_ID_RESTAURANT_BOOKING: xxxxxxxxxxxxxxxxxxxxxxxx 216 | ``` 217 | 3. In the RESTAURANT_BOOKING Bot, when the conversation is over (last node in a dialog), add a context parameter `destination_bot` and value as `AGENT`. It enables the control to be passed to back to the Agent Bot. You can refer to the leaf nodes in other already imported bots for examples. 218 | 4. Open Agent Bot. Add an intent for Restaurant Booking, say restaurant. Then in dialog, add a node for restaurant intent. Add a context parameter `destination_bot` and value as `RESTAURANT_BOOKING`. 219 | 5. Redeploy the application for the configuration changes to take effect. 220 | 6. That's it, you are set to you new `RESTAURANT_BOOKING` bot as a plug and play bot. 221 | 222 | 223 | # Summary 224 | We introduced an Agent Bot which understands intents of messages. Agent Bot will redirect a message to a specific bot which can handle that message. We saw how to configure Agent Bot, Specific Bots and orchestration application to have this arrangement possible. We also saw how to plug and play this setup to add a new bot. 225 | 226 | 227 | # Troubleshooting 228 | 229 | * The application page displays but there is no greetings message, when the application pages loads. 230 | * Verify Watson Assistant username and password are correct. 231 | * Verify workspace_id of the agent bot is correct. 232 | 233 | * No response for a chat message 234 | Verify workspace_id of a specific bots is correct. 235 | 236 | * If there is any other issue, check application logs using the below command 237 | ``` 238 | ibmcloud cf logs --recent 239 | ``` 240 | 241 | 242 | # License 243 | [Apache 2.0](LICENSE) 244 | -------------------------------------------------------------------------------- /WA/agent-bot.json: -------------------------------------------------------------------------------- 1 | { 2 | "intents": [ 3 | { 4 | "intent": "bye", 5 | "examples": [ 6 | { 7 | "text": "bye" 8 | }, 9 | { 10 | "text": "bye bye" 11 | }, 12 | { 13 | "text": "c u" 14 | }, 15 | { 16 | "text": "c ya" 17 | }, 18 | { 19 | "text": "good bye" 20 | }, 21 | { 22 | "text": "ok bye" 23 | }, 24 | { 25 | "text": "ok good bye" 26 | }, 27 | { 28 | "text": "see you" 29 | }, 30 | { 31 | "text": "That's it" 32 | } 33 | ], 34 | "description": "" 35 | }, 36 | { 37 | "intent": "General_Greetings", 38 | "examples": [ 39 | { 40 | "text": "Good day" 41 | }, 42 | { 43 | "text": "Good evening" 44 | }, 45 | { 46 | "text": "Good morning" 47 | }, 48 | { 49 | "text": "Good to see you" 50 | }, 51 | { 52 | "text": "Greetings" 53 | }, 54 | { 55 | "text": "Have you been well?" 56 | }, 57 | { 58 | "text": "Hello" 59 | }, 60 | { 61 | "text": "Hello Agent" 62 | }, 63 | { 64 | "text": "Hello I am looking for some help here" 65 | }, 66 | { 67 | "text": "Hey how are you doing" 68 | }, 69 | { 70 | "text": "Hey there" 71 | }, 72 | { 73 | "text": "Hey there all" 74 | }, 75 | { 76 | "text": "Hey twin" 77 | }, 78 | { 79 | "text": "Hey you" 80 | }, 81 | { 82 | "text": "Hi advisor" 83 | }, 84 | { 85 | "text": "Hi there" 86 | }, 87 | { 88 | "text": "How are things going?" 89 | }, 90 | { 91 | "text": "How are you today?" 92 | }, 93 | { 94 | "text": "How have you been?" 95 | }, 96 | { 97 | "text": "How is it going?" 98 | }, 99 | { 100 | "text": "How r u?" 101 | }, 102 | { 103 | "text": "Looking good eve" 104 | }, 105 | { 106 | "text": "Ok take me back" 107 | }, 108 | { 109 | "text": "What's new?" 110 | }, 111 | { 112 | "text": "What's up?" 113 | }, 114 | { 115 | "text": "Who is this?" 116 | }, 117 | { 118 | "text": "You there" 119 | } 120 | ], 121 | "description": "Greet the bot." 122 | }, 123 | { 124 | "intent": "thankyou", 125 | "examples": [ 126 | { 127 | "text": "Thanks" 128 | }, 129 | { 130 | "text": "Thanks a lot" 131 | }, 132 | { 133 | "text": "Thank you" 134 | }, 135 | { 136 | "text": "Thank you very much" 137 | } 138 | ] 139 | }, 140 | { 141 | "intent": "travel", 142 | "examples": [ 143 | { 144 | "text": "arrange" 145 | }, 146 | { 147 | "text": "book" 148 | }, 149 | { 150 | "text": "booking" 151 | }, 152 | { 153 | "text": "kindly arrange" 154 | }, 155 | { 156 | "text": "reservation" 157 | }, 158 | { 159 | "text": "reserve" 160 | }, 161 | { 162 | "text": "status" 163 | }, 164 | { 165 | "text": "travel" 166 | } 167 | ], 168 | "description": "Connect to travel bot" 169 | }, 170 | { 171 | "intent": "weather", 172 | "examples": [ 173 | { 174 | "text": "cloudy" 175 | }, 176 | { 177 | "text": "heat" 178 | }, 179 | { 180 | "text": "pleasant" 181 | }, 182 | { 183 | "text": "rain" 184 | }, 185 | { 186 | "text": "shiny" 187 | }, 188 | { 189 | "text": "sunny" 190 | }, 191 | { 192 | "text": "temperature" 193 | }, 194 | { 195 | "text": "weather" 196 | } 197 | ], 198 | "description": "Weather bot" 199 | } 200 | ], 201 | "entities": [], 202 | "metadata": { 203 | "api_version": { 204 | "major_version": "v2", 205 | "minor_version": "2018-11-08" 206 | } 207 | }, 208 | "dialog_nodes": [ 209 | { 210 | "type": "standard", 211 | "title": "Anything else", 212 | "output": { 213 | "generic": [ 214 | { 215 | "values": [ 216 | { 217 | "text": "I didn't understand. You can try rephrasing." 218 | }, 219 | { 220 | "text": "Can you reword your statement? I'm not understanding." 221 | }, 222 | { 223 | "text": "I didn't get your meaning." 224 | } 225 | ], 226 | "response_type": "text", 227 | "selection_policy": "sequential" 228 | } 229 | ] 230 | }, 231 | "metadata": {}, 232 | "conditions": "anything_else", 233 | "dialog_node": "Anything else", 234 | "previous_sibling": "node_2_1538635558838" 235 | }, 236 | { 237 | "type": "standard", 238 | "title": "Greeting", 239 | "output": { 240 | "generic": [ 241 | { 242 | "values": [ 243 | { 244 | "text": "Hi. How can I help you?" 245 | } 246 | ], 247 | "response_type": "text", 248 | "selection_policy": "sequential" 249 | } 250 | ] 251 | }, 252 | "metadata": {}, 253 | "conditions": "#General_Greetings", 254 | "dialog_node": "node_1_1537856992108", 255 | "previous_sibling": "Welcome" 256 | }, 257 | { 258 | "type": "standard", 259 | "title": "Travel", 260 | "output": { 261 | "generic": [ 262 | { 263 | "values": [ 264 | { 265 | "text": "Redirecting you to travel bot" 266 | } 267 | ], 268 | "response_type": "text", 269 | "selection_policy": "sequential" 270 | } 271 | ] 272 | }, 273 | "context": { 274 | "destination_bot": "travel", 275 | "redirect_to_another_bot": true 276 | }, 277 | "metadata": {}, 278 | "conditions": "#travel", 279 | "dialog_node": "node_1_1538024858380", 280 | "previous_sibling": "node_1_1537856992108" 281 | }, 282 | { 283 | "type": "standard", 284 | "title": "Weather", 285 | "output": { 286 | "generic": [ 287 | { 288 | "values": [ 289 | { 290 | "text": "redirecting to weather bot" 291 | } 292 | ], 293 | "response_type": "text", 294 | "selection_policy": "sequential" 295 | } 296 | ] 297 | }, 298 | "context": { 299 | "destination_bot": "weather", 300 | "redirect_to_another_bot": true 301 | }, 302 | "metadata": {}, 303 | "conditions": "#weather", 304 | "dialog_node": "node_1_1538584495780", 305 | "previous_sibling": "node_1_1538024858380" 306 | }, 307 | { 308 | "type": "standard", 309 | "title": "Thank you", 310 | "output": { 311 | "generic": [ 312 | { 313 | "values": [ 314 | { 315 | "text": "Is there anything else that I can help you with?" 316 | } 317 | ], 318 | "response_type": "text", 319 | "selection_policy": "sequential" 320 | } 321 | ] 322 | }, 323 | "metadata": {}, 324 | "conditions": "#thankyou", 325 | "dialog_node": "node_1_1538635503771", 326 | "previous_sibling": "node_1_1538584495780" 327 | }, 328 | { 329 | "type": "standard", 330 | "title": "bye", 331 | "output": { 332 | "generic": [ 333 | { 334 | "values": [ 335 | { 336 | "text": "Have a nice time. Bye." 337 | } 338 | ], 339 | "response_type": "text", 340 | "selection_policy": "sequential" 341 | } 342 | ] 343 | }, 344 | "metadata": {}, 345 | "conditions": "#bye", 346 | "dialog_node": "node_2_1538635558838", 347 | "previous_sibling": "node_1_1538635503771" 348 | }, 349 | { 350 | "type": "standard", 351 | "title": "Welcome", 352 | "output": { 353 | "generic": [ 354 | { 355 | "values": [ 356 | { 357 | "text": "Hello. How can I help you?" 358 | } 359 | ], 360 | "response_type": "text", 361 | "selection_policy": "sequential" 362 | } 363 | ] 364 | }, 365 | "metadata": {}, 366 | "conditions": "welcome", 367 | "dialog_node": "Welcome" 368 | } 369 | ], 370 | "counterexamples": [], 371 | "system_settings": { 372 | "tooling": { 373 | "store_generic_responses": true 374 | }, 375 | "disambiguation": { 376 | "prompt": "Did you mean:", 377 | "none_of_the_above_prompt": "None of the above" 378 | }, 379 | "human_agent_assist": { 380 | "prompt": "Did you mean:" 381 | } 382 | }, 383 | "learning_opt_out": false, 384 | "name": "agentBot", 385 | "language": "en", 386 | "description": "Interface Bot" 387 | } 388 | -------------------------------------------------------------------------------- /WA/travel-bot.json: -------------------------------------------------------------------------------- 1 | { 2 | "intents": [ 3 | { 4 | "intent": "bye", 5 | "examples": [ 6 | { 7 | "text": "bye" 8 | }, 9 | { 10 | "text": "bye bye" 11 | }, 12 | { 13 | "text": "c ya" 14 | }, 15 | { 16 | "text": "good bye" 17 | }, 18 | { 19 | "text": "ok bye" 20 | }, 21 | { 22 | "text": "see you" 23 | } 24 | ], 25 | "description": "" 26 | }, 27 | { 28 | "intent": "greetings", 29 | "examples": [ 30 | { 31 | "text": "greetings" 32 | }, 33 | { 34 | "text": "Hello" 35 | }, 36 | { 37 | "text": "Hey" 38 | }, 39 | { 40 | "text": "Heyya" 41 | }, 42 | { 43 | "text": "Hi" 44 | }, 45 | { 46 | "text": "What's up?" 47 | } 48 | ] 49 | }, 50 | { 51 | "intent": "reserve", 52 | "examples": [ 53 | { 54 | "text": "arrange" 55 | }, 56 | { 57 | "text": "book" 58 | }, 59 | { 60 | "text": "booking for" 61 | }, 62 | { 63 | "text": "kindly arrange" 64 | }, 65 | { 66 | "text": "kindly book" 67 | }, 68 | { 69 | "text": "kindly make reservation for" 70 | }, 71 | { 72 | "text": "make a booking" 73 | }, 74 | { 75 | "text": "make a reservation for" 76 | }, 77 | { 78 | "text": "please arrange" 79 | }, 80 | { 81 | "text": "please book" 82 | }, 83 | { 84 | "text": "please make a booking for" 85 | }, 86 | { 87 | "text": "please reserve" 88 | }, 89 | { 90 | "text": "reservation for" 91 | }, 92 | { 93 | "text": "reserve" 94 | } 95 | ] 96 | }, 97 | { 98 | "intent": "status", 99 | "examples": [ 100 | { 101 | "text": "can you please help me with status of" 102 | }, 103 | { 104 | "text": "please let me know the status" 105 | }, 106 | { 107 | "text": "status of" 108 | }, 109 | { 110 | "text": "status please" 111 | }, 112 | { 113 | "text": "what is the status" 114 | } 115 | ] 116 | }, 117 | { 118 | "intent": "thankyou", 119 | "examples": [ 120 | { 121 | "text": "Thanks" 122 | }, 123 | { 124 | "text": "Thanks a lot" 125 | }, 126 | { 127 | "text": "Thank you" 128 | }, 129 | { 130 | "text": "Thank you very much" 131 | } 132 | ] 133 | } 134 | ], 135 | "entities": [ 136 | { 137 | "entity": "mode_of_transportation", 138 | "values": [ 139 | { 140 | "type": "synonyms", 141 | "value": "Cab", 142 | "synonyms": [ 143 | "mini cab", 144 | "Taxi", 145 | "taxi cab" 146 | ] 147 | }, 148 | { 149 | "type": "synonyms", 150 | "value": "flight", 151 | "synonyms": [ 152 | "air journey", 153 | "air trip", 154 | "domestic flight", 155 | "plane journey", 156 | "plane trip" 157 | ] 158 | } 159 | ] 160 | }, 161 | { 162 | "entity": "sys-currency", 163 | "values": [] 164 | }, 165 | { 166 | "entity": "sys-date", 167 | "values": [] 168 | }, 169 | { 170 | "entity": "sys-number", 171 | "values": [] 172 | }, 173 | { 174 | "entity": "sys-percentage", 175 | "values": [] 176 | }, 177 | { 178 | "entity": "sys-time", 179 | "values": [] 180 | } 181 | ], 182 | "metadata": { 183 | "api_version": { 184 | "major_version": "v2", 185 | "minor_version": "2018-11-08" 186 | } 187 | }, 188 | "dialog_nodes": [ 189 | { 190 | "type": "standard", 191 | "title": "Anything else", 192 | "output": { 193 | "text": { 194 | "values": [ 195 | "I didn't understand. You can try rephrasing.", 196 | "Can you reword your statement? I'm not understanding.", 197 | "I didn't get your meaning." 198 | ], 199 | "selection_policy": "sequential" 200 | } 201 | }, 202 | "context": { 203 | "destination_bot": "agent" 204 | }, 205 | "metadata": {}, 206 | "conditions": "anything_else", 207 | "dialog_node": "Anything else", 208 | "previous_sibling": "node_26_1538577242962" 209 | }, 210 | { 211 | "type": "event_handler", 212 | "parent": "slot_12_1508426016722", 213 | "context": { 214 | "cabRequiredDate": "@sys-date" 215 | }, 216 | "conditions": "@sys-date", 217 | "event_name": "input", 218 | "dialog_node": "handler_13_1508426016722", 219 | "previous_sibling": "handler_14_1508426016722" 220 | }, 221 | { 222 | "type": "event_handler", 223 | "output": { 224 | "text": "Please enter the date you require cab" 225 | }, 226 | "parent": "slot_12_1508426016722", 227 | "event_name": "focus", 228 | "dialog_node": "handler_14_1508426016722" 229 | }, 230 | { 231 | "type": "event_handler", 232 | "parent": "slot_15_1508426098680", 233 | "context": { 234 | "cabRequiredTime": "@sys-time" 235 | }, 236 | "conditions": "@sys-time", 237 | "event_name": "input", 238 | "dialog_node": "handler_16_1508426098680", 239 | "previous_sibling": "handler_17_1508426098680" 240 | }, 241 | { 242 | "type": "event_handler", 243 | "output": { 244 | "text": "Please enter the time you require cab" 245 | }, 246 | "parent": "slot_15_1508426098680", 247 | "event_name": "focus", 248 | "dialog_node": "handler_17_1508426098680" 249 | }, 250 | { 251 | "type": "event_handler", 252 | "output": { 253 | "text": { 254 | "values": [ 255 | "Please enter the date you require cab!" 256 | ], 257 | "selection_policy": "sequential" 258 | } 259 | }, 260 | "parent": "slot_12_1508426016722", 261 | "event_name": "nomatch", 262 | "dialog_node": "handler_19_1508426178176", 263 | "previous_sibling": "handler_13_1508426016722" 264 | }, 265 | { 266 | "type": "event_handler", 267 | "output": { 268 | "text": { 269 | "values": [ 270 | "Please enter the time you require cab!" 271 | ], 272 | "selection_policy": "sequential" 273 | } 274 | }, 275 | "parent": "slot_15_1508426098680", 276 | "event_name": "nomatch", 277 | "dialog_node": "handler_21_1508426194221", 278 | "previous_sibling": "handler_16_1508426098680" 279 | }, 280 | { 281 | "type": "event_handler", 282 | "output": {}, 283 | "parent": "node_3_1508425828948", 284 | "disabled": true, 285 | "event_name": "focus", 286 | "dialog_node": "handler_6_1508425932620" 287 | }, 288 | { 289 | "type": "standard", 290 | "title": "Greetings", 291 | "output": { 292 | "text": { 293 | "values": [ 294 | "Hi, I can help you with domestic flight booking,cab booking and status of flights!" 295 | ], 296 | "selection_policy": "sequential" 297 | } 298 | }, 299 | "conditions": "#greetings", 300 | "dialog_node": "node_1_1508425352942", 301 | "previous_sibling": "Welcome" 302 | }, 303 | { 304 | "type": "standard", 305 | "title": "Bye", 306 | "output": { 307 | "text": { 308 | "values": [], 309 | "selection_policy": "sequential" 310 | } 311 | }, 312 | "context": { 313 | "destination_bot": "agent" 314 | }, 315 | "metadata": {}, 316 | "conditions": "#bye", 317 | "dialog_node": "node_26_1538577242962", 318 | "previous_sibling": "node_5_1508425880755" 319 | }, 320 | { 321 | "type": "frame", 322 | "title": "Book Cab", 323 | "output": { 324 | "text": { 325 | "values": [ 326 | "Sure $cabPersonName, your cab has been booked for $cabRequiredDate and you will be picked up from $cabPickuplocation at $cabRequiredTime" 327 | ], 328 | "selection_policy": "sequential" 329 | } 330 | }, 331 | "context": { 332 | "destination_bot": "agent" 333 | }, 334 | "metadata": { 335 | "fallback": "leave" 336 | }, 337 | "conditions": "#reserve && @mode_of_transportation:Cab", 338 | "dialog_node": "node_3_1508425828948", 339 | "previous_sibling": "node_1_1508425352942" 340 | }, 341 | { 342 | "type": "standard", 343 | "title": "Thank You", 344 | "output": { 345 | "text": { 346 | "values": [], 347 | "selection_policy": "sequential" 348 | } 349 | }, 350 | "context": { 351 | "destination_bot": "agent" 352 | }, 353 | "metadata": {}, 354 | "conditions": "#thankyou", 355 | "dialog_node": "node_5_1508425880755", 356 | "previous_sibling": "node_3_1508425828948" 357 | }, 358 | { 359 | "type": "slot", 360 | "parent": "node_3_1508425828948", 361 | "variable": "$cabRequiredDate", 362 | "dialog_node": "slot_12_1508426016722", 363 | "previous_sibling": "handler_6_1508425932620" 364 | }, 365 | { 366 | "type": "slot", 367 | "parent": "node_3_1508425828948", 368 | "variable": "$cabRequiredTime", 369 | "dialog_node": "slot_15_1508426098680", 370 | "previous_sibling": "slot_12_1508426016722" 371 | }, 372 | { 373 | "type": "standard", 374 | "title": "Welcome", 375 | "output": { 376 | "text": { 377 | "values": [ 378 | "Hi, I can help you with domestic flight booking,cab booking and status of flights!" 379 | ], 380 | "selection_policy": "sequential" 381 | } 382 | }, 383 | "conditions": "welcome", 384 | "dialog_node": "Welcome" 385 | } 386 | ], 387 | "counterexamples": [], 388 | "learning_opt_out": false, 389 | "name": "travelBot", 390 | "language": "en", 391 | "description": "Bot for booking flight,cab and knowing status of flight!" 392 | } 393 | -------------------------------------------------------------------------------- /WA/weather-bot.json: -------------------------------------------------------------------------------- 1 | { 2 | "intents": [ 3 | { 4 | "intent": "bye", 5 | "examples": [ 6 | { 7 | "text": "bye" 8 | }, 9 | { 10 | "text": "bye bye" 11 | }, 12 | { 13 | "text": "c ya" 14 | }, 15 | { 16 | "text": "good bye" 17 | }, 18 | { 19 | "text": "see you" 20 | } 21 | ], 22 | "description": "" 23 | }, 24 | { 25 | "intent": "generic_weather", 26 | "examples": [ 27 | { 28 | "text": "cloudy" 29 | }, 30 | { 31 | "text": "Give me weather data of Sydney", 32 | "mentions": [ 33 | { 34 | "entity": "location", 35 | "location": [ 36 | 24, 37 | 30 38 | ] 39 | } 40 | ] 41 | }, 42 | { 43 | "text": "How does the weather look like?" 44 | }, 45 | { 46 | "text": "How does the weather look like in Bengaluru", 47 | "mentions": [ 48 | { 49 | "entity": "location", 50 | "location": [ 51 | 34, 52 | 43 53 | ] 54 | } 55 | ] 56 | }, 57 | { 58 | "text": "how will be weather" 59 | }, 60 | { 61 | "text": "pleasant" 62 | }, 63 | { 64 | "text": "weather" 65 | }, 66 | { 67 | "text": "Weather in London", 68 | "mentions": [ 69 | { 70 | "entity": "location", 71 | "location": [ 72 | 11, 73 | 17 74 | ] 75 | } 76 | ] 77 | }, 78 | { 79 | "text": "Weather in Mumbai", 80 | "mentions": [ 81 | { 82 | "entity": "location", 83 | "location": [ 84 | 11, 85 | 17 86 | ] 87 | } 88 | ] 89 | }, 90 | { 91 | "text": "will it be pleasant?" 92 | } 93 | ], 94 | "description": "" 95 | }, 96 | { 97 | "intent": "greetings", 98 | "examples": [ 99 | { 100 | "text": "greetings" 101 | }, 102 | { 103 | "text": "Hello" 104 | }, 105 | { 106 | "text": "Hey" 107 | }, 108 | { 109 | "text": "Heyya" 110 | }, 111 | { 112 | "text": "Hi" 113 | }, 114 | { 115 | "text": "What's up?" 116 | } 117 | ] 118 | }, 119 | { 120 | "intent": "rain_forecast", 121 | "examples": [ 122 | { 123 | "text": "cloud" 124 | }, 125 | { 126 | "text": "cloudy" 127 | }, 128 | { 129 | "text": "is rain expected?" 130 | }, 131 | { 132 | "text": "rain" 133 | }, 134 | { 135 | "text": "shiny" 136 | }, 137 | { 138 | "text": "thunderstorm" 139 | }, 140 | { 141 | "text": "Will it be sunny" 142 | }, 143 | { 144 | "text": "Will it rain" 145 | }, 146 | { 147 | "text": "will there be rain" 148 | } 149 | ], 150 | "description": "" 151 | }, 152 | { 153 | "intent": "temperature_forecast", 154 | "examples": [ 155 | { 156 | "text": "celsius" 157 | }, 158 | { 159 | "text": "cold" 160 | }, 161 | { 162 | "text": "cool" 163 | }, 164 | { 165 | "text": "fahrenheit" 166 | }, 167 | { 168 | "text": "heat" 169 | }, 170 | { 171 | "text": "hot" 172 | }, 173 | { 174 | "text": "temp" 175 | }, 176 | { 177 | "text": "temperature" 178 | }, 179 | { 180 | "text": "What will be he temperature at" 181 | } 182 | ], 183 | "description": "" 184 | }, 185 | { 186 | "intent": "thankyou", 187 | "examples": [ 188 | { 189 | "text": "Thanks" 190 | }, 191 | { 192 | "text": "Thanks a lot" 193 | }, 194 | { 195 | "text": "Thank you" 196 | }, 197 | { 198 | "text": "Thank you very much" 199 | } 200 | ] 201 | } 202 | ], 203 | "entities": [ 204 | { 205 | "entity": "location", 206 | "values": [ 207 | { 208 | "type": "synonyms", 209 | "value": "Bengaluru", 210 | "synonyms": [] 211 | }, 212 | { 213 | "type": "synonyms", 214 | "value": "London", 215 | "synonyms": [] 216 | }, 217 | { 218 | "type": "synonyms", 219 | "value": "Mumbai", 220 | "synonyms": [] 221 | }, 222 | { 223 | "type": "synonyms", 224 | "value": "Sydney", 225 | "synonyms": [] 226 | } 227 | ], 228 | "fuzzy_match": true 229 | }, 230 | { 231 | "entity": "sys-date", 232 | "values": [] 233 | } 234 | ], 235 | "metadata": { 236 | "api_version": { 237 | "major_version": "v2", 238 | "minor_version": "2018-11-08" 239 | } 240 | }, 241 | "dialog_nodes": [ 242 | { 243 | "type": "standard", 244 | "title": "Anything else", 245 | "output": { 246 | "generic": [ 247 | { 248 | "values": [ 249 | { 250 | "text": "I am weather Bot. I didn't understand. You can try rephrasing." 251 | }, 252 | { 253 | "text": "I am weather Bot. Can you reword your statement? I'm not understanding." 254 | }, 255 | { 256 | "text": "I am weather Bot. I didn't get your meaning." 257 | } 258 | ], 259 | "response_type": "text", 260 | "selection_policy": "sequential" 261 | } 262 | ] 263 | }, 264 | "context": { 265 | "destination_bot": "agent" 266 | }, 267 | "metadata": {}, 268 | "conditions": "anything_else", 269 | "digress_in": "does_not_return", 270 | "dialog_node": "Anything else", 271 | "previous_sibling": "node_25_1538575846436" 272 | }, 273 | { 274 | "type": "event_handler", 275 | "parent": "node_9_1538559670989", 276 | "event_name": "focus", 277 | "dialog_node": "handler_10_1538559717653", 278 | "previous_sibling": "slot_14_1538559756688" 279 | }, 280 | { 281 | "type": "event_handler", 282 | "output": { 283 | "generic": [ 284 | { 285 | "values": [ 286 | { 287 | "text": "Please enter the location for forecast" 288 | } 289 | ], 290 | "response_type": "text", 291 | "selection_policy": "sequential" 292 | } 293 | ] 294 | }, 295 | "parent": "slot_10_1619154177044", 296 | "event_name": "focus", 297 | "dialog_node": "handler_10_1619154177046", 298 | "previous_sibling": "handler_7_1619154177046" 299 | }, 300 | { 301 | "type": "event_handler", 302 | "output": {}, 303 | "parent": "slot_11_1538559717657", 304 | "context": { 305 | "location": "@location" 306 | }, 307 | "metadata": {}, 308 | "conditions": "@location", 309 | "event_name": "input", 310 | "dialog_node": "handler_12_1538559717657" 311 | }, 312 | { 313 | "type": "event_handler", 314 | "output": { 315 | "text": "Please enter the place for forecast", 316 | "generic": [ 317 | { 318 | "values": [ 319 | { 320 | "text": "Please enter the place for forecast" 321 | } 322 | ], 323 | "response_type": "text", 324 | "selection_policy": "sequential" 325 | } 326 | ] 327 | }, 328 | "parent": "slot_11_1538559717657", 329 | "metadata": {}, 330 | "event_name": "focus", 331 | "dialog_node": "handler_13_1538559717657", 332 | "previous_sibling": "handler_12_1538559717657" 333 | }, 334 | { 335 | "type": "event_handler", 336 | "parent": "slot_14_1538559756688", 337 | "context": { 338 | "date": "@sys-date" 339 | }, 340 | "conditions": "@sys-date", 341 | "event_name": "input", 342 | "dialog_node": "handler_15_1538559756688" 343 | }, 344 | { 345 | "type": "event_handler", 346 | "output": { 347 | "text": "Please enter the date for forecast" 348 | }, 349 | "parent": "slot_14_1538559756688", 350 | "metadata": {}, 351 | "event_name": "focus", 352 | "dialog_node": "handler_16_1538559756688", 353 | "previous_sibling": "handler_15_1538559756688" 354 | }, 355 | { 356 | "type": "event_handler", 357 | "parent": "node_1_1538559415461", 358 | "event_name": "focus", 359 | "dialog_node": "handler_2_1538559468850", 360 | "previous_sibling": "slot_21_1538560793660" 361 | }, 362 | { 363 | "type": "event_handler", 364 | "parent": "node_1_1539855184973", 365 | "event_name": "focus", 366 | "dialog_node": "handler_2_1539855249720", 367 | "previous_sibling": "slot_10_1619154177044" 368 | }, 369 | { 370 | "type": "event_handler", 371 | "output": {}, 372 | "parent": "slot_21_1538560793660", 373 | "context": { 374 | "location": "@location" 375 | }, 376 | "conditions": "@location", 377 | "event_name": "input", 378 | "dialog_node": "handler_22_1538560793660" 379 | }, 380 | { 381 | "type": "event_handler", 382 | "output": { 383 | "text": "Please enter the place for rain forecast", 384 | "generic": [ 385 | { 386 | "values": [ 387 | { 388 | "text": "Please enter the place for rain forecast" 389 | } 390 | ], 391 | "response_type": "text", 392 | "selection_policy": "sequential" 393 | } 394 | ] 395 | }, 396 | "parent": "slot_21_1538560793660", 397 | "metadata": {}, 398 | "event_name": "focus", 399 | "dialog_node": "handler_23_1538560793660", 400 | "previous_sibling": "handler_22_1538560793660" 401 | }, 402 | { 403 | "type": "event_handler", 404 | "output": {}, 405 | "parent": "slot_6_1538559530971", 406 | "context": { 407 | "date": "@sys-date" 408 | }, 409 | "metadata": {}, 410 | "conditions": "@sys-date", 411 | "event_name": "input", 412 | "dialog_node": "handler_7_1538559530971" 413 | }, 414 | { 415 | "type": "event_handler", 416 | "output": {}, 417 | "parent": "slot_6_1539855276215", 418 | "context": { 419 | "date": "@sys-date" 420 | }, 421 | "conditions": "@sys-date", 422 | "event_name": "input", 423 | "dialog_node": "handler_7_1539855276215" 424 | }, 425 | { 426 | "type": "event_handler", 427 | "output": {}, 428 | "parent": "slot_10_1619154177044", 429 | "context": { 430 | "location": "@location" 431 | }, 432 | "conditions": "@location", 433 | "event_name": "input", 434 | "dialog_node": "handler_7_1619154177046" 435 | }, 436 | { 437 | "type": "event_handler", 438 | "output": { 439 | "text": "Please enter the date for rain forecast" 440 | }, 441 | "parent": "slot_6_1538559530971", 442 | "metadata": {}, 443 | "event_name": "focus", 444 | "dialog_node": "handler_8_1538559530971", 445 | "previous_sibling": "handler_7_1538559530971" 446 | }, 447 | { 448 | "type": "event_handler", 449 | "output": { 450 | "text": "Please enter the date for forecast", 451 | "generic": [ 452 | { 453 | "values": [ 454 | { 455 | "text": "Please enter the date for forecast" 456 | } 457 | ], 458 | "response_type": "text", 459 | "selection_policy": "sequential" 460 | } 461 | ] 462 | }, 463 | "parent": "slot_6_1539855276215", 464 | "event_name": "focus", 465 | "dialog_node": "handler_8_1539855276215", 466 | "previous_sibling": "handler_7_1539855276215" 467 | }, 468 | { 469 | "type": "frame", 470 | "title": "Rain_Forecast", 471 | "output": { 472 | "generic": [ 473 | { 474 | "values": [ 475 | { 476 | "text": "Light showers are expected in $location on $date" 477 | } 478 | ], 479 | "response_type": "text", 480 | "selection_policy": "sequential" 481 | } 482 | ] 483 | }, 484 | "context": { 485 | "destination_bot": "agent" 486 | }, 487 | "metadata": { 488 | "fallback": "leave" 489 | }, 490 | "conditions": "#rain_forecast", 491 | "digress_in": "does_not_return", 492 | "dialog_node": "node_1_1538559415461", 493 | "digress_out": "allow_all", 494 | "previous_sibling": "Welcome", 495 | "digress_out_slots": "not_allowed" 496 | }, 497 | { 498 | "type": "frame", 499 | "title": "Generic_Weather", 500 | "output": { 501 | "generic": [ 502 | { 503 | "values": [ 504 | { 505 | "text": "Weather will be sunny in $location on $date with an average temperature of about 30 degree celsius." 506 | } 507 | ], 508 | "response_type": "text", 509 | "selection_policy": "sequential" 510 | } 511 | ] 512 | }, 513 | "context": { 514 | "destination_bot": "agent" 515 | }, 516 | "metadata": { 517 | "fallback": "leave" 518 | }, 519 | "conditions": "#generic_weather", 520 | "digress_in": "does_not_return", 521 | "dialog_node": "node_1_1539855184973", 522 | "digress_out": "allow_all", 523 | "previous_sibling": "node_9_1538559670989", 524 | "digress_out_slots": "not_allowed" 525 | }, 526 | { 527 | "type": "standard", 528 | "title": "thank you", 529 | "output": { 530 | "generic": [ 531 | { 532 | "values": [ 533 | { 534 | "text": "Have a nice time. bye." 535 | } 536 | ], 537 | "response_type": "text", 538 | "selection_policy": "sequential" 539 | } 540 | ] 541 | }, 542 | "context": { 543 | "destination_bot": "agent" 544 | }, 545 | "metadata": {}, 546 | "conditions": "#thankyou", 547 | "dialog_node": "node_24_1538573581070", 548 | "previous_sibling": "node_1_1539855184973" 549 | }, 550 | { 551 | "type": "standard", 552 | "title": "Bye", 553 | "output": { 554 | "generic": [ 555 | { 556 | "values": [ 557 | { 558 | "text": "Have a nice time. Bye." 559 | } 560 | ], 561 | "response_type": "text", 562 | "selection_policy": "sequential" 563 | } 564 | ] 565 | }, 566 | "context": { 567 | "destination_bot": "agent" 568 | }, 569 | "metadata": {}, 570 | "conditions": "#bye", 571 | "dialog_node": "node_25_1538575846436", 572 | "previous_sibling": "node_24_1538573581070" 573 | }, 574 | { 575 | "type": "frame", 576 | "title": "Temperature_Forecast", 577 | "output": { 578 | "generic": [ 579 | { 580 | "values": [ 581 | { 582 | "text": "Weather will be pleasant in $location on $date with an average temperature of about 22 degree celsius." 583 | } 584 | ], 585 | "response_type": "text", 586 | "selection_policy": "random" 587 | } 588 | ] 589 | }, 590 | "context": { 591 | "destination_bot": "agent" 592 | }, 593 | "metadata": { 594 | "fallback": "leave" 595 | }, 596 | "conditions": "#temperature_forecast", 597 | "digress_in": "does_not_return", 598 | "dialog_node": "node_9_1538559670989", 599 | "digress_out": "allow_all", 600 | "previous_sibling": "node_1_1538559415461", 601 | "digress_out_slots": "not_allowed" 602 | }, 603 | { 604 | "type": "slot", 605 | "parent": "node_1_1539855184973", 606 | "variable": "$location", 607 | "dialog_node": "slot_10_1619154177044", 608 | "previous_sibling": "slot_6_1539855276215" 609 | }, 610 | { 611 | "type": "slot", 612 | "output": {}, 613 | "parent": "node_9_1538559670989", 614 | "metadata": {}, 615 | "variable": "$location", 616 | "dialog_node": "slot_11_1538559717657" 617 | }, 618 | { 619 | "type": "slot", 620 | "output": {}, 621 | "parent": "node_9_1538559670989", 622 | "metadata": {}, 623 | "variable": "$date", 624 | "dialog_node": "slot_14_1538559756688", 625 | "previous_sibling": "slot_11_1538559717657" 626 | }, 627 | { 628 | "type": "slot", 629 | "output": {}, 630 | "parent": "node_1_1538559415461", 631 | "metadata": {}, 632 | "variable": "$location", 633 | "dialog_node": "slot_21_1538560793660", 634 | "previous_sibling": "slot_6_1538559530971" 635 | }, 636 | { 637 | "type": "slot", 638 | "output": {}, 639 | "parent": "node_1_1538559415461", 640 | "metadata": {}, 641 | "variable": "$date", 642 | "dialog_node": "slot_6_1538559530971" 643 | }, 644 | { 645 | "type": "slot", 646 | "parent": "node_1_1539855184973", 647 | "variable": "$date", 648 | "dialog_node": "slot_6_1539855276215" 649 | }, 650 | { 651 | "type": "standard", 652 | "title": "Welcome", 653 | "output": { 654 | "generic": [ 655 | { 656 | "values": [ 657 | { 658 | "text": "Hello. I am weather Bot. How can I help you?" 659 | } 660 | ], 661 | "response_type": "text", 662 | "selection_policy": "sequential" 663 | } 664 | ] 665 | }, 666 | "metadata": {}, 667 | "conditions": "welcome || #greetings", 668 | "dialog_node": "Welcome" 669 | } 670 | ], 671 | "counterexamples": [], 672 | "system_settings": { 673 | "tooling": { 674 | "store_generic_responses": true 675 | }, 676 | "disambiguation": { 677 | "prompt": "Did you mean:", 678 | "none_of_the_above_prompt": "None of the above" 679 | }, 680 | "human_agent_assist": { 681 | "prompt": "Did you mean:" 682 | } 683 | }, 684 | "learning_opt_out": false, 685 | "name": "weatherBot", 686 | "language": "en", 687 | "description": "Weather Bot" 688 | } 689 | -------------------------------------------------------------------------------- /app.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 | 'use strict'; 18 | 19 | var express = require('express'); // app server 20 | var bodyParser = require('body-parser'); // parser for post requests 21 | const AssistantV1 = require('ibm-watson/assistant/v1'); 22 | const { IamAuthenticator } = require('ibm-watson/auth'); 23 | 24 | var app = express(); 25 | 26 | // Bootstrap application settings 27 | app.use(express.static('./public')); // load UI from public folder 28 | app.use(bodyParser.json()); 29 | 30 | var assistantAPIKey = process.env["ASSISTANT_IAM_API_KEY"]; 31 | var assistantURL = process.env["ASSISTANT_IAM_URL"]; 32 | var assistantVersion = process.env["VERSION"]; 33 | 34 | console.log("assistantVersion = " + assistantVersion); 35 | 36 | // Create the service wrapper 37 | const assistant = new AssistantV1({ 38 | version: assistantVersion, 39 | authenticator: new IamAuthenticator({ 40 | apikey: assistantAPIKey, 41 | }), 42 | url: assistantURL, 43 | }); 44 | 45 | 46 | // Endpoint to be call from the client side 47 | app.post('/api/message', function (req, res) { 48 | console.log(""); 49 | var workspace = getDestinationBot(req.body.context) || ''; 50 | console.log("workspace = " + workspace); 51 | if (!workspace || workspace === '') { 52 | return res.json({ 53 | 'output': { 54 | 'text': 'The app has not been configured with a WORKSPACE_ID environment variable. Please refer to the ' + 'README documentation on how to set this variable.
' + 'Once a workspace has been defined the intents may be imported from ' + 'here in order to get a working application.' 55 | } 56 | }); 57 | } 58 | var payload = { 59 | workspaceId: workspace, 60 | context: req.body.context || {}, 61 | input: req.body.input || {} 62 | }; 63 | 64 | // Send the input to the assistant service 65 | assistant.message(payload, function (err, data) { 66 | data = data.result 67 | console.log("Message: " + JSON.stringify(payload.input)); 68 | if (err) { 69 | console.log("Error occurred: " + JSON.stringify(err.message)) 70 | return res.status(err.code || 500).json(err); 71 | } 72 | 73 | 74 | if (isRedirect(data.context)) { 75 | // When there is a redirect, get the redirect bot workspace id 76 | payload.workspaceId = getDestinationBot(data.context); 77 | // When there is a redirect, update destination bot in context so it persists along with the conversation 78 | payload.context.destination_bot = data.context.destination_bot; 79 | // Where there is redirect, old conversation_id is not needed. Delete it 80 | delete payload.context.conversation_id; 81 | // For redirect, no user action is needed. Call the redirect bot automatically and send back that response to user 82 | assistant.message(payload, function (err, data) { 83 | data = data.result 84 | if (err) { 85 | return res.status(err.code || 500).json(err); 86 | } 87 | return res.json(updateMessage(payload, data)); 88 | }); 89 | } else { // There is no redirect. So send back the response to user for further action 90 | return res.json(updateMessage(payload, data)); 91 | } 92 | 93 | }); 94 | }); 95 | 96 | // The function checks if the bot response says messages to be redirected 97 | function isRedirect(context) { 98 | if (context && context.redirect_to_another_bot) { 99 | var isRedirect = context.redirect_to_another_bot; 100 | if (isRedirect == true) { 101 | return true; 102 | } else { 103 | return false; 104 | } 105 | } else { 106 | return false; 107 | } 108 | } 109 | 110 | // The agent bot decides which bot the request should be redirected to and updates that in context variable. 111 | // Get worspace_id for redirected bot details so messages can be sent to that bot 112 | function getDestinationBot(context) { 113 | var destination_bot = null; 114 | if (context && context.destination_bot) { 115 | destination_bot = context.destination_bot.toUpperCase(); 116 | } 117 | 118 | var wsId = process.env["WORKSPACE_ID_" + destination_bot]; 119 | 120 | if (!wsId) { 121 | wsId = process.env["WORKSPACE_ID_AGENT"]; 122 | } 123 | 124 | if (!destination_bot) { 125 | destination_bot = "AGENT"; 126 | } 127 | 128 | console.log("Message being sent to: " + destination_bot + " bot"); 129 | return wsId; 130 | } 131 | /** 132 | * Updates the response text using the intent confidence 133 | * @param {Object} input The request to the Assistant service 134 | * @param {Object} response The response from the Assistant service 135 | * @return {Object} The response with the updated message 136 | */ 137 | function updateMessage(input, response) { 138 | var responseText = null; 139 | if (!response.output) { 140 | response.output = {}; 141 | } else { 142 | console.log("Response message: " + JSON.stringify(response.output.text)); 143 | return response; 144 | } 145 | if (response.intents && response.intents[0]) { 146 | var intent = response.intents[0]; 147 | // Depending on the confidence of the response the app can return different messages. 148 | // The confidence will vary depending on how well the system is trained. The service will always try to assign 149 | // a class/intent to the input. If the confidence is low, then it suggests the service is unsure of the 150 | // user's intent . In these cases it is usually best to return a disambiguation message 151 | // ('I did not understand your intent, please rephrase your question', etc..) 152 | if (intent.confidence >= 0.75) { 153 | responseText = 'I understood your intent was ' + intent.intent; 154 | } else if (intent.confidence >= 0.5) { 155 | responseText = 'I think your intent was ' + intent.intent; 156 | } else { 157 | responseText = 'I did not understand your intent'; 158 | } 159 | } 160 | response.output.text = responseText; 161 | return response; 162 | } 163 | 164 | module.exports = app; 165 | -------------------------------------------------------------------------------- /images/CopyAPIKeyAndUrl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/CopyAPIKeyAndUrl.png -------------------------------------------------------------------------------- /images/ImportSkill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/ImportSkill.png -------------------------------------------------------------------------------- /images/SkillsTab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/SkillsTab.png -------------------------------------------------------------------------------- /images/app_home_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/app_home_page.png -------------------------------------------------------------------------------- /images/app_logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/app_logs.png -------------------------------------------------------------------------------- /images/app_logs_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/app_logs_1.png -------------------------------------------------------------------------------- /images/app_logs_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/app_logs_2.png -------------------------------------------------------------------------------- /images/app_logs_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/app_logs_3.png -------------------------------------------------------------------------------- /images/app_logs_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/app_logs_4.png -------------------------------------------------------------------------------- /images/app_logs_backup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/app_logs_backup.png -------------------------------------------------------------------------------- /images/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/architecture.png -------------------------------------------------------------------------------- /images/bot_actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/bot_actions.png -------------------------------------------------------------------------------- /images/chat_interface_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/chat_interface_full.png -------------------------------------------------------------------------------- /images/import_json_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/import_json_file.png -------------------------------------------------------------------------------- /images/import_workspace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/import_workspace.png -------------------------------------------------------------------------------- /images/launch_tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/launch_tool.png -------------------------------------------------------------------------------- /images/manifest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/manifest.png -------------------------------------------------------------------------------- /images/query_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/query_1.png -------------------------------------------------------------------------------- /images/service_credentials_link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/service_credentials_link.png -------------------------------------------------------------------------------- /images/view_credentials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/view_credentials.png -------------------------------------------------------------------------------- /images/visit_app_url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/visit_app_url.png -------------------------------------------------------------------------------- /images/workspaces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/images/workspaces.png -------------------------------------------------------------------------------- /manifest.yml: -------------------------------------------------------------------------------- 1 | applications: 2 | - name: watson-assistant-multi-bot-agent 3 | command: npm start 4 | path: . 5 | memory: 256M 6 | instances: 1 7 | random-route: true 8 | env: 9 | WORKSPACE_ID_AGENT: 10 | WORKSPACE_ID_TRAVEL: 11 | WORKSPACE_ID_WEATHER: 12 | ASSISTANT_IAM_API_KEY: 13 | ASSISTANT_IAM_URL: 14 | VERSION: 15 | -------------------------------------------------------------------------------- /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": "https://github.com/watson-developer-cloud/assistant-simple.git" 19 | }, 20 | "license": "Apache-2.0", 21 | "dependencies": { 22 | "body-parser": "^1.18.3", 23 | "dotenv": "^6.0.0", 24 | "express": "^4.16.3", 25 | "watson-developer-cloud": "^3.13.0", 26 | "ibm-watson": "^5.4.0" 27 | }, 28 | "publishConfig": { 29 | "registry": "https://registry.npmjs.org/", 30 | "access": "public" 31 | }, 32 | "devDependencies": { 33 | "babel-eslint": "^8.2.6", 34 | "casperjs": "^1.1.4", 35 | "codecov": "^3.0.4", 36 | "eslint": "^5.1.0", 37 | "jest": "^23.4.1", 38 | "phantomjs-prebuilt": "^2.1.16", 39 | "supertest": "^3.1.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /public/conversation.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /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 | height: 90%; 77 | padding: 0.9375rem 0 0.625rem 0; 78 | margin: auto; 79 | text-align: left; 80 | max-width: 25rem; 81 | min-width: 9.375rem; 82 | } 83 | 84 | .user-typing { 85 | border: none; 86 | color: #8d25e8; 87 | margin: 0.75rem; 88 | font-size: 15; 89 | font-family: Helvetica Neue for IBM, Helvetica Neue, Helvetica, Arial, sans-serif; 90 | } 91 | 92 | #scrollingChat { 93 | margin: 0.75rem; 94 | overflow-y: auto; 95 | overflow-x: hidden; 96 | height: calc(100% - 4rem); 97 | } 98 | 99 | .message-inner { 100 | opacity: 0; 101 | margin-top: 0.9375rem; 102 | -webkit-transition-property: opacity, margin-top; 103 | -webkit-transition-duration: 0.75s; 104 | -webkit-transition-timing-function: ease-in; 105 | -moz-transition-property: opacity, margin-top; 106 | -moz-transition-duration: 0.75s; 107 | -moz-transition-timing-function: ease-in; 108 | -o-transition-property: opacity, margin-top; 109 | -o-transition-duration: 0.75s; 110 | -o-transition-timing-function: ease-in; 111 | -ms-transition-property: opacity, margin-top; 112 | -ms-transition-duration: 0.75s; 113 | -ms-transition-timing-function: ease-in; 114 | transition-property: opacity, margin-top; 115 | transition-duration: 0.75s; 116 | transition-timing-function: ease-in; 117 | } 118 | 119 | .load .message-inner { 120 | opacity: 1; 121 | margin-top: 0.3125rem; 122 | } 123 | 124 | .from-user { 125 | text-align: right; 126 | } 127 | 128 | .from-user .message-inner { 129 | position: relative; 130 | font-size: 1rem; 131 | color: #fff; 132 | letter-spacing: 0.015rem; 133 | line-height: 1.3125rem; 134 | background: #00B4A0; 135 | border-radius: 1.25rem; 136 | border-bottom-right-radius: 0; 137 | text-align: left; 138 | display: inline-block; 139 | margin-left: 2.5rem; 140 | min-width: 2.5rem; 141 | } 142 | 143 | .from-user .message-inner p { 144 | margin: 0.3125rem; 145 | padding: 0 0.9375rem; 146 | } 147 | 148 | .from-user .message-inner:before, .from-user .message-inner:after { 149 | content: ""; 150 | position: absolute; 151 | } 152 | 153 | 154 | .from-user .message-inner:before { 155 | z-index: -2; 156 | bottom: -0.375rem; 157 | right: 0; 158 | height: 0.375rem; 159 | width: 0.5rem; 160 | background: #1cb3a0; 161 | } 162 | 163 | .from-user .message-inner:after { 164 | z-index: -1; 165 | bottom: -0.5rem; 166 | right: 0; 167 | height: 0.5rem; 168 | width: 0.5rem; 169 | background: #fff; 170 | border-top-right-radius: 1.25rem; 171 | } 172 | 173 | .from-watson, 174 | .message-inner { 175 | position: relative; 176 | border-radius: 1.5625rem; 177 | font-size: 1rem; 178 | color: #B5B5B5; 179 | letter-spacing: 0.015rem; 180 | line-height: 1.3125rem; 181 | } 182 | 183 | .from-watson.latest .message-inner { 184 | color: #323232; 185 | } 186 | 187 | .from-watson p { 188 | margin: 0.3125rem; 189 | padding: 0 1.25rem; 190 | } 191 | 192 | .from-watson.latest.top p:before { 193 | content: "."; 194 | color: #9855D4; 195 | background-image: url("../img/marker_image.png"); 196 | background-size: 0.3125rem 1.3125rem; 197 | position: absolute; 198 | z-index: 2; 199 | left: 0.4375rem; 200 | width: 0.3125rem; 201 | height: 1.3125rem; 202 | line-height: 1.3125rem; 203 | } 204 | 205 | #textInput { 206 | border: none; 207 | outline: none; 208 | background: transparent; 209 | font-size: 1rem; 210 | color: #323232; 211 | letter-spacing: 0.015rem; 212 | line-height: 1.3125rem; 213 | height: 2.5rem; 214 | max-width: 100%; 215 | padding-left: 0.125rem; 216 | margin-bottom: -0.125rem; 217 | font-family: Helvetica Neue for IBM, Helvetica Neue, Helvetica, Arial, sans-serif; 218 | 219 | } 220 | 221 | #textInput.underline { 222 | border-bottom: 2px solid #00B4A0; 223 | } 224 | 225 | ::-webkit-input-placeholder { 226 | color: #B5B5B5; 227 | } 228 | 229 | ::-moz-placeholder { 230 | color: #B5B5B5; 231 | opacity: 1; 232 | } 233 | 234 | input:-moz-placeholder { 235 | color: #B5B5B5; 236 | opacity: 1; 237 | } 238 | 239 | :-ms-input-placeholder { 240 | color: #B5B5B5; 241 | } 242 | 243 | ::-ms-clear { 244 | display: none; 245 | } 246 | 247 | .inputOutline { 248 | display: block; 249 | border-bottom: 0.0625rem solid #aeaeae; 250 | margin-left: 0.5rem; 251 | margin-right: 0.5rem; 252 | } 253 | 254 | #textInputDummy { 255 | position:absolute; 256 | white-space:pre; 257 | top: 0; 258 | left: -1000%; 259 | opacity: 0; 260 | } 261 | 262 | #payload-column { 263 | font-family: Monaco, monospace; 264 | font-size: 0.75rem; 265 | letter-spacing: 0; 266 | line-height: 1.125rem; 267 | background-color: #23292A; 268 | color: #fff; 269 | overflow-x: auto; 270 | 271 | width: 45%; 272 | max-width: 32.0625rem; 273 | min-width: 29.6875rem; 274 | } 275 | 276 | #payload-column.full { 277 | width: 100%; 278 | max-width: none; 279 | min-width: initial; 280 | } 281 | 282 | #payload-column .header-text, #payload-column #payload-initial-message { 283 | font-family: Helvetica Neue for IBM, Helvetica Neue, Helvetica, Arial, sans-serif; 284 | font-size: 1.125rem; 285 | color: #9E9E9E; 286 | letter-spacing: 0.01875rem; 287 | padding: 0.5rem; 288 | padding-left: 2.5rem; 289 | background: #383D3E; 290 | } 291 | 292 | .hide { 293 | display: none; 294 | } 295 | 296 | .payload .line-numbers, .payload .payload-text { 297 | padding: 0.5rem; 298 | } 299 | 300 | .line-numbers { 301 | width: 2rem; 302 | color: #898989; 303 | text-align: right; 304 | } 305 | 306 | pre { 307 | margin: 0; 308 | word-wrap: normal; 309 | } 310 | 311 | .string { 312 | color: #54EED0; 313 | } 314 | 315 | .boolean, .null, .number { 316 | color: #CE8EFF; 317 | } 318 | 319 | .key { 320 | color: #66B7FF; 321 | } 322 | 323 | html{ 324 | font-size: 16px; 325 | } 326 | 327 | @media only screen and (max-width: 1000px) { 328 | html { 329 | font-size: 15px; 330 | } 331 | } 332 | 333 | @media only screen and (max-width: 600px) { 334 | html { 335 | font-size: 14px; 336 | } 337 | 338 | .chat-column { 339 | padding-top: 4rem; 340 | } 341 | 342 | #payload-column { 343 | width: 0; 344 | max-width: none; 345 | min-width: initial; 346 | } 347 | } 348 | 349 | /* IBM Design fonts https://github.ibm.com/Design/fonts */ 350 | @font-face { 351 | font-family: 'Helvetica Neue for IBM'; 352 | src: url('../fonts/light/h-n-light.eot?') format('eot'), 353 | url('../fonts/light/h-n-light.woff2') format('woff2'), 354 | url('../fonts/light/h-n-light.woff') format('woff'), 355 | url('../fonts/light/h-n-light.ttf') format('truetype'); 356 | font-weight: 300; 357 | font-style: normal; 358 | } 359 | @font-face { 360 | font-family: 'Helvetica Neue for IBM'; 361 | src: url('../fonts/light-italic/h-n-light-italic.eot?') format('eot'), 362 | url('../fonts/light-italic/h-n-light-italic.woff2') format('woff2'), 363 | url('../fonts/light-italic/h-n-light-italic.woff') format('woff'), 364 | url('../fonts/light-italic/h-n-light-italic.ttf') format('truetype'); 365 | font-weight: 300; 366 | font-style: italic; 367 | } 368 | @font-face { 369 | font-family: 'Helvetica Neue for IBM'; 370 | src: url('../fonts/roman/h-n-roman.eot?') format('eot'), 371 | url('../fonts/roman/h-n-roman.woff2') format('woff2'), 372 | url('../fonts/roman/h-n-roman.woff') format('woff'), 373 | url('../fonts/roman/h-n-roman.ttf') format('truetype'); 374 | font-weight: 400; 375 | font-style: normal; 376 | } 377 | @font-face { 378 | font-family: 'Helvetica Neue for IBM'; 379 | src: url('../fonts/roman-italic/h-n-roman-italic.eot?') format('eot'), 380 | url('../fonts/roman-italic/h-n-roman-italic.woff2') format('woff2'), 381 | url('../fonts/roman-italic/h-n-roman-italic.woff') format('woff'), 382 | url('../fonts/roman-italic/h-n-roman-italic.ttf') format('truetype'); 383 | font-weight: 400; 384 | font-style: italic; 385 | } 386 | @font-face { 387 | font-family: 'Helvetica Neue for IBM'; 388 | src: url('../fonts/medium/h-n-medium.eot?') format('eot'), 389 | url('../fonts/medium/h-n-medium.woff2') format('woff2'), 390 | url('../fonts/medium/h-n-medium.woff') format('woff'), 391 | url('../fonts/medium/h-n-medium.ttf') format('truetype'); 392 | font-weight: 500; 393 | font-style: normal; 394 | } 395 | @font-face { 396 | font-family: 'Helvetica Neue for IBM'; 397 | src: url('../fonts/medium-italic/h-n-medium-italic.eot?') format('eot'), 398 | url('../fonts/medium-italic/h-n-medium-italic.woff2') format('woff2'), 399 | url('../fonts/medium-italic/h-n-medium-italic.woff') format('woff'), 400 | url('../fonts/medium-italic/h-n-medium-italic.ttf') format('truetype'); 401 | font-weight: 500; 402 | font-style: italic; 403 | } 404 | @font-face { 405 | font-family: 'Helvetica Neue for IBM'; 406 | src: url('../fonts/bold/h-n-bold.eot?') format('eot'), 407 | url('../fonts/bold/h-n-bold.woff2') format('woff2'), 408 | url('../fonts/bold/h-n-bold.woff') format('woff'), 409 | url('../fonts/bold/h-n-bold.ttf') format('truetype'); 410 | font-weight: 700; 411 | font-style: normal; 412 | } 413 | @font-face { 414 | font-family: 'Helvetica Neue for IBM'; 415 | src: url('../fonts/bold-italic/h-n-bold-italic.eot?') format('eot'), 416 | url('../fonts/bold-italic/h-n-bold-italic.woff2') format('woff2'), 417 | url('../fonts/bold-italic/h-n-bold-italic.woff') format('woff'), 418 | url('../fonts/bold-italic/h-n-bold-italic.ttf') format('truetype'); 419 | font-weight: 700; 420 | font-style: italic; 421 | } 422 | 423 | /* IBM Icons */ 424 | @font-face { 425 | font-family: 'ibm-icons'; 426 | src:url('../fonts/ibm-icons.eot?ytcz1z') format('eot'), 427 | url('../fonts/ibm-icons.eot?ytcz1z#iefix') format('embedded-opentype'), 428 | url('../fonts/ibm-icons.ttf?ytcz1z') format('truetype'), 429 | url('../fonts/ibm-icons.woff?ytcz1z') format('woff'), 430 | url('../fonts/ibm-icons.svg?ytcz1z#ibm-icons') format('svg'); 431 | font-weight: normal; 432 | font-style: normal; 433 | } 434 | 435 | /* IBM glyphs */ 436 | @font-face { 437 | font-family: 'ibm-glyph'; 438 | src:url('../fonts/ibm-glyphs.eot?1b8643') format('eot'), 439 | url('../fonts/ibm-glyphs.eot?1b8643#iefix') format('embedded-opentype'), 440 | url('../fonts/ibm-glyphs.ttf?1b8643') format('truetype'), 441 | url('../fonts/ibm-glyphs.woff?1b8643') format('woff'), 442 | url('../fonts/ibm-glyphs.svg?1b8643#ibm-glyph') format('svg'); 443 | font-weight: normal; 444 | font-style: normal; 445 | } 446 | 447 | .options-list { 448 | color: #8d25e8; 449 | cursor: pointer; 450 | } 451 | 452 | .options-button { 453 | color: white; 454 | background-color: #8d25e8; 455 | border-radius: 6px; 456 | padding-bottom: 4px; 457 | padding-top: 4px; 458 | padding-left: 6px; 459 | padding-right: 6px; 460 | margin: 3px; 461 | cursor: pointer; 462 | display: inline-block; 463 | } 464 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/public/favicon.png -------------------------------------------------------------------------------- /public/fonts/roman/h-n-roman.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/public/fonts/roman/h-n-roman.woff -------------------------------------------------------------------------------- /public/fonts/roman/h-n-roman.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/watson-assistant-multi-bot-agent/18a92ec1b33cdcd91c2214d5ac2e8a5a77443f2b/public/fonts/roman/h-n-roman.woff2 -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Watson Assistant Chat App 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 |
22 |
23 |
24 |
25 |

26 | 30 |
31 | * This system is for demonstration purposes only and is not intended to process Personal Data. No Personal 32 | Data is to be entered 33 | into this system as it may not have the necessary controls in place to meet the requirements of the General 34 | Data Protection 35 | Regulation (EU) 2016/679. 36 |
37 |
38 |
39 | 48 |
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /public/js/api.js: -------------------------------------------------------------------------------- 1 | // The Api module is designed to handle all interactions with the server 2 | 3 | var Api = (function() { 4 | var requestPayload; 5 | var responsePayload; 6 | var messageEndpoint = '/api/message'; 7 | 8 | // Publicly accessible methods defined 9 | return { 10 | sendRequest: sendRequest, 11 | 12 | // The request/response getters/setters are defined here to prevent internal methods 13 | // from calling the methods without any of the callbacks that are added elsewhere. 14 | getRequestPayload: function() { 15 | return requestPayload; 16 | }, 17 | setRequestPayload: function(newPayloadStr) { 18 | requestPayload = JSON.parse(newPayloadStr); 19 | }, 20 | getResponsePayload: function() { 21 | return responsePayload; 22 | }, 23 | setResponsePayload: function(newPayloadStr) { 24 | responsePayload = JSON.parse(newPayloadStr); 25 | } 26 | }; 27 | 28 | // Send a message request to the server 29 | function sendRequest(text, context) { 30 | // Build request payload 31 | var payloadToWatson = {}; 32 | if (text) { 33 | payloadToWatson.input = { 34 | text: text 35 | }; 36 | } 37 | if (context) { 38 | payloadToWatson.context = context; 39 | } 40 | 41 | // Built http request 42 | var http = new XMLHttpRequest(); 43 | http.open('POST', messageEndpoint, true); 44 | http.setRequestHeader('Content-type', 'application/json'); 45 | http.onreadystatechange = function() { 46 | if (http.readyState === 4 && http.status === 200 && http.responseText) { 47 | Api.setResponsePayload(http.responseText); 48 | } 49 | }; 50 | 51 | var params = JSON.stringify(payloadToWatson); 52 | // Stored in variable (publicly visible through Api.getRequestPayload) 53 | // to be used throughout the application 54 | if (Object.getOwnPropertyNames(payloadToWatson).length !== 0) { 55 | Api.setRequestPayload(params); 56 | } 57 | 58 | // Send request 59 | http.send(params); 60 | } 61 | }()); 62 | -------------------------------------------------------------------------------- /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/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.sendRequest('', null); 31 | setupInputBox(); 32 | } 33 | // Set up callbacks on payload setters in Api module 34 | // This causes the displayMessage function to be called when messages are sent / received 35 | function chatUpdateSetup() { 36 | var currentRequestPayloadSetter = Api.setRequestPayload; 37 | Api.setRequestPayload = function (newPayloadStr) { 38 | currentRequestPayloadSetter.call(Api, newPayloadStr); 39 | displayMessage(JSON.parse(newPayloadStr), settings.authorTypes.user); 40 | }; 41 | 42 | var currentResponsePayloadSetter = Api.setResponsePayload; 43 | Api.setResponsePayload = function (newPayloadStr) { 44 | currentResponsePayloadSetter.call(Api, newPayloadStr); 45 | displayMessage(JSON.parse(newPayloadStr), settings.authorTypes.watson); 46 | }; 47 | } 48 | 49 | // Set up the input box to underline text as it is typed 50 | // This is done by creating a hidden dummy version of the input box that 51 | // is used to determine what the width of the input text should be. 52 | // This value is then used to set the new width of the visible input box. 53 | function setupInputBox() { 54 | var input = document.getElementById('textInput'); 55 | var dummy = document.getElementById('textInputDummy'); 56 | var minFontSize = 14; 57 | var maxFontSize = 16; 58 | var minPadding = 4; 59 | var maxPadding = 6; 60 | 61 | // If no dummy input box exists, create one 62 | if (dummy === null) { 63 | var dummyJson = { 64 | 'tagName': 'div', 65 | 'attributes': [{ 66 | 'name': 'id', 67 | 'value': 'textInputDummy' 68 | }] 69 | }; 70 | 71 | dummy = Common.buildDomElement(dummyJson); 72 | document.body.appendChild(dummy); 73 | } 74 | 75 | function adjustInput() { 76 | if (input.value === '') { 77 | // If the input box is empty, remove the underline 78 | input.classList.remove('underline'); 79 | input.setAttribute('style', 'width:' + '100%'); 80 | input.style.width = '100%'; 81 | } else { 82 | // otherwise, adjust the dummy text to match, and then set the width of 83 | // the visible input box to match it (thus extending the underline) 84 | input.classList.add('underline'); 85 | var txtNode = document.createTextNode(input.value); 86 | ['font-size', 'font-style', 'font-weight', 'font-family', 'line-height', 87 | 'text-transform', 'letter-spacing' 88 | ].forEach(function (index) { 89 | dummy.style[index] = window.getComputedStyle(input, null).getPropertyValue(index); 90 | }); 91 | dummy.textContent = txtNode.textContent; 92 | 93 | var padding = 0; 94 | var htmlElem = document.getElementsByTagName('html')[0]; 95 | var currentFontSize = parseInt(window.getComputedStyle(htmlElem, null).getPropertyValue('font-size'), 10); 96 | if (currentFontSize) { 97 | padding = Math.floor((currentFontSize - minFontSize) / (maxFontSize - minFontSize) * 98 | (maxPadding - minPadding) + minPadding); 99 | } else { 100 | padding = maxPadding; 101 | } 102 | 103 | var widthValue = (dummy.offsetWidth + padding) + 'px'; 104 | input.setAttribute('style', 'width:' + widthValue); 105 | input.style.width = widthValue; 106 | } 107 | } 108 | 109 | // Any time the input changes, or the window resizes, adjust the size of the input box 110 | input.addEventListener('input', adjustInput); 111 | window.addEventListener('resize', adjustInput); 112 | 113 | // Trigger the input event once to set up the input box and dummy element 114 | Common.fireEvent(input, 'input'); 115 | } 116 | 117 | // Display a user or Watson message that has just been sent/received 118 | function displayMessage(newPayload, typeValue) { 119 | var isUser = isUserMessage(typeValue); 120 | var textExists = (newPayload.input && newPayload.input.text) || 121 | (newPayload.output && newPayload.output.text); 122 | if (isUser !== null && textExists) { 123 | // Create new message generic elements 124 | var responses = buildMessageDomElements(newPayload, isUser); 125 | var chatBoxElement = document.querySelector(settings.selectors.chatBox); 126 | var previousLatest = chatBoxElement.querySelectorAll((isUser ? settings.selectors.fromUser : settings.selectors.fromWatson) + 127 | settings.selectors.latest); 128 | // Previous "latest" message is no longer the most recent 129 | if (previousLatest) { 130 | Common.listForEach(previousLatest, function (element) { 131 | element.classList.remove('latest'); 132 | }); 133 | } 134 | setResponse(responses, isUser, chatBoxElement, 0, true); 135 | } 136 | } 137 | 138 | // Recurisive function to add responses to the chat area 139 | function setResponse(responses, isUser, chatBoxElement, index, isTop) { 140 | if (index < responses.length) { 141 | var res = responses[index]; 142 | if (res.type !== 'pause') { 143 | var currentDiv = getDivObject(res, isUser, isTop); 144 | chatBoxElement.appendChild(currentDiv); 145 | // Class to start fade in animation 146 | currentDiv.classList.add('load'); 147 | // Move chat to the most recent messages when new messages are added 148 | scrollToChatBottom(); 149 | setResponse(responses, isUser, chatBoxElement, index + 1, false); 150 | } else { 151 | var userTypringField = document.getElementById('user-typing-field'); 152 | if (res.typing) { 153 | userTypringField.innerHTML = 'Watson Assistant Typing...'; 154 | } 155 | setTimeout(function () { 156 | userTypringField.innerHTML = ''; 157 | setResponse(responses, isUser, chatBoxElement, index + 1, isTop); 158 | }, res.time); 159 | } 160 | } 161 | } 162 | 163 | // Constructs new DOM element from a message 164 | function getDivObject(res, isUser, isTop) { 165 | var classes = [(isUser ? 'from-user' : 'from-watson'), 'latest', (isTop ? 'top' : 'sub')]; 166 | var messageJson = { 167 | //
168 | 'tagName': 'div', 169 | 'classNames': ['segments'], 170 | 'children': [{ 171 | //
172 | 'tagName': 'div', 173 | 'classNames': classes, 174 | 'children': [{ 175 | //
176 | 'tagName': 'div', 177 | 'classNames': ['message-inner'], 178 | 'children': [{ 179 | //

{messageText}

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