How to get previous intent in dialogflow - node.js

I am facing one issue in this:
I have created 4 intents bookTable, bookingDate, bookingTime and people count intent.i have added intent chaining using input and output context.
Suppose I am on third intent right now which is “Booking Time” & when I will say “Book A Table” to google home, it will directly jump to the first intent.
I need solution to not jump to the first intent but if user will say any except required input, then it will call custom fallback intent and stuck on that particular intent.
Please provide me the solution for not jumping the intent to any other..
// welcome intent here
app.intent('DefaultWelcomeIntent', (conv) => {
conv.ask('What would you like me to do for you?. Book a table or Check your calendar?');
});
// Booktable intent
app.intent('BookTable', (conv) => {
conv.ask('Please provide a date?');
});
// bookingDate intent
app.intent('bookingDate', (conv, params) => {
var speech = new Speech();
const currentDate = moment().format("YYYY-MM-DD");
const bookingDate = moment(params.date).format("YYYY-MM-DD");
const displayBookingDate = moment(bookingDate).format("DD MMM YYYY");
let say = '';
if (bookingDate && moment(bookingDate).isSameOrAfter(currentDate)) {
speech.say(`OK, ${displayBookingDate}`)
.pause('300ms')
.say(`Please provide time?`);
const speechOutput = speech.toObject();
conv.ask(speechOutput.speech);
conv.data.restaurantData.date = bookingDate;
} else {
say = `Please provide today's or upcoming date.`;
conv.ask(say);
}
});
// bookingTime intent
app.intent('bookingTime', (conv, params) => {
var speech = new Speech();
const currentDateTime = moment().format("YYYY-MM-DD HH:mm");
const bookingDate = moment(conv.data.restaurantData.date).format("YYYY-MM-DD");
const bookingTime = moment(params.time).format("HH:mm");
const booking12HourTimeString = moment(bookingTime, ["HH:mm"]).format("hh:mm A");
const concateDateTime = moment(bookingDate + " " + bookingTime).format('YYYY-MM-DD HH:mm');
let say = '';
if(moment(concateDateTime).isSameOrAfter(currentDateTime)) {
speech.say(`OK, ${booking12HourTimeString}`)
.pause('300ms')
.say(`How many people?`);
const speechOutput = speech.toObject();
conv.ask(speechOutput.speech);
conv.data.restaurantData.time = bookingTime;
} else {
say = `please provide a valid booking time.`;
conv.ask(say);
}
});
// people count intent
app.intent('peopleCount', (conv, params) => {
let peopleCounts = parseInt(params.count);
let restaurantName = 'Dominos pizza';
let restaurantAddress = 'San Francisco, ca';
let bookingDate = conv.data.restaurantData.date;
let bookingTime = conv.data.restaurantData.time;
const booking12HourTimeString = moment(bookingTime, ["HH:mm"]).format("hh:mm A");
let bookingDisplayDate = moment(bookingDate).format("DD MMM YYYY");
if (peopleCounts > 0) {
conv.ask(`OK You would like to create a table for ${restaurantName} in ${restaurantAddress} on ${bookingDisplayDate} at ${booking12HourTimeString} for ${peopleCounts} people`);
} else {
conv.ask(`Please add people more than zero`);
}
});
app.intent('customFallback', (conv, params) => {
conv.ask(`I couldn't understand. Can you say that again?`);
});

As per my understanding from your questions which is really vague, the values from the previous intents in the current intent can be get with the help of the contexts.
If still there is any place of ambiguity from this answer, it would be helpful if you provide some kind of examples.

you can not directly get the previous intent name however you can identify intent based upon the "conversationToken" as follows.
function getFeedbackFunc(agent){
...
let convTokens= request.body.originalDetectIntentRequest.payload.conversation.conversationTo
ken;
console log("Original Intent :"+convTokens);
...
}
I got the output as:["awaiting_feedback","customer_phone","awaiting_contact","customer_name","phonepair-no-followup"]
so based on the output the original intent is related to the context "phonepair-no-followup" and you can select your intent accordingly.
Please provide your feedback if it helps.

