node-nlp how to extract email, phone, url? - node.js

I'm using node nlp to extract the phone, url ,email etc. The sample code given was just an object in the help page. I don't know how to initialize the extract code. The read me for extraction url is https://github.com/axa-group/nlp.js/blob/master/docs/builtin-entity-extraction.md#ip-extraction
One of the sample in that page is given below.
Email extraction
It can identify and extract valid emails accounts, this works for any language.
"utterance": "My email is something#somehost.com please write me",
"entities": [
{
"start": 12,
"end": 33,
"len": 22,
"accuracy": 0.95,
"sourceText": "something#somehost.com",
"utteranceText": "something#somehost.com",
"entity": "email",
"resolution": {
"value": "something#somehost.com"
}
}
]
I have installed the npm and initialized like this
const { NlpManager } = require('node-nlp');
const manager = new NlpManager({ languages: ['en'] });
What must be the next steps(Need a sample code) to do the extractions?
The npm url is : https://www.npmjs.com/package/node-nlp

I will provide you an example code:
const { NlpManager } = require('node-nlp');
const manager = new NlpManager({ languages: ['en'] });
async function mainExtractEntities() {
const result = await manager.extractEntities('en', 'Are you able to identify that meh#meh.com is an email and moh#moh.com is another email so there are 2 emails?');
console.log(result);
}
async function mainFullExample() {
manager.addDocument('en', 'My mail is %email%', 'email');
manager.addDocument('en', 'My email is %email%', 'email');
manager.addDocument('en', 'Here you have my email: %email%', 'email');
manager.addDocument('en', 'Hello', 'greet');
manager.addDocument('en', 'Good morning', 'greet');
manager.addDocument('en', 'good afternoon', 'greet');
manager.addDocument('en', 'good evening', 'greet');
manager.addAnswer('en', 'email', 'Your email is {{email}}');
manager.addAnswer('en', 'greet', 'Hi!');
await manager.train();
let result = await manager.process('en', 'I think that my mail is meh#meh.com');
console.log(result);
result = await manager.process('en', 'Hello bot!');
console.log(result);
}
mainExtractEntities();
mainFullExample();
This will show in console:
[ { start: 30,
end: 40,
len: 11,
accuracy: 0.95,
sourceText: 'meh#meh.com',
utteranceText: 'meh#meh.com',
entity: 'email',
resolution: { value: 'meh#meh.com' } },
{ start: 58,
end: 68,
len: 11,
accuracy: 0.95,
sourceText: 'moh#moh.com',
utteranceText: 'moh#moh.com',
entity: 'email',
resolution: { value: 'moh#moh.com' } },
{ start: 100,
end: 100,
len: 1,
accuracy: 0.95,
sourceText: '2',
utteranceText: '2',
entity: 'number',
resolution: { strValue: '2', value: 2, subtype: 'integer' } } ]
{ locale: 'en',
localeIso2: 'en',
language: 'English',
utterance: 'I think that my mail is meh#meh.com',
classification:
[ { label: 'email', value: 0.9994852170204532 },
{ label: 'greet', value: 0.0005147829795467752 } ],
intent: 'email',
domain: 'default',
score: 0.9994852170204532,
entities:
[ { start: 24,
end: 34,
len: 11,
accuracy: 0.95,
sourceText: 'meh#meh.com',
utteranceText: 'meh#meh.com',
entity: 'email',
resolution: [Object] } ],
sentiment:
{ score: 0.25,
comparative: 0.027777777777777776,
vote: 'positive',
numWords: 9,
numHits: 1,
type: 'senticon',
language: 'en' },
srcAnswer: 'Your email is {{email}}',
answer: 'Your email is meh#meh.com' }
{ locale: 'en',
localeIso2: 'en',
language: 'English',
utterance: 'Hello bot!',
classification:
[ { label: 'greet', value: 0.8826839762075465 },
{ label: 'email', value: 0.1173160237924536 } ],
intent: 'greet',
domain: 'default',
score: 0.8826839762075465,
entities: [],
sentiment:
{ score: 0,
comparative: 0,
vote: 'neutral',
numWords: 2,
numHits: 0,
type: 'senticon',
language: 'en' },
srcAnswer: 'Hi!',
answer: 'Hi!' }
Important things to know:
You can omit the language in the extractEntities and process and pass undefined instead, that way the language will be guessed from your sentence to fit the best language of your NlpManger.
The email extraction works for any language. You have other entities more complex, like text numbers, that will be extracted only for some languages
The entity extraction is only one part, other interesting partes are the NLU classifier and the Natural Language Generation, you will see that the answer "Your email is {{email}}" is a template and the email is replaced with the extracted one from the conversation.

