I have a google actions app and I am trying to save a variable between conversations. However if the cancel intent is invoked (the user says quit, exit, cancel etc. invoking actions_intent_CANCEL event) this data does not persist to the next conversation. I also have a custom save intent which too exits the conversation but it works fine.
This does not work (intent with event actions_intent_CANCEL)
app.intent('Exit', (conv) => {
if (conv.user.verification === 'VERIFIED') {
conv.user.storage.avar = 'somevalue';
}
conv.close('Goodbye!');
});
but this works (custom intent with no event)
app.intent('Save', (conv) => {
if (conv.user.verification === 'VERIFIED') {
conv.user.storage.avar = 'somevalue';
}
conv.close('Goodbye!');
});
In both cases the conversation exits and in the response there is the correct value for the variable.
"userStorage": "{\"data\":{\"avar\":\"somevalue\"}}"
However when I activate my app again, if exited via the Exit intent the data is missing or wrong. Does anyone know if this is bug or am I doing something wrong?
Help is appreciated!
Related
Is there possible way to handle error that will return right back after error is triggered?
if(command === 'test') {
message.author.send('Dm Test').catch(error => {
message.reply('I failed to send you a private message!')
return;
})
//some code below, should not trigger after sending message error.
The problem is that .catch will respond as last, how to handle this error and immediately return instead of running also code below? I tried to use try { but that didn't work.
message.author.send('👌')
.catch(() => message.reply("Can't send DM to your user!"));
Would like to know if there is another way to handle error. Any help will be much appreciated.
The reason why .catch() executes after the rest of your code is because .send() is asynchronous and returns a Promise. Think of it this way: each time you send a message, discord.js has to send an HTTP request to the Discord API, wait for a response, and then give you the data from the response. That process is not instantaneous and takes some time, which is why using the Promise structure is very useful.
As for the specific problem, you simply want to await the response of your .catch(). This can be done by making the function you are running this code in async. Here is an example:
client.on("messageCreate", async (message) => {
let response = await message.author.send('👌').catch(() => {
message.reply("Can't send DM to your user!"));
return false;
});
// (If error occurred, 'response' will be false)
if (!response) return; // Return if the error occurred
// ... Code for if error did not occur
// (If no error occurred, 'response' will contain sent message)
});
The await keyword will wait until the asynchronous line you are executing has been fulfilled (i.e. until the return value has been obtained or an error has been caught) before continuing on with your code. Note the use of the async keyword. You can only use await within functions marked async.
If you place a return before sending the error message, JavaScript will read that as you're returning the message so if you do the following:
message.author.send('👌')
.catch(() => return message.reply("Can't send DM to your user!"));
you'll have the error message be the final command run and nothing else will follow.
You can use try inside of if & else statement to know if you can dm the person or not.
if(command === 'test') {
try {
message.author.send("Something right here")
} catch {
message.channel.send("I can't dm this person.")
} else {
//something exploration code here
}
}
I want to have dialog flow like this
step1 -> delay -> step2
async step1(stepContext) {
await stepContext.context.sendActivity('Give user some task to do');
return await stepContext.next();
}
async delay(stepContext) {
await stepContext.context.sendActivity({type: 'delay', value:5000});
return await stepContext.next();
}
async step2(stepContext) {}
The above code is not working as wanted. When I run the bot it waits for 5 seconds and then executes step1 and step2. I want the delay to be after step1.
Actually, I want the delay to be of 2 minutes. I was wondering that won't the bot go on sleep or something. I am sorry I am new to this.
I think the best way to accomplish this while using waterfall dialogs is going to be via the use of setTimeout and Proactive Messages. I'm going to make some assumptions here that you are familiar with things like Conversation State, Bot Framework Adapter, and Turn Handlers, but if you need further guidance on anything let me know.
First, foundationally you are going to need to capture the conversation reference in order to send the proactive message. I find this easiest to just do in your onTurn function and save it to conversation state every turn as follows:
const conversationData = await this.dialogState.get(context, {});
conversationData.conversationReference = TurnContext.getConversationReference(context.activity);
await this.conversationState.saveChanges(context);
Now, you can set up your dialog as follows. You could probably do this several different ways. I typically try to make each step a prompt, but you could probably switch these actions around and still make it work.
async step1(stepContext) {
// First give the user the task
await stepContext.context.sendActivity('Give user some task to do');
// Next set up a timeout interval. The action here is to send a proactive message.
const conversationData = await this.dialogState.get(stepContext.context, {});
this.inactivityTimer = setTimeout(async function(conversationReference) {
try {
const adapter = new BotFrameworkAdapter({
appId: process.env.microsoftAppID,
appPassword: process.env.microsoftAppPassword
});
await adapter.continueConversation(conversationReference, async turnContext => {
await turnContext.sendActivity('Were you able to successfully complete the task?');
});
} catch (error) {
//console.log('Bad Request. Please ensure your message contains the conversation reference and message text.');
console.log(error);
}
}, 120000, conversationData.conversationReference);
// Give the user a confirmation prompt
return await stepContext.prompt('choicePrompt', 'Please confirm the results of the task when you are finished',['Passed','Failed']);
}
async step2(stepContext) {
// Clear timeout to prevent the proactive message if user has already responded
clearTimeout(this.inactivityTimer);
/* REST OF YOUR DIALOG HERE */
return await stepContext.endDialog();
}
As mentioned, make sure you are importing your Bot Framework Adapter, setting up your prompt (I used choice but obviously it can be whatever), importing conversation state, etc. Also, one thing to be aware of is that if the user responds very close to the timer (2 minutes as I've set it up here), it is possible that the proactive message will be invoked before the bot reaches the clearTimeout statement. I think this would be an annoyance at most, but you would need to decide if that UX is ok based on how frequently someone would complete the task in near exactly 2 minutes.
One final note, you could probably put the proactive message invocation in a separate helper function, especially if you are going to use this in many different dialogs. That way you don't have to create multiple instances of the Adapter, in addition to making your code easier to update. That said, if you only need it in only place like I did, I find it much easier just to insert it into the dialog since it's not that much code.
I'm using node.js 8 runtime in my Google Cloud function, attached to my DialogFlow app (V2 API).
I can use conv.data to store temporary data within the current conversation. Unfortunately, conv.data does not seem to retain data after a followup intent.
For example, in my intent the following code:
conv.data.result = "Hello!";
console.log("[DEBUG] conv.data.result = "+conv.data.result);
conv.followup("customEvent1");
produces the following log:
[DEBUG] conv.data.result = Hello!
This is my followup intent:
app.intent('CUSTOM_EVENT_INTENT', (conv) => {
console.log("[DEBUG] - CUSTOM_EVENT_INTENT");
console.log("[DEBUG] - conv.data.result = "+conv.data.result);
if(!conv.data.result) {
console.log("[DEBUG] - I give up");
conv.close("Nessuna risposta");
}
else conv.ask(conv.data.result);
});
which produces the following log:
[DEBUG] - conv.data.result = undefined
[DEBUG] - I give up
Looks like I'm missing something very important in followup intents...
Thanks,
Roberto
I would suggest using context instead of data. Context has few more advantages over data objects. It can be accessible in multiple dialog and intents.
function getParamFromContext(key){
// get the value set earlier in the context
let globalContext = agent.context.get('global_main_context');
return globalContext.parameters[key];
}
function updateGlobalContext(agent, key, value) {
// create a global method to maintain all required data within conversation
let globalContext = agent.context.get('global_main_context');
let param = globalContext ? globalContext.parameters : {};
param[key] = value;
agent.context.set({
name: 'global_main_context',
lifespan: 5,
parameters: param
});
}
Call the method within a conversation with agent object.
in app.js
intentMap.set("Size Intent", setSize);
function setSize(){
let size = agent.parameters.size;
updateShippingObjectContext(agent, 'size', size);
}
I think you're talking about Followup Events rather than Followup Intents. Followup Intents are set as Intents that may be triggered after an Intent has by the user doing some action - this is done by setting a Context. Followup Events are set during fulfillment and are intended to trigger an Intent that has this Event set.
In most cases, you don't need to use Followup Events.
Instead - just call a function that does the processing and replies how you want it to do. There is nothing saying that your Handler function has to do everything itself - it can call a function just like any other function, and can call it with parameters.
So it is perfectly reasonable to have Intent Handlers like
app.intent('intent.one', (conv) => {
reply( conv, "Hello!" );
});
app.intent('intent.two', (conv) => {
reply( conv, "How are you?" );
});
app.intent('intent.quit', (conv) => {
reply( conv );
});
function reply( conv, msg ){
if( !msg ){
conv.close( "I give up!" );
} else {
cov.ask( msg );
}
}
That still doesn't explain why it didn't work.
What you are "missing" with how you use Followup Events is that using conv.followup() does not send anything that you may have sent back to Dialogflow when it redirects to the Event. As the documentation says:
Triggers an intent of your choosing by sending a followup event from the webhook. [...] Dialogflow will not pass anything back to Google Assistant, therefore Google Assistant specific information, most notably conv.user.storage, is ignored.
What you can do, however, is send parameters to the new Intent that is detected from the Event. Something like this might work:
const params = {
result: "Hello!"
};
conv.followup("customEvent1", params);
and then in the handler for the event's Intent:
app.intent('CUSTOM_EVENT_INTENT', (conv) => {
console.log("[DEBUG] - CUSTOM_EVENT_INTENT");
console.log("[DEBUG] - conv.parameters.result = "+conv.parameters.result);
if(!conv.parameters.result) {
console.log("[DEBUG] - I give up");
conv.close("Nessuna risposta");
}
else conv.ask(conv.parameters.result);
});
I've created a chatbot using Node.js and the flow of dialogs works fine til the endDialog. Im having issues implementing a back option so it can jump back only to previous dialog. Can anyone please guide me how to solve that?
.reloadAction(
"restartBenefits", "Ok. Let's start over.",
{
matches: /^start over$|^restart$/i
}
)
.cancelAction(
"cancelRequest", "Thank you for reaching out, Good bye!",
{
matches: /^nevermind$|^cancel$|^cancel.*request/i,
confirmPrompt: "This will cancel your request. Are you sure?"
}
);
Use a customAction
In this case you can listen for whatever you want to be a keyword for "back" and then simply route the user back to that dialog using replaceDialog
bot.customAction({
matches: /back|last/gi, //whatever prompts you want to match.
onSelectAction: (session, args, next) => {
session.replaceDialog(PreviousDialog); //variable with the last dialog in it, set that somewhere, such as the end of your previous dialog
}
})
I think that at the final step of the dialog waterfall you need to add the last lines in this sample step:
/**
* This is the final step in the main waterfall dialog.
* It wraps up the sample "book a flight" interaction with a simple confirmation.
*/
async finalStep(stepContext) {
// If the child dialog ("bookingDialog") was cancelled or the user failed to confirm, the Result here will be null.
if (stepContext.result) {
const result = stepContext.result;
// Now we have all the booking details.
// This is where calls to the booking AOU service or database would go.
// If the call to the booking service was successful tell the user.
const timeProperty = new TimexProperty(result.travelDate);
const travelDateMsg = timeProperty.toNaturalLanguage(new Date(Date.now()));
await stepContext.context.sendActivity(ActivityFactory.fromObject(this.lgTemplates.evaluate('BookingConfirmation', {
Destination: result.destination,
Origin: result.origin,
DateMessage: travelDateMsg
})));
}
// =====> HERE: Restart the main dialog with a different message the second time around
return await stepContext.replaceDialog(this.initialDialogId, { restartMsg: 'What else can I do for you?' });
}
Just change the this.initialDialogId accordingly.
Note
I am using Microsoft Botbuilder SDK in node.js using ES6 babel.
The Problem
I have a dialog, '/MainMenu', that prompts the user for a free-response reply in order to dive into another, more relevant dialog. I also, however, want the user to be able to trigger an action that is completely irrelevant to subject matter (i.e. a dialog asking the bot, "how are you?"), returning back to the original MainMenu dialog just as they left off. I understand that in the documentation for the SDK, onSelectAction can be used to put the triggered dialog on top of the stack rather than replacing the entire stack, but once the '/HowAreYou' dialog ends, the bot also thinks that response was for the initial MainMenu prompt, replying with "I didn't understand. Please try again," like so:
Code
// I am using the builder.Library routing standard, and have confirmed that
// this gets triggered as expected. this dialog exists in a different file
lib.dialog('/HowAreYou', [
(session, args, next) => {
session.send('I\'m doing well. Thanks for asking!');
builder.Prompts.text(session, 'How are you doing today?');
}, (session, results) => {
session.endDialog('Good to hear that!');
}
]).triggerAction({
matches: /^how are you?$/i,
onSelectAction: (session, args, next) => {
// Add the help dialog to the top of the dialog stack (override the
// default behavior of replacing the stack)
session.beginDialog(args.action, args);
}
});
bot.dialog('mainMenu', [
(session, args, next) => {
builder.Prompts.text(session, 'Hi there! What can I do for you today?');
},
(session, results) => {
session.endConversation('Goodbye!');
}
]).beginDialogAction('weatherAction', '/Weather', {
matches: /^weather$/i,
}).beginDialogAction('sportsAction', '/Sports', {
matches: /^sports$/i,
}).beginDialogAction('cookingAction', '/Cooking', {
matches: /^cooking$/i,
});
Desired Behavior
Although the current result is very close to the desired behavior, I ideally want the bot to reply with the same MainMenu prompt it began with, without saying it didn't understand after the HowAreYou dialog finishes.
The Question
Is this possible? If so, how? If not, what are alternatives?
Thank you for any help you can give.
There is an explain on GitHub at https://github.com/Microsoft/BotBuilder/issues/2421, with what we can realize that the Prompts are built-in dialog which will handle the input validation.
As you first step in the Prompts dialog in mainMenu, and then you trigger the HowAreYou dialog when the first Prompts dialog is waiting for the input text.
Then the HowAreYou end as session.endDialog('Good to hear that!'); without an result for the first Prompts dialog in mainMenu, which is failed in the validation.
the root cause should be equivalent to input an empty text for builder.Prompts.text(session, 'Hi there! What can I do for you today?');.
update
Found the promptAfterAction property of IPromptOptions for prompt dialog, and i think about this issue. I think this should be by design.
As the sentence I don't understand... is a default text for property retryPrompt. So when you end the HowAreYou dialog and back the to mainPage dialog stack. The prompt dialog will restart and send the retryPrompt to user, which raises your issue.
For the accessibility, you can try to use:
builder.Prompts.text(session, 'Hi there! What can I do for you today?', {
retryPrompt: 'Hi there! What can I do for you today?'
});