Search code examples
node.jsbotframeworkazure-bot-service

Waterfall in ComponentDialogs


I am having problems implementing WaterfallDialogs in ComponentDialogs trigered by LUIS in the BotFramework V4.2. After the first step, the dialog does not continue the Waterfall. I don't really understand what should be the correct approach to persist the ComponentDialog: save the ConversationState? set the step.context? I've tried both but none worked for me so far 😣

This is my code for bot.js, where I have a LUIS instance to act as an orchestrator between dialogs:

async onTurn(turnContext) {
...
switch (dialogResult.status) {
   case DialogTurnStatus.empty:
      // Call to LUIS recognizer to get intent + entities
      const results = await this.luisRecognizer.recognize(dc.context);
      const topIntent = results.luisResult.topScoringIntent;

      switch (topIntent.intent) {
          case FMM_INTENT:
              return await dc.beginDialog(FeedbackDialog.Name);
          ...
      }
   case DialogTurnStatus.waiting:
      // The active dialog is waiting for a response from the user, so do nothing
       break;
...
}

And this is my code por FeedbackDialog.js where I have the logic of the WaterfallDialog with Prompts:

module.exports = {
FeedbackDialog: class extends ComponentDialog {
    static get Name() { return DIALOG_NAME; }
    constructor(conversationState, botConfig, feedbackStateAccessor, dialogId) {
        (dialogId === undefined) ? super(DIALOG_NAME) : super(dialogId);

        this.conversationState = conversationState;
        this.feedbackStateAccessor = feedbackStateAccessor;

        this.addDialog(new WaterfallDialog(FBK_EVAL, [
            this.feedbackPrompt.bind(this),
            this.feedbackEvaluation.bind(this)
        ]));

        this.addDialog(new ChoicePrompt(FBK_PROMPT));
    }

    async feedbackPrompt(step) {
        let fmmState = await this.feedbackStateAccessor.get(step.context);
        if (fmmState === undefined) {
            await this.feedbackStateAccessor.set(step.context);
            //opciones válidas, las mayúsculas las detecta también
            const options = ['👍','👎'];
            return await step.prompt(FBK_PROMPT, {
                prompt: `¿Te ha resultado útil la respuesta?`,
                retryPrompt: `¿Qué te ha parecido la respuesta?`,
                choices: options
            });
        }
    }

    async feedbackEvaluation(step) {
        let fmmState = await this.feedbackStateAccessor.get(step.context);
        if (fmmState === undefined && step.result){
            if (step.result == '👍'){
                await step.context.sendActivity("¡Gracias por el feedback!");
                return await step.endDialog();
            }else {
                await step.context.sendActivity("¡Revisaremos tu pregunta para seguir mejorando!");
                return await this.conversationState.saveChanges(step.context);
            }             
        } else {
            return await step.next();
            //next steps omitted since the code doesn't reach this step
        }          
    }

Solution

  • I'm unsure what you're trying to achieve but if you're using a ChoicePrompt I believe the step result comes back into a value key, i.e. instead of

    if (step.result == '👍') 
    

    try

    if (step.result.value === '👍')
    

    Then you can send a message back to the user and end the dialog and it should be fine:

    await step.context.sendActivity("¡Gracias por el feedback!");
    return step.endDialog();
    

    or

    await step.context.sendActivity("¡Revisaremos tu pregunta para seguir mejorando!");
    return step.endDialog();
    

    Also, remember to initialise the dialog correctly, i.e. in your bot file:

    const { FeedbackDialog } = require('path/to/feedbackDialog')
    const FEEDBACK_DIALOG = 'FeedbackDialog'
    const FEEDBACK_PROPERTY = 'feedbackProperty'
    

    and in the bot constructor (I use userState for state accessors, not conversation state):

    this.dialogs = new DialogSet(this.dialogState)
    this.feedbackAccessor = userState.createProperty(FEEDBACK_PROPERTY)
    this.dialogs.add(new FeedbackDialog(
      FEEDBACK_DIALOG,
      this.feedbackAccessor
    ))
    

    then call it as:

    await dc.beginDialog(FEEDBACK_DIALOG);