Related

Convert CVS to JSON with duplicate keys

For a project I'm working on we get some data delivered in an Excel sheet which I convert to CSV through Excel.
These files contain measurements with different categories but the same ID.
Example
readingId; category; result;
1 ; cat 1 ; A
1 ; cat 2 ; B
2 ; cat1 ; C
I've then converted the CSV to JSON and wrote a function to output the data into different objects
const fs = require('fs');
const path = require('path');
exports.convertJson = (file) => {
let rawData = fs.readFileSync(file);
let jsonData = JSON.parse(rawData);
let rawOutput = [];
for (output of jsonData) {
rawOutput.push({
locationId: output.Meetlocatienummer,
date: output.Aanmaakdatum_score,
subCategorie: output.Bestekspost,
score: output.Score,
scoreNumber: output.Cijfer,
categories: output.Categorie,
coordinates: output.Coordinaten,
neighbourhoodIndex: output.BUURTCODE,
quality: output.KWALITEIT,
district: output.STADSDEEL,
distrcitIndex: output.STADSDLCD,
street: output.STRAATNAAM,
neighbourhood: output.WIJK,
cluster: output.Cluster,
});
}
return rawOutput;
};
Which outputs the following results
[
{
locationId: 10215,
date: undefined,
subCategorie: 'Meubilair-afvalbak-vullingsgraad',
score: '',
scoreNumber: 8,
categories: 'Meubilair',
coordinates: '52.072843, 4.287723',
neighbourhoodIndex: 10,
quality: 'residentiekwaliteit',
district: 'Segbroek',
distrcitIndex: 3,
street: 'Xaverystraat',
neighbourhood: 'Regentessekwartier',
cluster: 'WRF'
},
{
locationId: 10215,
date: undefined,
subCategorie: 'Meubilair-container-bijgeplaatst afval rondom container',
score: 'A+',
scoreNumber: 10,
categories: 'Meubilair',
coordinates: '52.072843, 4.287723',
neighbourhoodIndex: 10,
quality: 'residentiekwaliteit',
district: 'Segbroek',
distrcitIndex: 3,
street: 'Xaverystraat',
neighbourhood: 'Regentessekwartier',
cluster: 'WRF'
},
{
locationId: 10215,
date: undefined,
subCategorie: 'Riolering-kolk-belemmering inlaat',
score: 'A+',
scoreNumber: 10,
categories: 'Riolering',
coordinates: '52.072843, 4.287723',
neighbourhoodIndex: 10,
quality: 'residentiekwaliteit',
district: 'Segbroek',
distrcitIndex: 3,
street: 'Xaverystraat',
neighbourhood: 'Regentessekwartier',
cluster: 'WRF'
},
{
locationId: 10215,
date: undefined,
subCategorie: 'Verharding-open verharding-elementenverharding-onkruid',
score: 'A',
scoreNumber: 8,
categories: 'Verharding',
coordinates: '52.072843, 4.287723',
neighbourhoodIndex: 10,
quality: 'residentiekwaliteit',
district: 'Segbroek',
distrcitIndex: 3,
street: 'Xaverystraat',
neighbourhood: 'Regentessekwartier',
cluster: 'WRF'
},
{
locationId: 10215,
date: undefined,
subCategorie: 'Verharding-natuurlijk afval',
score: 'A',
scoreNumber: 8,
categories: 'Verharding',
coordinates: '52.072843, 4.287723',
neighbourhoodIndex: 10,
quality: 'residentiekwaliteit',
district: 'Segbroek',
distrcitIndex: 3,
street: 'Xaverystraat',
neighbourhood: 'Regentessekwartier',
cluster: 'WRF'
},
{
locationId: 10215,
date: undefined,
subCategorie: 'Verharding-uitwerpselen',
score: 'A+',
scoreNumber: 10,
categories: 'Verharding',
coordinates: '52.072843, 4.287723',
neighbourhoodIndex: 10,
quality: 'residentiekwaliteit',
district: 'Segbroek',
distrcitIndex: 3,
street: 'Xaverystraat',
neighbourhood: 'Regentessekwartier',
cluster: 'WRF'
},
{
locationId: 10215,
date: undefined,
subCategorie: 'Verharding-zwerfafval grof',
score: 'A',
scoreNumber: 8,
categories: 'Verharding',
coordinates: '52.072843, 4.287723',
neighbourhoodIndex: 10,
quality: 'residentiekwaliteit',
district: 'Segbroek',
distrcitIndex: 3,
street: 'Xaverystraat',
neighbourhood: 'Regentessekwartier',
cluster: 'WRF'
},
{
locationId: 10215,
date: undefined,
subCategorie: 'Verharding-veegvuil goten',
score: 'A',
scoreNumber: 8,
categories: 'Verharding',
coordinates: '52.072843, 4.287723',
neighbourhoodIndex: 10,
quality: 'residentiekwaliteit',
district: 'Segbroek',
distrcitIndex: 3,
street: 'Xaverystraat',
neighbourhood: 'Regentessekwartier',
cluster: 'WRF'
},
{
locationId: 10215,
date: undefined,
subCategorie: 'Verharding-onkruid rondom obstakels',
score: 'B',
scoreNumber: 6,
categories: 'Verharding',
coordinates: '52.072843, 4.287723',
neighbourhoodIndex: 10,
quality: 'residentiekwaliteit',
district: 'Segbroek',
distrcitIndex: 3,
street: 'Xaverystraat',
neighbourhood: 'Regentessekwartier',
cluster: 'WRF'
},
{
locationId: 10215,
date: undefined,
subCategorie: 'Verharding-grof vuil',
score: 'A+',
scoreNumber: 10,
categories: 'Verharding',
coordinates: '52.072843, 4.287723',
neighbourhoodIndex: 10,
quality: 'residentiekwaliteit',
district: 'Segbroek',
distrcitIndex: 3,
street: 'Xaverystraat',
neighbourhood: 'Regentessekwartier',
cluster: 'WRF'
},
{
locationId: 10215,
date: undefined,
subCategorie: 'Verharding-zwerfafval fijn',
score: 'A',
scoreNumber: 8,
categories: 'Verharding',
coordinates: '52.072843, 4.287723',
neighbourhoodIndex: 10,
quality: 'residentiekwaliteit',
district: 'Segbroek',
distrcitIndex: 3,
street: 'Xaverystraat',
neighbourhood: 'Regentessekwartier',
cluster: 'WRF'
},
{
locationId: 7466,
date: undefined,
subCategorie: 'Meubilair-afvalbak-vullingsgraad',
score: 'B',
scoreNumber: 6,
categories: 'Meubilair',
coordinates: '52.072647, 4.288656',
neighbourhoodIndex: 10,
quality: 'residentiekwaliteit',
district: 'Segbroek',
distrcitIndex: 3,
street: 'Jan Krosstraat',
neighbourhood: 'Regentessekwartier',
cluster: 'WRF'
}
]
In the end I would like to write this information to MongoDB and I had the following scheme in mind to reduce the loads of duplicated data
{
locationId: output.Meetlocatienummer,
date: output.Aanmaakdatum_score,
subCategories: [
{
subCategory: output.Bestekspost,
score: output.Score,
scoreNumber: output.Cijfer,
},
],
categories: [{ category: output.Categorie }],
coordinates: output.Coordinaten,
neighbourhoodIndex: output.BUURTCODE,
quality: output.KWALITEIT,
district: output.STADSDEEL,
distrcitIndex: output.STADSDLCD,
street: output.STRAATNAAM,
neighbourhood: output.WIJK,
cluster: output.Cluster,
}
This project is a hobby project while learning NodeJS. The actual data are readings how much the streets of the city I work for a poluted with litter. It's a bit boring to read thousands of lines in Excel to find the hotspots of the city as it's a bit boring to just read some scores and graphs so I though it would be nice to import it into Leaflet through NodeJS.
The actual backend will contain more functionality as I learn Node and maybe in the future React, that's why I try to write it myself rather then importing the data into Google maps, which works oke but lacks detailed category filtering.
I hope my idea is a bit clear and someone can point me in the right direction.
Edit 1
I got a bit further with lodash.
return _(rawOutput)
.groupBy('locationId')
.map((obj) => _.assignWith({}, ...obj, (val1, val2) => val1 || val2))
.value();
I found the above snippet and now I only get 1 output per unique locationId but now I'm stuck with contructing the final output with the subcategories.
I was also playing around a bit with csv-parser to directly go from the csv to a proper json output, which would be ideal because I don't have to convert it manually then.
I'll get back to it tomorrow :-)
If you take that JSON and mongoimport into MongoDB, you can use the following pipeline to transform it -- although honestly, a little python script on the outside could construct the structure just as easily and then you would still import the condensed data.
db.foo.aggregate([
{$group: {_id: "$locationId",
subCategories: {$push: {subCategory: "$subCategorie", score:"$score", scoreNumber: "$scoreNumber"}},
categories: {$push: "$categories"},
// Just take the first occurance of each of these since they are claimed
// to be the same.
date: {$first: "$date"},
neighbourhoodIndex: {$first: "$neighbourhoodIndex"},
quality: {$first: "$quality"},
district: {$first: "$district"},
distrcitIndex: {$first: "$distrcitIndex"},
street: {$first: "$street"},
neighbourhood: {$first: "$neighbourhood"},
cluster: {$first: "$cluster"},
coordinates: {$first: "$coordinates"}
}}
// Now that we have a single doc with locationId x and a coordinate, convert
// the string lat,long "52.072843, 4.287723" into a GeoJSON Point which is
// a long,lat array of doubles. We convert by using $addFields to
// overwrite the original coordinates field:
,{$addFields: {"coordinates": {$let: {
vars: {pt: {$split:["$coordinates",","]}},
in: {"type": "Point", "coordinates": [
{$toDouble: {$trim: {input:{$arrayElemAt:["$$pt",1]}}}},
{$toDouble: {$trim: {input:{$arrayElemAt:["$$pt",0]}}}}
]
}
}}
}}
// Put the whole transformed thing into a new collection named "foo2":
,{$out: "foo2"}
]);
Alright, in the end the code from Buzz Moschetti was exactly what I wanted to get rid of the duplicated data. I didn't hear about aggregates yet so thanks for that.
I ended up using the CSV Parse library to convert the CSV to JSON, drop that into the database and then query out the duplicates with the code from Buzz.
I haven't written the code yet to write back the cleaned up data back to the database but that shouldn't be to hard so I'll just post what I have now for as reference for others.
First of all I have written a csv helper for the conversion.
const fs = require('fs');
const { parse } = require('csv');
const moment = require('moment');
exports.processFile = async (filePath) => {
const records = [];
const input = fs.createReadStream(filePath);
const parser = parse({
// CSV options
bom: true,
delimiter: ';',
cast: (value, context) => {
if (context.header) return value;
// Convert data
if (context.column === 'date') {
const dateString = moment(value, 'dd-mm-yyyy h:mm');
const date = dateString.toDate();
return date;
}
// Convert coordinates to GeoJSON
if (context.column === 'coordinates') {
const coordinate = value.split(',');
const geoData = {
type: 'Point',
coordinate: [coordinate[0], coordinate[1]],
};
return geoData;
}
// Output rest of the fields
return String(value);
},
columns: [
'locationId', // meetlocatienummer
'date', // aanmaakdatum score
'subCategory', //bestekpost
'category', // categorie
'score', // score
'coordinates', //coordinaten
undefined, // buurt
undefined, // buurtcode
undefined, // gebied
undefined, // id
'quality', //kwaliteit
undefined, // stadsdeel
'districtIndex', //stadsdlcd
'street', //straatnaam
undefined, //vaknr
'neighbourhood', //wijk
undefined, //wijkcode
'cluster', //cluster
'scoreNumber', //cijfer
undefined, // week
undefined, // maand
undefined, // jr-mnd
undefined, // jaar
],
trim: true,
from_line: 2,
skip_records_with_empty_values: true,
});
// parser.on('error', (err) => {
// console.log(err);
// const error = new Error(err);
// error.httpStatusCode = 500;
// throw error;
// });
//const transformer = transform((record, callback) => {});
input.pipe(parser).on('error', (err) => {
input.close();
});
for await (let record of parser) {
// Skip all lines without coordinates
if (record.coordinates.coordinate[1] === undefined) {
continue;
}
// Push filename to the record object
record.fileName = filePath;
// Push records for final output
records.push(record);
//console.log('Records converted');
}
return records;
};
I'm uploading the file with the Multer lirbary. Here's the POST action in my import data controller. After the file has been uploaded the conversion starts. If an error occurs the file gets deleted again and no records are written to the database. If the conversion succeeds the records will be written to importdatas in MongoDB, these are still the 'dirty' records, so loads of duplicates but without useless data which gets filtered by the CSV Parse helper. (Basically all the data without coordinates)
exports.postImportData = (req, res, next) => {
const uploadedCSV = req.file;
//console.log(uploadedCSV);
// Load imported CSV files from DB to be able to delete them
ImportedCSVFile.find()
.sort({ date: -1 })
.then((result) => {
// Check if there are already files imported
let hasFiles = null;
if (result.length > 0) {
hasFiles = 1;
}
// If there are any erros with the file being uploaded
if (req.fileValidationError) {
return res.render('admin/import-data/import-data', {
pageTitle: 'Importeer data',
path: '/admin/import-data',
files: result,
activeAdmin: true,
errorMessage: req.fileValidationError,
validationErrors: [],
hasFiles,
});
}
// If there's no file uploaded
if (!uploadedCSV) {
return res.render('admin/import-data/import-data', {
pageTitle: 'Importeer data',
path: '/admin/import-data',
files: result,
activeAdmin: true,
errorMessage: 'Geen bestand geselecteerd',
validationErrors: [],
hasFiles,
});
}
(async () => {
const csvFile = await fileHelper.hasFile(uploadedCSV);
try {
const records = await convert.processFile(csvFile);
// Write all CSV data to importdatas in MongoDB
await ImportData.insertMany(records)
.then((result) => {
console.log('Data imported');
// Push info about the uploaded file into 'importedcsvfiles'
const importedCSVFile = new ImportedCSVFile({
filePath: fileHelper.hasFile(uploadedCSV),
originalName: uploadedCSV.originalname,
});
return (
importedCSVFile
.save() // Save all CSV data into 'importedcsvfiles' in MongoDB
.then((result) => {
res.redirect('/admin/import-data');
})
// Catch save filepath error
.catch((err) => {
console.log('save failed');
const error = new Error(err);
error.httpStatusCode = 500;
return next(error);
})
);
})
// Catch insert CSV data into DB error
.catch((err) => {
console.log('insert many failed');
const error = new Error(err);
error.httpStatusCode = 500;
return next(error);
});
} catch (err) {
// console.log(error);
fileHelper.removeFile(csvFile);
return res.render('admin/import-data/import-data', {
pageTitle: 'Importeer data',
path: '/admin/import-data',
files: result,
activeAdmin: true,
errorMessage:
'Het geselecteerde bestand heeft niet de juiste indeling. Neem contact op met de beheerder.',
validationErrors: [],
hasFiles,
});
}
})();
});
};
Also wrote a delete option which removes the CSV file and all the database records which are linked to that file
exports.postDeleteData = (req, res, next) => {
const dataId = req.body.dataId;
ImportedCSVFile.findById(dataId)
.then((result) => {
// console.log('FilePath:');
// console.log(result.filePath);
const filePath = result.filePath;
const deleteData = async () => {
await ImportData.deleteMany({ filePath: filePath })
.then((result) => {})
.catch((err) => {
const error = new Error(err);
error.httpStatusCode = 500;
return next(error);
});
await ImportedCSVFile.findByIdAndDelete(dataId)
.then((result) => {
console.log('Data deleted');
fileHelper.removeFile(filePath);
res.redirect('/admin/import-data');
})
.catch((err) => {
console.log('here');
const error = new Error(err);
error.httpStatusCode = 500;
return next(error);
});
};
return deleteData();
})
.catch((err) => {
const error = new Error(err);
error.httpStatusCode = 500;
return next(error);
});
};
And for now the Aggregate code from Buzz to clean up the data and drop it into Leaflet so I get 1 point with all the different categories.
const { ImportData, OutputData } = require('../models/importData.model');
// Main controller for the homepage
exports.getMainController = (req, res, next) => {
ImportData.aggregate([
{
$group: {
_id: '$coordinates',
subCategories: {
$push: {
subCategory: '$subCategory',
score: '$score',
scoreNumber: '$scoreNumber',
},
},
categories: { $push: '$category' },
// Just take the first occurance of each of these since they are claimed
// to be the same.
date: { $first: '$date' },
quality: { $first: '$quality' },
districtIndex: { $first: '$districtIndex' },
street: { $first: '$street' },
neighbourhood: { $first: '$neighbourhood' },
cluster: { $first: '$cluster' },
},
},
]).exec((err, locations) => {
if (err) {
throw next(err);
}
//console.log(locations);
res.render('index.ejs', {
pageTitle: 'Kaart',
path: '/kaart',
activeAdmin: true,
data: locations,
errorMessage: null,
});
});
};
As of now I just query this data like I said in the beginning. As I am still learning a lot about Javascript and Node I now started to build a frontend with React. Once I got on going there I will convert all this code to an API and I'll finish this part of the project.

