node-red-node-watson
Version:
A collection of Node-RED nodes for IBM Watson services
528 lines (468 loc) • 14.5 kB
JavaScript
/**
* Copyright 2017, 2022 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function (RED) {
const SERVICE_IDENTIFIER = 'speech-to-text';
var temp = require('temp'),
fs = require('fs'),
fsp = require('fs').promises,
fileType = require('file-type'),
serviceutils = require('../../utilities/service-utils'),
payloadutils = require('../../utilities/payload-utils'),
responseutils = require('../../utilities/response-utils'),
sttutils = require('./stt-utils'),
service = serviceutils.getServiceCreds(SERVICE_IDENTIFIER),
apikey = '', sApikey = '',
endpoint = '',
sEndpoint = 'https://stream.watsonplatform.net/speech-to-text/api';
temp.track();
// Require the Cloud Foundry Module to pull credentials from bound service
// If they are found then the key and password will be stored in
// the variable sApiket.
//
// This separation between is to allow
// the end user to modify the credentials when the service is not bound.
// Otherwise, once set credentials are never reset, resulting in a frustrated
// user who, when he errenously enters bad credentials, can't figure out why
// the edited ones are not being taken.
if (service) {
sApikey = service.apikey ? service.apikey : '';
sEndpoint = service.url;
}
function executeCreateCustomisation(node, stt, params, msg) {
return new Promise(function resolver(resolve, reject) {
stt.createLanguageModel(params)
.then((response) => {
responseutils.parseResponseFor(msg, response, 'customization_id');
resolve();
})
.catch((err) => {
reject(err);
})
});
}
function executeListCustomisations(node, stt, params, msg) {
return new Promise(function resolver(resolve, reject) {
stt.listLanguageModels(params)
.then((response) => {
responseutils.parseResponseFor(msg, response, 'customizations');
resolve();
})
.catch((err) => {
reject(err);
});
});
}
function executeGetCustomisation(node, stt, params, msg) {
return new Promise(function resolver(resolve, reject) {
stt.getLanguageModel(params)
.then((response) => {
responseutils.parseResponseFor(msg, response, 'customization');
resolve();
})
.catch((err) => {
reject(err);
});
});
}
function executeDeleteCustomisation(node, stt, params, msg) {
return new Promise(function resolver(resolve, reject) {
stt.deleteLanguageModel(params)
.then((response) => {
msg['delcustomresponse'] = response;
resolve();
})
.catch((err) => {
reject(err);
})
});
}
function executeUpgradeCustomisation(node, stt, params, msg) {
return new Promise(function resolver(resolve, reject) {
stt.upgradeLanguageModel(params)
.then((response) => {
msg['updcustomresponse'] = response;
resolve();
})
.catch((err) => {
reject(err);
})
});
}
function executeResetCustomisation(node, stt, params, msg) {
return new Promise(function resolver(resolve, reject) {
stt.resetLanguageModel(params)
.then((response) => {
msg['rescustomresponse'] = response;
resolve();
})
.catch((err) => {
reject(err);
})
});
}
function executeAddCorpus(node, stt, params, msg) {
return new Promise(function resolver(resolve, reject) {
stt.addCorpus(params)
.then((response) => {
msg['addcorpusresponse'] = response;
resolve();
})
.catch((err) => {
reject(err);
})
});
}
function executeGetCorpora(node, stt, params, msg) {
return new Promise(function resolver(resolve, reject) {
stt.listCorpora(params)
.then((response) => {
responseutils.parseResponseFor(msg, response, 'corpora');
resolve();
})
.catch((err) => {
reject(err);
})
});
}
function executeGetCorpus(node, stt, params, msg) {
return new Promise(function resolver(resolve, reject) {
stt.getCorpus(params)
.then((response) => {
responseutils.parseResponseFor(msg, response, 'corpus');
resolve();
})
.catch((err) => {
reject(err);
})
});
}
function executeTrain(node, stt, params, msg) {
return new Promise(function resolver(resolve, reject) {
stt.trainLanguageModel(params)
.then((response) => {
msg['train'] = response;
resolve();
})
.catch((err) => {
reject(err);
})
});
}
function executeGetCustomWords(node, stt, params, msg) {
return new Promise(function resolver(resolve, reject) {
stt.listWords(params)
.then((response) => {
responseutils.parseResponseFor(msg, response, 'words');
resolve();
})
.catch((err) => {
reject(err);
})
});
}
function executeAddWords(node, stt, params, msg) {
return new Promise(function resolver(resolve, reject) {
stt.addWords(params)
.then((response) => {
msg['addwordsresponse'] = response;
resolve();
})
.catch((err) => {
reject(err);
})
});
}
function executeDeleteCorpus(node, stt, params, msg) {
return new Promise(function resolver(resolve, reject) {
stt.deleteCorpus(params)
.then((response) => {
msg['delcorpusresponse'] = response;
resolve();
})
.catch((err) => {
reject(err);
})
});
}
function executeDeleteWord(node, stt, params, msg) {
return new Promise(function resolver(resolve, reject) {
stt.deleteWord(params)
.then((response) => {
msg['delwordresponse'] = response;
resolve();
})
.catch((err) => {
reject(err);
})
});
}
function executeUnknownMethod(node, stt, params, msg) {
return new Promise(function resolver(resolve, reject) {
msg.error = 'Unable to process as unknown mode has been specified';
reject(msg.error);
});
}
function determineService() {
return sttutils.determineService(apikey, endpoint);
}
function executeMethod(node, method, params, msg) {
let stt = determineService();
let p = null;
node.status({fill:'blue', shape:'dot', text:'executing'});
switch (method) {
case 'createCustomisation':
p = executeCreateCustomisation(node, stt, params, msg);
break;
case 'listCustomisations':
p = executeListCustomisations(node, stt, params, msg);
break;
case 'getCustomisation':
p = executeGetCustomisation(node, stt, params, msg);
break;
case 'deleteCustomisation':
p = executeDeleteCustomisation(node, stt, params, msg);
break;
case 'resetCustomisation':
p = executeResetCustomisation(node, stt, params, msg);
break;
case 'upgradeCustomisation':
p = executeUpgradeCustomisation(node, stt, params, msg);
break;
case 'addCorpus':
p = executeAddCorpus(node, stt, params, msg);
break;
case 'getCorpora':
p = executeGetCorpora(node, stt, params, msg);
break;
case 'getCorpus':
p = executeGetCorpus(node, stt, params, msg);
break;
case 'train':
p = executeTrain(node, stt, params, msg);
break;
case 'listCustomWords':
p = executeGetCustomWords(node, stt, params, msg);
break;
case 'addWords':
p = executeAddWords(node, stt, params, msg);
break;
case 'deleteCorpus':
p = executeDeleteCorpus(node, stt, params, msg);
break;
case 'deleteCustomWord':
p = executeDeleteWord(node, stt, params, msg);
break;
default:
p = executeUnknownMethod(node, stt, params, msg);
break;
}
return p;
}
function paramsForNewCustom(config) {
var params = {};
if (config['stt-base-model']) {
params['baseModelName'] = config['stt-base-model'];
}
if (config['stt-custom-model-name']) {
params['name'] = config['stt-custom-model-name'];
}
if (config['stt-custom-model-description']) {
params['description'] = config['stt-custom-model-description'];
}
return params;
}
function paramsForCorpus(config, method) {
var params = {};
if (config['stt-corpus-name']) {
params['corpusName'] = config['stt-corpus-name'];
}
if (config['stt-custom-id']) {
params['customizationId'] = config['stt-custom-id'];
}
if ('addCorpus' === method) {
params['allowOverwrite'] = config['stt-allow-overwrite'];
}
return params;
}
function buildParams(msg, method, config) {
var params = {};
switch (method) {
case 'createCustomisation':
params = paramsForNewCustom(config);
break;
case 'listCustomisations':
break;
case 'deleteCustomWord':
if (config['stt-word-name']) {
params['wordName'] = config['stt-word-name'];
}
// Deliberate no break;
case 'getCustomisation':
case 'getCorpora':
case 'train':
case 'listCustomWords':
case 'addWords':
case 'deleteCustomisation':
case 'upgradeCustomisation':
case 'resetCustomisation':
if (config['stt-custom-id']) {
params['customizationId'] = config['stt-custom-id'];
}
break;
case 'addCorpus':
case 'getCorpus':
case 'deleteCorpus':
params = paramsForCorpus(config, method);
break;
}
return params;
}
function setFileParams(method, params, msg) {
switch (method) {
case 'addCorpus':
params.corpusFile = msg.payload;
break;
case 'addWords':
params.words = msg.payload;
break;
}
return params;
}
function loadFile(node, method, params, msg) {
return new Promise(function resolver(resolve, reject) {
temp.open({
suffix: '.txt'
}, function(err, info) {
if (err) {
node.status({
fill: 'red',
shape: 'dot',
text: 'Error receiving the data buffer for training'
});
reject(err);
}
// Syncing up the asynchronous nature of the stream
// so that the full file can be sent to the API.
fsp.writeFile(info.path, msg.payload)
.then(() => {
switch (method) {
case 'addCorpus':
params.corpusFile = fs.createReadStream(info.path);
break;
case 'addWords':
try {
params.words = JSON.parse(fs.readFileSync(info.path, 'utf8'));
} catch (err) {
params.words = fs.createReadStream(info.path);
}
}
resolve();
})
.catch((err) => {
reject(err);
})
});
});
}
function checkForFile(method) {
switch (method) {
case 'addCorpus':
case 'addWords':
return Promise.resolve(true);
}
return Promise.resolve(false);
}
// These are APIs that the node has created to allow it to dynamically fetch IBM Cloud
// credentials, and also translation models. This allows the node to keep up to
// date with new tranlations, without the need for a code update of this node.
// Node RED Admin - fetch and set vcap services
RED.httpAdmin.get('/watson-speech-to-text-v1-query-builder/vcap', function (req, res) {
res.json(service ? {bound_service: true} : null);
});
// API used by widget to fetch available models
RED.httpAdmin.get('/watson-speech-to-text-v1-query-builder/models', function (req, res) {
endpoint = req.query.e ? req.query.e : sEndpoint;
var stt = sttutils.initSTTService(req, sApikey, sEndpoint);
stt.listModels({})
.then((response) => {
let models = response;
if (response.result) {
models = response.result;
}
res.json(models);
})
.catch((err) => {
res.json(err);
})
});
// This is the Speech to Text V1 Query Builder Node
function Node (config) {
RED.nodes.createNode(this, config);
var node = this;
this.on('input', function(msg, send, done) {
var method = config['stt-custom-mode'],
message = '',
params = {};
apikey = sApikey || this.credentials.apikey || config.apikey;
endpoint = sEndpoint;
if (config['service-endpoint']) {
endpoint = config['service-endpoint'];
}
if (!apikey) {
message = 'Missing Watson Speech to Text service credentials';
} else if (!method || '' === method) {
message = 'Required mode has not been specified';
} else {
params = buildParams(msg, method, config);
}
if (message) {
payloadutils.reportError(node, msg, message);
return;
}
checkForFile(method)
.then((lookForBuffer) => {
if (! lookForBuffer) {
return Promise.resolve();
} else if (msg.payload instanceof Buffer) {
return loadFile(node, method, params, msg);
} else {
params = setFileParams(method, params, msg);
return Promise.resolve();
}
})
.then(() => {
return executeMethod(node, method, params, msg)
})
.then(() => {
node.status({});
send(msg);
temp.cleanup();
done();
})
.catch((err) => {
node.status({});
let errMsg = payloadutils.reportError(node, msg, err);
temp.cleanup();
done(errMsg);
});
});
}
RED.nodes.registerType('watson-speech-to-text-v1-query-builder', Node, {
credentials: {
apikey: {type:'password'}
}
});
};