Search code examples
node.jsbotframework

How to have delay between two steps in Bot Framework v4?


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.


Solution

  • 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.