Your use case is not very clear, but as per my understanding what you really need is contexts or follow-up intents. Using these you can chain the intents.
If you still want to get previous intents name, i think you should store that in cache so that you can get that. There is no provision in dialogflow to get that.

Related

Intent Chaining in ASK game

I'm creating a game in ASK (Alexa Skills Kit) where a Alexa will ask the product between two random numbers in a range specified by the user. When the user answers, Alexa should tell them if the answer is correct or incorrect (UserAnswerIntentHandler), and then ask a new question (GamePlayIntentHandler).
const GamePlayIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'GamePlayIntent';
},
async handle(handlerInput){
const attributesManager = handlerInput.attributesManager;
const attributes = await attributesManager.getSessionAttributes() || {};
var lowestNum = attributes.lowestNum
var highestNum = attributes.highestNum
function getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1) + min)
}
var randomOne = getRandomIntInclusive(lowestNum, highestNum)
var randomTwo = getRandomIntInclusive(lowestNum, highestNum)
attributes.randomOne = randomOne
attributes.randomTwo = randomTwo
const speakOutput = `What is ${randomOne} times ${randomTwo}?` //Here, alexa is asking for the product between two random numbers in a range the user previously specified.
return handlerInput.responseBuilder
.addElicitSlotDirective('answer', {
name: 'UserAnswer',
confirmationStatus: 'NONE'
})
.speak(speakOutput)
.reprompt(`Sorry, I didn't get that. What is ${randomOne} times ${randomTwo}?`)
.getResponse();
}
}
const UserAnswerIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'UserAnswerIntent';
},
async handle(handlerInput) {
const {requestEnvelope} = handlerInput
const {intent} = requestEnvelope.request
const attributesManager = handlerInput.attributesManager;
const attributes = await attributesManager.getSessionAttributes() || {};
var userAnswer = parseInt(Alexa.getSlotValue(requestEnvelope, 'answer'))
const randomOne = attributes.randomOne
const randomTwo = attributes.randomTwo
var correctAnswer = randomOne * randomTwo
var points = 0
var speakOutput = ""
if (userAnswer > 0){
if (userAnswer === correctAnswer){
points +=1
speakOutput = `That's correct! You now have ${points} points.`
return handlerInput.responseBuilder
.speak(speakOutput)
.addDelegateDirective({
name: 'GamePlayIntent',
confirmationStatus: 'NONE'
})
.getResponse();
}//If the answer is correct, the user gets a point and Alexa should immediatley ask the next question
else{
speakOutput = `Your answer is incorrect. The correct answer is ${correctAnswer}. You currently have ${points} points.`
return handlerInput.responseBuilder
.speak(speakOutput)
.addDelegateDirective({
name: 'GamePlayIntent',
confirmationStatus: 'NONE'
})
.getResponse();
}//If the answer is incorrect, Alexa will give the correct answer and total points and immediatley go to the next question
}
}
}
What's happening right now is that Alexa isn't telling the user if their answer is correct or incorrect, and is immediately asking the next question.
Is there any way to fix this or create a loop such that as soon as the user answers, Alexa can evaluate the answer, and then immediately ask the next question?
When the GamePlayIntent is triggered, Alexa will ask a question then get the answer provided by the user.
The answer should then trigger UserAnswerIntent.
At this point, UserAnswerIntentHandler is triggered and goes to the handle(handlerInput) where you have your logic to verify the answer.
Once done, Alexa gives the correct answer then chain with the new question in the same message string.
You can build you string message by having one part for verifying the answer and a second part for the next question, you concatenate the strings together and return it in the same responseBuilder .speak(), followed by a repromt to make sure the user is giving an answer.
Once the user's input is captured, it will again triggered the corresponding intent to verify the new answer.
Another way would be to ask the user if they would like to have a new question through YesIntent/NoIntent.
If the answer is yes then you redirect them to the GamePlayIntent using
return GamePlayIntentHandler.handle(handlerInput);
If the answer is no then you add a goodbye message and close the session or so depending on the skill functionality/logic.
Here are some sample skills (1, 2) I found that you can get guided by, one includes questions/answers handled in a different way.

