Is there a way to proactively end a prompt, i.e with a timer or event, from the WaterfallStepContext in the Microsoft Bot Framework

无人久伴 提交于 2020-01-06 04:31:14

问题


From what I see, in the nodejs framework, I notice you can't prompt a user within a dialogflow dialog and retrieve the that prompt within the same dialog function... I may be wrong about that. Either way, you can return prompt and retrieve it in the next dialog flow.

Is there a way to cut off the awaiting prompt for user input and end or resume an activity of the dialog?

I would like to cut the prompt off at a certain point, ideally on a time basis.


回答1:


This is possible thru using a combination of sending a proactive message, filtering via onMessage() and onSendActivities, and a dialog interruption.

First, in index.js, I setup an API for sending proactive messages.

server.get('/api/interrupt', async (req, res) => {
  for (const conversationReference of Object.values(conversationReferences)) {
    await adapter.continueConversation(conversationReference, async turnContext => {
      var reply = { type: ActivityTypes.Message };

      const yesBtn = { type: ActionTypes.PostBack, title: 'Yes', value: 'Yes' };
      const noBtn = { type: ActionTypes.PostBack, title: 'No', value: 'No' };

      const card = CardFactory.heroCard(
        'Want to skip out and see Star Wars?',
        null,
        [ yesBtn, noBtn ]
      );

      reply.attachments = [ card ];

      await turnContext.sendActivity(reply);
      return { status: DialogTurnStatus.waiting };
    });
  }

  res.setHeader('Content-Type', 'text/html');
  res.writeHead(200);
  res.write('<html><body><h1>Proactive messages have been sent.</h1></body></html>');
  res.end();
});

In mainBot.js:

  • I create a state property for setting whether the timer should be active or not. I also create an empty timer object.
  • I assign the timer state property to a variable (activityTimer) along with a default value (null).
  • In onMessage(), I filter on channelId ("directline") and role ("user").
  • I filter on whether the user selected the 'Yes' option from the proactive message. If so, the dialog(s) are cancelled, timerState is set to false, and the timer is cleared. This ensures the timer won't fire once the dialog is cancelled. New input from the user is required to start the dialog, again.
  • Any other user input sets the timerState to true and clears the timer. This ensures multiple timers do not stack when new user input is received by clearing any current timers and flagging a new timer to start.
  • Lastly, if timerState is set to true, then setTimeout() is called, assigned to the timer variable, with a timeout value of 5 seconds. At this point, an API call is made to the "interrupt" endpoint I created in index.js, which sends the proactive message, and timerState is set back to false.
this.activityTimerAccessor = conversationState.createProperty('activityTimer');

let timer;
this.onMessage(async (context, next) => {
  if (activity.channelId === 'directline') {
    if (activity.from.role === 'user') {
      let text = activity.text.toLowerCase();
      switch (text === 'yes') {
      case true:
        if (activity.channelData.postBack) {
          activityTimer.timerState = false;
        }
        if (timer) clearTimeout(timer);
        break;
      default:
        activityTimer.timerState = true;
        if (timer) clearTimeout(timer);
      }
    }

    if (activityTimer.timerState === true) {
      timer = await setTimeout(async () => {
        await fetch('http://localhost:3978/api/interrupt');
      }, 5000);
      activityTimer.timerState = false;
    }
  }

}

In mainDialog.js, I extend cancelAndHelpDialog.js which is where any interruptions are caught. If you are unfamiliar with interruptions, you can read about them in the docs here and reference a sample here.

const { CancelAndHelpDialog } = require( './cancelAndHelpDialog' );
const MAIN_WATERFALL_DIALOG = 'MainWaterfallDialog';

class MainDialog extends CancelAndHelpDialog {
  constructor ( id, userState, conversationState ) {
    super( id, userState, conversationState );

    this.mainId = id;
    this.userState = userState;
    this.conversationState = conversationState;

    this.addDialog( new AdaptiveCardDialog( ADAPTIVE_CARD_WATERFALL_DIALOG ) )
      .addDialog( new WaterfallDialog( MAIN_WATERFALL_DIALOG, [
           this.simpleSend.bind(this)
      ] ) ) ;

    this.initialDialogId = MAIN_WATERFALL_DIALOG;
  }

  async simpleSend ( stepContext ) {
    await stepContext.context.sendActivity('Looks like you said or did something...')
    return await stepContext.next();
  }
}

module.exports.MainDialog = MainDialog;

In cancelAndHelpDialog.js, I create a new ComponentDialog which contains an "interrupt" method. In interrupt(), I filter for the user selecting the "Yes" button from the proactive message indicating the user's wish to cancel the dialog(s). If pressed, I notify the user and cancel all dialogs.

class CancelAndHelpDialog extends ComponentDialog {

  [...]

  async interrupt(innerDc, next) {
    const activity = innerDc.context.activity;
    const text = innerDc.context.activity.text.toLowerCase();
    if (activity.type === 'message' && activity.channelId === 'directline') {
      if (text === 'yes' && activity.channelData.postBack === true) {
        await innerDc.context.sendActivity("Cancelling...Let's go see a movie!!");

        return await innerDc.cancelAllDialogs();
      }
    }
  }
}

This should set you up going in the right direction. Obviously, alter to fit your needs. For instance, the proactive message doesn't have to take user input sending just a message that is picked up and triggers an interruption.

Hope of help!



来源:https://stackoverflow.com/questions/58964283/is-there-a-way-to-proactively-end-a-prompt-i-e-with-a-timer-or-event-from-the

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!