how to extract parameters from the context for list response using dialogflow fulfillment - node.js

Is it possible to pass the value of selected list item to any other intent. in my case I am passing a list and user select an item from the list, now I want to show this selected item name in another intent but I can't do that.
is anything wrong with my code
code runs properly only problem is instead of "selected item name" it returns "undefined".
this is my code
'use strict';
const functions = require('firebase-functions');
const {dialogflow, SimpleResponse} = require ('actions-on-google');
const {Suggestions, List, Image, BasicCard} = require ('actions-on-google');
const SHOW_PHONE_INTENT = 'Default Welcome Intent';
const FALLBACK_INTENT = 'Default Fallback Intent';
const SELECTED_PHONE_INTENT = 'SelectedPhoneIntent';
const ADD_TO_CART_INTENT = 'AddToCartIntent';
const AppContexts = {AWAITING_PHONE: 'awaiting-phone'};
const AppContexts1 = {AWAITING_REPLY: 'awaiting-reply'};
const app = dialogflow();
const PhoneDetail = {
'Phone1': {
text: `screen size = 5 inches \n
price = $100`,
subtitle: 'This is phone1',
title: 'Phone1 Details',
image: new Image({
url: 'https://img.icons8.com/plasticine/2x/name.png',
alt: 'pic1',
}),
display: 'WHITE',
};
'Phone2': {
text: `screen size = 5.5 inches \n
price = $150`,
subtitle: 'This is phone2',
title: 'Phone2 Details',
image: new Image({
url: 'https://img.icons8.com/plasticine/2x/name.png',
alt: 'pic2',
}),
display: 'WHITE',
};
'Phone3': {
text: `screen size = 6 inches \n
price = $200`,
subtitle: 'This is phone3',
title: 'Phone3 Details',
image: new Image({
url: 'https://img.icons8.com/plasticine/2x/name.png',
alt: 'pic3',
}),
display: 'WHITE',
};
};
app.intent(FALLBACK_INTENT, (conv) => {
conv.ask("Sorry! Could you please repeat that?");
});
app.intent(SHOW_PHONE_INTENT, (conv) => {
conv.contexts.set(AppContexts.AWAITING_PHONE, 1);
conv.ask("Here's the list of phone's.");
conv.ask(new List({
title: "Select a phone to see details.",
items: {
"Phone1": {
title: "phone1",
description: "Click here to check phone1 details.",
image: new Image({
url: 'https://img.icons8.com/plasticine/2x/name.png',
alt: 'p1',
}),
},
"Phone2": {
title: "phone2",
description: "Click here to check phone2 details.",
image: new Image({
url: 'https://img.icons8.com/plasticine/2x/name.png',
alt: 'plc',
}),
},
"Phone3": {
title: "phone3",
description: "Click here to check phone3 details.",
image: new Image({
url: 'https://img.icons8.com/plasticine/2x/name.png',
alt: 'obj',
}),
},
},
}));
});
app.intent(SELECTED_PHONE_INTENT, (conv, input, option) => {
const context = conv.contexts.get(AppContexts.AWAITING_PHONE);
if (option) {
conv.ask(`${option} Details`);
conv.ask(new BasicCard(PhoneDetail[option]));
conv.ask(new Suggestions(['Show List', 'Add to Cart']));
} else {
conv.close('Sorry! there might be some issue, please contact support.');
}
conv.contexts.set(AppContexts1.AWAITING_REPLY, 1);
});
app.intent(ADD_TO_CART_INTENT, (conv, parameters) => {
const context1 = conv.contexts.get(AppContexts1.AWAITING_REPLY);
const selectedPhone = context1.parameters;
const qty = context1.parameters.qty;
if ('Add to Cart'){
let missingSlots = [];
if (!qty) { missingSlots.push('qty'); }
if (missingSlots.length === 1){
conv.ask(`How many phone's do you need?`);
} else {
conv.ask(`You have ordered ${qty} ${selectedPhone}. `);
conv.close("Thanks for shopping with us.");
}
} else {
conv.close('Sorry! there might be some issue, please contact support.');
}
});
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);
let suppose user selects phone1 and orders 2 qty
then my response comes as "You have ordered 2 undefined. Thanks for shopping with us."
need help in getting the selected item name instead of undefined.
Here is the Intent that handles selecting the item from the list:

The issue is that you're not actually setting the parameters in the Context, so no values are preserved between the calls to your webhook.
In the SELECTED_PHONE_INTENT handler, the line should be something more like
conv.contexts.set(AppContexts1.AWAITING_REPLY, 5, {
phone: option
});
while in the ADD_TO_CART_INTENT handler, you would get this information with a line such as
const selectedPhone = context1.parameters.phone;

