Skip to content

Commit 2c08598

Browse files
committed
Merge pull request #90 from chughts/master
Dynamic Speech to Text node
2 parents e6e14fe + 44642a1 commit 2c08598

4 files changed

Lines changed: 500 additions & 144 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ Node-RED Watson Nodes for IBM Bluemix
33

44
[![Codacy Badge](https://api.codacy.com/project/badge/grade/e157cf8407f2442396789dc78075340a)](https://www.codacy.com/app/rezgui-y/node-red-node-watson)
55

6+
### New in next version
7+
- The transcription returned for the Speech to Text node now returns full (untruncated) transcription. The
8+
alternatives are returned in msg.fullresult. The dialog now loads available models dynamically. This will
9+
allow new speech models to be identified without requiring a further code change.
10+
611
### New in version 0.4.1
712
- AlchemyAPI Image Analysis and Language nodes migrated from old Alchemy SDK to current
813
Watson Developer Cloud SDK for Node.js

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "node-red-node-watson",
3-
"version": "0.4.1",
3+
"version": "0.4.2",
44
"description": "A collection of Node-RED nodes for IBM Watson services",
55
"dependencies": {
66
"alchemy-api": "^1.3.0",

services/speech_to_text/v1.html

Lines changed: 261 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -20,42 +20,50 @@
2020
<i class="fa fa-question-circle"></i><b> Please wait: </b> Checking for bound service credentials...
2121
</div>
2222
</div>
23-
<div class="form-row">
23+
24+
<div>
25+
<label id="node-label-message"><i class="fa fa-exclamation-triangle"></i></label>
26+
</div>
27+
28+
<div class="form-row">
2429
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
25-
<input type="text" id="node-input-name" placeholder="Name">
30+
<input type="text" id="node-input-name" placeholder="Name"></input>
2631
</div>
32+
2733
<div class="form-row credentials" style="display: none;">
2834
<label for="node-input-username"><i class="fa fa-user"></i> Username</label>
29-
<input type="text" id="node-input-username" placeholder="Username">
35+
<input type="text" id="node-input-username" placeholder="Username"></input>
3036
</div>
3137
<div class="form-row credentials" style="display: none;">
3238
<label for="node-input-password"><i class="fa fa-key"></i> Password</label>
33-
<input type="password" id="node-input-password" placeholder="Password">
39+
<input type="password" id="node-input-password" placeholder="Password"></input>
3440
</div>
35-
<div class="form-row">
41+
42+
<div class="form-row">
3643
<label for="node-input-lang"><i class="fa fa-language"></i> Language</label>
3744
<select type="text" id="node-input-lang" style="display: inline-block; width: 70%;">
38-
<option value="en-US">English (US)</option>
39-
<option value="es-ES">Spanish</option>
40-
<option value="ja-JP">Japanese</option>
41-
<option value="pt-BR">Brazilian Portuguese</option>
42-
<option value="zh-CN">Mandarin Chinese</option>
4345
</select>
4446
</div>
47+
<div class="form-row">
48+
<input type="hidden" id="node-input-langhidden"/>
49+
</div>
50+
4551
<div class="form-row">
4652
<label for="node-input-band"><i class="fa fa-audio-o"></i> Quality</label>
4753
<select type="text" id="node-input-band" style="display: inline-block; width: 70%;">
48-
<option value="BroadbandModel">Broadband (>= 16Khz)</option>
49-
<option value="NarrowbandModel">Narrowband (>= 8Khz)</option>
5054
</select>
5155
</div>
56+
<div class="form-row">
57+
<input type="hidden" id="node-input-bandhidden"/>
58+
</div>
59+
60+
5261
<div class="form-row">
5362
<label for="node-input-continuous"><i class="fa fa-audio-o"></i> Continuous</label>
54-
<select type="text" id="node-input-continuous" style="display: inline-block; width: 70%;">
55-
<option value="true" selected="selected">True</option>
56-
<option value="false">False</option>
63+
<input type="checkbox" id="node-input-continuous"></input>
5764
</select>
58-
</div>
65+
</div>
66+
5967
</script>
6068

6169
<script type="text/x-red" data-help-name="watson-speech-to-text">
@@ -76,45 +84,260 @@
7684
automatically but the is not true in reverse.</p>
7785
<p>Use the continuous property to choose whether the decoding should stop at the first pause or wait until the end of the file.</p>
7886
<p>The returned audio transcription will be returned on <code>msg.transcription</code>.</p>
87+
<p>The full response, including alternative transcriptions can be found on
88+
<code>msg.fullresult</code>.</p>
7989
<p>For more information about the Speech To Text service, read the <a href="https://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/speech-to-text.html">documentation</a>.</p>
8090
</script>
8191

8292
<script type="text/javascript">
93+
var models = null;
94+
var languages = null;
95+
var bands = null;
96+
97+
var language_selected = '';
98+
var band_selected = '';
99+
100+
var LANGUAGES = { 'en-US' : 'US English',
101+
'pt-BR': 'Portuguese Braziilian',
102+
'en-UK': 'UK English' ,
103+
'zh-CN': 'Mandarin',
104+
'es-ES': 'Spanish',
105+
'ar-AR': 'Arablic',
106+
'ja-JP': 'Japanese'
107+
};
108+
109+
110+
// sorting functions
111+
function onlyUnique(value, index, self) {
112+
return self.indexOf(value) === index;
113+
}
114+
115+
// Function to be used at the start, as don't want to expose any fields, unless the models are
116+
// available. The models can only be fetched if the credentials are available.
117+
function hideEverything() {
118+
if (!models) {
119+
$('#credentials-not-found').show();
120+
$('label#node-label-message').parent().hide();
121+
$('input#node-input-continuous').parent().hide();
122+
$('select#node-input-lang').parent().hide();
123+
$('select#node-input-band').parent().hide();
124+
}
125+
}
126+
127+
// Check if there is a model then can show the fields.
128+
// available. The models can only be fetched if the credentials are available.
129+
function visibilityCheck() {
130+
if (models) {
131+
$('label#node-label-message').parent().hide();
132+
$('input#node-input-continuous').parent().show();
133+
$('select#node-input-lang').parent().show();
134+
$('select#node-input-band').parent().show();
135+
} else {
136+
$('label#node-label-message').parent().hide();
137+
$('input#node-input-continuous').parent().hide();
138+
$('select#node-input-lang').parent().hide();
139+
$('select#node-input-band').parent().hide();
140+
}
141+
}
142+
143+
144+
// Simple check that is only invoked if the service is not bound into bluemix. In this case the
145+
// user has to provide credentials. Once there are credentials, then the models are retrieved.
146+
function checkCredentials() {
147+
var u = $('#node-input-username').val();
148+
var p = $('#node-input-password').val();
149+
150+
if (u && u.length && p) {
151+
if (!models) {
152+
getModels();
153+
}
154+
}
155+
}
156+
157+
// Populate the quality select field
158+
function populateBands() {
159+
if (!bands && models) {
160+
bands = models.map(function(m) {
161+
return m.name.split('_')[1];
162+
});
163+
var unique_bands = bands.filter(onlyUnique);
164+
bands = unique_bands;
165+
}
166+
167+
if (bands) {
168+
$('select#node-input-band').empty();
169+
170+
bands.forEach(function(b) {
171+
var selectedText = '';
172+
if (band_selected === b) {
173+
selectedText = 'selected="selected"';
174+
}
175+
$('select#node-input-band')
176+
.append('<option value='
177+
+ '"' + b + '"'
178+
+ selectedText
179+
+ '>'
180+
+ b
181+
+ '</option>');
182+
});
183+
}
184+
}
185+
186+
// Register the handlers for the fields
187+
function handlersUI() {
188+
$('#node-input-username').change(function(val){
189+
checkCredentials();
190+
});
191+
$('#node-input-password').change(function(val){
192+
checkCredentials();
193+
});
194+
195+
$('#node-input-lang').change(function (val) {
196+
language_selected = $('#node-input-lang').val();
197+
populateBands();
198+
});
199+
200+
$('#node-input-band').change(function (val) {
201+
band_selected = $('#node-input-band').val();
202+
});
203+
}
204+
205+
// Function called when either when the models have been retrieved, or
206+
// on dialog load, if the model has already been retrieved
207+
function postModelCheck() {
208+
processModels();
209+
visibilityCheck();
210+
}
211+
212+
// Retrieve the available models from the server, if data is returned, then
213+
// can enable the dynamic selection fields.
214+
function getModels(haveCredentials) {
215+
var u = $('#node-input-username').val();
216+
var p = $('#node-input-password').val();
217+
218+
$.getJSON('/watson-speech-to-text/models/', {un: u, pwd: p}).done(function (data) {
219+
if (data.error) {
220+
$('label#node-label-message').parent().show();
221+
$('label#node-label-message').text(data.error);
222+
} else if (data.models) {
223+
models = data.models;
224+
//have_credentials = true;
225+
postModelCheck();
226+
}
227+
}).fail(function (err) {
228+
$('label#node-label-message').parent().show();
229+
$('label#node-label-message').text('Error trying to determine available service models');
230+
231+
}).always(function () {});
232+
}
233+
234+
// Called to complete the languages selection table
235+
function processLanguages() {
236+
if (!languages && models) {
237+
languages = models.map(function(m) {
238+
return m.language;
239+
});
240+
}
241+
if (languages) {
242+
$('select#node-input-lang').empty();
243+
var unique_langs = languages.filter(onlyUnique);
244+
245+
unique_langs.forEach(function(l) {
246+
var selectedText = '';
247+
if (language_selected === l) {
248+
selectedText = 'selected="selected"';
249+
}
250+
$('select#node-input-lang')
251+
.append('<option value='
252+
+ '"' + l + '"'
253+
+ selectedText
254+
+ '>'
255+
+ (LANGUAGES[l] ? LANGUAGES[l] : l)
256+
+ '</option>');
257+
});
258+
259+
}
260+
}
261+
262+
// Called to work through the models, completing the dyanmic selection fields.
263+
function processModels() {
264+
if (models) {
265+
processLanguages();
266+
populateBands();
267+
}
268+
}
269+
270+
// The dynamic nature of the selection fields in this node has caused problems.
271+
// Whenever there is a fetch for the models, on a page refresh or applicaiton
272+
// restart, the settings for the dynamic fields are lost.
273+
// So hidden (text) fields are being used to squirrel away the values, so that
274+
// they can be restored.
275+
function restoreFromHidden() {
276+
language_selected = $('#node-input-langhidden').val();
277+
$('select#node-input-lang').val(language_selected);
278+
279+
band_selected = $('#node-input-bandhidden').val();
280+
$('select#node-input-band').val(band_selected);
281+
}
282+
283+
// This is the on edit prepare function, which will be invoked everytime the dialog
284+
// is shown.
285+
function oneditprepare() {
286+
hideEverything();
287+
restoreFromHidden();
288+
handlersUI();
289+
290+
$.getJSON('watson-speech-to-text/vcap/')
291+
// for some reason the getJSON resets the vars so need to restoreFromHidden again
292+
// so again.
293+
.done(function (service) {
294+
restoreFromHidden();
295+
$('.credentials').toggle(!service);
296+
if (!models) {getModels(service);}
297+
else {postModelCheck();}
298+
})
299+
.fail(function () {
300+
$('.credentials').show();
301+
}).always(function () {
302+
$('#credentials-check').hide();
303+
});
304+
}
305+
306+
// Save the values in the dyanmic lists to the hidden fields.
307+
function oneditsave(){
308+
$('#node-input-langhidden').val(language_selected);
309+
$('#node-input-bandhidden').val(band_selected);
310+
}
311+
83312
(function() {
84313
RED.nodes.registerType('watson-speech-to-text', {
85314
category: 'IBM Watson',
86315
defaults: {
87-
name: {value: ""},
88-
lang: {value: "en-US"},
89-
band: {value: "BroadbandModel"},
90-
continuous: {value: "true"}
316+
name: {value: ''},
317+
continuous: {value: true},
318+
lang: {value: ''},
319+
langhidden: {value: ''},
320+
band: {value: ''},
321+
bandhidden: {value: ''},
322+
password: {value: ''}
91323
},
92324
credentials: {
93-
username: {type:"text"},
94-
password: {type:"password"}
325+
username: {type:'text'} //,
326+
// password: {type: 'password'} - // Taken out because, was not being restored on dialog open.
95327
},
96-
color: "rgb(140, 198, 63)",
328+
color: 'rgb(140, 198, 63)',
97329
inputs: 1,
98330
outputs: 1,
99-
icon: "speech_to_text.png",
100-
paletteLabel: "speech to text",
331+
icon: 'speech_to_text.png',
332+
paletteLabel: 'speech to text',
101333
label: function() {
102-
return this.name || "speech to text";
334+
return this.name || 'speech to text';
103335
},
104336
labelStyle: function() {
105-
return this.name ? "node_label_italic" : "";
337+
return this.name ? 'node_label_italic' : '';
106338
},
107-
oneditprepare: function() {
108-
$.getJSON('watson-speech-to-text/vcap/')
109-
.done(function (service) {
110-
$('.credentials').toggle(!service);
111-
})
112-
.fail(function () {
113-
$('.credentials').show();
114-
}).always(function () {
115-
$('#credentials-check').hide();
116-
})
117-
}
339+
oneditprepare: oneditprepare,
340+
oneditsave: oneditsave
118341
});
119342
})();
120343
</script>

0 commit comments

Comments
 (0)