Spotipy - Listing only track and artists names in a playlist

Hello All and thank you in advance for your help :)
Can someone help me understand how I can take the below code, which displays data for a specified playlist, and have it only show the artist and track names? I have been toying around with the API documentation for several hours and I have not been able to make heads or tales of it. Right now when it displays data it gives me a whole bunch of data in a jumbled mess. Also, note that I put dummy values in the client_id and Secret parts of this code.
from spotipy.oauth2 import SpotifyClientCredentials
import spotipy
import json
PlaylistExample = '37i9dQZEVXbMDoHDwVN2tF'
cid = '123'
secret = 'xyz'
auth_manager = SpotifyClientCredentials(client_id=cid, client_secret=secret)
sp = spotipy.Spotify(auth_manager=auth_manager)
playlist_id = 'spotify:user:spotifycharts:playlist:37i9dQZEVXbJiZcmkrIHGU'
results = sp.playlist(playlist_id)
print(json.dumps(results, indent=4))
Would something like this be useful?:
print("Song - Artist - Album\n")
for item in results['tracks']['items']:
print(
item['track']['name'] + ' - ' +
item['track']['artists'][0]['name'] + ' - ' +
item['track']['album']['name']
)
Your output will look similar to this:
Song - Artist - Album
ONLY - ZHU - ONLY
Bad - 2012 Remaster - Michael Jackson - Bad 25th Anniversary
Orion - Rodrigo y Gabriela - Rodrigo y Gabriela
Shape of You - Ed Sheeran - ÷ (Deluxe)
Alternatively, you could create your own structure based on the returned one by Spotify but just keeping what you need:
result_dict = {
'tracks': {
'items': [],
'limit': 100,
'next': None,
'offset': 0,
'previous': None,
'total': 16
},
'type': 'playlist',
'uri': '<playlist_uri>'
}
And your track structure that goes inside 'items' from above:
track_dict = {
'track': {
'album': {
'name': item['track']['album']['name'],
},
'artists': [{
'name': item['track']['artists'][0]['name'],
}],
'name': item['track']['name'],
}
}
Then iterate and insert one by one:
for item in results['tracks']['items']:
track_dict = {
'track': {
'album': {
'name': item['track']['album']['name'],
},
'artists': [{
'name': item['track']['artists'][0]['name'],
}],
'name': item['track']['name'],
}
}
# Append the track dict structure to your results dict structure
result_dict['tracks']['items'].append(track_dict)
Having this as a result when printing result_dict:
{
'tracks': {
'items': [{
'track': {
'album': {
'name': 'ONLY'
},
'artists': [{
'name': 'ZHU'
}],
'name': 'ONLY'
}
}, {
'track': {
'album': {
'name': 'Bad 25th Anniversary'
},
'artists': [{
'name': 'Michael Jackson'
}],
'name': 'Bad - 2012 Remaster'
}
}, {
'track': {
'album': {
'name': 'Rodrigo y Gabriela'
},
'artists': [{
'name': 'Rodrigo y Gabriela'
}],
'name': 'Orion'
}
}, {
'track': {
'album': {
'name': '÷ (Deluxe)'
},
'artists': [{
'name': 'Ed Sheeran'
}],
'name': 'Shape of You'
}
}],
'limit': 100,
'next': None,
'offset': 0,
'previous': None,
'total': 4
},
'type': 'playlist',
'uri': '<playlist_uri>'
}