Related

Can we update a Netsuite "Custom Record type" field by clicking a button?

I have a Suitelet script to create a form, there I pull the information contained in a "Custom Record type" as the default value for the form fields.
Also create a ClientScript for the Suitelet form with the functions that are executed by clicking a button.
The function works, but I don't know how to update the information of the custom record type.
Here is part of my code:
Suitelet:
/**
*#NApiVersion 2.0
*#NScriptType Suitelet
*/
define(['N/record', 'N/search', 'N/ui/serverWidget'],
function(record, search, serverWidget) {
function onRequest(context) {
try {
if (context.request.method === 'GET') {
var form = serverWidget.createForm({
title: 'SKU Information'
});
form.clientScriptFileId = 3060;
//create fields groups to organize the fields
var itemgroup = form.addFieldGroup({
id : 'itemgroup',
label : 'Next item SKU Number'
});
var usegroup = form.addFieldGroup({
id : 'usegroup',
label : 'Who is using this?'
});
//Add the fields
var skufield = form.addField({
id: 'skufield',
type: serverWidget.FieldType.TEXT,
label: 'SKU Number',
container: 'itemgroup'
});
skufield.isMandatory = true;
// create a field with the user using this numbers when clicking the button
var whousingskunumber = form.addField({
id: 'namefield',
type: serverWidget.FieldType.TEXT,
label: 'User:',
container: 'usegroup'
});
whousingskunumber.updateDisplayType({
displayType: serverWidget.FieldDisplayType.DISABLED
});
var usingsince = form.addField({
id: 'sincefield',
type: serverWidget.FieldType.TEXT,
label: 'Using since',
container: 'usegroup'
});
usingsince.updateDisplayType({
displayType: serverWidget.FieldDisplayType.DISABLED
});
// Add the buttons
form.addSubmitButton({
label: 'Update Number'
});
form.addResetButton({
label: 'Cancel'
});
var useNumber = form.addButton({
label: 'Use this number',
id: 'useNumber',
functionName: 'useNumberFunction',
});
var releaseNumber = form.addButton({
label: 'Release usage',
id: 'relaseNumber',
functionName: 'relaseNumberFunction',
});
context.response.writePage(form);
} else {
// Section Four - Output - Used in all sections
var delimiter = /\u0001/;
var skuField = context.request.parameters.skufield;
var whoField = context.request.parameters.whofield;
var ccField = context.request.parameters.cctypefield;
context.response.write('You have entered:'
+ '<br/> New SKU Number: '+ whoField + skuField);
}
var skuNumObj = search.lookupFields({
type: 'customrecordere_lw_lastskunum',
id: 2,
columns: ['custrecord_ere_lw_usingsince', 'custrecordere_lw_nextskunum', 'custrecord_ere_lw_whousingskunum']
});
var usingSince = skuNumObj.custrecord_ere_lw_usingsince;
var whoIsUsing = skuNumObj.custrecord_ere_lw_whousingskunum;
var nextSku = skuNumObj.custrecordere_lw_nextskunum;
skufield.defaultValue = nextSku;
whousingskunumber.defaultValue = 'Nobody';
usingsince.defaultValue = 'You can use it!';
} catch (error) {
log.error({title: 'Failed to get items to update', details: error});
}
}
return {
onRequest: onRequest
};
});
ClientScript
/**
* #NApiVersion 2.x
* #NScriptType ClientScript
*/
//Noslen Pena
define(['N/runtime', 'N/currentRecord', 'N/ui/dialog', 'N/search'],
function (runtime, currentRecord, dialog, search) {
function pageInit(context){
try {
} catch (error) {
log.error({title: 'Failed initializing', details: error});
}
}
function useNumberFunction(){
try {
var skuNumObj = search.lookupFields({
type: 'customrecordere_lw_lastskunum',
id: 2,
columns: ['custrecord_ere_lw_usingsince', 'custrecordere_lw_nextskunum', 'custrecord_ere_lw_whousingskunum']
});
var nextSku = skuNumObj.custrecordere_lw_nextskunum[0].value;
skuNumObj.custrecordere_lw_nextskunum[0].value = Number(nextSku)+1;
dialog.alert({
title: "Information",
message: 'Remember to unlock after you have used'
});
context.skufield.updateDisplayType({
displayType: serverWidget.FieldDisplayType.DISABLED
});
} catch (error) {
log.error({title: 'Failed clicking the button', details: error});
}
}
function relaseNumberFunction(){
dialog.alert({
title: "Thank you!",
message: "Remember to update the number and save the changes"
});
record.namefield.setValue('Nobody');
log.debug({title: 'liberaron el uso', details:'Nobody is using the number now'})
}
return {
pageInit : pageInit,
useNumberFunction : useNumberFunction,
relaseNumberFunction : relaseNumberFunction
};
});
I figured it out, The way to save the changes or submit a new value to the custom record type is like this:
var id = record.submitFields({
type: 'customrecordere_lw_lastskunum',
id: 2,
values: {
custrecord_ere_lw_usingsince : since,
custrecord_ere_lw_whousingskunum : username
},
options: {
enableSourcing: false,
ignoreMandatoryFields : true
}
});

