├── .gitignore ├── CLA.md ├── CONTRIBUTING.md ├── ChangeHistory.md ├── LICENSE ├── README.md ├── codacy └── codacy-conf.json ├── package.json ├── services ├── assistant │ ├── icons │ │ └── conversation-v1-25x25.png │ ├── v1-workspace-manager.html │ ├── v1-workspace-manager.js │ ├── v1.html │ ├── v1.js │ ├── v2.html │ └── v2.js ├── discovery │ ├── discovery-utils.js │ ├── discovery-utils2.js │ ├── icons │ │ └── discovery.png │ ├── v1-document-loader.html │ ├── v1-document-loader.js │ ├── v1-query-builder.html │ ├── v1-query-builder.js │ ├── v1.html │ ├── v1.js │ ├── v2-project-manager.html │ └── v2-project-manager.js ├── language_translator │ ├── icons │ │ └── LanguageTranslator25x25.png │ ├── translator-utils.js │ ├── v3-doc.html │ ├── v3-doc.js │ ├── v3.html │ └── v3.js ├── language_translator_identify │ ├── icons │ │ └── LanguageTranslator25x25.png │ ├── v3.html │ └── v3.js ├── language_translator_util │ ├── icons │ │ └── LanguageTranslator25x25.png │ ├── v3.html │ └── v3.js ├── natural_language_classifier │ ├── icons │ │ ├── NLClassifier.png │ │ └── temp.txt │ ├── v1.html │ └── v1.js ├── natural_language_understanding │ ├── icons │ │ └── NaturalLanguageUnderstanding.png │ ├── v1-model-manager.html │ ├── v1-model-manager.js │ ├── v1.html │ └── v1.js ├── speech_to_text │ ├── icons │ │ └── speech_to_text.png │ ├── stt-utils.js │ ├── v1-corpus-builder.html │ ├── v1-corpus-builder.js │ ├── v1.html │ └── v1.js ├── text_to_speech │ ├── icons │ │ └── text_to_speech.png │ ├── tts-utils.js │ ├── v1-corpus-builder.html │ ├── v1-corpus-builder.js │ ├── v1.html │ └── v1.js └── tone_analyzer │ ├── icons │ └── tone_analyzer.png │ ├── tone_analyser_sample.txt │ ├── v3.html │ └── v3.js └── utilities ├── payload-utils.js ├── response-utils.js ├── service-utils.js └── tone-utils.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /CLA.md: -------------------------------------------------------------------------------- 1 | Dan Cunnington 2 | Yves Le Cleach 3 | Soheel Chughtai 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING 2 | 3 | ## Code 4 | 5 | * The code should follow Watson Developer Cloud [coding guidances](https://github.com/watson-developer-cloud/api-guidelines) 6 | * The code should follow: https://github.com/airbnb/javascript 7 | * 2 spaces identation 8 | * `snake_case` 9 | 10 | ## Issues 11 | 12 | If you encounter an issue with using the labs here, you are welcome to submit 13 | a [bug report](https://github.com/watson_developer_cloud/node-red-labs/issues). 14 | Before that, please search for similar issues. It's possible somebody has already encountered this issue. 15 | 16 | ## Pull Requests 17 | 18 | If you want to contribute to the repository, follow these steps: 19 | 20 | 1. Fork the repo. 21 | 2. Develop code changes. 22 | 5. Commit your changes. 23 | 6. Push to your fork and submit a pull request. 24 | 25 | # Getting start ! 26 | 27 | The main steps are : 28 | * check pre-requisites on your laptop (Linux, Mac) 29 | * setup your development environment to use Node-RED on localhost 30 | * coding : modify or create a new node using Node-RED on localhost 31 | * commit your work and pull-request 32 | 33 | Note : this procedure have been tested on OS X Yosemite 34 | 35 | ## Pre-requisites: 36 | 37 | Install a Node version manager if not already done : 38 | - Linux/OS X : use [nvm](https://github.com/creationix/nvm) 39 | Example : 40 | ``` 41 | curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.0/install.sh | bash 42 | ``` 43 | Using npm, install Node 4.5 : 44 | ``` 45 | nvm install 4.5 46 | ``` 47 | - and use it : 48 | ``` 49 | nvm use 4.5 50 | ``` 51 | Notice : npm is provided along with Node.js 52 | 53 | ## Setup your development environment 54 | Here are the steps to successfully setup your development environment to contribute to this project 55 | 56 | - Fork the main [Node-RED Watson project](https://github.com/watson-developer-cloud/node-red-node-watson) using your GitHub account (ex: @ylecleach) 57 | - Create a work directory that will contains the source code of your fork 58 | ``` 59 | mkdir ~/dev/src ; cd ~/dev/src 60 | git clone https://github.com/ylecleach/node-red-node-watson 61 | ``` 62 | **Notice** : replace **ylecleach** by your own GitHub ID 63 | 64 | - create a npm link to your forked project. This will also build this project dependencies. 65 | ``` 66 | cd ~/dev/src/node-red-node-watson 67 | npm link 68 | ``` 69 | 70 | - Install Node-RED on localhost, assuming we install it on ~/dev directory (you can install it in another location as you wish) 71 | ``` 72 | cd ~/dev 73 | npm install node-red 74 | ``` 75 | 76 | - Install your fork project into local Node-RED using npm link: 77 | ``` 78 | cd ~/dev 79 | npm link node-red-node-watson 80 | ``` 81 | 82 | - Starting Node-RED on localhost 83 | ``` 84 | cd ~/dev/node_modules/node-red 85 | npm start 86 | ``` 87 | 88 | - open Node-RED on [http://localhost:1880](http://localhost:1880) and check you have the Watson nodes properly installed in the Node-RED palette. 89 | 90 | Then you can work on your project locally, update or create nodes, and all you have to do is to stop and start your local Node-RED. 91 | 92 | ## Co-requisites 93 | 94 | If you need to install co-requisites nodes such as the nodes for dropbox support 95 | ``` 96 | cd ~/dev 97 | npm install node-red-node-dropbox 98 | ``` 99 | 100 | 101 | ## Modify or Create new Watson nodes 102 | 103 | If your want to modify an existing Watson nodes, just search the node in the following file under the node-red / nodes section : 104 | ``` 105 | vi ~/dev/src/node-red-node-watson/package.json 106 | ``` 107 | 108 | If you want to add a watson node, then you have to create an entry such as : 109 | ``` 110 | "watson-dialog": "services/dialog/v1.js" 111 | ``` 112 | using the same name convention. For example the Watson Dialog Node is composed of these two files : 113 | - services/dialog/v1.js 114 | - services/dialog/v1.html 115 | 116 | Please refer to those examples and to the [NodeRED.org](http://nodered.org/docs/creating-nodes/) documentation. 117 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Node-RED Watson Nodes for IBM Cloud 2 | ===================================== 3 | 4 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/4f98536040924add9da4ca1deecb72b4)](https://www.codacy.com/app/BetaWorks-NodeRED-Watson/node-red-node-watson?utm_source=github.com&utm_medium=referral&utm_content=watson-developer-cloud/node-red-node-watson&utm_campaign=Badge_Grade) 5 | [![npm-version](https://img.shields.io/npm/v/node-red-node-watson.svg)](https://www.npmjs.com/package/node-red-node-watson) 6 | [![npm-downloads](https://img.shields.io/npm/dm/node-red-node-watson.svg)](https://www.npmjs.com/package/node-red-node-watson) 7 | 8 | CLA assistant 9 | 10 | 11 | ### New in version 0.10.3 12 | - fix bug in cloud bound STT configuration 13 | 14 | ### New in version 0.10.2 15 | - fix bug in cloud bound TTS configuration 16 | 17 | ### New in version 0.10.1 18 | - README update 19 | 20 | ### New in version 0.10.0 21 | - Bump of dependencies 22 | - ibm-watson to 6.2.2 23 | - Min version of Node is 12.20.0 24 | - Drop Personality Insights node 25 | - Drop Visual Recognition nodes 26 | - Remove Username / Password fields from all nodes 27 | - Bump to latest version of services 28 | - New Discovery V2 node 29 | - Add Classifications to Natural Language Understanding node 30 | 31 | 32 | ### Watson Nodes for Node-RED 33 | A collection of nodes to interact with the IBM Watson services in [IBM Cloud](http://cloud.ibm.com). 34 | 35 | # Nodes 36 | 37 | - Assistant (formerly Conversation) 38 | - Add conversational capabilities into applications. 39 | - Discovery 40 | - List environments created for the Discovery service 41 | - Language Identification 42 | - Detects the language used in text 43 | - Language Translator 44 | - Translates text from one language to another 45 | - Natural Language Classifier 46 | - Uses machine learning algorithms to return the top matching predefined classes for short text inputs. 47 | - Natural Language Understanding 48 | - Analyze text to extract meta-data from content such as concepts, entities, keywords ... 49 | - Speech To Text 50 | - Convert audio containing speech to text 51 | - Text To Speech 52 | - Convert text to audio speech 53 | - Tone Analyzer 54 | - Discover, understand, and revise the language tones in text. 55 | 56 | ### Usage 57 | Example usage flows can be found here [node-red-labs](https://github.com/watson-developer-cloud/node-red-labs) 58 | 59 | ### Contributing 60 | 61 | For simple typos and fixes please just raise an issue pointing out our mistakes. 62 | If you need to raise a pull request please read our [contribution guidelines](https://github.com/watson-developer-cloud/node-red-node-watson/blob/master/CONTRIBUTING.md) 63 | before doing so. 64 | 65 | ### Copyright and license 66 | 67 | Copyright 2018, 2022 IBM Corp. under [the Apache 2.0 license](LICENSE). 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-red-node-watson", 3 | "version": "0.10.3", 4 | "description": "A collection of Node-RED nodes for IBM Watson services", 5 | "dependencies": { 6 | "cfenv": "~1.2.4", 7 | "file-type": "^12.4.2", 8 | "request": "~2.88.2", 9 | "temp": "^0.9.4", 10 | "qs": "^6.10.3", 11 | "ibm-watson": "^6.2.2", 12 | "is-docx": "^0.0.3", 13 | "stream-to-array": "^2.3.0", 14 | "ws": "^8.5.0" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/watson-developer-cloud/node-red-node-watson" 19 | }, 20 | "license": "Apache-2.0", 21 | "keywords": [ 22 | "node-red", 23 | "ibm-cloud", 24 | "watson" 25 | ], 26 | "contributors": [ 27 | { 28 | "name": "Soheel Chughtai", 29 | "email": "soheel_chughtai@uk.ibm.com" 30 | } 31 | ], 32 | "node-red": { 33 | "nodes": { 34 | "watson-conversation-v1": "services/assistant/v1.js", 35 | "watson-conversation-workspace-manager-v1": "services/assistant/v1-workspace-manager.js", 36 | "watson-assistant-v2": "services/assistant/v2.js", 37 | "watson-discovery-v1": "services/discovery/v1.js", 38 | "watson-discovery-v1-document-loader": "services/discovery/v1-document-loader.js", 39 | "watson-discovery-v1-query-builder": "services/discovery/v1-query-builder.js", 40 | "watson-discovery-v2-project-manager": "services/discovery/v2-project-manager.js", 41 | "watson-language-translator-v3": "services/language_translator/v3.js", 42 | "watson-doc-translator-v3": "services/language_translator/v3-doc.js", 43 | "watson-language-translator-identify-v3": "services/language_translator_identify/v3.js", 44 | "watson-language-translator-util-v3": "services/language_translator_util/v3.js", 45 | "watson-natural-language-classifier-v1": "services/natural_language_classifier/v1.js", 46 | "watson-natural-language-understanding-v1": "services/natural_language_understanding/v1.js", 47 | "watson-natural-language-understanding-model-manager-v1": "services/natural_language_understanding/v1-model-manager.js", 48 | "watson-speech-to-text-v1": "services/speech_to_text/v1.js", 49 | "watson-speech-to-text-corpus-builder-v1": "services/speech_to_text/v1-corpus-builder.js", 50 | "watson-text-to-speech-v1": "services/text_to_speech/v1.js", 51 | "watson-text-to-speech-corpus-builder-v1": "services/text_to_speech/v1-corpus-builder.js", 52 | "watson-tone-analyzer-v3": "services/tone_analyzer/v3.js" 53 | } 54 | }, 55 | "engines": { 56 | "node": ">=12.20.0" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /services/assistant/icons/conversation-v1-25x25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/node-red-node-watson/dc29f8dd09bdbfd669c5560ed0e0b8eccb4e149f/services/assistant/icons/conversation-v1-25x25.png -------------------------------------------------------------------------------- /services/assistant/v1.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 73 | 74 | 75 | 113 | 114 | 170 | -------------------------------------------------------------------------------- /services/assistant/v1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016, 2022 IBM Corp. 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 | module.exports = function(RED) { 18 | const SERVICE_IDENTIFIER = 'assistant', 19 | OLD_SERVICE_IDENTIFIER = 'conversation', 20 | AssistantV1 = require('ibm-watson/assistant/v1'), 21 | { IamAuthenticator } = require('ibm-watson/auth'); 22 | 23 | var pkg = require('../../package.json'), 24 | serviceutils = require('../../utilities/service-utils'), 25 | payloadutils = require('../../utilities/payload-utils'), 26 | service = null, 27 | sApikey = null; 28 | 29 | 30 | service = serviceutils.getServiceCreds(SERVICE_IDENTIFIER); 31 | if (!service) { 32 | service = serviceutils.getServiceCreds(OLD_SERVICE_IDENTIFIER); 33 | } 34 | 35 | if (service) { 36 | sApikey = service.apikey ? service.apikey : ''; 37 | } 38 | 39 | RED.httpAdmin.get('/watson-conversation/vcap', function(req, res) { 40 | res.json(service ? { 41 | bound_service: true 42 | } : null); 43 | }); 44 | 45 | function verifyPayload(node, msg, config) { 46 | if (!(msg.payload || config['empty-payload'])) { 47 | node.status({ 48 | fill: 'red', 49 | shape: 'ring', 50 | text: 'missing payload' 51 | }); 52 | return Promise.reject('Missing property: msg.payload'); 53 | } 54 | return Promise.resolve(); 55 | } 56 | 57 | function verifyOptionalInputs(node, msg, config, params) { 58 | // next 3 not documented but present in WDC Node SDK. 59 | // optional output 60 | if (msg.params && msg.params.output) { 61 | params.output = msg.params.output; 62 | } 63 | if (msg.params && msg.params.entities) { 64 | params.entities = msg.params.entities; 65 | } 66 | if (msg.params && msg.params.intents) { 67 | params.intents = msg.params.intents; 68 | } 69 | } 70 | 71 | function setContextParams(node, msg, config, params) { 72 | if (config.context) { 73 | if (config.multiuser) { 74 | if (msg.user) { 75 | params.context = node.context().flow.get('context-' + msg.user); 76 | } else { 77 | var warning = 'Missing msg.user property, using global context. ' + 78 | 'Multiuser conversation may not function as expected.'; 79 | node.warn(warning, msg); 80 | params.context = node.context().flow.get('context'); 81 | } 82 | } else { 83 | params.context = node.context().flow.get('context'); 84 | } 85 | } 86 | 87 | if (msg.additional_context) { 88 | params.context = params.context ? params.context : {}; 89 | 90 | for (prop in msg.additional_context) { 91 | if (msg.additional_context.hasOwnProperty(prop)) { 92 | params.context[prop] = msg.additional_context[prop]; 93 | } 94 | } 95 | } 96 | } 97 | 98 | function setWorkspaceParams(node, msg, config, params) { 99 | // workspaceid can be either configured in the node, 100 | // or sent into msg.params.workspace_id 101 | if (config.workspaceid) { 102 | params.workspaceId = config.workspaceid; 103 | } 104 | if (msg.params && msg.params.workspace_id) { 105 | params.workspaceId = msg.params.workspace_id; 106 | } 107 | } 108 | 109 | function setSavedContextParams(node, msg, config, params) { 110 | // option context in JSON format 111 | if (msg.params && msg.params.context) { 112 | if (config.context) { 113 | node.warn('Overridden saved context'); 114 | 115 | if (msg.user) { 116 | node.context().flow.set('context-' + msg.user, null); 117 | } else { 118 | node.context().flow.set('context', null); 119 | } 120 | } 121 | params.context = msg.params.context; 122 | } 123 | } 124 | 125 | function setAlternativeIntentsParams(node, msg, config, params) { 126 | // optional alternate_intents : boolean 127 | if (msg.params && msg.params.alternate_intents) { 128 | params.alternateIntents = msg.params.alternate_intents; 129 | } 130 | } 131 | 132 | function verifyInputs(node, msg, config, params) { 133 | return new Promise(function resolver(resolve, reject) { 134 | if (!config.workspaceid && (!msg || !msg.params ||!msg.params.workspace_id)) { 135 | reject('Missing workspace_id. check node documentation.'); 136 | } 137 | 138 | // mandatory message 139 | params.input = { 140 | text: msg.payload 141 | }; 142 | 143 | var prop = null; 144 | 145 | // Invoke each of the functions building up the params in turn 146 | [setContextParams, setWorkspaceParams, setSavedContextParams, 147 | setAlternativeIntentsParams, verifyOptionalInputs].forEach((f) => { 148 | f(node, msg, config, params); 149 | }); 150 | 151 | resolve(); 152 | 153 | }); 154 | } 155 | 156 | function verifyCredentials(msg, k) { 157 | if ( !(k) ) { 158 | if ( (!msg.params) || 159 | (!(msg.params.apikey)) ) { 160 | return false; 161 | } 162 | } 163 | return true; 164 | } 165 | 166 | 167 | function verifyServiceCredentials(node, msg, config) { 168 | // If it is present the newly provided user entered key 169 | // takes precedence over the existing one. 170 | // If msg.params contain credentials then these will Overridde 171 | // the bound or configured credentials. 172 | return new Promise(function resolver(resolve, reject) { 173 | let authSettings = {}, 174 | serviceSettings = { 175 | headers: { 176 | 'User-Agent': pkg.name + '-' + pkg.version 177 | } 178 | }; 179 | 180 | let apiKey = sApikey || node.credentials.apikey, 181 | endpoint = '', 182 | optoutLearning = false, 183 | version = '2021-11-27'; 184 | 185 | if (!verifyCredentials(msg, apiKey)) { 186 | reject('Missing Watson Assistant API service credentials'); 187 | } 188 | 189 | if (msg.params) { 190 | if (msg.params.apikey) { 191 | apiKey = msg.params.apikey; 192 | } 193 | if (msg.params.version) { 194 | version = msg.params.version; 195 | } 196 | if (msg.params.customerId) { 197 | serviceSettings.headers['X-Watson-Metadata'] = msg.params.customerId; 198 | } 199 | } 200 | 201 | if (apiKey) { 202 | authSettings.apikey = apiKey; 203 | } 204 | serviceSettings.authenticator = new IamAuthenticator(authSettings); 205 | 206 | serviceSettings.version = version; 207 | serviceSettings.version_date = version; 208 | 209 | if (service) { 210 | endpoint = service.url; 211 | } 212 | if (config['service-endpoint']) { 213 | endpoint = config['service-endpoint']; 214 | } 215 | if (msg.params && msg.params.endpoint) { 216 | endpoint = msg.params.endpoint; 217 | } 218 | 219 | if (endpoint) { 220 | serviceSettings.url = endpoint; 221 | } 222 | 223 | if ((msg.params && msg.params['optout_learning'])){ 224 | optoutLearning = true; 225 | } else if (config['optout-learning']){ 226 | optoutLearning = true; 227 | } 228 | 229 | if (optoutLearning){ 230 | serviceSettings.headers = serviceSettings.headers || {}; 231 | serviceSettings.headers['X-Watson-Learning-Opt-Out'] = '1'; 232 | } 233 | 234 | if (config['timeout'] && config['timeout'] !== '0' && isFinite(config['timeout'])){ 235 | serviceSettings.timeout = parseInt(config['timeout']); 236 | } 237 | 238 | if (msg.params && msg.params.timeout !== '0' && isFinite(msg.params.timeout)){ 239 | serviceSettings.timeout = parseInt(msg.params.timeout); 240 | } 241 | 242 | if (msg.params && msg.params.disable_ssl_verification){ 243 | serviceSettings.disable_ssl_verification = true; 244 | } 245 | 246 | node.service = new AssistantV1(serviceSettings); 247 | resolve(); 248 | }); 249 | } 250 | 251 | function processResponse(response, node, msg, config) { 252 | return new Promise(function resolver(resolve, reject) { 253 | if (response === null || typeof (response) === 'undefined') { 254 | reject('call to watson conversation service failed'); 255 | } 256 | 257 | msg.payload = response; 258 | if (response && response.result) { 259 | msg.payload = response.result; 260 | } 261 | 262 | let body = msg.payload; 263 | 264 | if (config.context && body && body.context) { 265 | if (config.multiuser && msg.user) { 266 | node.context().flow.set('context-' + msg.user, body.context); 267 | } else { 268 | if (msg.user) { 269 | node.warn('msg.user ignored when multiple users not set in node'); 270 | } 271 | node.context().flow.set('context', body.context); 272 | } 273 | } 274 | 275 | resolve(); 276 | }); 277 | } 278 | 279 | function execute(params, node, msg, config) { 280 | return new Promise(function resolver(resolve, reject) { 281 | node.status({ 282 | fill: 'blue', 283 | shape: 'dot', 284 | text: 'Calling Conversation service ...' 285 | }); 286 | // call POST /message through SDK 287 | node.service.message(params) 288 | .then((response) => { 289 | return processResponse(response, node, msg, config); 290 | }) 291 | .then(() => { 292 | resolve(); 293 | }) 294 | .catch((err) => { 295 | reject(err); 296 | }); 297 | }); 298 | } 299 | 300 | // This is the Watson Conversation V1 (GA) Node 301 | function WatsonConversationV1Node(config) { 302 | var node = this; 303 | 304 | RED.nodes.createNode(this, config); 305 | 306 | this.on('input', function(msg, send, done) { 307 | var params = {}; 308 | 309 | node.status({}); 310 | 311 | verifyPayload(node, msg, config) 312 | .then(() => { 313 | return verifyInputs(node, msg, config, params); 314 | }) 315 | .then(() => { 316 | return verifyServiceCredentials(node, msg, config); 317 | }) 318 | .then(() => { 319 | return execute(params, node, msg, config); 320 | }) 321 | .then(() => { 322 | send(msg); 323 | node.status({}); 324 | done(); 325 | }) 326 | .catch(function(err){ 327 | let errMsg = payloadutils.reportError(node, msg, err); 328 | done(errMsg); 329 | }); 330 | }); 331 | } 332 | 333 | RED.nodes.registerType('watson-conversation-v1', WatsonConversationV1Node, { 334 | credentials: { 335 | apikey: {type: 'password'} 336 | } 337 | }); 338 | }; 339 | -------------------------------------------------------------------------------- /services/assistant/v2.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 98 | 99 | 153 | 154 | 155 | 226 | -------------------------------------------------------------------------------- /services/discovery/discovery-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016, 2022 IBM Corp. 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 | const pkg = require('../../package.json'), 17 | DiscoveryV1 = require('ibm-watson/discovery/v1'), 18 | { IamAuthenticator } = require('ibm-watson/auth'); 19 | 20 | 21 | function DiscoveryUtils() {} 22 | DiscoveryUtils.prototype = { 23 | 24 | buildService: function(apikey, endpoint) { 25 | let authSettings = {}; 26 | let serviceSettings = { 27 | version: '2019-04-30', 28 | headers: { 29 | 'User-Agent': pkg.name + '-' + pkg.version 30 | } 31 | }; 32 | 33 | if (apikey) { 34 | authSettings.apikey = apikey; 35 | } 36 | serviceSettings.authenticator = new IamAuthenticator(authSettings); 37 | 38 | if (endpoint) { 39 | serviceSettings.url = endpoint; 40 | serviceSettings.serviceUrl = endpoint; 41 | } 42 | 43 | return new DiscoveryV1(serviceSettings); 44 | 45 | }, 46 | 47 | buildParamsForName: function(msg, config, params) { 48 | if (msg.discoveryparams && msg.discoveryparams.environmentname) { 49 | params.name = msg.discoveryparams.environmentname; 50 | } else if (config.environmentname) { 51 | params.name = config.environmentname; 52 | } else if (msg.discoveryparams && msg.discoveryparams.configurationname) { 53 | params.name = msg.discoveryparams.configurationname; 54 | } else if (config.configurationname) { 55 | params.name = config.configurationname; 56 | } else if (msg.discoveryparams && msg.discoveryparams.collection_name) { 57 | params.name = msg.discoveryparams.collection_name; 58 | } else if (config.collection_name) { 59 | params.name = config.collection_name; 60 | } 61 | return params; 62 | }, 63 | 64 | buildParamsForQuery: function(msg, config, params) { 65 | var sourceField = 'query', 66 | targetField = 'query'; 67 | 68 | if (config.nlp_query || (msg.discoveryparams && msg.discoveryparams.nlp_query)) { 69 | targetField = 'naturalLanguageQuery'; 70 | } 71 | if (msg.discoveryparams && msg.discoveryparams[sourceField]) { 72 | params[targetField] = msg.discoveryparams[sourceField]; 73 | } else if (config[sourceField]) { 74 | params[targetField] = config[sourceField]; 75 | } 76 | return params; 77 | }, 78 | 79 | buildParamsForPayload: function(msg, config, params) { 80 | var isJSON = this.isJsonString(msg.payload) || 81 | this.isJsonObject(msg.payload); 82 | 83 | // Payload (text to be analysed) must be a string (content is either raw string or Buffer) 84 | if (typeof msg.payload === 'string' || isJSON) { 85 | params.file = this.isJsonObject(msg.payload) ? 86 | JSON.stringify(msg.payload) : 87 | msg.payload; 88 | params.fileContentType = 'application/json'; 89 | } 90 | return params; 91 | }, 92 | 93 | buildParamsFor: function(msg, config, params, field) { 94 | if (msg.discoveryparams && msg.discoveryparams[field]) { 95 | params[field] = msg.discoveryparams[field]; 96 | } else if (config[field]) { 97 | params[field] = config[field]; 98 | } 99 | return params; 100 | }, 101 | 102 | buildParamsForPassages: function (me, msg, config, params) { 103 | let passagesFound = false; 104 | 105 | // Allow the passages parameters to be passed in three ways 106 | // 1. As the API was expecting (watson-developer-cloud) 107 | ['passages.fields', 'passages.count', 'passages.characters' 108 | ].forEach(function(f) { 109 | params = me.buildParamsFor(msg, config, params, f); 110 | passagesFound = true; 111 | }); 112 | 113 | // 2. As anyone misreading the documentation might do it. 114 | // (again watson-developer-cloud) 115 | if (msg.discoveryparams && msg.discoveryparams.passages) { 116 | passagesFound = true; 117 | ['fields', 'count', 'characters' 118 | ].forEach(function(f) { 119 | if (msg.discoveryparams.passages[f]) { 120 | params['passages.' + f] = msg.discoveryparams.passages[f]; 121 | } 122 | }); 123 | } 124 | 125 | // 3. As the ibm-watson SDK expects 126 | ['passagesFields', 'passagesCount', 'passagesCharacters' 127 | ].forEach(function(f) { 128 | params = me.buildParamsFor(msg, config, params, f); 129 | passagesFound = true; 130 | }); 131 | 132 | if (passagesFound) { 133 | // Perform autocorrect for watson-developer-cloud to ibm-watson 134 | // differences 135 | ['passages.fields', 'passages.count', 'passages.characters' 136 | ].forEach(function(f) { 137 | if (params[f]) { 138 | let parts = f.split("."); 139 | let secondPart = parts[1] 140 | let reformedField = parts[0] + secondPart[0].toUpperCase() + secondPart.slice(1); 141 | params[reformedField] = params[f]; 142 | } 143 | }); 144 | params.passages = true; 145 | } 146 | 147 | return params; 148 | }, 149 | 150 | buildParamsFromConfig: function(config, params, field) { 151 | if (config[field]) { 152 | params[field] = config[field]; 153 | } 154 | return params; 155 | }, 156 | 157 | // The field to create a new language collection is language, but 158 | // the SDK creates a language_code field which it defaults to 'en-us' 159 | languageCodeFix: function(params) { 160 | if (params.language_code) { 161 | params.language = params.language_code; 162 | } 163 | return params; 164 | }, 165 | 166 | buildParams: function(msg, config) { 167 | var params = {}, 168 | me = this; 169 | 170 | params = me.buildParamsForName(msg, config, params); 171 | params = me.buildParamsForQuery(msg, config, params); 172 | 173 | ['environmentId', 'collectionId', 'configurationId', 174 | 'collection_name', 'language_code', 175 | 'passages', 'description', 'size', 'filename', 176 | 'highlight' 177 | ].forEach(function(f) { 178 | params = me.buildParamsFor(msg, config, params, f); 179 | }); 180 | 181 | params = me.buildParamsForPassages(me, msg, config, params); 182 | 183 | ['count', 'filter', 'aggregation', 'return'].forEach(function(f) { 184 | params = me.buildParamsFromConfig(config, params, f); 185 | }); 186 | 187 | params = me.languageCodeFix(params); 188 | 189 | params = me.buildParamsForPayload(msg, config, params); 190 | 191 | return params; 192 | }, 193 | 194 | buildMsgOverrides: function(msg, config) { 195 | var params = {}; 196 | if (config.environment) { 197 | params.environmentId = config.environment; 198 | } 199 | if (config.collection) { 200 | params.collectionId = config.collection; 201 | } 202 | if (config.passages) { 203 | params.passages = config.passages; 204 | } 205 | if (config.collection) { 206 | params.filename = config.filename; 207 | } 208 | 209 | params = this.buildMsgQueryOverrides(msg, config, params); 210 | 211 | return params; 212 | }, 213 | 214 | buildMsgQueryOverrides: function(msg, config, params) { 215 | if (config.nlp_query) { 216 | params.query = config.querynlp; 217 | params.nlp_query = config.nlp_query; 218 | } else { 219 | params = this.buildStructuredQuery(msg, config, params); 220 | } 221 | return params; 222 | }, 223 | 224 | buildStructuredQuery: function(msg, config, params) { 225 | if (config.query1 && config.queryvalue1) { 226 | params.query = config.query1 + ':"' + config.queryvalue1 + '"'; 227 | } 228 | if (config.query2 && config.queryvalue2) { 229 | if (params.query) { 230 | params.query += ','; 231 | } 232 | params.query += config.query2 + ':"' + config.queryvalue2 + '"'; 233 | } 234 | if (config.query3 && config.queryvalue3) { 235 | if (params.query) { 236 | params.query += ','; 237 | } 238 | params.query += config.query3 + ':"' + config.queryvalue3 + '"'; 239 | } 240 | return params; 241 | }, 242 | 243 | 244 | paramEnvCheck: function(params) { 245 | var response = ''; 246 | if (!params.environmentId) { 247 | response = 'Missing Environment ID '; 248 | } 249 | return response; 250 | }, 251 | 252 | paramJSONCheck: function(params) { 253 | var response = ''; 254 | if (!params.file) { 255 | response = 'Missing JSON file on payload'; 256 | } 257 | return response; 258 | }, 259 | 260 | paramDocumentCheck: function(params) { 261 | var response = ''; 262 | if (!params.file) { 263 | response = 'Missing document file on payload'; 264 | } 265 | return response; 266 | }, 267 | 268 | paramNameCheck: function(params) { 269 | var response = ''; 270 | if (!params.name) { 271 | response = 'Missing Name '; 272 | } 273 | return response; 274 | }, 275 | 276 | paramDescriptionCheck: function(params) { 277 | var response = ''; 278 | if (!params.description) { 279 | response = 'Missing Description '; 280 | } 281 | return response; 282 | }, 283 | 284 | paramCollectionCheck: function(params) { 285 | var response = ''; 286 | if (!params.collectionId) { 287 | response = 'Missing Collection ID '; 288 | } 289 | return response; 290 | }, 291 | 292 | paramConfigurationCheck: function(params) { 293 | var response = ''; 294 | if (!params.configurationId) { 295 | response = 'Missing Configuration ID '; 296 | } 297 | return response; 298 | }, 299 | 300 | // Looking for Text, Type and label 301 | buildFieldByStep: function(d, fields, txt) { 302 | for (var k in d) { 303 | var t = txt; 304 | if (isNaN(k)) { 305 | t += txt ? '.' : ''; 306 | t += k; 307 | } 308 | 309 | if ('object' === typeof d[k]) { 310 | fields = this.buildFieldByStep(d[k], fields, t); 311 | } else { 312 | switch (k) { 313 | case 'text': 314 | case 'type': 315 | case 'label': 316 | fields.push(t); 317 | break; 318 | } 319 | } 320 | } 321 | return fields; 322 | }, 323 | 324 | // sorting functions 325 | uniqueFilter: function(value, index, self) { 326 | return self.indexOf(value) === index; 327 | }, 328 | 329 | // Looking for Text, Type and label 330 | buildFieldList: function(schemaData) { 331 | var fields = []; 332 | 333 | if (schemaData && 334 | 'object' === typeof schemaData && 335 | schemaData.result && 336 | 'object' === typeof schemaData.result && 337 | schemaData.result['fields'] && 338 | Array.isArray(schemaData.result['fields'])) { 339 | schemaData.result['fields'].forEach((f) => { 340 | if (f['field'] && f['type'] && 'nested' !== f['type']) { 341 | fields.push(f['field']); 342 | } 343 | }); 344 | } 345 | 346 | if (fields.length) { 347 | fields = fields.filter(this.uniqueFilter); 348 | fields.sort(); 349 | } 350 | 351 | return fields; 352 | }, 353 | 354 | // reportError: function (node, msg, message) { 355 | // var messageTxt = message.error ? message.error : message; 356 | // node.status({fill:'red', shape:'dot', text: messageTxt}); 357 | // node.error(message, msg); 358 | // } , 359 | 360 | isJsonString: function(str) { 361 | try { 362 | JSON.parse(str); 363 | } catch (e) { 364 | return false; 365 | } 366 | return true; 367 | }, 368 | 369 | isJsonObject: function(str) { 370 | if (str instanceof Array || str instanceof Object || 371 | 'object' === typeof str || Array.isArray(str)) { 372 | return true; 373 | } 374 | return false; 375 | } 376 | 377 | 378 | }; 379 | 380 | var discoveryutils = new DiscoveryUtils(); 381 | 382 | module.exports = discoveryutils; 383 | -------------------------------------------------------------------------------- /services/discovery/discovery-utils2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 IBM Corp. 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 | const pkg = require('../../package.json'), 17 | DiscoveryV2 = require('ibm-watson/discovery/v2'), 18 | { IamAuthenticator } = require('ibm-watson/auth'); 19 | 20 | 21 | function DiscoveryUtils2() {} 22 | DiscoveryUtils2.prototype = { 23 | 24 | buildService: function(apikey, endpoint) { 25 | let authSettings = {}; 26 | let serviceSettings = { 27 | version: '2020-08-30', 28 | headers: { 29 | 'User-Agent': pkg.name + '-' + pkg.version 30 | } 31 | }; 32 | 33 | if (apikey) { 34 | authSettings.apikey = apikey; 35 | } 36 | serviceSettings.authenticator = new IamAuthenticator(authSettings); 37 | 38 | if (endpoint) { 39 | serviceSettings.url = endpoint; 40 | serviceSettings.serviceUrl = endpoint; 41 | } 42 | 43 | return new DiscoveryV2(serviceSettings); 44 | }, 45 | 46 | 47 | paramProjectCheck: function(params) { 48 | return this.paramFieldCheck(params, 'projectId'); 49 | }, 50 | 51 | paramCollectionCheck: function(params) { 52 | return this.paramFieldCheck(params, 'projectId') 53 | + this.paramFieldCheck(params, 'collectionId'); 54 | }, 55 | 56 | paramQueryIdCheck: function(params) { 57 | return this.paramFieldCheck(params, 'projectId') 58 | + this.paramFieldCheck(params, 'queryId'); 59 | }, 60 | 61 | paramQueryFieldCheck: function(params) { 62 | if (! params['query'] && ! params['naturalLanguageQuery']) { 63 | return "No query found on msg.payload"; 64 | } 65 | return ''; 66 | }, 67 | 68 | 69 | paramNameCheck: function(params) { 70 | return this.paramFieldCheck(params, 'name'); 71 | }, 72 | 73 | paramTypeCheck: function(params) { 74 | return this.paramFieldCheck(params, 'type'); 75 | }, 76 | 77 | paramFieldCheck: function(params, field) { 78 | var response = ''; 79 | if (!params[field]) { 80 | response = 'Missing ' + field + ' '; 81 | } 82 | return response; 83 | }, 84 | 85 | buildParamsFor: function(msg, config, params, field) { 86 | if (msg.discoveryparams && msg.discoveryparams[field]) { 87 | params[field] = msg.discoveryparams[field]; 88 | } else if (config[field]) { 89 | params[field] = config[field]; 90 | } 91 | return params; 92 | }, 93 | 94 | buildParamsForName: function(msg, config, params) { 95 | let name = ''; 96 | if (msg.discoveryparams) { 97 | for (let f of ['projectName', 'projectname', 98 | 'collectionName', 'collectionname']) { 99 | if (msg.discoveryparams[f]) { 100 | name = msg.discoveryparams[f]; 101 | break; 102 | } 103 | } 104 | } 105 | if (!name) { 106 | for (let f of ['projectName', 'collectionName']) { 107 | if (config[f]) { 108 | name = config[f]; 109 | break; 110 | } 111 | } 112 | } 113 | if (name) { 114 | params.name = name; 115 | } 116 | return params; 117 | }, 118 | 119 | buildParamsForType: function(msg, config, params) { 120 | let type = ''; 121 | if (msg.discoveryparams) { 122 | if (msg.discoveryparams.projectType) { 123 | type = msg.discoveryparams.projectType; 124 | } else if (msg.discoveryparams.projecttype) { 125 | type = msg.discoveryparams.projecttype; 126 | } 127 | } 128 | if (!type) { 129 | if (config.projectType) { 130 | type = config.projectType 131 | } 132 | } 133 | if (type) { 134 | params.type = type; 135 | } 136 | return params; 137 | }, 138 | 139 | addOtherParams: function(msg, params) { 140 | if (msg.discoveryparams) { 141 | if (msg.discoveryparams.defaultQueryParameters) { 142 | params.defaultQueryParameters = msg.discoveryparams.defaultQueryParameters; 143 | } 144 | if (msg.discoveryparams.enrichments) { 145 | params.defaultQueryParameters = msg.discoveryparams.enrichments; 146 | } 147 | } 148 | return params 149 | }, 150 | 151 | addQueryParams: function(msg, params) { 152 | if (msg.discoveryparams) { 153 | ['collectionIds', 'filter', 'aggregation', 154 | 'count', '_return', 'offset', 'sort', 155 | 'highlight', 'spellingSuggestions', 156 | 'tableResults', 'suggestedRefinements', 157 | 'passages' 158 | ].forEach(function(f) { 159 | if (msg.discoveryparams[f]) { 160 | params[f] = msg.discoveryparams[f]; 161 | } 162 | }); 163 | } 164 | return params; 165 | }, 166 | 167 | buildParams: function(msg, config) { 168 | var params = {}, 169 | me = this; 170 | 171 | params = me.buildParamsForName(msg, config, params); 172 | params = me.buildParamsForType(msg, config, params); 173 | params = me.addOtherParams(msg, params); 174 | 175 | ['projectId', 'collectionId', 'queryId', 176 | 'description', 'language'].forEach(function(f) { 177 | params = me.buildParamsFor(msg, config, params, f); 178 | }); 179 | 180 | return params; 181 | } 182 | 183 | 184 | }; 185 | 186 | var discoveryutils2 = new DiscoveryUtils2(); 187 | 188 | module.exports = discoveryutils2; 189 | -------------------------------------------------------------------------------- /services/discovery/icons/discovery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/node-red-node-watson/dc29f8dd09bdbfd669c5560ed0e0b8eccb4e149f/services/discovery/icons/discovery.png -------------------------------------------------------------------------------- /services/discovery/v1-document-loader.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 51 | 52 | 71 | 72 | 129 | -------------------------------------------------------------------------------- /services/discovery/v1-document-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 20016, 2022 IBM Corp. 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 | module.exports = function (RED) { 18 | 19 | const SERVICE_IDENTIFIER = 'discovery'; 20 | var fs = require('fs'), 21 | temp = require('temp'), 22 | fileType = require('file-type'), 23 | isDocx = require('is-docx'), 24 | discoveryutils = require('./discovery-utils'), 25 | serviceutils = require('../../utilities/service-utils'), 26 | payloadutils = require('../../utilities/payload-utils'), 27 | dservice = serviceutils.getServiceCreds(SERVICE_IDENTIFIER), 28 | apikey = null, 29 | sApikey = null, 30 | endpoint = '', 31 | sEndpoint = ''; 32 | 33 | temp.track(); 34 | 35 | function initialCheck(k) { 36 | var message = ''; 37 | if (!k) { 38 | message = 'Missing Watson Discovery service credentials'; 39 | } 40 | if (message) { 41 | return Promise.reject(message); 42 | } 43 | return Promise.resolve(); 44 | } 45 | 46 | function checkParams(params){ 47 | var response = discoveryutils.paramEnvCheck(params) + 48 | discoveryutils.paramCollectionCheck(params); 49 | if (response) { 50 | return Promise.reject(response); 51 | } 52 | return Promise.resolve(); 53 | } 54 | 55 | function verifyPayload(msg) { 56 | if (!msg.payload) { 57 | return Promise.reject('Missing property: msg.payload'); 58 | } else if ( (msg.payload instanceof Buffer) || 59 | (discoveryutils.isJsonObject(msg.payload)) ) { 60 | return Promise.resolve(); 61 | } 62 | return Promise.reject('msg.payload should be a data buffer or json object'); 63 | } 64 | 65 | function determineSuffix(msg) { 66 | // Let's assume that if we can't determine the suffix that 67 | // its a word doc. 68 | let ext = '.json'; 69 | if (! discoveryutils.isJsonObject(msg.payload)) { 70 | let ext = '.json', 71 | ft = fileType(msg.payload); 72 | 73 | if (ft && ft.ext) { 74 | ext = '.' + ft.ext; 75 | } 76 | 77 | if (isDocx(msg.payload)) { 78 | ext = '.docx'; 79 | } 80 | } 81 | 82 | return Promise.resolve(ext); 83 | } 84 | 85 | function openTheFile(suffix) { 86 | var p = new Promise(function resolver(resolve, reject){ 87 | var options = {}; 88 | if (suffix) { 89 | options.suffix = suffix; 90 | } 91 | temp.open(options, function(err, info) { 92 | if (err) { 93 | reject('Error receiving the data buffer'); 94 | } else { 95 | resolve(info); 96 | } 97 | }); 98 | }); 99 | return p; 100 | } 101 | 102 | function syncTheFile(info, msg) { 103 | var p = new Promise(function resolver(resolve, reject){ 104 | fs.writeFile(info.path, msg.payload, function(err) { 105 | if (err) { 106 | reject('Error processing pdf buffer'); 107 | } 108 | resolve(); 109 | }); 110 | }); 111 | return p; 112 | } 113 | 114 | function createStream(info) { 115 | //var theStream = fs.createReadStream(info.path, 'utf8'); 116 | var theStream = fs.readFileSync(info.path, 'utf8'); 117 | return Promise.resolve(theStream); 118 | } 119 | 120 | function whatName(params, suffix){ 121 | if (params.filename) { 122 | return params.filename; 123 | } 124 | return 'Doc ' + (new Date()).toString() + suffix; 125 | } 126 | 127 | function execute(params, msg, suffix) { 128 | var p = new Promise(function resolver(resolve, reject) { 129 | let discovery = discoveryutils.buildService(apikey, endpoint); 130 | 131 | // modify as getting addJsonDocument will be deprecated messages 132 | if ('.json' === suffix) { 133 | //method = 'addJsonDocument'; 134 | //params.file = JSON.stringify(params.file); 135 | 136 | params.file = Buffer.from(JSON.stringify(params.file)); 137 | //} else { 138 | //method = 'addDocument'; 139 | } 140 | method = 'addDocument'; 141 | 142 | discovery[method](params) 143 | .then((response) => { 144 | msg.document = response.result ? response.result : response; 145 | resolve(); 146 | }) 147 | .catch((err) => { 148 | reject(err); 149 | }); 150 | 151 | }); 152 | return p; 153 | } 154 | 155 | if (dservice) { 156 | sApikey = dservice.apikey ? dservice.apikey : ''; 157 | sEndpoint = dservice.url ? dservice.url : ''; 158 | } 159 | 160 | RED.httpAdmin.get('/watson-discovery-docs/vcap', function (req, res) { 161 | res.json(serviceutils.checkServiceBound(SERVICE_IDENTIFIER)); 162 | }); 163 | 164 | 165 | function Node (config) { 166 | var node = this; 167 | RED.nodes.createNode(this, config); 168 | 169 | this.on('input', function(msg, send, done) { 170 | var message = '', 171 | fileInfo = '', 172 | fileSuffix = '', 173 | params = {}; 174 | 175 | apikey = sApikey || this.credentials.apikey; 176 | 177 | endpoint = sEndpoint; 178 | if (config['service-endpoint']) { 179 | endpoint = config['service-endpoint']; 180 | } 181 | 182 | node.status({}); 183 | initialCheck(apikey) 184 | .then(function(){ 185 | return verifyPayload(msg); 186 | }) 187 | .then(function(){ 188 | params = discoveryutils.buildParams(msg, config); 189 | return checkParams(params); 190 | }) 191 | .then(function(){ 192 | return determineSuffix(msg); 193 | }) 194 | .then(function(suffix) { 195 | fileSuffix = suffix; 196 | node.status({ fill: 'blue', shape: 'dot', text: 'reading' }); 197 | return openTheFile(suffix); 198 | }) 199 | .then(function(info){ 200 | fileInfo = info; 201 | return syncTheFile(fileInfo, msg); 202 | }) 203 | .then(function(){ 204 | return createStream(fileInfo); 205 | }) 206 | .then(function(theStream){ 207 | //params.file = theStream; 208 | //var fname = 'temp' + fileSuffix; 209 | var fname = whatName(params, fileSuffix); 210 | params.file = { 211 | value: theStream, 212 | options: { 213 | filename: fname 214 | } 215 | }; 216 | 217 | node.status({ fill: 'blue', shape: 'dot', text: 'processing' }); 218 | //return Promise.reject('temp disabled'); 219 | return execute(params, msg, fileSuffix); 220 | }) 221 | .then(function(){ 222 | temp.cleanup(); 223 | node.status({}); 224 | send(msg); 225 | done(); 226 | }) 227 | .catch(function(err){ 228 | temp.cleanup(); 229 | let errMsg = payloadutils.reportError(node, msg, err); 230 | done(errMsg); 231 | }); 232 | }); 233 | } 234 | 235 | RED.nodes.registerType('watson-discovery-v1-document-loader', Node, { 236 | credentials: { 237 | apikey: {type:'password'} 238 | } 239 | }); 240 | }; 241 | -------------------------------------------------------------------------------- /services/discovery/v1-query-builder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 20016 IBM Corp. 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 | module.exports = function(RED) { 18 | 19 | const SERVICE_IDENTIFIER = 'discovery'; 20 | var discoveryutils = require('./discovery-utils'), 21 | serviceutils = require('../../utilities/service-utils'), 22 | dservice = serviceutils.getServiceCreds(SERVICE_IDENTIFIER), 23 | sUsername = null, 24 | sPassword = null, 25 | sApikey = null, 26 | sEndpoint = ''; 27 | 28 | 29 | if (dservice) { 30 | sUsername = dservice.username ? dservice.username : ''; 31 | sPassword = dservice.password ? dservice.password : ''; 32 | sApikey = dservice.apikey ? dservice.apikey : ''; 33 | sEndpoint = dservice.url ? dservice.url : ''; 34 | } 35 | 36 | RED.httpAdmin.get('/watson-discovery-v1-query-builder/vcap', function(req, res) { 37 | res.json(serviceutils.checkServiceBound(SERVICE_IDENTIFIER)); 38 | }); 39 | 40 | function processResponse(response, field) { 41 | let reply = response; 42 | if (response) { 43 | if (response.result) { 44 | if (response.result[field]) { 45 | reply = response.result[field]; 46 | } else { 47 | reply = response.result; 48 | } 49 | } 50 | } 51 | return reply; 52 | } 53 | 54 | // API used by widget to fetch available environments 55 | RED.httpAdmin.get('/watson-discovery-v1-query-builder/environments', function(req, res) { 56 | 57 | let discovery = discoveryutils.buildService(sUsername ? sUsername : req.query.un, 58 | sPassword ? sPassword : req.query.pwd, 59 | sApikey ? sApikey : req.query.key, 60 | req.query.endpoint ? req.query.endpoint : sEndpoint); 61 | 62 | discovery.listEnvironments({}) 63 | .then((response) => { 64 | res.json(processResponse(response,'environments')); 65 | }) 66 | .catch((err) => { 67 | res.json(err); 68 | }); 69 | }); 70 | 71 | // API used by widget to fetch available collections in environment 72 | RED.httpAdmin.get('/watson-discovery-v1-query-builder/collections', function(req, res) { 73 | let discovery = discoveryutils.buildService(sUsername ? sUsername : req.query.un, 74 | sPassword ? sPassword : req.query.pwd, 75 | sApikey ? sApikey : req.query.key, 76 | req.query.endpoint ? req.query.endpoint : sEndpoint); 77 | 78 | discovery.listCollections({environmentId: req.query.environment_id}) 79 | .then((response) => { 80 | res.json(processResponse(response,'collections')); 81 | }) 82 | .catch((err) => { 83 | res.json(err); 84 | }); 85 | }); 86 | 87 | 88 | // API used by widget to fetch available collections in environment 89 | RED.httpAdmin.get('/watson-discovery-v1-query-builder/schemas', function(req, res) { 90 | let discovery = discoveryutils.buildService(sUsername ? sUsername : req.query.un, 91 | sPassword ? sPassword : req.query.pwd, 92 | sApikey ? sApikey : req.query.key, 93 | req.query.endpoint ? req.query.endpoint : sEndpoint); 94 | 95 | discovery.listCollectionFields({ 96 | environmentId: req.query.environment_id, 97 | collectionId: req.query.collection_id 98 | }) 99 | .then((response) => { 100 | let fieldList = discoveryutils.buildFieldList(response); 101 | res.json(fieldList); 102 | }) 103 | .catch((err) => { 104 | res.json(err); 105 | }); 106 | 107 | }); 108 | 109 | function Node(config) { 110 | var node = this; 111 | RED.nodes.createNode(this, config); 112 | 113 | this.on('input', function(msg) { 114 | // Simply return params for query on msg object 115 | msg.discoveryparams = discoveryutils.buildMsgOverrides(msg, config); 116 | node.send(msg); 117 | }); 118 | } 119 | 120 | RED.nodes.registerType('watson-discovery-v1-query-builder', Node, { 121 | credentials: { 122 | username: { 123 | type: 'text' 124 | }, 125 | password: { 126 | type: 'password' 127 | }, 128 | apikey: { 129 | type: 'password' 130 | } 131 | } 132 | }); 133 | }; 134 | -------------------------------------------------------------------------------- /services/discovery/v1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016, 2022 IBM Corp. 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 | module.exports = function (RED) { 18 | 19 | const SERVICE_IDENTIFIER = 'discovery'; 20 | var discoveryutils = require('./discovery-utils'), 21 | serviceutils = require('../../utilities/service-utils'), 22 | payloadutils = require('../../utilities/payload-utils'), 23 | responseutils = require('../../utilities/response-utils'), 24 | dservice = serviceutils.getServiceCreds(SERVICE_IDENTIFIER), 25 | apikey = null, 26 | sApikey = null, 27 | endpoint = '', 28 | sEndpoint = ''; 29 | 30 | const ExecutionList = { 31 | 'createEnvrionment': executeCreateEnvrionment, 32 | 'listEnvrionments': executeListEnvrionments, 33 | 'getEnvironmentDetails': executeEnvrionmentDetails, 34 | 'createCollection': executeCreateCollection, 35 | 'listCollections': executeListCollections, 36 | 'getCollectionDetails': executeGetCollectionDetails, 37 | 'deleteCollection': executeDeleteCollection, 38 | 'createConfiguration': executeCreateConfiguration, 39 | 'listConfigurations': executeListConfigurations, 40 | 'getConfigurationDetails': executeGetConfigurationDetails, 41 | 'deleteConfiguration': executeDeleteConfiguration, 42 | 'deleteEnvironment': executeDeleteEnvironment, 43 | 'listExpansions': executeListExpansions, 44 | 'listTrainingData': executeListTrainingData, 45 | 'query': executeQuery, 46 | 'queryNotices': executeQueryNotices 47 | }; 48 | 49 | 50 | function checkParams(method, params){ 51 | var response = ''; 52 | switch (method) { 53 | case 'createEnvrionment': 54 | response = discoveryutils.paramNameCheck(params) + 55 | discoveryutils.paramDescriptionCheck(params); 56 | break; 57 | case 'createConfiguration': 58 | response = discoveryutils.paramNameCheck(params) + 59 | discoveryutils.paramEnvCheck(params) + 60 | discoveryutils.paramJSONCheck(params); 61 | break; 62 | case 'getEnvironmentDetails': 63 | case 'listCollections': 64 | response = discoveryutils.paramEnvCheck(params); 65 | break; 66 | case 'getCollectionDetails': 67 | case 'query': 68 | case 'queryNotices': 69 | case 'listExpansions': 70 | case 'listTrainingData': 71 | response = discoveryutils.paramEnvCheck(params) + 72 | discoveryutils.paramCollectionCheck(params); 73 | break; 74 | case 'listConfigurations': 75 | response = discoveryutils.paramEnvCheck(params); 76 | break; 77 | case 'getConfigurationDetails': 78 | response = discoveryutils.paramEnvCheck(params) + 79 | discoveryutils.paramConfigurationCheck(params); 80 | break; 81 | } 82 | if (response) { 83 | return Promise.reject(response); 84 | } else { 85 | return Promise.resolve(); 86 | } 87 | } 88 | 89 | function executeCreateEnvrionment(node, discovery, params, msg) { 90 | var p = new Promise(function resolver(resolve, reject){ 91 | discovery.createEnvironment(params) 92 | .then((response) => { 93 | msg.environment = response.result ? response.result : response; 94 | resolve(); 95 | }) 96 | .catch((err) => { 97 | reject(err); 98 | }); 99 | }); 100 | return p; 101 | } 102 | 103 | function executeListEnvrionments(node, discovery, params, msg) { 104 | var p = new Promise(function resolver(resolve, reject){ 105 | discovery.listEnvironments(params) 106 | .then((response) => { 107 | responseutils.parseResponseFor(msg, response, 'environments'); 108 | resolve(); 109 | }) 110 | .catch((err) => { 111 | reject(err); 112 | }); 113 | }); 114 | return p; 115 | } 116 | 117 | function executeEnvrionmentDetails(node, discovery, params, msg) { 118 | var p = new Promise(function resolver(resolve, reject){ 119 | discovery.getEnvironment(params) 120 | .then((response) => { 121 | msg.environment_details = response; 122 | if (response && response.result) { 123 | msg.environment_details = response.result; 124 | } 125 | resolve(); 126 | }) 127 | .catch((err) => { 128 | reject(err); 129 | }); 130 | }); 131 | return p; 132 | } 133 | 134 | function executeCreateCollection(node, discovery, params, msg) { 135 | var p = new Promise(function resolver(resolve, reject){ 136 | 137 | //params.body = {}; 138 | //['name','description','collection_name' 139 | // 'configuration_id'].forEach(function(f) { 140 | // params.body[f] = params[f]; 141 | // //delete params[f]; 142 | //}); 143 | 144 | discovery.createCollection(params) 145 | .then((response) => { 146 | msg.collection = response.result ? response.result : response; 147 | resolve(); 148 | }) 149 | .catch((err) => { 150 | reject(err); 151 | }); 152 | }); 153 | return p; 154 | } 155 | 156 | function executeDeleteCollection(node, discovery, params, msg) { 157 | var p = new Promise(function resolver(resolve, reject){ 158 | discovery.deleteCollection(params) 159 | .then((response) => { 160 | msg.collection = response.result ? response.result : response; 161 | resolve(); 162 | }) 163 | .catch((err) => { 164 | reject(err); 165 | }); 166 | }); 167 | return p; 168 | } 169 | 170 | function executeDeleteConfiguration(node, discovery, params, msg) { 171 | var p = new Promise(function resolver(resolve, reject){ 172 | discovery.deleteConfiguration(params) 173 | .then((response) => { 174 | msg.configuration = response.result ? response.result : response; 175 | resolve(); 176 | }) 177 | .catch((err) => { 178 | reject(err); 179 | }); 180 | }); 181 | return p; 182 | } 183 | 184 | 185 | function executeDeleteEnvironment(node, discovery, params, msg) { 186 | var p = new Promise(function resolver(resolve, reject){ 187 | discovery.deleteEnvironment(params) 188 | .then((response) => { 189 | msg.environment = response.result ? response.result : response; 190 | resolve(); 191 | }) 192 | .catch((err) => { 193 | reject(err); 194 | }); 195 | }); 196 | return p; 197 | } 198 | 199 | function executeListCollections(node, discovery, params, msg) { 200 | var p = new Promise(function resolver(resolve, reject){ 201 | discovery.listCollections(params) 202 | .then((response) => { 203 | responseutils.parseResponseFor(msg, response, 'collections'); 204 | resolve(); 205 | }) 206 | .catch((err) => { 207 | reject(err); 208 | }); 209 | }); 210 | return p; 211 | } 212 | 213 | function executeGetCollectionDetails(node, discovery, params, msg) { 214 | var p = new Promise(function resolver(resolve, reject){ 215 | discovery.getCollection(params) 216 | .then((response) => { 217 | msg.collection_details = response.result ? response.result : response; 218 | resolve(); 219 | }) 220 | .catch((err) => { 221 | reject(err); 222 | }); 223 | }); 224 | return p; 225 | } 226 | 227 | function executeListExpansions(node, discovery, params, msg) { 228 | var p = new Promise(function resolver(resolve, reject){ 229 | discovery.listExpansions(params) 230 | .then((response) => { 231 | responseutils.parseResponseFor(msg, response, 'expansions'); 232 | resolve(); 233 | }) 234 | .catch((err) => { 235 | reject(err); 236 | }); 237 | }); 238 | return p; 239 | } 240 | 241 | function executeListTrainingData(node, discovery, params, msg) { 242 | var p = new Promise(function resolver(resolve, reject){ 243 | discovery.listTrainingData(params) 244 | .then((response) => { 245 | msg.trainingData = response.result ? response.result : response; 246 | resolve(); 247 | }) 248 | .catch((err) => { 249 | reject(err); 250 | }); 251 | }); 252 | return p; 253 | } 254 | 255 | 256 | function executeCreateConfiguration(node, discovery, params, msg) { 257 | var p = new Promise(function resolver(resolve, reject){ 258 | discovery.createConfiguration(params) 259 | .then((response) => { 260 | msg.configuration = response.result ? response.result : response; 261 | resolve(); 262 | }) 263 | .catch((err) => { 264 | reject(err); 265 | }); 266 | }); 267 | return p; 268 | } 269 | 270 | function executeListConfigurations(node, discovery, params, msg) { 271 | var p = new Promise(function resolver(resolve, reject){ 272 | discovery.listConfigurations(params) 273 | .then((response) => { 274 | responseutils.parseResponseFor(msg, response, 'configurations'); 275 | resolve(); 276 | }) 277 | .catch((err) => { 278 | reject(err); 279 | }); 280 | }); 281 | return p; 282 | } 283 | 284 | function executeGetConfigurationDetails(node, discovery, params, msg) { 285 | var p = new Promise(function resolver(resolve, reject){ 286 | discovery.getConfiguration(params) 287 | .then((response) => { 288 | msg.configuration_details = response.result ? response.result : response; 289 | resolve(); 290 | }) 291 | .catch((err) => { 292 | reject(err); 293 | }); 294 | }); 295 | return p; 296 | } 297 | 298 | function executeQuery(node, discovery, params, msg) { 299 | var p = new Promise(function resolver(resolve, reject){ 300 | discovery.query(params) 301 | .then((response) => { 302 | msg.search_results = response; 303 | if (response && response.result) { 304 | msg.search_results = response.result; 305 | } 306 | resolve(); 307 | }) 308 | .catch((err) => { 309 | reject(err); 310 | }); 311 | }); 312 | return p; 313 | } 314 | 315 | function executeQueryNotices(node, discovery, params, msg) { 316 | var p = new Promise(function resolver(resolve, reject){ 317 | discovery.queryNotices(params) 318 | .then((response) => { 319 | msg.search_results = response; 320 | if (response && response.result) { 321 | msg.search_results = response.result; 322 | } 323 | resolve(); 324 | }) 325 | .catch((err) => { 326 | reject(err); 327 | }); 328 | }); 329 | return p; 330 | } 331 | 332 | function unknownMethod(node, discovery, params, msg) { 333 | return Promise.reject('Unable to process as unknown mode has been specified'); 334 | } 335 | 336 | function executeMethod(node, method, params, msg) { 337 | let discovery = discoveryutils.buildService(apikey, endpoint); 338 | 339 | let exe = ExecutionList[method]; 340 | if (!exe) { 341 | exe = unknownMethod 342 | } 343 | 344 | return exe(node, discovery, params, msg); 345 | } 346 | 347 | function initialCheck(k, m) { 348 | var message = ''; 349 | if (!k) { 350 | message = 'Missing Watson Discovery service credentials'; 351 | } else if (!m || '' === m) { 352 | message = 'Required Discovery method has not been specified'; 353 | } 354 | if (message){ 355 | return Promise.reject(message); 356 | } 357 | return Promise.resolve(); 358 | } 359 | 360 | if (dservice) { 361 | sApikey = dservice.apikey ? dservice.apikey : ''; 362 | sEndpoint = dservice.url ? dservice.url : ''; 363 | } 364 | 365 | RED.httpAdmin.get('/watson-discovery/vcap', function (req, res) { 366 | res.json(serviceutils.checkServiceBound(SERVICE_IDENTIFIER)); 367 | }); 368 | 369 | 370 | function Node (config) { 371 | var node = this; 372 | RED.nodes.createNode(this, config); 373 | 374 | this.on('input', function(msg, send, done) { 375 | 376 | var method = config['discovery-method'], 377 | message = '', 378 | params = {}; 379 | 380 | apikey = sApikey || node.credentials.apikey; 381 | 382 | endpoint = sEndpoint; 383 | if (config['service-endpoint']) { 384 | endpoint = config['service-endpoint']; 385 | } 386 | 387 | node.status({}); 388 | initialCheck(apikey, method) 389 | .then(function(){ 390 | params = discoveryutils.buildParams(msg,config); 391 | return checkParams(method, params); 392 | }) 393 | .then(function(){ 394 | node.status({fill:'blue', shape:'dot', text:'requesting'}); 395 | return executeMethod(node, method, params, msg); 396 | }) 397 | .then(function(){ 398 | node.status({}); 399 | send(msg); 400 | done(); 401 | }) 402 | .catch(function(err){ 403 | let errMsg = payloadutils.reportError(node, msg, err); 404 | done(errMsg); 405 | }); 406 | }); 407 | } 408 | 409 | RED.nodes.registerType('watson-discovery-v1', Node, { 410 | credentials: { 411 | apikey: {type:'password'} 412 | } 413 | }); 414 | }; 415 | -------------------------------------------------------------------------------- /services/discovery/v2-project-manager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 IBM Corp. 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 | module.exports = function (RED) { 18 | 19 | const SERVICE_IDENTIFIER = 'discovery'; 20 | var discoveryutils = require('./discovery-utils2'), 21 | serviceutils = require('../../utilities/service-utils'), 22 | payloadutils = require('../../utilities/payload-utils'), 23 | responseutils = require('../../utilities/response-utils'), 24 | dservice = serviceutils.getServiceCreds(SERVICE_IDENTIFIER), 25 | apikey = null, 26 | sApikey = null, 27 | endpoint = '', 28 | sEndpoint = ''; 29 | 30 | const ExecutionList = { 31 | 'listProjects' : executeListProjects, 32 | 'getProject' : executeDiscoveryMethod, 33 | 'createProject' : executeDiscoveryMethod, 34 | 'updateProject' : executeDiscoveryMethod, 35 | 'deleteProject' : executeDiscoveryMethod, 36 | 37 | 'listCollections' : executeListCollections, 38 | 'getCollection' : executeDiscoveryMethod, 39 | 'createCollection' : executeDiscoveryMethod, 40 | 'updateCollection' : executeDiscoveryMethod, 41 | 'deleteCollection' : executeDiscoveryMethod, 42 | 43 | 'getComponentSettings' : executeDiscoveryMethod, 44 | 45 | 'listTrainingQueries' : executeListQueries, 46 | 'getTrainingQuery' : executeDiscoveryMethod, 47 | 'deleteTrainingQueries' : executeDiscoveryMethod, 48 | 'deleteTrainingQuery' : executeDiscoveryMethod, 49 | 50 | 'query' : executeDiscoveryMethod 51 | }; 52 | 53 | function executeListProjects(fields) { 54 | fields.response = "projects"; 55 | return executeListMethod(fields) 56 | } 57 | 58 | function executeListCollections(fields) { 59 | fields.response = "collections"; 60 | return executeListMethod(fields) 61 | } 62 | 63 | function executeListQueries(fields) { 64 | fields.response = "queries"; 65 | return executeListMethod(fields) 66 | } 67 | 68 | function executeListMethod(fields) { 69 | var p = new Promise(function resolver(resolve, reject){ 70 | fields.discovery[fields.method](fields.params) 71 | .then((response) => { 72 | responseutils.parseResponseFor(fields.msg, response, fields.response); 73 | resolve(); 74 | }) 75 | .catch((err) => { 76 | reject(err); 77 | }); 78 | }); 79 | return p; 80 | } 81 | 82 | 83 | function executeDiscoveryMethod(fields) { 84 | var p = new Promise(function resolver(resolve, reject){ 85 | fields.discovery[fields.method](fields.params) 86 | .then((response) => { 87 | fields.msg.discovery_response = response; 88 | if (response && response.result) { 89 | fields.msg.discovery_response = response.result; 90 | } 91 | resolve(); 92 | }) 93 | .catch((err) => { 94 | reject(err); 95 | }); 96 | }); 97 | return p; 98 | } 99 | 100 | 101 | function initialCheck(k, m) { 102 | var message = ''; 103 | if (!k) { 104 | message = 'Missing Watson Discovery service credentials'; 105 | } else if (!m || '' === m) { 106 | message = 'Required Discovery method has not been specified'; 107 | } 108 | if (message){ 109 | return Promise.reject(message); 110 | } 111 | return Promise.resolve(); 112 | } 113 | 114 | function checkParams(method, params) { 115 | var response = ''; 116 | 117 | switch (method) { 118 | case 'query': 119 | response = discoveryutils.paramProjectCheck(params) 120 | + discoveryutils.paramQueryFieldCheck(params) ; 121 | break; 122 | 123 | case 'getProject': 124 | case 'deleteProject': 125 | case 'listCollections': 126 | case 'getComponentSettings': 127 | case 'listTrainingQueries': 128 | case 'deleteTrainingQueries': 129 | response = discoveryutils.paramProjectCheck(params); 130 | break; 131 | 132 | case 'getCollection': 133 | response = discoveryutils.paramCollectionCheck(params); 134 | break; 135 | 136 | case 'createProject': 137 | response = discoveryutils.paramNameCheck(params) 138 | + discoveryutils.paramTypeCheck(params); 139 | break; 140 | 141 | case 'updateProject': 142 | case 'createCollection': 143 | response = discoveryutils.paramProjectCheck(params) 144 | + discoveryutils.paramNameCheck(params); 145 | break; 146 | 147 | case 'updateCollection': 148 | case 'deleteCollection': 149 | response = discoveryutils.paramProjectCheck(params) 150 | + discoveryutils.paramCollectionCheck(params); 151 | break; 152 | 153 | case 'getTrainingQuery': 154 | case 'deleteTrainingQuery': 155 | response = discoveryutils.paramQueryIdCheck(params); 156 | break; 157 | } 158 | 159 | if (response) { 160 | return Promise.reject(response); 161 | } else { 162 | return Promise.resolve(); 163 | } 164 | } 165 | 166 | function unknownMethod(node, discovery, params, msg) { 167 | return Promise.reject('Unable to process as unknown mode has been specified'); 168 | } 169 | 170 | function executeMethod(node, method, params, msg) { 171 | let discovery = discoveryutils.buildService(apikey, endpoint); 172 | 173 | let exe = ExecutionList[method]; 174 | if (!exe) { 175 | exe = unknownMethod 176 | } 177 | 178 | let fields = { 179 | node : node, 180 | discovery : discovery, 181 | params : params, 182 | msg : msg, 183 | method : method 184 | } 185 | 186 | return exe(fields); 187 | } 188 | 189 | 190 | if (dservice) { 191 | sApikey = dservice.apikey ? dservice.apikey : ''; 192 | sEndpoint = dservice.url ? dservice.url : ''; 193 | } 194 | 195 | RED.httpAdmin.get('/watson-discovery-v2-pm/vcap', function (req, res) { 196 | res.json(serviceutils.checkServiceBound(SERVICE_IDENTIFIER)); 197 | }); 198 | 199 | 200 | function Node (config) { 201 | var node = this; 202 | RED.nodes.createNode(this, config); 203 | 204 | this.on('input', function(msg, send, done) { 205 | 206 | var method = config['discovery-method'], 207 | params = {}; 208 | 209 | apikey = sApikey || node.credentials.apikey; 210 | 211 | endpoint = sEndpoint; 212 | if (config['service-endpoint']) { 213 | endpoint = config['service-endpoint']; 214 | } 215 | 216 | node.status({}); 217 | initialCheck(apikey, method) 218 | .then(() => { 219 | params = discoveryutils.buildParams(msg,config); 220 | if (method == 'query' || method == 'querySearch') { 221 | if (method == 'query') { 222 | params.naturalLanguageQuery = msg.payload; 223 | } else { 224 | params.query = msg.payload; 225 | method = 'query'; 226 | } 227 | params = discoveryutils.addQueryParams(msg, params); 228 | } 229 | return checkParams(method, params); 230 | }) 231 | .then(function(){ 232 | node.status({fill:'blue', shape:'dot', text:'requesting'}); 233 | return executeMethod(node, method, params, msg); 234 | }) 235 | .then(function(){ 236 | node.status({}); 237 | send(msg); 238 | done(); 239 | }) 240 | .catch(function(err){ 241 | let errMsg = payloadutils.reportError(node, msg, err); 242 | done(errMsg); 243 | }); 244 | }); 245 | } 246 | 247 | RED.nodes.registerType('watson-discovery-v2-project-manager', Node, { 248 | credentials: { 249 | apikey: {type:'password'} 250 | } 251 | }); 252 | }; 253 | -------------------------------------------------------------------------------- /services/language_translator/icons/LanguageTranslator25x25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/node-red-node-watson/dc29f8dd09bdbfd669c5560ed0e0b8eccb4e149f/services/language_translator/icons/LanguageTranslator25x25.png -------------------------------------------------------------------------------- /services/language_translator/translator-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, 2022 IBM Corp. 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 | 18 | class TranslatorUtils { 19 | constructor() { 20 | } 21 | 22 | credentialCheck(k) { 23 | if (!k) { 24 | return Promise.reject('Missing Watson Language Translator service credentials'); 25 | } 26 | return Promise.resolve(); 27 | } 28 | 29 | checkForAction(action) { 30 | if (!action) { 31 | return Promise.reject('Missing action, please select one'); 32 | } 33 | return Promise.resolve(); 34 | } 35 | 36 | } 37 | 38 | var translatorutils = new TranslatorUtils(); 39 | module.exports = translatorutils ; 40 | -------------------------------------------------------------------------------- /services/language_translator_identify/icons/LanguageTranslator25x25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/node-red-node-watson/dc29f8dd09bdbfd669c5560ed0e0b8eccb4e149f/services/language_translator_identify/icons/LanguageTranslator25x25.png -------------------------------------------------------------------------------- /services/language_translator_identify/v3.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 38 | 39 | 55 | 56 | 97 | -------------------------------------------------------------------------------- /services/language_translator_identify/v3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013,2022 IBM Corp. 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 | module.exports = function (RED) { 18 | const SERVICE_IDENTIFIER = 'language-translator', 19 | LanguageTranslatorV3 = require('ibm-watson/language-translator/v3'), 20 | { IamAuthenticator } = require('ibm-watson/auth'); 21 | 22 | var pkg = require('../../package.json'), 23 | payloadutils = require('../../utilities/payload-utils'), 24 | serviceutils = require('../../utilities/service-utils'), 25 | responseutils = require('../../utilities/response-utils'), 26 | service = serviceutils.getServiceCreds(SERVICE_IDENTIFIER), 27 | apikey = null, 28 | sApikey = null, 29 | endpoint = '', sEndpoint = ''; 30 | //endpointUrl = 'https://gateway.watsonplatform.net/language-translator/api'; 31 | 32 | if (service) { 33 | sApikey = service.apikey ? service.apikey : ''; 34 | sEndpoint = service.url; 35 | } 36 | 37 | function initialCheck(k) { 38 | if (!k) { 39 | return Promise.reject('Missing Watson Language Translator service credentials'); 40 | } 41 | return Promise.resolve(); 42 | } 43 | 44 | function payloadCheck(msg) { 45 | if (!msg.payload) { 46 | return Promise.reject('Missing property: msg.payload'); 47 | } 48 | return Promise.resolve(); 49 | } 50 | 51 | function execute(node, msg) { 52 | var p = new Promise(function resolver(resolve, reject) { 53 | let language_translator = null, 54 | authSettings = {}, 55 | serviceSettings = { 56 | version: '2018-05-01', 57 | headers: { 58 | 'User-Agent': pkg.name + '-' + pkg.version 59 | } 60 | }; 61 | 62 | if (apikey) { 63 | authSettings.apikey = apikey; 64 | } 65 | serviceSettings.authenticator = new IamAuthenticator(authSettings); 66 | 67 | if (endpoint) { 68 | serviceSettings.url = endpoint; 69 | } 70 | 71 | language_translator = new LanguageTranslatorV3(serviceSettings); 72 | 73 | language_translator.identify({text: msg.payload}) 74 | .then((response) => { 75 | responseutils.parseResponseFor(msg, response, 'languages'); 76 | 77 | if (msg.languages && Array.isArray(msg.languages)) { 78 | msg.lang = msg.languages[0]; 79 | } 80 | resolve(); 81 | }) 82 | .catch((err) => { 83 | reject(err); 84 | }) 85 | }); 86 | return p; 87 | } 88 | 89 | RED.httpAdmin.get('/watson-language-translator-identify/vcap', function (req, res) { 90 | res.json(service ? {bound_service: true} : null); 91 | }); 92 | 93 | function Node (config) { 94 | var node = this; 95 | RED.nodes.createNode(this, config); 96 | 97 | this.on('input', function(msg, send, done) { 98 | apikey = sApikey || this.credentials.apikey; 99 | 100 | endpoint = sEndpoint; 101 | if (config['service-endpoint']) { 102 | endpoint = config['service-endpoint']; 103 | } 104 | 105 | node.status({}); 106 | initialCheck(apikey) 107 | .then(function(){ 108 | return payloadCheck(msg); 109 | }) 110 | .then(function(){ 111 | node.status({fill:'blue', shape:'dot', text:'requesting'}); 112 | return execute(node, msg); 113 | }) 114 | .then(function(){ 115 | node.status({}); 116 | send(msg); 117 | done(); 118 | }) 119 | .catch(function(err){ 120 | let errMsg = payloadutils.reportError(node, msg, err); 121 | done(errMsg); 122 | }); 123 | }); 124 | } 125 | RED.nodes.registerType('watson-language-translator-identify', Node, { 126 | credentials: { 127 | apikey: {type:'password'} 128 | } 129 | }); 130 | }; 131 | -------------------------------------------------------------------------------- /services/language_translator_util/icons/LanguageTranslator25x25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/node-red-node-watson/dc29f8dd09bdbfd669c5560ed0e0b8eccb4e149f/services/language_translator_util/icons/LanguageTranslator25x25.png -------------------------------------------------------------------------------- /services/language_translator_util/v3.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 38 | 39 | 56 | 57 | 58 | 94 | -------------------------------------------------------------------------------- /services/language_translator_util/v3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013,2016 IBM Corp. 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 | module.exports = function (RED) { 18 | const SERVICE_IDENTIFIER = 'language-translator', 19 | LanguageTranslatorV3 = require('ibm-watson/language-translator/v3'), 20 | { IamAuthenticator } = require('ibm-watson/auth'), 21 | LANGS = { 22 | 'es': 'Spanish', 23 | 'ar': 'Arabic', 24 | 'arz': 'Spoken Arabic', 25 | 'en': 'English', 26 | 'fr': 'French', 27 | 'it': 'Italian', 28 | 'zh': 'Chinese', 29 | 'ko': 'Korean', 30 | 'pt': 'Portuguese', 31 | 'de': 'German', 32 | 'ja': 'Japanese', 33 | 'nl': 'Dutch', 34 | 'pl': 'Polish', 35 | 'ru': 'Russian', 36 | 'tr': 'Turkish', 37 | 'zh-TW' : 'Taiwanese', 38 | 'zht': 'Traditional Chinese', 39 | 'bg' : 'Bulgarian', 40 | 'ca' : 'Catalan', 41 | 'cs' : 'Czech', 42 | 'da' : 'Danish', 43 | 'el' : 'Greek', 44 | 'et' : 'Estonian', 45 | 'fi' : 'Finnish', 46 | 'ga' : 'Galican', 47 | 'he' : 'Hebrew', 48 | 'hi' : 'Hindi', 49 | 'hr' : 'Croatian', 50 | 'hu' : 'Hungarian', 51 | 'id' : 'Indonesian', 52 | 'lt' : 'Lithuanian', 53 | 'ms' : 'Malay', 54 | 'nb' : 'Norwegian Bokmål', 55 | 'ro' : 'Romanian', 56 | 'sk' : 'Slovak', 57 | 'sl' : 'Slovenian', 58 | 'sv' : 'Swedish', 59 | 'th' : 'Thai' 60 | }; 61 | 62 | var pkg = require('../../package.json'), 63 | cfenv = require('cfenv'), 64 | apikey = null, sApikey = null, 65 | payloadutils = require('../../utilities/payload-utils'), 66 | serviceutils = require('../../utilities/service-utils'), 67 | responseutils = require('../../utilities/response-utils'), 68 | //service = cfenv.getAppEnv().getServiceCreds(/language translator/i), 69 | service = serviceutils.getServiceCreds(SERVICE_IDENTIFIER), 70 | endpoint = '', 71 | sEndpoint = 'https://gateway.watsonplatform.net/language-translator/api'; 72 | 73 | //endpointUrl = 'https://gateway.watsonplatform.net/language-translator/api'; 74 | 75 | if (service) { 76 | sApikey = service.apikey ? service.apikey : ''; 77 | sEndpoint = service.url; 78 | } 79 | 80 | // These are APIs that the node has created to allow it to dynamically fetch IBM Cloud 81 | // credentials, and also translation models. This allows the node to keep up to 82 | // date with new tranlations, without the need for a code update of this node. 83 | 84 | // Node RED Admin - fetch and set vcap services 85 | RED.httpAdmin.get('/watson-translator-util/vcap', function (req, res) { 86 | res.json(service ? {bound_service: true} : null); 87 | }); 88 | 89 | // This is the Language Translation Node. 90 | // The node supports four modes 91 | // 92 | // 1. translate, for which it will specify a domain, obtained from the available models 93 | // along with source and target languages. The node will have only displayed 94 | // available translations for the model / domain 95 | // 2. train, for which a glossary file is required. 96 | // 3. status, to determine whethere a trained corpus is available 97 | // 4. delete, to remove a trained corpus extension. 98 | 99 | function SMTNode (config) { 100 | RED.nodes.createNode(this, config); 101 | var node = this; 102 | 103 | // The dynamic nature of this node has caused problems with the password field. it is 104 | // hidden but not a credential. If it is treated as a credential, it gets lost when there 105 | // is a request to refresh the model list. 106 | // Credentials are needed for each of the modes. 107 | 108 | apikey = sApikey || this.credentials.apikey || config.apikey; 109 | 110 | // The node has received an input as part of a flow, need to determine 111 | // what the request is for, and based on that if the required fields 112 | // have been provided. 113 | this.on('input', function(msg, send, done) { 114 | 115 | let message = '', 116 | authSettings = {}, 117 | serviceSettings = { 118 | version: '2018-05-01', 119 | headers: { 120 | 'User-Agent': pkg.name + '-' + pkg.version 121 | } 122 | }; 123 | 124 | if (!apikey) { 125 | message = 'Missing Language Translation service credentials'; 126 | node.error(message, msg); 127 | return; 128 | } 129 | 130 | authSettings.apikey = apikey; 131 | serviceSettings.authenticator = new IamAuthenticator(authSettings); 132 | 133 | endpoint = sEndpoint; 134 | if ((!config['default-endpoint']) && config['service-endpoint']) { 135 | endpoint = config['service-endpoint']; 136 | } 137 | if (endpoint) { 138 | serviceSettings.url = endpoint; 139 | } 140 | 141 | var lt = new LanguageTranslatorV3(serviceSettings); 142 | 143 | // set global variable in order to make them accessible for the tranlsation node 144 | var globalContext = this.context().global; 145 | 146 | globalContext.set('g_domain',''); 147 | globalContext.set('g_src',''); 148 | globalContext.set('g_dest',''); 149 | globalContext.set('g_model_id',''); 150 | 151 | // ---- UTILITY FUNCTIONS ---- 152 | // this functions creates a N dimensional array 153 | // it will be used in order to make an array of arrays from the wanted options to populate a dashboard dropdown list 154 | // the entries of the array to be created would be 'domains', 'model_id', 'source' & 'target 155 | function capitalize (string) { 156 | return string.charAt(0).toUpperCase() + string.slice(1); 157 | } 158 | 159 | function makeLanguageBeautifier(string) { 160 | if (LANGS[string]) { 161 | return LANGS[string]; 162 | } 163 | return string; 164 | } 165 | // ---- END OF UTILITY FUNCTIONS ---- 166 | 167 | if (lt) { 168 | node.status({fill:'blue', shape:'dot', text:'fetching models'}); 169 | lt.listModels({}) 170 | .then((response) => { 171 | node.status({fill:'blue', shape:'dot', text:'parsing response'}); 172 | responseutils.parseResponseFor(msg, response, 'models'); 173 | 174 | msg.payload = msg.models; 175 | // the overall array would be used to populate the dropdown list 176 | var dropdown_array = []; 177 | var domain_src_target_model = []; 178 | var domain_src_target = ''; 179 | var sTmp3 = ''; 180 | msg.dropdown_object = {}; 181 | 182 | // Populating 'DOMAIN's into an array which would be returned by the msg object 183 | var ldom = []; // domains array 184 | msg.options_ldom = {}; 185 | msg.domains = {}; 186 | 187 | // Populating 'model_id' into an array which would be returned by the msg object 188 | var model_id_array = []; // model_id array 189 | msg.options_model_id = {}; 190 | msg.model_id_obj = {}; 191 | 192 | // Populating 'source's into an array which would be returned by the msg object 193 | var src_lang_array = []; // source language array 194 | msg.options_src_lang = {}; 195 | msg.src_lang_object = {}; 196 | 197 | // Populating 'target's into an array which would be returned by the msg object 198 | var target_lang_array = []; // dest language array 199 | msg.options_target_lang = {}; 200 | msg.target_lang_object = {}; 201 | 202 | for (var i = 0; i < msg.models.length; i++) { 203 | ldom[i] = msg.models[i].domain; 204 | ldom[i] = capitalize(ldom[i]); 205 | model_id_array[i] = msg.models[i].model_id; 206 | src_lang_array[i] = msg.models[i].source; 207 | src_lang_array[i] = makeLanguageBeautifier(src_lang_array[i]); 208 | target_lang_array[i] = msg.models[i].target; 209 | target_lang_array[i] = makeLanguageBeautifier(target_lang_array[i]); 210 | 211 | sTmp3 = makeLanguageBeautifier(target_lang_array[i]); 212 | 213 | domain_src_target = ldom[i] + ', ' + src_lang_array[i] + ', ' + target_lang_array[i]; 214 | 215 | var j = {}; 216 | j[domain_src_target] = model_id_array[i]; 217 | domain_src_target_model.push(j); 218 | 219 | dropdown_array[i] = domain_src_target_model[i]; 220 | } 221 | 222 | model_id_array.sort(); 223 | 224 | dropdown_array.sort(); 225 | 226 | // Domains unique values 227 | ldom.forEach(function(item) { 228 | msg.options_ldom[item] = item; 229 | }); 230 | 231 | // model_id unique values 232 | model_id_array.forEach(function(item) { 233 | msg.options_model_id[item] = item; 234 | }); 235 | 236 | // source language unique values 237 | src_lang_array.forEach(function(item) { 238 | msg.options_src_lang[item] = item; 239 | }); 240 | 241 | // target language unique values 242 | target_lang_array.forEach(function(item) { 243 | msg.options_target_lang[item] = item; 244 | }); 245 | 246 | msg.domains = Object.keys(msg.options_ldom); 247 | msg.model_id_obj = Object.keys(msg.options_model_id); 248 | msg.src_lang_object = Object.keys(msg.options_src_lang); 249 | msg.target_lang_object = Object.keys(msg.options_target_lang); 250 | msg.dropdown_object = dropdown_array; 251 | 252 | node.status({}); 253 | send(msg); 254 | done(); 255 | }) 256 | .catch((err) => { 257 | let errMsg = payloadutils.reportError(node, msg, err); 258 | done(errMsg); 259 | }); 260 | } else { 261 | node.error('Error instantiating the language service',msg); 262 | } 263 | }); 264 | } 265 | 266 | RED.nodes.registerType('watson-translator-util', SMTNode, { 267 | credentials: { 268 | apikey: {type:'password'} 269 | } 270 | }); 271 | }; 272 | -------------------------------------------------------------------------------- /services/natural_language_classifier/icons/NLClassifier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/node-red-node-watson/dc29f8dd09bdbfd669c5560ed0e0b8eccb4e149f/services/natural_language_classifier/icons/NLClassifier.png -------------------------------------------------------------------------------- /services/natural_language_classifier/icons/temp.txt: -------------------------------------------------------------------------------- 1 | temp text file 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /services/natural_language_classifier/v1.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 73 | 74 | 114 | 115 | 192 | -------------------------------------------------------------------------------- /services/natural_language_classifier/v1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013,2015 IBM Corp. 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 | module.exports = function(RED) { 18 | const SERVICE_IDENTIFIER = 'natural-language-classifier', 19 | NaturalLanguageClassifierV1 = require('ibm-watson/natural-language-classifier/v1'), 20 | { IamAuthenticator } = require('ibm-watson/auth'); 21 | 22 | var pkg = require('../../package.json'), 23 | temp = require('temp'), 24 | fs = require('fs'), 25 | serviceutils = require('../../utilities/service-utils'), 26 | payloadutils = require('../../utilities/payload-utils'), 27 | responseutils = require('../../utilities/response-utils'), 28 | service = serviceutils.getServiceCreds(SERVICE_IDENTIFIER), 29 | apikey = null, 30 | sApikey = null, 31 | endpoint = '', 32 | sEndpoint = 'https://gateway.watsonplatform.net/natural-language-classifier/api'; 33 | 34 | if (service) { 35 | sApikey = service.apikey ? service.apikey : ''; 36 | sEndpoint = service.url; 37 | } 38 | 39 | temp.track(); 40 | 41 | 42 | RED.httpAdmin.get('/watson-natural-language-classifier/vcap', function(req, res) { 43 | res.json(service ? { 44 | bound_service: true 45 | } : null); 46 | }); 47 | 48 | function Node(config) { 49 | RED.nodes.createNode(this, config); 50 | let node = this; 51 | 52 | // Perform basic check to see that credentials 53 | // are provided, altough they may still be 54 | // invalid 55 | node.verifyCredentials = function(msg) { 56 | apikey = sApikey || node.credentials.apikey; 57 | 58 | endpoint = sEndpoint; 59 | if (config['service-endpoint']) { 60 | endpoint = config['service-endpoint']; 61 | } 62 | 63 | if (!apikey) { 64 | return Promise.reject('Missing Natural Language Classifier credentials'); 65 | } else { 66 | return Promise.resolve(); 67 | } 68 | }; 69 | 70 | // default the mode if not what expected. 71 | node.modeCheck = function(msg) { 72 | switch(config.mode) { 73 | case 'classify': 74 | case 'createClassifier': 75 | case 'listClassifiers': 76 | case 'getClassifier': 77 | case 'deleteClassifier': 78 | break; 79 | default: 80 | config.mode = 'classify'; 81 | } 82 | return Promise.resolve(); 83 | }; 84 | 85 | // Sanity check on the payload, must be present 86 | node.payloadCheck = function(msg) { 87 | switch(config.mode) { 88 | case 'classify': 89 | case 'createClassifier': 90 | if (!msg.payload) { 91 | return Promise.reject('Payload is required'); 92 | } 93 | break; 94 | } 95 | return Promise.resolve(); 96 | }; 97 | 98 | node.payloadCollectionCheck = function(msg, config, payloadData) { 99 | if ('classify' === config.mode) { 100 | if ('string' === typeof msg.payload && (! config['collections-off'])) { 101 | let collection = msg.payload.match( /\(?([^.?!]|\.\w)+[.?!]\)?/g ); 102 | if (collection && collection.length > 1) { 103 | payloadData.collection = []; 104 | collection.forEach((s) => { 105 | let textObject = { text : s }; 106 | payloadData.collection.push(textObject); 107 | }); 108 | } 109 | } else if (Array.isArray(msg.payload)){ 110 | payloadData.collection = []; 111 | msg.payload.forEach((p) => { 112 | if ('string' === typeof p) { 113 | let textObject = { text : p }; 114 | payloadData.collection.push(textObject); 115 | } else if ('object' === typeof p) { 116 | payloadData.collection.push(p); 117 | } 118 | }); 119 | } 120 | } 121 | return Promise.resolve(); 122 | }; 123 | 124 | // Standard temp file open 125 | node.openTemp = function() { 126 | var p = new Promise(function resolver(resolve, reject) { 127 | temp.open({ 128 | suffix: '.csv' 129 | }, function(err, info) { 130 | if (err) { 131 | reject(err); 132 | } else { 133 | resolve(info); 134 | } 135 | }); 136 | }); 137 | return p; 138 | }; 139 | 140 | node.streamFile = function(msg, config, info) { 141 | var p = new Promise(function resolver(resolve, reject){ 142 | payloadutils.stream_buffer(info.path, msg.payload, function(format) { 143 | resolve(info); 144 | }); 145 | }); 146 | return p; 147 | }; 148 | 149 | 150 | // If this is a create then the payload will be a stream 151 | node.checkForCreate = function(msg, config) { 152 | if ('createClassifier' !== config.mode) { 153 | return Promise.resolve(null); 154 | } else if ('string' === typeof msg.payload) { 155 | return Promise.resolve(null); 156 | } 157 | return node.openTemp() 158 | .then(function(info) { 159 | return node.streamFile(msg, config, info); 160 | }); 161 | }; 162 | 163 | node.buildParams = function(msg, config, info, payloadData) { 164 | var params = {}, 165 | message = ''; 166 | 167 | switch (config.mode) { 168 | case 'classify': 169 | if (payloadData && payloadData.collection) { 170 | params.collection = payloadData.collection; 171 | } else { 172 | params.text = msg.payload; 173 | } 174 | 175 | params.classifierId = config.classifier; 176 | if (msg.nlcparams && msg.nlcparams.classifier_id) { 177 | params.classifierId = msg.nlcparams.classifier_id; 178 | } 179 | break; 180 | case 'createClassifier': 181 | params.language = config.language; 182 | 183 | let meta = { 184 | language : config.language 185 | }; 186 | 187 | params.trainingMetadata = Buffer.from(JSON.stringify(meta)); 188 | 189 | //params.trainingMetadata = meta; 190 | 191 | if ('string' === typeof msg.payload) { 192 | params.trainingData = msg.payload; 193 | } else { 194 | params.trainingData = fs.createReadStream(info.path); 195 | } 196 | break; 197 | case 'deleteClassifier': 198 | case 'listClassifiers': 199 | case 'getClassifier': 200 | params.classifierId = config.classifier ? config.classifier : msg.payload; 201 | if (msg.nlcparams && msg.nlcparams.classifier_id) { 202 | params.classifierId = msg.nlcparams.classifier_id; 203 | } 204 | break; 205 | case 'listClassifiers': 206 | break; 207 | default: 208 | message = 'Unknown Natural Language Classification mode, ' + config.mode; 209 | } 210 | 211 | if (message) { 212 | return Promise.reject(message); 213 | } else { 214 | return Promise.resolve(params); 215 | } 216 | }; 217 | 218 | node.performOperation = function(msg, config, params) { 219 | var p = new Promise(function resolver(resolve, reject) { 220 | let natural_language_classifier = null, 221 | authSettings = {}; 222 | serviceSettings = { 223 | version: 'v1', 224 | headers: { 225 | 'User-Agent': pkg.name + '-' + pkg.version 226 | } 227 | }; 228 | 229 | if (apikey) { 230 | authSettings.apikey = apikey; 231 | } 232 | serviceSettings.authenticator = new IamAuthenticator(authSettings); 233 | 234 | if (endpoint) { 235 | serviceSettings.url = endpoint; 236 | } 237 | 238 | natural_language_classifier = new NaturalLanguageClassifierV1(serviceSettings); 239 | 240 | let mode = config.mode; 241 | if (params.collection) { 242 | mode = 'classifyCollection'; 243 | } 244 | 245 | natural_language_classifier[mode](params) 246 | .then((response) => { 247 | switch (mode) { 248 | case 'classify': 249 | responseutils.parseResponseFor(msg, response, 'result'); 250 | msg.payload = { 251 | classes: msg.result.classes, 252 | top_class: msg.result.top_class 253 | }; 254 | break; 255 | case 'classifyCollection': 256 | responseutils.parseResponseFor(msg, response, 'collection'); 257 | msg.payload = msg.collection; 258 | break; 259 | case 'listClassifiers': 260 | responseutils.parseResponseFor(msg, response, 'classifiers'); 261 | msg.payload = msg.classifiers; 262 | break; 263 | case 'getClassifier': 264 | responseutils.parseResponseFor(msg, response, 'result'); 265 | msg.payload = msg.result; 266 | break; 267 | default: 268 | msg.payload = response; 269 | } 270 | resolve(); 271 | }) 272 | .catch((err) => { 273 | reject(err); 274 | }); 275 | }); 276 | 277 | return p; 278 | }; 279 | 280 | 281 | this.on('input', function(msg, send, done) { 282 | //var params = {} 283 | let payloadData = {}; 284 | 285 | node.verifyCredentials(msg) 286 | .then(function() { 287 | return node.modeCheck(msg); 288 | }) 289 | .then(function() { 290 | return node.payloadCheck(msg); 291 | }) 292 | .then(function() { 293 | return node.payloadCollectionCheck(msg, config, payloadData); 294 | }) 295 | .then(function() { 296 | return node.checkForCreate(msg, config); 297 | }) 298 | .then(function(info) { 299 | return node.buildParams(msg, config, info, payloadData); 300 | }) 301 | .then(function(params) { 302 | node.status({ 303 | fill: 'blue', 304 | shape: 'dot', 305 | text: 'requesting' 306 | }); 307 | return node.performOperation(msg, config, params); 308 | }) 309 | .then(function() { 310 | temp.cleanup(); 311 | node.status({}); 312 | send(msg); 313 | done(); 314 | }) 315 | .catch(function(err) { 316 | let errMsg = payloadutils.reportError(node, msg, err); 317 | done(errMsg); 318 | }); 319 | }); 320 | } 321 | RED.nodes.registerType('watson-natural-language-classifier', Node, { 322 | credentials: { 323 | apikey: {type:'password'} 324 | } 325 | }); 326 | }; 327 | -------------------------------------------------------------------------------- /services/natural_language_understanding/icons/NaturalLanguageUnderstanding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/node-red-node-watson/dc29f8dd09bdbfd669c5560ed0e0b8eccb4e149f/services/natural_language_understanding/icons/NaturalLanguageUnderstanding.png -------------------------------------------------------------------------------- /services/natural_language_understanding/v1-model-manager.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 45 | 46 | 47 | 86 | 87 | 114 | -------------------------------------------------------------------------------- /services/natural_language_understanding/v1-model-manager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 IBM Corp. 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 | module.exports = function(RED) { 18 | const SERVICE_IDENTIFIER = 'natural-language-understanding', 19 | NaturalLanguageUnderstandingV1 = require('ibm-watson/natural-language-understanding/v1'), 20 | { IamAuthenticator } = require('ibm-watson/auth'), 21 | FEATURE = 'nlu-model-mode', 22 | DEFAULT_MODE = 'listModels'; 23 | 24 | var pkg = require('../../package.json'), 25 | serviceutils = require('../../utilities/service-utils'), 26 | payloadutils = require('../../utilities/payload-utils'), 27 | sAPIKey = null, 28 | apikey = '', 29 | service = null, 30 | endpoint = '', 31 | sEndpoint = ''; 32 | 33 | 34 | service = serviceutils.getServiceCreds(SERVICE_IDENTIFIER); 35 | 36 | if (service) { 37 | sAPIKey = service.api_key || service.apikey; 38 | sEndpoint = service.url; 39 | } 40 | 41 | RED.httpAdmin.get('/watson-nlu-model-manager-v4/vcap', function(req, res) { 42 | res.json(service ? { 43 | bound_service: true 44 | } : null); 45 | }); 46 | 47 | function invokeMethod(node, msg) { 48 | return new Promise(function resolver(resolve, reject) { 49 | let service = node.service; 50 | let method = node.config[FEATURE]; 51 | let params = node.params; 52 | 53 | service[method](params) 54 | .then((data) => { 55 | let result = data 56 | if (data && data.result) { 57 | result = data.result; 58 | } 59 | resolve(result); 60 | }) 61 | .catch((err) => { 62 | reject(err); 63 | }) 64 | }); 65 | } 66 | 67 | function responseForDeleteMode(node, msg) { 68 | let feature = node.config[FEATURE]; 69 | let field = null; 70 | 71 | switch (feature) { 72 | case 'deleteModel': 73 | msg.payload = 'Successfully deleted model: ' + node.params.modelId; 74 | break; 75 | default: 76 | return false; 77 | } 78 | return true; 79 | } 80 | 81 | function processTheResponse (body, node, msg) { 82 | return new Promise(function resolver(resolve, reject) { 83 | if (body == null) { 84 | return reject('call to watson nlu v1 service failed'); 85 | } else if (! responseForDeleteMode(node, msg)) { 86 | msg.payload = body; 87 | if (body && body.models) { 88 | msg.payload = body.models; 89 | } 90 | resolve(); 91 | } 92 | }); 93 | } 94 | 95 | function execute(node, msg) { 96 | return new Promise(function resolver(resolve, reject) { 97 | node.status({ 98 | fill: 'blue', 99 | shape: 'dot', 100 | text: 'Invoking ' + node.config[FEATURE] + ' ...' 101 | }); 102 | 103 | invokeMethod(node, msg) 104 | .then((data)=> { 105 | return processTheResponse(data, node, msg); 106 | }) 107 | .then(() => { 108 | resolve(); 109 | }) 110 | .catch((err) => { 111 | reject(err); 112 | }); 113 | }); 114 | } 115 | 116 | function verifyServiceCredentials(node, msg) { 117 | // If it is present the newly provided user entered key 118 | // takes precedence over the existing one. 119 | apikey = sAPIKey || node.credentials.apikey; 120 | if (!apikey) { 121 | return Promise.reject('Missing Watson NLU API service credentials'); 122 | } 123 | 124 | let authSettings = {}; 125 | 126 | var serviceSettings = { 127 | version: '2021-08-01', 128 | headers: { 129 | 'User-Agent': pkg.name + '-' + pkg.version 130 | } 131 | }; 132 | 133 | if (endpoint) { 134 | serviceSettings.url = endpoint; 135 | } 136 | 137 | authSettings.apikey = apikey; 138 | serviceSettings.authenticator = new IamAuthenticator(authSettings); 139 | 140 | node.service = new NaturalLanguageUnderstandingV1(serviceSettings); 141 | 142 | return Promise.resolve(); 143 | } 144 | 145 | 146 | function determineEndpoint(config) { 147 | endpoint = sEndpoint; 148 | if (!endpoint && config['nlu-service-endpoint']) { 149 | endpoint = config['nlu-service-endpoint']; 150 | } 151 | 152 | if (!endpoint) { 153 | return Promise.reject('No endpoint URL has been provided'); 154 | } 155 | return Promise.resolve(); 156 | } 157 | 158 | function paramCheckFor(requiredFields, msg){ 159 | let theMissing = []; 160 | 161 | if (!msg || !msg.params) { 162 | theMissing = requiredFields; 163 | } else { 164 | requiredFields.forEach((r) => { 165 | if (! msg.params[r]) { 166 | theMissing.push(r); 167 | } 168 | }) 169 | } 170 | 171 | return theMissing; 172 | } 173 | 174 | 175 | function bufferCheck(data) { 176 | return data instanceof Buffer; 177 | } 178 | 179 | 180 | function imagesExpected(feature) { 181 | switch(feature) { 182 | case 'addImages': 183 | case 'analyze': 184 | return true; 185 | default: 186 | return false; 187 | } 188 | } 189 | 190 | function processPayload(node, msg) { 191 | return new Promise(function resolver(resolve, reject) { 192 | if ('deleteModel' === node.config[FEATURE]) { 193 | node.params['modelId'] = msg.payload; 194 | } 195 | return resolve(); 196 | }); 197 | } 198 | 199 | function verifyPayload(node, msg) { 200 | switch (node.config[FEATURE]) { 201 | case 'listModels': 202 | return Promise.resolve(); 203 | case 'deleteModel': 204 | if (!msg.payload || 'string' !== typeof msg.payload) { 205 | return Promise.reject('Missing property model identifier: msg.payload'); 206 | } 207 | return Promise.resolve(); 208 | default: 209 | return Promise.reject('Unknown mode has been specified'); 210 | } 211 | } 212 | 213 | function verifyFeatureMode(node, msg) { 214 | let f = node.config[FEATURE]; 215 | if (!f) { 216 | node.config[FEATURE] = DEFAULT_MODE; 217 | } 218 | return Promise.resolve(); 219 | } 220 | 221 | 222 | // This is the processing of the On input event 223 | function processOnInput(node, msg) { 224 | return new Promise(function resolver(resolve, reject) { 225 | // Verify that a mode has been set 226 | verifyFeatureMode(node, msg) 227 | .then(() => { 228 | // Using the mode verify that the payload conforms 229 | return verifyPayload(node, msg); 230 | }) 231 | .then(() => { 232 | return processPayload(node, msg); 233 | }) 234 | .then(() => { 235 | return determineEndpoint(node.config); 236 | }) 237 | .then(() => { 238 | return verifyServiceCredentials(node, msg); 239 | }) 240 | .then(() => { 241 | return execute(node, msg); 242 | }) 243 | .then(() => { 244 | resolve(); 245 | }) 246 | .catch((err) => { 247 | reject(err); 248 | }); 249 | }); 250 | } 251 | 252 | // This is the Watson NLU Model Manager Node 253 | function NLUModelManagerNode(config) { 254 | let node = this; 255 | 256 | RED.nodes.createNode(this, config); 257 | node.config = config; 258 | node.params = {}; 259 | 260 | node.on('input', function(msg, send, done) { 261 | var params = {}; 262 | 263 | node.status({}); 264 | 265 | processOnInput(node, msg) 266 | .then(() => { 267 | node.status({}); 268 | send(msg); 269 | done(); 270 | }) 271 | .catch((err) => { 272 | payloadutils.reportError(node, msg, err); 273 | done(err); 274 | }); 275 | }); 276 | } 277 | 278 | 279 | RED.nodes.registerType('natural-language-understanding-model-manager-v1', NLUModelManagerNode, { 280 | credentials: { 281 | apikey: { 282 | type: 'password' 283 | } 284 | } 285 | }); 286 | 287 | }; 288 | -------------------------------------------------------------------------------- /services/natural_language_understanding/v1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017, 2022 IBM Corp. 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 | module.exports = function (RED) { 18 | const SERVICE_IDENTIFIER = 'natural-language-understanding', 19 | NaturalLanguageUnderstandingV1 = require('ibm-watson/natural-language-understanding/v1'), 20 | { IamAuthenticator } = require('ibm-watson/auth'); 21 | 22 | const NLU_FEATURES = { 23 | 'categories': 'categories', 24 | 'classifications': 'classifications', 25 | 'concepts': 'concepts', 26 | 'doc-emotion': 'emotion', 27 | 'doc-sentiment': 'sentiment', 28 | 'entity': 'entities', 29 | 'keyword': 'keywords', 30 | 'metadata': 'metadata', 31 | 'relation': 'relations', 32 | 'semantic': 'semantic_roles', 33 | 'syntax': 'syntax' 34 | }; 35 | 36 | var pkg = require('../../package.json'), 37 | payloadutils = require('../../utilities/payload-utils'), 38 | serviceutils = require('../../utilities/service-utils'), 39 | service = serviceutils.getServiceCreds(SERVICE_IDENTIFIER), 40 | apikey = null, 41 | sApikey = null, 42 | endpoint = '', 43 | sEndpoint = 'https://gateway.watsonplatform.net/natural-language-understanding/api'; 44 | 45 | 46 | function initialCheck(k) { 47 | if (!k) { 48 | return Promise.reject('Missing Watson Natural Language Understanding service credentials'); 49 | } 50 | return Promise.resolve(); 51 | } 52 | 53 | function payloadCheck(msg, options) { 54 | var message = ''; 55 | if (!msg.payload) { 56 | message = 'Missing property: msg.payload'; 57 | } else if (payloadutils.urlCheck(msg.payload)) { 58 | options['url'] = msg.payload; 59 | } else { 60 | options['text'] = msg.payload; 61 | } 62 | if (message) { 63 | return Promise.reject(message); 64 | } 65 | return Promise.resolve(); 66 | } 67 | 68 | 69 | function checkAdditonalMsgOptions(msg, options) { 70 | if (msg.nlu_options && msg.nlu_options.language) { 71 | options['language'] = msg.nlu_options.language; 72 | } 73 | return Promise.resolve(); 74 | } 75 | 76 | function checkNonFeatureOptions(config, options) { 77 | var limitCharacters = parseInt(config.limittextcharacters); 78 | 79 | if (! isNaN(limitCharacters) && 0 < limitCharacters) { 80 | options.limitTextCharacters = limitCharacters; 81 | } 82 | 83 | return Promise.resolve(); 84 | } 85 | 86 | 87 | function checkFeatureRequest(config, options) { 88 | var message = '', 89 | enabled_features = null; 90 | 91 | enabled_features = Object.keys(NLU_FEATURES).filter(function (feature) { 92 | return config[feature]; 93 | }); 94 | 95 | if (!enabled_features.length) { 96 | message = 'Node must have at least one selected feature.'; 97 | } else { 98 | options.features = {}; 99 | for (var f in enabled_features) { 100 | options.features[NLU_FEATURES[enabled_features[f]]] = {}; 101 | } 102 | } 103 | if (message) { 104 | return Promise.reject(message); 105 | } 106 | return Promise.resolve(); 107 | } 108 | 109 | function processConceptsOptions(config, features) { 110 | if (features.concepts) { 111 | features.concepts.limit = 112 | config['maxconcepts'] ? parseInt(config['maxconcepts']) : 8; 113 | } 114 | } 115 | 116 | 117 | 118 | function processClassificationsOptions(msg, config, features) { 119 | if (features.classifications) { 120 | if (msg.nlu_options && msg.nlu_options.classifications_model) { 121 | features.classifications.model = msg.nlu_options.classifications_model; 122 | } else if (config['classifications-model']) { 123 | features.classifications.model = config['classifications-model'] ; 124 | } 125 | } 126 | } 127 | 128 | function processCategoriesOptions(config, features) { 129 | if (features.categories) { 130 | features.categories.limit = 131 | config['limitcategories'] ? parseInt(config['limitcategories']) : 3; 132 | } 133 | } 134 | 135 | function processEmotionOptions(config, features) { 136 | if (features.emotion && config['doc-emotion-target']) { 137 | features.emotion.targets = config['doc-emotion-target'].split(','); 138 | } 139 | } 140 | 141 | function processSentimentOptions(config, features) { 142 | if (features.sentiment && config['doc-sentiment-target']) { 143 | features.sentiment.targets = config['doc-sentiment-target'].split(','); 144 | } 145 | } 146 | 147 | function processEntitiesOptions(msg, config, features) { 148 | if (features.entities) { 149 | features.entities.emotion = 150 | config['entity-emotion'] ? config['entity-emotion'] : false; 151 | features.entities.sentiment = 152 | config['entity-sentiment'] ? config['entity-sentiment'] : false; 153 | if (config['maxentities']) { 154 | features.entities.limit = parseInt(config['maxentities']); 155 | } 156 | if (msg.nlu_options && msg.nlu_options.entity_model) { 157 | features.entities.model = msg.nlu_options.entity_model; 158 | } 159 | } 160 | } 161 | 162 | function processSyntaxOptions(msg, config, features) { 163 | if (features.syntax) { 164 | features.syntax.sentences = 165 | config['syntax-sentences'] ? config['syntax-sentences'] : false; 166 | if (config['syntax-tokens-lemma'] || config['syntax-tokens-pos']) { 167 | features.syntax.tokens = {}; 168 | features.syntax.tokens.lemma = 169 | config['syntax-tokens-lemma'] ? config['syntax-tokens-lemma'] : false; 170 | features.syntax.tokens.part_of_speech = 171 | config['syntax-tokens-pos'] ? config['syntax-tokens-pos'] : false; 172 | } 173 | } 174 | } 175 | 176 | function processRelationsOptions(msg, config, features) { 177 | if (features.relations) { 178 | if (msg.nlu_options && msg.nlu_options.relations_model) { 179 | features.relations.model = msg.nlu_options.relations_model; 180 | } 181 | } 182 | } 183 | 184 | function processKeywordsOptions(config, features) { 185 | if (features.keywords) { 186 | features.keywords.emotion = 187 | config['keyword-emotion'] ? config['keyword-emotion'] : false; 188 | features.keywords.sentiment = 189 | config['keyword-sentiment'] ? config['keyword-sentiment'] : false; 190 | if (config['maxkeywords']) { 191 | features.keywords.limit = parseInt(config['maxkeywords']); 192 | } 193 | } 194 | } 195 | 196 | function processSemanticRolesOptions(config, features) { 197 | if (features.semantic_roles) { 198 | features.semantic_roles.entities = 199 | config['semantic-entities'] ? config['semantic-entities'] : false; 200 | features.semantic_roles.keywords = 201 | config['semantic-keywords'] ? config['semantic-keywords'] : false; 202 | if (config['maxsemantics']) { 203 | features.semantic_roles.limit = parseInt(config['maxsemantics']); 204 | } 205 | } 206 | } 207 | 208 | function checkFeatureOptions(msg, config, options) { 209 | if (options && options.features) { 210 | processConceptsOptions(config, options.features); 211 | processClassificationsOptions(msg, config, options.features); 212 | processCategoriesOptions(config, options.features); 213 | processEmotionOptions(config, options.features); 214 | processSentimentOptions(config, options.features); 215 | processEntitiesOptions(msg, config, options.features); 216 | processRelationsOptions(msg, config, options.features); 217 | processKeywordsOptions(config, options.features); 218 | processSemanticRolesOptions(config, options.features); 219 | processSyntaxOptions(msg, config, options.features); 220 | } 221 | return Promise.resolve(); 222 | } 223 | 224 | function invokeService(options) { 225 | let nlu = null, 226 | authSettings = {}; 227 | serviceSettings = { 228 | version: '2021-08-01', 229 | headers: { 230 | 'User-Agent': pkg.name + '-' + pkg.version 231 | } 232 | }; 233 | 234 | if (apikey) { 235 | authSettings.apikey = apikey; 236 | } 237 | serviceSettings.authenticator = new IamAuthenticator(authSettings); 238 | 239 | if (endpoint) { 240 | serviceSettings.url = endpoint; 241 | } 242 | 243 | nlu = new NaturalLanguageUnderstandingV1(serviceSettings); 244 | 245 | var p = new Promise(function resolver(resolve, reject) { 246 | nlu.analyze(options) 247 | .then((response) => { 248 | resolve(response); 249 | }) 250 | .catch((err) => { 251 | reject(err); 252 | }); 253 | }); 254 | return p; 255 | } 256 | 257 | if (service) { 258 | sApikey = service.apikey ? service.apikey : ''; 259 | sEndpoint = service.url; 260 | } 261 | 262 | RED.httpAdmin.get('/natural-language-understanding/vcap', function (req, res) { 263 | res.json(service ? {bound_service: true} : null); 264 | }); 265 | 266 | 267 | // This is the Natural Language Understanding Node 268 | 269 | function NLUNode (config) { 270 | RED.nodes.createNode(this, config); 271 | var node = this; 272 | 273 | this.on('input', function(msg, send, done) { 274 | var message = '', 275 | options = {}; 276 | 277 | node.status({}); 278 | 279 | apikey = sApikey || this.credentials.apikey; 280 | 281 | endpoint = sEndpoint; 282 | if (config['service-endpoint']) { 283 | endpoint = config['service-endpoint']; 284 | } 285 | 286 | initialCheck(apikey) 287 | .then(function(){ 288 | return payloadCheck(msg, options); 289 | }) 290 | .then(function(){ 291 | return checkAdditonalMsgOptions(msg, options); 292 | }) 293 | .then(function(){ 294 | return checkFeatureRequest(config, options); 295 | }) 296 | .then(function(){ 297 | return checkFeatureOptions(msg, config, options); 298 | }) 299 | .then(function(){ 300 | return checkNonFeatureOptions(config, options); 301 | }) 302 | .then(function(){ 303 | node.status({fill:'blue', shape:'dot', text:'requesting'}); 304 | return invokeService(options); 305 | }) 306 | .then(function(data){ 307 | msg.features = data; 308 | if (data && data.result) { 309 | msg.features = data.result; 310 | } 311 | send(msg); 312 | node.status({}); 313 | done(); 314 | }) 315 | .catch(function(err){ 316 | let errMsg = payloadutils.reportError(node, msg, err); 317 | done(errMsg); 318 | }); 319 | 320 | }); 321 | } 322 | 323 | //Register the node as natural-language-understanding to nodeRED 324 | RED.nodes.registerType('natural-language-understanding', NLUNode, { 325 | credentials: { 326 | apikey: {type: 'password'} 327 | } 328 | }); 329 | }; 330 | -------------------------------------------------------------------------------- /services/speech_to_text/icons/speech_to_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/node-red-node-watson/dc29f8dd09bdbfd669c5560ed0e0b8eccb4e149f/services/speech_to_text/icons/speech_to_text.png -------------------------------------------------------------------------------- /services/speech_to_text/stt-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, 2022 IBM Corp. 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 | const pkg = require('../../package.json'), 18 | STTV1 = require('ibm-watson/speech-to-text/v1'), 19 | { IamAuthenticator } = require('ibm-watson/auth'); 20 | 21 | class STTUtils { 22 | constructor() { 23 | } 24 | 25 | static initSTTService(req, sApikey, sEndpoint) { 26 | const endpoint = req.query.e ? req.query.e : sEndpoint; 27 | 28 | let authSettings = {}; 29 | let serviceSettings = { 30 | url: endpoint, 31 | headers: { 32 | 'User-Agent': pkg.name + '-' + pkg.version 33 | } 34 | }; 35 | 36 | if (sApikey || req.query.key) { 37 | authSettings.apikey = sApikey ? sApikey : req.query.key; 38 | } 39 | serviceSettings.authenticator = new IamAuthenticator(authSettings); 40 | 41 | return new STTV1(serviceSettings); 42 | } 43 | 44 | static determineService(apikey, endpoint) { 45 | let authSettings = {}; 46 | let serviceSettings = { 47 | headers: { 48 | 'User-Agent': pkg.name + '-' + pkg.version 49 | } 50 | }; 51 | 52 | if (apikey) { 53 | authSettings.apikey = apikey; 54 | } 55 | 56 | if (endpoint) { 57 | serviceSettings.url = endpoint; 58 | } 59 | serviceSettings.authenticator = new IamAuthenticator(authSettings); 60 | 61 | return new STTV1(serviceSettings); 62 | } 63 | 64 | static determineServiceFromToken(accessToken, endpoint) { 65 | let authSettings = {}; 66 | let serviceSettings = { 67 | headers: { 68 | 'User-Agent': pkg.name + '-' + pkg.version 69 | } 70 | }; 71 | 72 | authSettings.accessToken = accessToken; 73 | 74 | if (endpoint) { 75 | serviceSettings.url = endpoint; 76 | } 77 | 78 | serviceSettings.authenticator = new IamAuthenticator(authSettings); 79 | 80 | return new STTV1(serviceSettings); 81 | } 82 | 83 | } 84 | 85 | module.exports = STTUtils; 86 | -------------------------------------------------------------------------------- /services/text_to_speech/icons/text_to_speech.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/node-red-node-watson/dc29f8dd09bdbfd669c5560ed0e0b8eccb4e149f/services/text_to_speech/icons/text_to_speech.png -------------------------------------------------------------------------------- /services/text_to_speech/tts-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, 2022 IBM Corp. 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 | const pkg = require('../../package.json'), 18 | TextToSpeechV1 = require('ibm-watson/text-to-speech/v1'), 19 | { IamAuthenticator } = require('ibm-watson/auth'); 20 | 21 | class TTSUtils { 22 | constructor() { 23 | } 24 | 25 | static buildStdSettings (apikey, endpoint) { 26 | let authSettings = {}; 27 | let serviceSettings = { 28 | headers: { 29 | 'User-Agent': pkg.name + '-' + pkg.version 30 | } 31 | }; 32 | 33 | if (apikey) { 34 | authSettings.apikey = apikey; 35 | } 36 | 37 | serviceSettings.authenticator = new IamAuthenticator(authSettings); 38 | 39 | if (endpoint) { 40 | serviceSettings.url = endpoint; 41 | } 42 | 43 | return new TextToSpeechV1(serviceSettings); 44 | } 45 | 46 | static initTTSService(req, sApikey, sEndpoint) { 47 | const endpoint = req.query.e ? req.query.e : sEndpoint; 48 | 49 | let authSettings = {}; 50 | let serviceSettings = { 51 | url: endpoint, 52 | headers: { 53 | 'User-Agent': pkg.name + '-' + pkg.version 54 | }}; 55 | 56 | if (sApikey || req.query.key) { 57 | authSettings.apikey = sApikey ? sApikey : req.query.key; 58 | } 59 | 60 | serviceSettings.authenticator = new IamAuthenticator(authSettings); 61 | 62 | return new TextToSpeechV1(serviceSettings); 63 | } 64 | 65 | } 66 | 67 | module.exports = TTSUtils; 68 | -------------------------------------------------------------------------------- /services/text_to_speech/v1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015, 2022 IBM Corp. 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 | module.exports = function(RED) { 18 | const SERVICE_IDENTIFIER = 'text-to-speech'; 19 | 20 | var pkg = require('../../package.json'), 21 | serviceutils = require('../../utilities/service-utils'), 22 | payloadutils = require('../../utilities/payload-utils'), 23 | ttsutils = require('./tts-utils'), 24 | endpoint = '', 25 | sEndpoint = 'https://stream.watsonplatform.net/text-to-speech/api', 26 | apikey = '', sApikey = ''; 27 | 28 | 29 | var service = serviceutils.getServiceCreds(SERVICE_IDENTIFIER); 30 | 31 | if (service) { 32 | sApikey = service.apikey ? service.apikey : ''; 33 | sEndpoint = service.url; 34 | } 35 | 36 | // Node RED Admin - fetch and set vcap services 37 | RED.httpAdmin.get('/watson-text-to-speech/vcap', function(req, res) { 38 | res.json(service ? {bound_service: true} : null); 39 | }); 40 | 41 | 42 | // API used by widget to fetch available models 43 | RED.httpAdmin.get('/watson-text-to-speech/voices', function (req, res) { 44 | var tts = ttsutils.initTTSService(req, sApikey, sEndpoint); 45 | 46 | tts.listVoices({}) 47 | .then((response) => { 48 | let voices = response; 49 | if (response.result) { 50 | voices = response.result; 51 | } 52 | res.json(voices); 53 | }) 54 | .catch((err) => { 55 | if (!err.error) { 56 | err.error = 'Error ' + err.code + ' in fetching voices'; 57 | } 58 | res.json(err); 59 | }); 60 | }); 61 | 62 | // API used by widget to fetch available customisations 63 | RED.httpAdmin.get('/watson-text-to-speech/customs', function (req, res) { 64 | var tts = ttsutils.initTTSService(req, sApikey, sEndpoint); 65 | 66 | tts.listCustomModels({}) 67 | .then((response) => { 68 | let customs = response; 69 | if (response.result) { 70 | customs = response.result; 71 | } 72 | res.json(customs); 73 | }) 74 | .catch((err) => { 75 | res.json(err); 76 | }); 77 | }); 78 | 79 | function Node(config) { 80 | RED.nodes.createNode(this, config); 81 | var node = this; 82 | 83 | function initialCheck(apikey) { 84 | if (!apikey) { 85 | return Promise.reject('Missing Text To Speech service credentials'); 86 | } 87 | return Promise.resolve(); 88 | } 89 | 90 | function payloadCheck(msg) { 91 | if (!msg.payload) { 92 | return Promise.reject('Missing property: msg.payload'); 93 | } 94 | return Promise.resolve(); 95 | } 96 | 97 | function buildParams(msg) { 98 | var params = { 99 | text: msg.payload, 100 | voice: msg.voice || config.voice, 101 | accept: config.format 102 | }; 103 | 104 | // Check the params for customisation options 105 | if (config.langcustom && 'NoCustomisationSetting' !== config.langcustom) { 106 | params.customizationId = config.langcustom; 107 | } 108 | return Promise.resolve(params); 109 | } 110 | 111 | function performTTS(msg, params) { 112 | var p = new Promise(function resolver(resolve, reject) { 113 | let tts = ttsutils.buildStdSettings(apikey, endpoint); 114 | 115 | tts.synthesize(params) 116 | .then((body) => { 117 | resolve(body); 118 | }) 119 | .catch((err) => { 120 | reject(err); 121 | }); 122 | 123 | }); 124 | return p; 125 | } 126 | 127 | function processResponse(msg, data) { 128 | return new Promise(function resolver(resolve, reject) { 129 | let body = data 130 | if (data && data.result) { 131 | body = data.result; 132 | } 133 | 134 | let tmpHolder = msg.payload; 135 | msg.payload = body; 136 | 137 | payloadutils.checkForStream(msg) 138 | .then(() => { 139 | if (! config['payload-response']) { 140 | msg.speech = msg.payload; 141 | msg.payload = tmpHolder; 142 | } 143 | resolve(); 144 | }) 145 | .catch((err) => { 146 | reject(err); 147 | }); 148 | }); 149 | } 150 | 151 | this.on('input', function(msg, send, done) { 152 | apikey = sApikey || this.credentials.apikey || config.apikey; 153 | 154 | endpoint = sEndpoint; 155 | if (config['service-endpoint']) { 156 | endpoint = config['service-endpoint']; 157 | } 158 | 159 | node.status({}); 160 | 161 | initialCheck(apikey) 162 | .then(function(){ 163 | return payloadCheck(msg); 164 | }) 165 | .then(function(){ 166 | return buildParams(msg); 167 | }) 168 | .then(function(params){ 169 | node.status({fill:"blue", shape:"dot", text:"requesting"}); 170 | return performTTS(msg, params); 171 | }) 172 | .then(function(body){ 173 | return processResponse(msg, body); 174 | }) 175 | .then(function(){ 176 | node.status({}); 177 | send(msg); 178 | done(); 179 | }) 180 | .catch(function(err){ 181 | payloadutils.reportError(node,msg,err); 182 | done(err); 183 | }); 184 | }) 185 | } 186 | RED.nodes.registerType('watson-text-to-speech', Node, { 187 | credentials: { 188 | apikey: {type:'password'} 189 | } 190 | }); 191 | }; 192 | -------------------------------------------------------------------------------- /services/tone_analyzer/icons/tone_analyzer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/node-red-node-watson/dc29f8dd09bdbfd669c5560ed0e0b8eccb4e149f/services/tone_analyzer/icons/tone_analyzer.png -------------------------------------------------------------------------------- /services/tone_analyzer/tone_analyser_sample.txt: -------------------------------------------------------------------------------- 1 | Hi Team, I know the times are difficult! Our sales have been disappointing for the past three quarters for our data analytics product suite. We have a competitive data analytics product suite in the industry. But we need to do our job selling it! 2 | -------------------------------------------------------------------------------- /services/tone_analyzer/v3.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 87 | 88 | 117 | 118 | 237 | -------------------------------------------------------------------------------- /services/tone_analyzer/v3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013,2022 IBM Corp. 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 | module.exports = function (RED) { 18 | const SERVICE_IDENTIFIER = 'tone-analyzer', 19 | ToneAnalyzerV3 = require('ibm-watson/tone-analyzer/v3'), 20 | { IamAuthenticator } = require('ibm-watson/auth'); 21 | 22 | var pkg = require('../../package.json'), 23 | serviceutils = require('../../utilities/service-utils'), 24 | payloadutils = require('../../utilities/payload-utils'), 25 | toneutils = require('../../utilities/tone-utils'), 26 | apikey = '', sApikey = '', 27 | endpoint = '', 28 | sEndpoint = 'https://gateway.watsonplatform.net/tone-analyzer/api', 29 | service = null; 30 | 31 | service = serviceutils.getServiceCreds(SERVICE_IDENTIFIER); 32 | 33 | if (service) { 34 | sApikey = service.apikey ? service.apikey : ''; 35 | sEndpoint = service.url; 36 | } 37 | 38 | // Node RED Admin - fetch and set vcap services 39 | RED.httpAdmin.get('/watson-tone-analyzer/vcap', function (req, res) { 40 | res.json(service ? {bound_service: true} : null); 41 | }); 42 | 43 | 44 | // Check that the credentials have been provided 45 | // Credentials are needed for each the service. 46 | var checkCreds = function(credentials) { 47 | var taSettings = {}; 48 | 49 | apikey = sApikey || credentials.apikey; 50 | 51 | if (apikey) { 52 | taSettings.iam_apikey = apikey; 53 | } else { 54 | taSettings = null; 55 | } 56 | 57 | return taSettings; 58 | } 59 | 60 | 61 | // Function that checks the configuration to make sure that credentials, 62 | // payload and options have been provied in the correct format. 63 | var checkConfiguration = function(msg, node) { 64 | var message = null, 65 | taSettings = null; 66 | 67 | taSettings = checkCreds(node.credentials); 68 | 69 | if (!taSettings) { 70 | message = 'Missing Tone Analyzer service credentials'; 71 | } else if (msg.payload) { 72 | message = toneutils.checkPayload(msg.payload); 73 | } else { 74 | message = 'Missing property: msg.payload'; 75 | } 76 | 77 | if (message) { 78 | return Promise.reject(message); 79 | } else { 80 | return Promise.resolve(taSettings); 81 | } 82 | }; 83 | 84 | 85 | function invokeService(config, options, settings) { 86 | let authSettings = {}; 87 | 88 | let serviceSettings = { 89 | version: '2017-09-21', 90 | headers: { 91 | 'User-Agent': pkg.name + '-' + pkg.version 92 | } 93 | }; 94 | 95 | if (settings.iam_apikey) { 96 | authSettings.apikey = settings.iam_apikey; 97 | } 98 | 99 | serviceSettings.authenticator = new IamAuthenticator(authSettings); 100 | 101 | endpoint = sEndpoint; 102 | if (config['service-endpoint']) { 103 | endpoint = config['service-endpoint']; 104 | } 105 | 106 | if (endpoint) { 107 | serviceSettings.url = endpoint; 108 | } 109 | 110 | if (config['interface-version']) { 111 | serviceSettings.version = config['interface-version']; 112 | } 113 | 114 | const tone_analyzer = new ToneAnalyzerV3(serviceSettings); 115 | 116 | var p = new Promise(function resolver(resolve, reject){ 117 | var m = 'tone'; 118 | switch (config['tone-method']) { 119 | case 'generalTone' : 120 | break; 121 | case 'customerEngagementTone' : 122 | m = 'toneChat'; 123 | break; 124 | } 125 | 126 | tone_analyzer[m](options) 127 | .then((response) => { 128 | resolve(response); 129 | }) 130 | .catch((err) => { 131 | reject(err); 132 | }) 133 | }); 134 | 135 | return p; 136 | } 137 | 138 | // function when the node recieves input inside a flow. 139 | // Configuration is first checked before the service is invoked. 140 | var processOnInput = function(msg, send, done, config, node) { 141 | checkConfiguration(msg, node) 142 | .then(function(settings) { 143 | var options = toneutils.parseOptions(msg, config); 144 | options = toneutils.parseLanguage(msg, config, options); 145 | node.status({fill:'blue', shape:'dot', text:'requesting'}); 146 | return invokeService(config, options, settings); 147 | }) 148 | .then(function(data){ 149 | node.status({}) 150 | if (data && data.result) { 151 | msg.response = data.result; 152 | } else { 153 | msg.response = data; 154 | } 155 | send(msg); 156 | node.status({}); 157 | done(); 158 | }) 159 | .catch(function(err){ 160 | payloadutils.reportError(node,msg,err); 161 | send(msg); 162 | done(err); 163 | }); 164 | } 165 | 166 | 167 | // This is the Tone Analyzer Node. 168 | function Node (config) { 169 | RED.nodes.createNode(this, config); 170 | var node = this; 171 | 172 | // Invoked when the node has received an input as part of a flow. 173 | this.on('input', function(msg, send, done) { 174 | processOnInput(msg, send, done, config, node); 175 | }); 176 | } 177 | 178 | RED.nodes.registerType('watson-tone-analyzer-v3', Node, { 179 | credentials: { 180 | apikey: {type:'password'} 181 | } 182 | }); 183 | }; 184 | -------------------------------------------------------------------------------- /utilities/payload-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 IBM Corp. 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 | var url = require('url'), 17 | fs = require('fs'), 18 | fileType = require('file-type'), 19 | request = require('request'), 20 | path = require('path'), 21 | toArray = require('stream-to-array'), 22 | stream = require('stream'); 23 | 24 | function PayloadUtils() {} 25 | 26 | PayloadUtils.prototype = { 27 | check: function() { 28 | return 'IBM Watson Node-RED Utilities for Payload Handling'; 29 | }, 30 | 31 | // Function that checks if the input string is a url 32 | urlCheck: function(str) { 33 | var parsed = url.parse(str); 34 | return (!!parsed.hostname && !!parsed.protocol && str.indexOf(' ') < 0); 35 | }, 36 | 37 | // Function that checks if the input is a valid json object 38 | isJsonObject: function (payload) { 39 | if (payload instanceof Array || payload instanceof Object || 40 | 'object' === typeof payload || Array.isArray(payload)) { 41 | return true; 42 | } 43 | return false; 44 | }, 45 | 46 | // Function that is syncing up the asynchronous nature of the stream 47 | // so that the full file can be sent to the API. 48 | stream_buffer: function(file, contents, cb) { 49 | fs.writeFile(file, contents, function(err) { 50 | if (err) { 51 | throw err; 52 | } 53 | var ft = fileType(contents); 54 | cb(ft ? ft.ext : 'tmp'); 55 | }); 56 | }, 57 | 58 | // Function that is syncing up the asynchronous nature of the stream 59 | // so that the full file can be sent to the API. 60 | stream_url: function(file, url, cb) { 61 | var wstream = fs.createWriteStream(file); 62 | 63 | wstream.on('finish', function() { 64 | fs.readFile(file, function(err, buf) { 65 | var fmt = null, 66 | error = null; 67 | 68 | if (err) { 69 | error = err; 70 | } 71 | if (fileType(buf)) { 72 | fmt = fileType(buf).ext; 73 | } else { 74 | error = 'Unrecognised file format'; 75 | } 76 | cb(error, fmt); 77 | }); 78 | }); 79 | request(url).pipe(wstream); 80 | }, 81 | 82 | // Function that matches the buffer streaming so that they can be used together. 83 | stream_url_format: function(file, url, cb) { 84 | var wstream = fs.createWriteStream(file); 85 | 86 | wstream.on('finish', function() { 87 | fs.readFile(file, function(err, buf) { 88 | var fmt = null, 89 | error = null; 90 | 91 | if (err) { 92 | throw err; 93 | } 94 | if (fileType(buf)) { 95 | fmt = fileType(buf).ext; 96 | } 97 | cb(fmt); 98 | }); 99 | }); 100 | request(url).pipe(wstream); 101 | }, 102 | 103 | // Convert filestream in msg.payload to a buffer 104 | checkForStream: function (msg) { 105 | let me = this; 106 | return new Promise(function resolver(resolve, reject) { 107 | if (me.isReadableStream(msg.payload)) { 108 | //msg.payload.resume(); 109 | toArray(msg.payload) 110 | .then(function(parts) { 111 | var buffers = [], part = null; 112 | 113 | for (var i = 0; i < parts.length; ++i) { 114 | part = parts[i]; 115 | buffers.push((part instanceof Buffer) ? part : new Buffer(part)); 116 | } 117 | 118 | msg.payload = Buffer.concat(buffers); 119 | resolve(); 120 | }); 121 | } else { 122 | resolve(); 123 | } 124 | }); 125 | }, 126 | 127 | runThroughErrOptions: function(err) { 128 | let messageTxt = err; 129 | if (err.error) { 130 | messageTxt = err.error; 131 | } else if (err.description) { 132 | messageTxt = err.description; 133 | } else if (err.message) { 134 | messageTxt = err.message; 135 | } 136 | return messageTxt; 137 | }, 138 | 139 | reportError: function (node, msg, err) { 140 | let messageTxt = err; 141 | 142 | if (err.body && 'string' === typeof err.body) { 143 | messageTxt = err.body; 144 | try { 145 | let errBody = JSON.parse(err.body); 146 | messageTxt = PayloadUtils.prototype.runThroughErrOptions(errBody); 147 | } catch(e) {} 148 | } else { 149 | messageTxt = PayloadUtils.prototype.runThroughErrOptions(err); 150 | } 151 | 152 | msg.watsonerror = messageTxt; 153 | node.status({fill:'red', shape:'dot', text: messageTxt}); 154 | node.error(messageTxt, msg); 155 | return messageTxt; 156 | }, 157 | 158 | isReadableStream : function (obj) { 159 | return obj instanceof stream.Stream && 160 | typeof (obj._read === 'function') && 161 | typeof (obj._readableState === 'object'); 162 | }, 163 | 164 | langTransToSTTFormat : function (code) { 165 | switch (code) { 166 | case 'pt': 167 | code = 'pt-BR'; 168 | break; 169 | case 'de': 170 | code = 'de-DE'; 171 | break; 172 | case 'ko': 173 | code = 'ko-KR'; 174 | break; 175 | case 'fr': 176 | code = 'fr-FR'; 177 | break; 178 | case 'en': 179 | code = 'en-US'; 180 | break; 181 | case 'ja': 182 | code = 'ja-JP'; 183 | break; 184 | case 'es': 185 | code = 'es-ES'; 186 | break; 187 | case 'ar': 188 | code = 'ar-AR'; 189 | break; 190 | case 'zh': 191 | code = 'zh-CN'; 192 | break; 193 | } 194 | return code; 195 | }, 196 | 197 | }; 198 | 199 | var payloadutils = new PayloadUtils(); 200 | 201 | module.exports = payloadutils; 202 | -------------------------------------------------------------------------------- /utilities/response-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 IBM Corp. 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 | // We are only looking at the token expiry, then refreshing that if 18 | // needed. An alternative would be to refresh the token, before expiry 19 | // using the refresh token, and checking if the refresh token has expired, 20 | // but the token time has proven to be sufficient so far. If not will need 21 | // to make the change to add refresh token processing. 22 | 23 | class ResponseUtils { 24 | 25 | constructor() { 26 | } 27 | 28 | static parseResponseFor(msg, response, field) { 29 | if (response && response.result) { 30 | if (response.result[field]) { 31 | msg[field] = response.result[field]; 32 | } else { 33 | msg[field] = response.result; 34 | } 35 | } else { 36 | msg[field] = response; 37 | } 38 | } 39 | 40 | static assignResultToField(msg, response, field) { 41 | if (response && response.result) { 42 | msg[field] = response.result; 43 | } else { 44 | msg[field] = response; 45 | } 46 | } 47 | 48 | 49 | } 50 | 51 | module.exports = ResponseUtils; 52 | -------------------------------------------------------------------------------- /utilities/service-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 IBM Corp. 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 | var cfenv = require('cfenv'); 17 | var appEnv = cfenv.getAppEnv(); 18 | 19 | function ServiceUtils() {} 20 | ServiceUtils.prototype = { 21 | //function to determine if WDC service is bound. A simple check on 22 | // name may fail because of duplicate usage. This function verifies 23 | // that the url associated with the service, contains the matched 24 | // input value, hence reducing the chances of a false match. 25 | checkCFForService: function(serviceName, returnBoolean) { 26 | var regex = RegExp('(http|https)(:\/\/)([^\/]+).*('+serviceName+').*'); 27 | 28 | var services = appEnv.getServices(); 29 | 30 | for (var service in services) { 31 | let section = services[service]; 32 | if (Array.isArray(section)) { 33 | section = section[0]; 34 | } 35 | 36 | if (section.hasOwnProperty('credentials')) { 37 | if (section.credentials.hasOwnProperty('url')){ 38 | if (section.credentials.url.search(regex) === 0){ 39 | return returnBoolean ? true : section.credentials; 40 | } 41 | } 42 | } 43 | } 44 | return returnBoolean ? false : null; 45 | }, 46 | 47 | // Function to return all the details of a service. Used by Document 48 | // Conversion Node to provide a list and a choice to the user. 49 | // Node: Like the original Document Conversion check, this 50 | // function will look for all bound instances of Document Conversion. 51 | getAllServiceDetails: function(serviceName) { 52 | var regex = RegExp('(http|https)(://)([^\/]+)(/)('+serviceName+').*'), 53 | services = appEnv.getServices(), 54 | theList = []; 55 | 56 | for (var service in services) { 57 | if (services[service].hasOwnProperty('credentials')) { 58 | if (services[service].credentials.hasOwnProperty('url')) { 59 | if (services[service].credentials.url.search(regex) === 0) { 60 | var newCandidate = {}; 61 | var v = services[service]; 62 | newCandidate.name = v.name ? v.name : ''; 63 | newCandidate.label = v.label ? v.label : ''; 64 | newCandidate.url = v.credentials.url ? 65 | v.credentials.url : ''; 66 | newCandidate.username = v.credentials.username ? 67 | v.credentials.username : ''; 68 | newCandidate.password = v.credentials.password ? 69 | v.credentials.password : ''; 70 | theList = theList.concat(newCandidate); 71 | } 72 | } 73 | } 74 | } 75 | return theList; 76 | }, 77 | 78 | // Check for service return a boolean to indicate if it is bound in 79 | checkServiceBound: function(serviceName) { 80 | return ServiceUtils.prototype.checkCFForService(serviceName, true); 81 | }, 82 | 83 | // Check for and return bound servie 84 | getServiceCreds: function(serviceName) { 85 | return ServiceUtils.prototype.checkCFForService(serviceName, false); 86 | }, 87 | 88 | getServiceCredsAlchemy: function(serviceName) { 89 | return ServiceUtils.prototype.checkCFForService(serviceName, false); 90 | }, 91 | 92 | }; 93 | 94 | var serviceutils = new ServiceUtils(); 95 | 96 | module.exports = serviceutils; 97 | -------------------------------------------------------------------------------- /utilities/tone-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 IBM Corp. 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 | function ToneUtils () { 18 | } 19 | 20 | ToneUtils.prototype = { 21 | check: function () { 22 | return '"IBM Watson Node-RED Utilities for Tone Analyser'; 23 | }, 24 | 25 | isJsonString: function(str) { 26 | try { 27 | JSON.parse(str); 28 | } catch (e) { 29 | return false; 30 | } 31 | return true; 32 | }, 33 | 34 | isJsonObject: function(str) { 35 | if (str instanceof Array || str instanceof Object || 36 | Array.isArray(str) || 'object' === typeof str) { 37 | return true; 38 | } 39 | return false; 40 | }, 41 | 42 | // Function that checks the payload and determines 43 | // whether it is JSON or a Buffer 44 | checkPayload: function(payload) { 45 | var message = null, 46 | isJSON = this.isJsonString(payload) || this.isJsonObject(payload); 47 | 48 | // Payload (text to be analysed) must be a string (content is either raw string or Buffer) 49 | if (typeof payload !== 'string' && isJSON !== true) { 50 | message = 'The payload must be either a string, JSON or a Buffer'; 51 | } 52 | 53 | return message; 54 | }, 55 | 56 | // Function to parse and determine tone setting 57 | // 'all' is the setting which needs be be blanked 58 | // if not the service will throw an error 59 | parseToneOption: function(msg, config) { 60 | var tones = msg.tones || config.tones; 61 | 62 | return (tones === 'all' ? '' : tones); 63 | }, 64 | 65 | // function to parse through the options in preparation 66 | // for the sevice call. 67 | parseOptions: function(msg, config) { 68 | var options = {}; 69 | 70 | if (!config['tone-method']) { 71 | config['tone-method'] = 'generalTone'; 72 | } 73 | 74 | switch (config['tone-method']) { 75 | case 'generalTone' : 76 | let isHTML = msg.contentType || config.contentType; 77 | options.sentences = msg.sentences || config.sentences; 78 | options.contentType = isHTML ? 'text/html' : 'text/plain'; 79 | options.tones = this.parseToneOption(msg, config); 80 | options.toneInput = this.isJsonObject(msg.payload) ? 81 | JSON.stringify(msg.payload) : 82 | msg.payload; 83 | break; 84 | case 'customerEngagementTone' : 85 | options.utterances = this.isJsonString(msg.payload) ? 86 | JSON.parse(msg.payload) : 87 | msg.payload; 88 | break; 89 | } 90 | 91 | return options; 92 | }, 93 | 94 | // function to splice in language options into header 95 | parseLanguage: function(msg, config, options) { 96 | var inputlang = config.inputlang ? config.inputlang : 'en'; 97 | //outputlang = config.outputlang ? config.outputlang : 'en'; 98 | 99 | // The SDK is currently ignoring this, but this is how it should be done. 100 | // If you need it, then Personality Insights as full set of accept-language 101 | //options.headers = { 102 | // 'content-language': inputlang, 103 | // 'accept-language': outputlang 104 | //} 105 | 106 | // This is how it is currently working. 107 | options.language = inputlang; 108 | return options; 109 | } 110 | 111 | }; 112 | 113 | var toneutils = new ToneUtils(); 114 | 115 | module.exports = toneutils; 116 | --------------------------------------------------------------------------------