How can I set the list style to force prompts to be buttons?

I much prefer the look of buttons to a numbered list, but the default handling of prompt in waterfall dialog is to automatically change from buttons (default) to a numbered list after a certain length of content.
I'm currently implementing the prompt like this:
return await step.prompt(FOCUS_AREA_PROMPT, {
prompt: 'Got it. Can you confirm the focus area this is for?',
choices: ChoiceFactory.toChoices(FOCUS_AREAS)
});
I've tried adding a style attribute within this prompt, and also tried adding to the addDialogs line this.dialogs.add(new ChoicePrompt(FOCUS_AREA_PROMPT)); but nothing I have tried has modified the behavior of the options.
I've reviewed the ListStyle enum in the MS docs, but any method I've tried to add these in with has not made any difference. Is there any way to force buttons regardless of content length?
You can set up a choice prompt in the following manner to achieve the buttons look you are seeking. For reference, you can read more about forChannel here.
Alter to match your needs.
Hope of help!
[edit]
Updated below to represent the two ways a Choice Prompt can be assembled and how the value is output (via imBack). When using toChoices, the associated button value is returned in activity.text and in stepContext.result.value (as type Object). When using forChannel, the associated button value is returned in activity.text and in stepContext.result (as type String).
As discussed in the comments, the button title length has a character limit however this is channel specific. When testing in Web Chat, the limit is 20 characters. Adjusting the FOCUS_AREAS "AI & Machine Learning" value (21 chars) to "AI/Machine Learning" (19 chars) results in the choices displaying as buttons and not a list.
Option 1: using toChoices
async choiceStep ( stepContext ) {
const stepResult = stepContext.context.activity.text;
const FOCUS_AREAS = [ 'Chatbots', 'RPA', 'Blockchain', 'AR/VR', 'AI/Machine Learning' ]
if ( stepResult ) {
return await stepContext.prompt( CHOICE_PROMPT, {
prompt: 'Got it. Can you confirm the focus area this is for?',
choices: ChoiceFactory.toChoices( FOCUS_AREAS )
} );
}
}
activity:
{ type: 'message',
id: 'A50eelAPrFIHKv9XeCRm24-o|0000021',
timestamp: 2019-09-25T20:34:30.562Z,
serviceUrl: 'https://directline.botframework.com/',
channelId: 'directline',
from: [Object],
conversation: [Object],
recipient: [Object],
textFormat: 'plain',
locale: 'en-US',
text: 'Chatbots',
channelData: [Object] },
info:
{ index: 1,
options: {},
reason: 'endCalled',
result:
{ value: 'Chatbots', index: 0, score: 1, synonym: 'Chatbots' },
values: { instanceId: 'c10ed437-77eb-4502-cd24-e89d4c5e45cf' },
onNext: [AsyncFunction: onNext] }
Option 2: using forChannel
async choiceStep ( stepContext ) {
const stepResult = stepContext.context.activity.text;
if ( stepResult ) {
const message = ChoiceFactory.forChannel(
stepContext.context, [
{ value: 'Chatbots', action: { type: 'imBack', title: 'Chatbots', value: 'Chatbots' } },
{ value: 'RPA', action: { type: 'imBack', title: 'RPA', value: 'RPA' } },
{ value: 'Blockchain', action: { type: 'imBack', title: 'Blockchain', value: 'Blockchain' } },
{ value: 'AR/VR', action: { type: 'imBack', title: 'AR/VR', value: 'AR/VR' } },
{ value: 'AI/Machine Learning', action: { type: 'imBack', title: '', value: 'AI/Machine Learning' }, text: 'AI/Machine Learning' },
], `Which do you choose?`
);
await stepContext.context.sendActivity( message );
}
return { status: DialogTurnStatus.waiting };
}
activity:
{ type: 'message',
id: 'Cw5xvHTv6RCDWf3kkyS3Ir-o|0000205',
timestamp: 2019-09-25T20:21:30.320Z,
serviceUrl: 'https://directline.botframework.com/',
channelId: 'directline',
from: [Object],
conversation: [Object],
recipient: [Object],
textFormat: 'plain',
locale: 'en-US',
text: 'Chatbots',
channelData: [Object] },
info:
{ index: 1,
options: {},
reason: 'continueCalled',
result: 'Chatbots',
values: { instanceId: '4becefed-88d2-773e-6184-91456609a26a' },
onNext: [AsyncFunction: onNext] }

