Waterfall prompt using activity.text instead of actually prompting user - node.js

I have a dialog where a prompt in the first step is getting skipped. I determined that what is happening is that somehow step.context.activity.text is automatically being interpreted as the prompt response. For example, if I trigger the dialog with "Expedite my order", step.context.activity.text in the first step is "Expedite my order", the prompt is skipped, and step.result in the next step is "Expedite my order".
I tried creating a "buffer step" as step 1 that just did return await step.next(), but the same activity.text was then captured as the prompt response in step 2!
Interestingly, I created a mock dialog test with mocha and it did NOT exhibit these issues. I have other dialogs within the bot that also have a text prompt on step one and they are not exhibiting these issues either in mocha tests OR running in emulator (or deployed on Azure). I did note there is one difference within step.context: the dialogs that are working have step.context.responded = false and the one which is skipping the prompt has step.context.responded = true`. But I have no idea why this is set in one case but not others. This seems to be an important part of the puzzle.
I did "solve" this by setting step.context.activity.text = '' at the beginning of the step, but that seems like a bad practice. Any idea on why this is happening within this prompt? Below is my dialog code through that first step.
const { TextPrompt, ChoicePrompt, ChoiceFactory, ComponentDialog, WaterfallDialog } = require('botbuilder-dialogs');
const { oemLocatorHelper } = require('../helpers/oemLocatorHelper');
const WATERFALL_DIALOG = 'waterfallDialog2';
const CHOICE_PROMPT = 'choicePrompt';
const TEXT_PROMPT = 'textPrompt';
const ESCALATION_OPTIONS = ['Chat','Email','Call','No Thanks'];
class escalationDialog extends ComponentDialog {
constructor(dialogId, userDialogStateAccessor, userState) {
super(dialogId);
this.addDialog(new ChoicePrompt(CHOICE_PROMPT));
this.addDialog(new TextPrompt(TEXT_PROMPT));
this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.promptAccount.bind(this),
this.promptChannel.bind(this),
this.promptSummary.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
// State accessors
this.userDialogStateAccessor = userDialogStateAccessor;
this.userState = userState;
} // End constructor
async promptAccount(step) {
if (step.context.responded) {
step.context.activity.text = '';
}
const userData = await this.userDialogStateAccessor.get(step.context, {});
if (userData.accountNumber) {
return userData.accountNumber;
} else {
return await step.prompt(TEXT_PROMPT, `To help me get you to the right agent, can you please provide me your account number?`);
}
}
EDIT: Should be step.context.activity.text, not step.activity.text. Updated in several spots. Also, the change now clears this value only if step.context.responded is true.

I believe the problem is that your bot class is calling continueDialog on the same turn that it begins escalationDialog. I have commented on your private GitHub issue.

Related

Chrome Plugin Manifest V3 - strange behavior of a promise function in popup.js

I'm trying to create a small plugin to make my day-to-day job easier. I have faced a very strange situation within the popup.js script. The promise function randomly refuses to get executed. I have spent some hours trying to debug or at least understand where the issue could be but without any results.
Here is the skeleton of the code:
document.addEventListener('DOMContentLoaded', function () {
// some initialization
document.getElementById("signinbutton").addEventListener("click", function(event) {
try {
// some more initialization
var user_email = '';
var advertiserId = '';
var checkibm = '';
user_email = $('#emailfield').val().trim();
advertiserId = $('#advertiseridfield').val().trim();
checkibm = $('#checkibm').is(':checked');
if (advertiserId && checkibm) {
_act = 'getTokenIdByAdvId',
_data = advertiserId
}
else if (advertiserId && !checkibm) {
_act = 'getTokenIdByAdvId',
_data = advertiserId
}
else if (user_email && validateEmail(user_email))
{
_act = 'getTokenIdByEmail',
_data = user_email
}
else
{
throw new Error("Valid input has not been provided");
}
sendMessagePromise({
act : 'getTokenIdByAdvId',
data: '16910'//encodeURIComponent(user_email)
})
.then(responseHandler)
.then(responseReplaceTokenHandler)
.then(show_ok('Done'))
.catch(failureCallback);
}
catch (error){
//doing some error catching here
});
});
The code above works perfectly. However, as soon as I fill in the real values in sendMessagePromise e.g
//_act and _data show the proper values when inspected
sendMessagePromise({
act : _act,
data: _data//encodeURIComponent(user_email)
})
the flow skips execution of sendMessagePromise and any other chained function, except the last one ".then(show_ok('Done'))", i.e the only result is the "Done" message on the screen.
I made sure the values are correct. I'm able to debug step-by-step and see the values being properly supplied. I have also put a bunch of console messages inside the chain promise functions to see where the execution gets stuck, but it seems like it doesn't even start executing sendMessagePromise.
As soon as I replace expression back to hardcoded values i.e
sendMessagePromise({
act : 'getTokenIdByAdvId',
data: '16910'//encodeURIComponent(user_email)
})
it starts working again. I'm really stuck and not sure how to debug or which steps to take further.
Please assist

Microsoft Teams Bot-Framework Skill Dialog: endDialog() does not work

I am implementing a dialogRootBot that will call a skillDialogBot.
My dialogRootBot is able to invoke the skillDialogBot. The conversation can progress until the point where the skillDialogBot is supposed to return the results to the dialogRootBot.
The skillDialogBot has the following setup
this.addDialog(new TextPrompt(TEXT_PROMPT))
.addDialog(new ConfirmPrompt(CONFIRM_PROMPT))
.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.processStep.bind(this)
]));
The processStep is laid out like this
async processStep(stepContext) {
const details = stepContext.options;
details.result = {
status: 'success'
};
return await stepContext.endDialog(stepContext.options);
}
I was expecting the dialogRootBot to get the result from the skillDialogBot after processStep has called endDialog, but that never happens. Instead, the user is stuck with the skillDialogBot until the user manually types in "abort", which is the command the dialogRootBot is monitoring to cancel all dialogs in its onContinueDialog() implementation
Here is how the onContinueDialog() looks like
async onContinueDialog(innerDc) {
const activeSkill = await this.activeSkillProperty.get(innerDc.context, () => null);
const activity = innerDc.context.activity;
if (activeSkill != null && activity.type === ActivityTypes.Message && activity.text) {
if (activity.text.toLocaleLowerCase() === 'abort') {
// Cancel all dialogs when the user says abort.
// The SkillDialog automatically sends an EndOfConversation message to the skill to let the
// skill know that it needs to end its current dialogs, too.
await innerDc.cancelAllDialogs();
return await innerDc.replaceDialog(this.initialDialogId, { text: 'Request canceled!' });
}
}
return await super.onContinueDialog(innerDc);
}
I modeled this after the botbuilder-samples\samples\javascript_nodejs\81.skills-skilldialog sample. If I were to change the skillDialogBot and have it do a ConfirmPrompt() before the finalStep()'s endDialog(), then the conversation ends correctly with the skillDialogBot() posting the dialog's results to the rootDialogBot.
For the sake of clarity, this is how the bookingDialog in the skills-skilldialog sample looks like
/**
* Confirm the information the user has provided.
*/
async confirmStep(stepContext) {
const bookingDetails = stepContext.options;
// Capture the results of the previous step.
bookingDetails.travelDate = stepContext.result;
const messageText = `Please confirm, I have you traveling to: ${ bookingDetails.destination } from: ${ bookingDetails.origin } on: ${ bookingDetails.travelDate }. Is this correct?`;
const msg = MessageFactory.text(messageText, messageText, InputHints.ExpectingInput);
// Offer a YES/NO prompt.
return await stepContext.prompt(CONFIRM_PROMPT, { prompt: msg });
}
/**
* Complete the interaction and end the dialog.
*/
async finalStep(stepContext) {
if (stepContext.result === true) {
const bookingDetails = stepContext.options;
return await stepContext.endDialog(bookingDetails);
}
return await stepContext.endDialog();
}
Is it not possible to endDialog() without a prompt? Why is my skillDialogBot unable to endDialog and pass the results to the rootDialogBot?
Thank You
For the sake of reference, when using stepContext.beginDialog(), it appears that we cannot reliably endDialog() if the waterfall dialog does not have a prompt step added. If there is a use case where we want to use a skillDialogBot and call a specific dialog in the skillDialogBot via stepContext.beginDialog(), and the called dialog is only doing processing (eg. call REST APIs) without the need to prompt users for further information, at the end of the processing, we can opt to end the conversation instead.
Hence, assuming we have finalStep() bound to the waterfall dialog as the last step, just use the following in finalStep()
return await stepContext.context.sendActivity({
type: ActivityTypes.EndOfConversation,
code: EndOfConversationCodes.CompletedSuccessfully,
value: stepContext.options
});
At least for my use case, I was able to terminate the dialog without needing a user prompt and got the results back to the rootDialog.

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);

ChatBot back to previous dialog

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.

Prompt input validation

The context is MS Bot Framework on Node. I need to custom validate an input from a Prompt.text or Prompt.Number. I have been taking a look at examples and docs. I have implemented a quick example with DialogAction.validatePrompt(), which works ok but has the problem that I cannot (or at least I don't know how) customize the message in case the validation fails.
The validation could fail for many reasons and it would be great to be able to choose a response message based on the failure reason.
Also I have seen the basics-custom-prompt example in: (https://github.com/Microsoft/BotBuilder/tree/master/Node/examples/basics-customPrompt) and it uses and IntentDialog to get the desired behavior. It also mentions it is a replacement for the basics-validatedPrompt example.
My questions are:
Which one is recommend to use, validatedPrompt or the IntentDialog
approach?
Is the validatedPrompt() going to be deprecated?
Does the validatedPrompt() provide a mechanism for custom message?
Microsoft Bot Framework Version 4 Prompt Validation Example link is specified in below :
https://github.com/Microsoft/BotBuilder-Samples/tree/master/samples/javascript_nodejs/10.prompt-validations
For Proper Text Prompt Validation is you create separate file which perform validation task
Code: namePrompt/index.js
const { TextPrompt } = require('botbuilder-dialogs');
module.exports.NamePrompt = class NamePrompt extends TextPrompt {
constructor(dialogId) {
super(dialogId, async (prompt) => {
if (!prompt.recognized.succeeded) {
await prompt.context.sendActivity('Please tell me your name.');
return false;
} else {
const value = prompt.recognized.value;
if (value.length < 1) {
await prompt.context.sendActivity('Your name has to include at least one character.');
return false;
} else if (value.length > 50) {
await prompt.context.sendActivity(`Sorry, but I can only handle names of up to 50 characters. Yours was ${ value.length }.`);
return false;
} else {
return true;
}
}
});
}
};
Now inside your current dialog you can import above file:
const { NamePrompt } = require('../../prompts/namePrompt');
const GET_NAME_PROMPT = 'namePrompt';
this.addDialog(new NamePrompt(GET_NAME_PROMPT));
inside your dialog step you can prompt such like this:
async promptForName(step) {
return await step.prompt(GET_NAME_PROMPT, `What is your name, human?`);
}

Resources