Bot framework Hero card without image parameter

How to avoid the image parameter in Hero card bot framework so that I can display only the options.
following code is not working, only BotFramework Hero Card title is diplaying
createHeroCard() {
return CardFactory.heroCard(
'BotFramework Hero Card',
CardFactory.actions([
{
type: 'imBack',
title: 'ABC',
value: 'ABC'
},
{
type: 'imBack',
title: 'DATA',
value: 'DATA'
},
])
);
}
I am noob at node.js I did same on C# which worked as expected. You could try converting the same. Here is the code snippet for your assistance.
public IMessageActivity YesCreateSolutionFlow()
{
try
{
var yesNoActivity = Activity.CreateMessageActivity();
var buttonList = new List<CardAction>();
//Btn 1
buttonList.Add(
new CardAction()
{
Value = "https://partner.microsoft.com/en-US/solutions/my-solutions/create-solution",
Type = "openUrl",
Title = "Yes, please"
});
//Btn 2
buttonList.Add(
new CardAction()
{
Value = "I Would like a tutorial first",
Type = "imBack",
Title = "I Would like a tutorial first"
});
var responseCard = new HeroCard()
{
Text = "",
Subtitle = string.Empty,
Buttons = buttonList
};
// Create the attachment.
var attachment = responseCard.ToAttachment();
yesNoActivity.Attachments.Add(attachment);
yesNoActivity.AttachmentLayout = AttachmentLayoutTypes.Carousel;
return yesNoActivity;
}
catch (Exception ex)
{
throw new NotImplementedException(ex.Message, ex.InnerException);
}
}
Finally, pass this card on turnContext.SendActivityAsync like below:
var yesCreateSolutionFlow= _customFlowRepository.YesCreateSolutionFlow();
await turnContext.SendActivityAsync(yesCreateSolutionFlow).ConfigureAwait(false);
Bot Conversation Output:
Hope that would help.
CardFactory.heroCard is expecting 3 or 4 parameters, You have only provided 2, so I believe the method is trying to interpret your buttons as images. if you pass an empty array as your second argument, I believe it will work as expected i.e.
createHeroCard() {
return CardFactory.heroCard(
'BotFramework Hero Card',
[],
CardFactory.actions([
{
type: 'imBack',
title: 'ABC',
value: 'ABC'
},
{
type: 'imBack',
title: 'DATA',
value: 'DATA'
},
])
);
}
If you don't want a title at all, you can just pass a blank string (i.e. '') as the first argument.

How to set output context for list options in dialogflow?