Returning wrong intent should be buy_food not hello

I'm new using node-nlp but according to the examples I saw and the data I provided I should get a buy_food intent. But I'm not, it's returning hello intent. Any suggestions? If I remove the
manager.addDocument('en', 'hi', 'hello') line, it returns the right intent.
Any suggestions?
const { NlpManager } = require('node-nlp');
const manager = new NlpManager({ languages: 'en', ner: { threshold: 0.8 } });
manager.addNamedEntityText('drink', 'sprite', ['en'], ['sprite']);
manager.addNamedEntityText('size', 'large', ['en'], ['large', 'big']);
manager.addDocument('en', 'hello', 'hello');
manager.addDocument('en', 'hi', 'hello');
manager.addDocument('en', '%size% %drink%', 'buy_food');
manager.addDocument('en', 'i want %drink% please', 'buy_food');
manager.addDocument('en', 'i want %size% %drink% please', 'buy_food');
manager.addDocument('en', 'i want a %drink% please', 'buy_food');
manager.addDocument('en', 'i want a %size% %drink% please', 'buy_food');
manager.addDocument('en', 'i want to order a %size% %drink%', 'buy_food');
manager.addDocument('en', 'i want to order a %drink%', 'buy_food');
manager.addDocument('en', 'bye', 'bye');
manager.addDocument('en', 'bye bye', 'bye');
manager.addAnswer('en', 'hello', 'Welcome! How may I help you?');
manager.addAnswer('en', 'bye', 'Till next time');
manager.addAnswer('en', 'bye', 'see you soon!');
manager.addAnswer('en', 'buy_food', 'Got it, what else?');
manager.addAnswer('en', 'buy_food', 'Ok. Next item.');
var text = 'i want a large sprite';
(async () => {
await manager.train();
var result= await manager.process('en', text);
console.log(result);
})();
The result is
{ utterance: 'i want a large sprite',
locale: 'en',
languageGuessed: false,
localeIso2: 'en',
language: 'English',
domain: 'default',
classifications:
[ { label: 'hello', value: 0.5484776863751949 },
{ label: 'buy_food', value: 0.36024868417489125 },
{ label: 'bye', value: 0.09127362944991388 } ],
intent: 'hello',
score: 0.5484776863751949,
entities:
[ { start: 9,
end: 13,
len: 5,
levenshtein: 0,
accuracy: 1,
option: 'large',
sourceText: 'large',
entity: 'size',
utteranceText: 'large' },
{ start: 15,
end: 20,
len: 6,
levenshtein: 0,
accuracy: 1,
option: 'sprite',
sourceText: 'sprite',
entity: 'drink',
utteranceText: 'sprite' } ],
sentiment:
{ score: 0.29699999999999993,
comparative: 0.05939999999999999,
vote: 'positive',
numWords: 5,
numHits: 6,
type: 'senticon',
language: 'en' },
actions: [],
srcAnswer: 'Welcome! How may I help you?',
answer: 'Welcome! How may I help you?' }