Helping Finding A friend bot

My friend has a discord server called Find A Friend and he wants me to code a bot to type a command to ask a series of questions and put it in a channel. People can see if anybody interests them and becomes friends with them. I have already coded part of it.
One of the questions it asks is: What games do you like?
My friend wants to make it able that the bot searches the other answers to this same question and dm the user if they have a game in common. I want to do the same for another question: What are some shows, movies, and anime that you like?
Here is my code for the series of questions:
const { Client, Message, MessageEmbed } = require("discord.js")
module.exports = {
name: "faf",
/**
* #param {Client} client
* #param {Message} message
* #param {String[]} args
*/
run: async (client, message, args) => {
message.delete()
const questions = [
"What is your usernames for any games you have including discord (is fine if you dont put them all but you MUST include your discord)",
"What is your age (not required)",
"What is your gender",
"Do you talk in Voicechannel or through text?",
"What games do you like?",
"What are some shows, movies, and anime that you like",
"Where are you from (not required)",
"Please state anything else you want to say"
]
let collectCounter = 0;
let endCounter = 0;
const filter = (m) => m.author.id === message.author.id;
const appStart = await message.author.send(questions[collectCounter++]);
const channel = appStart.channel;
const collector = channel.createMessageCollector(filter);
collector.on("collect", () => {
if(collectCounter < questions.length) {
channel.send(questions[collectCounter++])
} else {
channel.send('Your Find A Friend thread has been sent, Good Luck!')
collector.stop("fulfilled");
}
});
const appsChannel = client.channels.cache.get("831292911437086760");
collector.on('end', (collected, reason) => {
if(reason === 'fulfilled') {
let index = 1;
const mappedResponses = collected
.map((msg) => {
return `${index++}) ${questions[endCounter++]}\n-> ${msg.content}`;
}).join('\n\n')
appsChannel.send(
new MessageEmbed()
.setAuthor(
message.author.tag,
message.author.displayAvatarURL({ dynamic: true })
)
.setTitle('Friend Thread')
.setDescription(mappedResponses)
.setColor('RED')
.setTimestamp()
)
}
});
},
};
TL;DR I currently have a message collector bot that records responses to questions. How can I make the bot recognize when two people share common interests, such as games, movies, shows, or anime, based on their responses?

Discord.js AutoRole Reaction do nothing

my little problem is the next one :
When i react a message, my bot do nothing :D and it's not the objectives.
My events :
module.exports = async(client, messageReaction, user) => {
const message = messageReaction.message;
const member = message.guild.members.cache.get(user.id);
const emoji = messageReaction.emoji.name;
const channel = message.guild.channels.cache.find(c => c.id === '739163072064651296');
const lol = message.guild.roles.cache.get("763104201588473896"); //id role lol
const wz = message.guild.roles.cache.get("763104236119785584"); //id role wz
if (["lol", "wz"].includes(emoji) && message.channel.id === channel.id) {// lol = emoji name and wz too
switch (emoji) {
case "lol":
member.roles.add(lol);
message.channel.send('test')
break;
case "wz":
member.roles.add(wz);
message.channel.send('test')
break;
};
};
};
My events handler :
fs.readdir("./Events/",(error , f) => {
if(error) console.log(error);
console.log(`${f.length} events chargés`);
f.forEach((f) => {
const events = require(`./Events/${f}`);
const event = f.split(".")[0];
client.on(event, events.bind(null, client));
});
});
If someone can help it's perfect ! Thank you !
I tried to understand what you wanted to make but couln't understand you at all, your trying to make a autoRole sytem that listen's to reactions? AutoRole is supposed to be in a guildMemberAdd event, so if a member join's, you add a role automatically, but from the code you provided, it looks like it listen's to reactions on some message? If your trying to make a Reaction Role system, then follow this. Other than that, i can't help you.
If your trying to make a autoRole system follow this
From what i know, when you add a role to someone, you will have to do the following
member.roles.add("roleid")
But what your doing is basically this:
member.roles.add(message.guild.roles.cache.get("763104201588473896")) //as lol is defined like that
So, to fix it, instead of getting the role object, then trying to add the role to the user, do this, and be sure to make the file name "guildMemberAdd.js":
module.exports = async(client, member) => {
const lol = "763104201588473896" //id role lol
const wz = "763104236119785584"; //id role wz
member.roles.add(lol);
member.roles.add(wz);
};