How do I give an output context to each item of a list ?
I am having trouble with handling the list options because the output context is not there.
For example in this code :
const meditatetrackList = () => {
const list = new List({
title: 'Choose a track.',
items: {
'healing water': {
title: 'Healing Water',
synonyms: ['healing water'],
image: new Image({
url: 'http://www.vstplanet.com/News/2016/Nature-sounds/Relaxing-nature-sounds.jpg',
alt: 'healing water',
}),
},
'elven forest': {
title: 'Elven Forest',
synonyms: ['elven' , 'forest' , 'elven forest'],
image: new Image({
url: 'https://scx2.b-cdn.net/gfx/news/2018/europeslostf.jpg',
alt: 'elven forest',
}),
},
'warm light' : {
title : 'Warm Light',
synonyms: ['warm','light','warm light'],
image: new Image({
url: 'https://www.socwall.com/images/wallpapers/37753-2000x1300.jpg',
alt: 'warm light',
}),
}
}
});
return list;
};
Okay, I am late here but I recently did this.
In this example code, I am returning a dropoff list of items
Util.js
getDropOffListCard: function(nearestDropOffResponse, agent) {
let objItem = {};
for (let index = 0; index < nearestDropOffResponse.length; index++) {
const element = nearestDropOffResponse[index];
let key = element.name.toUpperCase().replace(/ /g, "_");
let each = {
synonyms: [
'synonym 1',
'synonym 2',
'synonym 3',
],
title: element.name,
description: element.address + '\n' + element.distance + ' ' + element.unit,
image: new Image({
url: url + 'info-icon.png',
alt: 'Image alternate text',
})
};
objItem[key] = each;
}
agent.context.set({ // here set the context
name: 'global_context',
lifespan: 2,
parameters: {
dropLocationList: objItem
}
});
return new List({
title: 'Nearest drop off location',
items: objItem
});
}
app.js
intentMap.set("Nearest Drop Off Intent - yes", function(agent){
const conv = agent.conv();
conv.ask('Here are nearest drop off location. Where you can drop or pickup your parcel.');
conv.ask(trackingUtil.getDropOffListCard(nearestDropOffResponse, agent));
agent.add(conv);
});
Create another intent to handle this and add the event Google Assistant Option this event do the trick, it will send the selected option to your intent handler, where you can access the selected option as well as a list that we set in context.
intentMap.set("Nearest Drop Off Intent - yes - selected", function(agent){
const option_intent = agent.context.get('actions_intent_option');
const option_key = option_intent.parameters.OPTION;
const global_context = agent.context.get('global_context');
const selection = global_context.parameters.dropLocationList[option_key]
agent.add(selection.title);
agent.add(selection.description);
agent.add(trackingUtil.dropOffDetailCard(selection))
});

Microsoft Bot Framework (v4): How to make hero card list as prompt choice

I'm trying to create a prompt dialog with hero card list as the choices.
I've created a function that will return the herocard list and use it as the dialog prompt choice.
How can i achieved this? or there is a better way to implement it.
Note: I need to put it in the dialog prompt because I need to implement a sequential conversation. I also put the herocard list in a separate function because I will use it in other dialog prompt.
async selectProduct(stepContext){
return await stepContext.prompt(CHOICE_PROMPT, {
prompt: 'Select Product:',
choices: this.productChoices()
});
}
productChoices(){
const productSeriesOptions = [
CardFactory.heroCard(
'Title 1',
CardFactory.images(['image URL 1']),
CardFactory.actions([
{
type: ActionTypes.ImBack,
title: 'Title 1',
value: 'Value 1'
}
])
),
CardFactory.heroCard(
'Title 2',
CardFactory.images(['image URL 2']),
CardFactory.actions([
{
type: ActionTypes.ImBack,
title: 'Title 2',
value: 'Value 2'
}
])
)
];
return productSeriesOptions;
}
I've included a sample Dialog that demonstrates presenting a carousel of HeroCards to the user (below). The HeroCard has a single button that when clicked results in the next Waterfall step being run.
I originally pulled this dialog from the 'using-cards' sample. So if you wanted to give it a run, you could replace the mainDialog.js in that project and run it in the emulator.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { ActionTypes, AttachmentLayoutTypes, CardFactory } = require('botbuilder');
const { ChoicePrompt, ComponentDialog, DialogSet, DialogTurnStatus, WaterfallDialog, ChoiceFactory } = require('botbuilder-dialogs');
const MAIN_WATERFALL_DIALOG = 'mainWaterfallDialog';
class MainDialog extends ComponentDialog {
constructor() {
super('MainDialog');
// Define the main dialog and its related components.
this.addDialog(new WaterfallDialog(MAIN_WATERFALL_DIALOG, [
this.showProductChoicesStep.bind(this),
this.showCardSelectionStep.bind(this)
]));
// The initial child Dialog to run.
this.initialDialogId = MAIN_WATERFALL_DIALOG;
}
/**
* The run method handles the incoming activity (in the form of a TurnContext) and passes it through the dialog system.
* If no dialog is active, it will start the default dialog.
* #param {*} turnContext
* #param {*} accessor
*/
async run(turnContext, accessor) {
const dialogSet = new DialogSet(accessor);
dialogSet.add(this);
const dialogContext = await dialogSet.createContext(turnContext);
const results = await dialogContext.continueDialog();
if (results.status === DialogTurnStatus.empty) {
await dialogContext.beginDialog(this.id);
}
}
/**
* Send a carousel of HeroCards for the user to pick from.
* #param {WaterfallStepContext} stepContext
*/
async showProductChoicesStep(stepContext) {
console.log('MainDialog.showProductChoicesStep');
await stepContext.context.sendActivity({
attachments: this.productChoices(),
attachmentLayout: AttachmentLayoutTypes.Carousel
});
return { status: DialogTurnStatus.waiting };
}
async showCardSelectionStep(stepContext) {
console.log('MainDialog.showCardSelectionStep');
await stepContext.context.sendActivity('You picked ' + stepContext.context.activity.value);
// Give the user instructions about what to do next
await stepContext.context.sendActivity('Type anything to see another card.');
return await stepContext.endDialog();
}
// ======================================
// Helper functions used to create cards.
// ======================================
productChoices(){
const productSeriesOptions = [
CardFactory.heroCard(
'Product 1',
CardFactory.images(['https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg']),
CardFactory.actions([
{
type: 'messageBack',
title: 'Pick Me',
value: 'product1'
}
])
),
CardFactory.heroCard(
'Product 2',
CardFactory.images(['https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg']),
CardFactory.actions([
{
type: 'messageBack',
title: 'Pick Me',
value: 'product2'
}
])
),
CardFactory.heroCard(
'Product 3',
CardFactory.images(['https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg']),
CardFactory.actions([
{
type: 'messageBack',
title: 'Pick Me',
value: 'product3'
}
])
),
CardFactory.heroCard(
'Product 4',
CardFactory.images(['https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg']),
CardFactory.actions([
{
type: 'messageBack',
title: 'Pick Me',
value: 'product4'
}
])
)
];
return productSeriesOptions;
}
}
module.exports.MainDialog = MainDialog;