how to get the particular value from the response array received after wallet creation in bitgo Js

This is my array and want to get values from it:
{ wallet:
Wallet {
bitgo:
BitGo {
_baseUrl: 'https://test.bitgo.com',
env: 'test',
_baseApiUrl: 'https://test.bitgo.com/api/v1',
del: [Function] },
baseCoin:
BaseCoin {
network: [Object],
coinKeychains: [Keychains] },
_wallet:
{ id: '5b055bae749a9ba207410c65ff58c325',
users: [Array],
coin: 'tltc',
label: 'My Test Wallet',
m: 2,
n: 3,
pendingApprovals: [] } },
userKeychain:
{ id: '47i3ygdhfhdhjfj84378r'
users: [ '3w65gsdhfgshg93w2r2839'],
pub: 'zsnadfsheg94t854tidghfhdhgdhh kladjfghgdhfhdhfghdhhfghgh',
ethAddress: '0xzxder4tewre79618eceret3a2eabf6c8',
encryptedPrv: '{}',
prv: ' },
backupKeychain:
{ id: '54idhjfhdj9a9ba207410c505912c1b1',
users: [ '90jdfjgbja7007sdjbgjffc7a5065006' ],
pub: '',
ethAddress: '0x753ce0sdfvsdsgdf8baef8sdfesdfssfs2bdd8cb',
prv: '',
source: 'backup' },
bitgoKeychain:
{ id: '5b055bad39230ccc07814e0589388100',
users: [ '5ac1fd5563fa7007d5a17fc7a5065006' ],
pub: '',
ethAddress: '0xbfdgfdgdfdgfhfgtr6756ghfghfg719320fcc7e',
isBitGo: true },
}
// here want to get userKeychain, backupKeychain and bitgoKeychainPlease help how to fetch the this value. Is there any function to access this values or need any parsing method in bitgo Js.
You can get it using like : wallet.wallet._wallet.id
This will give you the wallet id.To get the userKeychain value use like wallet.userKeychain

Resources