Botframework with NodeJS while prompt system doesn't read what user input the value

Azure BotFramework
SDK4
NodeJS
In dialog state I am using 'return await step.prompt' inside async function. But once user enters the value it is not considering the user input value as a input for prompt instead it is going to luisrecognizer for the match.
I have written the similar code for different dialog where it works fine.
Kindly requesting you provide some valuable input.
Azure BotFramework
SDK4
NodeJS
this.addDialog(new WaterfallDialog('OrderStatusDialog', [
async function(step) {
return await step.context.sendActivity(reply);
},
async function(step) {
var cardValue = step.context.activity.value;
if (cardValue) {
if (cardValue.action == "Order_Number") {
return await step.prompt('textPrompt', 'enter order number');
} else if (cardValue.action == "Serial_Number") {
return await step.prompt('textPrompt', 'enter serial number');
} else {
}
}
return await step.next();
// return await step.endDialog();
},
async function (step) {
var cardValue = step.context.activity;
console.log("****** cardValue");
console.log(cardValue);
console.log("****** step After");
console.log(step);
return await step.endDialog();
}
]));
at prompt step it should read the value what user is entering. Also stack is empty when i console the step ..
Unfortunately, you have a few issues with your code. But, hopefully this will help iron them out. I had no issues running the below code.
One, double check which version of Botbuilder you have installed. At least one call ('this.addDialog') I am unfamiliar with, doesn't appear to be a part of the current SDK, and didn't work for me when testing.
Two, configure your bot like below. Technically, you should be able pass the various steps in as you have them in your question. However, something about yours was not work and I couldn't figure out what. The below setup DOES work, however, and conforms to already accepted practices.
Three, your first step calls 'step.context.sendActivity(reply)'. In the next step you are trying to read the returned value of that call. That isn't going to work as sendActivity simply sends a statement from the bot to the user ("Welcome to my bot!", for example). You need to perform a prompt to capture the user's input response (see below).
It looks like you are trying to read a value from a card. Since you didn't supply that bit of code, I faked the value by supplying the 'Order_Number' and 'Serial_Number' via a text prompt from the user.
You should also take advantage of the bot state options. Rather than use the variable 'cardValue', consider using UserState or DialogState to store user values important to the conversation.
Lastly, in this simple example, order and serial numbers will overwrite each other if you pass thru multiple times.
const START_DIALOG = 'starting_dialog';
const DIALOG_STATE_PROPERTY = 'dialogState';
const USER_PROFILE_PROPERTY = 'user';
const ACTION_PROMPT = 'action_prompt';
const ORDER_PROMPT= 'order_prompt';
const SERIAL_PROMPT= 'serial_prompt';
...
class ExampleBot {
constructor(conversationState, userState) {
this.conversationState = conversationState;
this.userState = userState;
this.dialogState = this.conversationState.createProperty(DIALOG_STATE_PROPERTY);
this.userData = this.userState.createProperty(USER_PROFILE_PROPERTY);
this.dialogs = new DialogSet(this.dialogState);
this.dialogs
.add(new TextPrompt(ACTION_PROMPT))
.add(new TextPrompt(ORDER_PROMPT))
.add(new TextPrompt(SERIAL_PROMPT));
this.dialogs.add(new WaterfallDialog(START_DIALOG, [
this.promptForAction.bind(this),
this.promptForNumber.bind(this),
this.consoleLogResult.bind(this)
]));
}
async promptForAction(step) {
return await step.prompt(ACTION_PROMPT, `Action?`,
{
retryPrompt: 'Try again. Action??'
}
);
}
async promptForNumber(step) {
const user = await this.userData.get(step.context, {});
user.action = step.result;
if (user) {
if (user.action == 'Order_Number') {
return await step.prompt(ORDER_PROMPT, 'enter order number');
} else if (user.action == 'Serial_Number') {
return await step.prompt(SERIAL_PROMPT, 'enter serial number');
} else {
}
}
return await step.next();
}
async consoleLogResult(step) {
const user = await this.userData.get(step.context, {});
user.orderNumber = step.result;
console.log('****** cardValue');
console.log(user);
console.log('****** step After');
console.log(step);
return await step.endDialog();
}
async onTurn(turnContext) {
... //Other code
// Save changes to the user state.
await this.userState.saveChanges(turnContext);
// End this turn by saving changes to the conversation state.
await this.conversationState.saveChanges(turnContext);
}
}
Hope of help!
somehow it worked for me after saving conversation state where ever i am replacing the dialog.
await this.conversationState.saveChanges(context);