Not getting the items in Carousel in Actions on Google

app.intent('Our Events', (conv) =>{
if (!conv.surface.capabilities.has('actions.capability.SCREEN_OUTPUT')) {
conv.ask('Sorry, try this on a screen device or select the ' +
'phone surface in the simulator.');
return;
}
conv.ask(getdata());
conv.ask('Events are here!');
});
function getdata()
{
let carouselItems ={};
var events = db.collection('events').get().then(snapshot =>{
snapshot.forEach(doc=>{
let itemdetails ={
title:doc.data().title,
description:doc.data().info,
image: new Image({
url:doc.data().image_url,
alt:'Image',
}),
};
carouselItems[doc.id]=itemdetails;
console.log(carouselItems[doc.id]);
});
});
return new Carousel({
title:'Our Events',
items: carouselItems,
});
}
Carousels are visual selection responses and they need to a unique key to be recognized. So you have to give selection key to all carousel item objects. In your case it seems you add objects without key to array.
Please check the sample below and try to create objects according to structure.
items: {
// Add the first item to the carousel
'SELECTION_KEY_ONE': {
synonyms: [
'synonym 1',
'synonym 2',
'synonym 3',
],
title: 'Title of First Carousel Item',
description: 'This is a description of a carousel item.',
image: new Image({
url: 'IMG_URL_AOG.com',
alt: 'Image alternate text',
}),
},
// Add the second item to the carousel
'SELECTION_KEY_GOOGLE_HOME': {
synonyms: [
'Google Home Assistant',
'Assistant on the Google Home',
],
title: 'Google Home',
description: 'Google Home is a voice-activated speaker powered by ' +
'the Google Assistant.',
image: new Image({
url: 'IMG_URL_GOOGLE_HOME.com',
alt: 'Google Home',
}),
},
// Add third item to the carousel
'SELECTION_KEY_GOOGLE_PIXEL': {
synonyms: [
'Google Pixel XL',
'Pixel',
'Pixel XL',
],
title: 'Google Pixel',
description: 'Pixel. Phone by Google.',
image: new Image({
url: 'IMG_URL_GOOGLE_PIXEL.com',
alt: 'Google Pixel',
}),
},
},
}));
Your function needs to be rewritten so that your intent handler knows the operation is asynchronous. Note the addition of async and await keywords which will inform the interpreter when a given command is complete.
app.intent('Our Events', async (conv) =>{
if (!conv.surface.capabilities.has('actions.capability.SCREEN_OUTPUT')) {
conv.ask('Sorry, try this on a screen device or select the ' +
'phone surface in the simulator.');
return;
}
conv.ask(await getdata());
conv.ask('Events are here!');
});
async function getdata() {
let carouselItems ={};
const snapshot = db.collection('events').get()
snapshot.forEach(doc=>{
let itemdetails ={
title:doc.data().title,
description:doc.data().info,
image: new Image({
url:doc.data().image_url,
alt:'Image',
}),
};
carouselItems[doc.id]=itemdetails;
console.log(carouselItems[doc.id]);
});
return new Carousel({
title:'Our Events',
items: carouselItems,
});
}

Resources