How to get current intent's name in Dialogflow fulfillment?

I want to get the name of the current intent in the fulfillment so I can deal with different response depending on different intent i'm at. But I cannot find a function for it.
function getDateAndTime(agent) {
date = agent.parameters.date;
time = agent.parameters.time;
// Is there any function like this to help me get current intent's name?
const intent = agent.getIntent();
}
// I have two intents are calling the same function getDateAndTime()
intentMap.set('Start Booking - get date and time', getDateAndTime);
intentMap.set('Start Cancelling - get date and time', getDateAndTime);
There is nothing magical or special about using the intentMap or creating a single Intent Handler per intent. All the handleRequest() function does is look at action.intent to get the Intent name, get the handler with that name from the map, call it, and possibly dealing with the Promise that it returns.
But if you're going to violate the convention, you should have a very good reason for doing so. Having a single Intent Handler per Intent makes it very clear what code is being executed for each matched Intent, and that makes your code easier to maintain.
It looks like your reason for wanting to do this is because there is significant duplicate code between the two handlers. In your example, this is getting the date and time parameters, but it could be many more things as well.
If this is true, do what programmers have been doing for decades: push these tasks to a function that can be called from each handler. So your examples might look something like this:
function getParameters( agent ){
return {
date: agent.parameters.date,
time: agent.parameters.time
}
}
function bookingHandler( agent ){
const {date, time} = getParameters( agent );
// Then do the stuff that uses the date and time to book the appointment
// and send an appropriate reply
}
function cancelHandler( agent ){
const {date, time} = getParameters( agent );
// Similarly, cancel things and reply as appropriate
}
intentMap.set( 'Start Booking', bookingHandler );
intentMap.set( 'Cancel Booking', cancelHandler );
request.body.queryResult.intent.displayName will give the the intent name.
'use strict';
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
function getDateAndTime(agent) {
// here you will get intent name
const intent = request.body.queryResult.intent.displayName;
if (intent == 'Start Booking - get date and time') {
agent.add('booking intent');
} else if (intent == 'Start Cancelling - get date and time'){
agent.add('cancelling intent');
}
}
let intentMap = new Map();
intentMap.set('Start Booking - get date and time', getDateAndTime);
intentMap.set('Start Cancelling - get date and time', getDateAndTime);
agent.handleRequest(intentMap);
});
But it would made more sense if you use two different functions in intentMap.set
you can try using "agent.intent" but it doesn't make sense to use the same function for two different intents.

Resources