【问题标题】:Processing answer to reminder dialog处理提醒对话框的答案
【发布时间】:2019-09-07 07:23:34
【问题描述】:

我想在向用户发送主动消息时启动“提醒对话框”。该对话框已发布。但在处理答案时,它会返回主对话框。

目前我创建我的机器人如下:

const conversationState = new ConversationState(mongoStorage);
const userState = new UserState(memoryStorage);

const bot = new DialogAndWelcomeBot(conversationState, userState, logger);

// Listen for incoming activities and route them to your bot main dialog.
server.post("/api/messages", (req, res) => {
  adapter.use(new GraphQLMiddleware(bot.getAuthState()));

  // Route received a request to adapter for processing
  adapter.processActivity(req, res, async turnContext => {
    await bot.run(turnContext);
  });
});

并像这样发送主动消息:

await adapter.createConversation(conversationReferenceAdapted, bot.remind);

其中DialogAndWelcomeBot具有以下功能:

remind = async turnContext => {
  const reminderDialog = new ReminderDialog(this.logger);
  await reminderDialog.run(turnContext, this.dialogState);
  await this.conversationState.saveChanges(turnContext, false);
  await this.userState.saveChanges(turnContext, false);
};

但是,ReminderDialog 会正确触发(带有是和否按钮)。但是当我按下这些按钮中的任何一个时,我会得到:

[onTurnError]: Error: DialogContext.continue(): Can't continue dialog. A dialog with an id of 'ReminderDialog' wasn't found.

这让我怀疑它不知道原始流程中的ReminderDialog(通过DialogAndWelcomeBot 的构造函数,其中MainDialog 被实例化并运行)。

关于如何解决这个问题的任何想法?

所需功能

我有一个询问有关用户的一些详细信息的主要流程。可以通过向机器人发送任何内容来调用此流程。然后它会回复请求一些输入。

我尝试实现的流程是主要流程的替代流程。它应该与用户签到(例如每天)。所以它应该开始一个(不同于主要的)对话,要求输入他做运动的小时数,然后确认。

简而言之:

  • 主要流程:用户类型 hi -> Bot 回复“你的年龄” -> 用户类型 28 -> Bot 回复“好的,谢谢”

  • 主动流程:Bot 询问用户“您今天运动了多长时间?” -> 用户输入 1 小时 -> 机器人回复“好的,1 小时正确吗?” -> 用户点击“是”按钮 -> 机器人回复“谢谢,明天见”

【问题讨论】:

  • 我有很多关于您的代码的问题,但在我问这些问题之前,您能否详细解释一下您要实现的过程是什么?您正在设计的流程是什么,您希望用户体验的流程是什么? (您可以使用任何其他详细信息更新您的原始帖子。)
  • 谢谢@StevenKanberg 我已经用流程描述编辑了我的原始问题

标签: javascript node.js botframework


【解决方案1】:

您需要为此工作设置 cron 作业和主动消息传递。幸运的是,已经有一些帖子可以参考,其中介绍了如何做到这两点。

cron 作业将允许您设置执行主动消息的时间。这个 Stack Overflow post 讨论了如何创建一个可以与您的机器人一起运行的简单项目。或者,您也可以在 Azure Function 中运行 cron 作业,类似地,它会按照设定的时间表调用您的主动消息 API。

关于主动消息,请查看此 Stack Overflow post,其中详细介绍了设置此服务。特定于该用户问题的某些要点不适用于您,可以忽略。这个来自 BotBuilder-Samples 存储库的 sample 也可以作为一个很好的参考点。

希望有帮助!


[编辑]

以下是调用 API 以发送主动消息的基本设置,该消息也会启动特定的对话流。显然,您需要进行更改以满足您的需求,但这应该会让您走上正确的道路。

简而言之,对您的机器人公开的 API 进行调用,其中包含 conversationId 作为参数。当 API 被命中时,会创建一个conversationReference 并用于发送英雄卡。英雄卡询问用户他们是否接受过培训(是/否),当他们回复时发送PostBack。 PostBack 是通过中断方法在组件对话框中监视的触发器。当进行匹配时,“主动”对话框开始。当用户完成时,“主动”对话框从堆栈中弹出,然后用户返回到对话停止的地方(如果他们在一个对话中)。

请注意,主动消息需要令牌和 conversationId。用户需要事先与机器人进行过交谈,或者您需要事先通过机器人生成令牌和对话 ID,然后使用用户的 Slack user.id 发送这些内容以开始。

Index.js

const conversationReferences = {};

const dialog = new MainDialog( 'MainDialog', userState, conversationState );
const bot = new WelcomeBot( conversationState, userState, dialog, conversationReferences );

server.post( '/api/message', async ( req, res ) => {
[...]
}

server.get( '/api/notify/:conversationID', async ( req, res ) => {
  const { conversationID, query } = req.params;
  const conversationReference = conversationReferences[ conversationID ];

  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(
      'Have you trained today?',
      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();
} );

mainDialog.js

const { DialogSet } = require( 'botbuilder-dialogs' );
const { InterruptionDialog} = require( './interruptionDialog' );

const MAIN_WATERFALL_DIALOG = 'MainWaterfallDialog';
const ADAPTIVE_CARD = 'AdaptiveCard';

class MainDialog extends CancelAndHelpDialog {
  constructor ( id, userState, conversationState ) {
    this.mainId = id;
    this.userState = userState;
    this.conversationState = conversationState;
    [...]
  };

  async run ( turnContext, accessor ) {
    const dialogSet = new DialogSet( accessor );
    this.id = this.mainId;
    dialogSet.add( this );

    const dialogContext = await dialogSet.createContext( turnContext );
    const results = await dialogContext.continueDialog();
    if ( results.status === DialogTurnStatus.empty ) {
      return await dialogContext.beginDialog( this.id );
    }
  };

  [...]
};

interruptionDialog.js

const { ProactiveDialog, PROACTIVE_DIALOG } = require( './proactiveDialog' );

class InterruptionDialog extends ComponentDialog {
  constructor ( id ) {
    super( id );
    this.addDialog( new ConfirmPrompt( 'ConfirmPrompt' ) );
    this.addDialog( new ProactiveDialog() );
  }

  async onBeginDialog ( innerDc, options ) {
    const result = await this.interrupt( innerDc );
    if ( result ) {
      return result;
    }
    return await super.onBeginDialog( innerDc, options );
  }

  async onContinueDialog ( innerDc ) {
    const result = await this.interrupt( innerDc );
    if ( result ) {
      return result;
    }
    return await super.onContinueDialog( innerDc );
  }

  async onEndDialog ( innerDc ) {
    const result = await this.interrupt( innerDc );
    if ( result ) {
      return result;
    }
    return await super.onEndDialog( innerDc );
  }

  async interrupt ( innerDc, next ) {
    if ( innerDc.context.activity.type === 'message' ) {
      if ( activity.channelId === 'slack' && activity.channelData.Payload ) {
        if ( activity.channelData.Payload.actions[ 0 ].name === 'postBack' ) {
          return await innerDc.beginDialog( PROACTIVE_DIALOG );          
        }
      } 
    }
  }
}

module.exports.InterruptionDialog = InterruptionDialog;

proactiveDialog.js

const {
  NumberPrompt,
  ComponentDialog,
  DialogTurnStatus,
  WaterfallDialog
} = require( 'botbuilder-dialogs' );

const PROACTIVE_DIALOG = 'proactiveDialog';
const WATERFALL_DIALOG = 'WATERFALL_DIALOG';
const NUMBER_PROMPT = 'NUMBER_PROMPT';

class ProactiveDialog extends ComponentDialog {
  constructor () {
    super( PROACTIVE_DIALOG );

    this.addDialog( new NumberPrompt( NUMBER_PROMPT ) );
    this.addDialog( new WaterfallDialog( WATERFALL_DIALOG, [
      this.didTrainStep.bind( this ),
      this.trainingStep.bind( this )
    ] ) );
    this.initialDialogId = WATERFALL_DIALOG;
  }

  async didTrainStep ( stepContext ) {
    const activity = stepContext.context.activity;
    const response = activity.channelData.Payload.actions[ 0 ].value.toLowerCase();
    if ( response === 'yes' ) {
      return await stepContext.prompt( NUMBER_PROMPT, 'Fantastic! How many minutes?' )
    } else if ( response === 'no' ) {
      await stepContext.context.sendActivity( 'Rubbish...serious rubbish.' )
    }
    return await stepContext.next();
  }

  async trainingStep ( stepContext ) {
    const activity = stepContext.context.activity;
    const stepResult = stepContext.result;
    const textResponse = activity.text.toLowerCase();

    if ( textResponse === 'no' ) {
      await stepContext.context.sendActivity( "I would recommend at least 5-10 mins of training." )
    } else
      if ( typeof ( stepResult ) === 'number' && stepResult > 0 ) {
        await stepContext.context.sendActivity( "I'll log that for you." );
      } else if ( stepResult <= 0 ) {
        await stepContext.context.sendActivity( "I can't log that value." )
      }
    return { status: DialogTurnStatus.complete }
  }
}
module.exports.ProactiveDialog = ProactiveDialog;
module.exports.PROACTIVE_DIALOG = PROACTIVE_DIALOG;

【讨论】:

  • 感谢您的回答。事实上,我已经使用 cron(通过公牛队列)设置了主动消息传递本身。但是一旦对话开始,我在完成对话时遇到了一些困难。上面代码中的remind 函数正在正确启动对话框。但是一旦我在该对话框中走得更远,它就会终止说`错误:DialogContext.continue():无法继续对话框。找不到 ID 为“ReminderDialog”的对话框。` 示例 repo 并不真正包含任何用于启动主动对话框的内容。据我所知,仅用于发送主动消息
  • 我假设当您说“主动对话”时,您指的是专门响应发送的主动消息的对话(即,退出主对话并在提问行完成后返回),是吗?
  • 除了上面的问题,这个定位的是什么渠道?
  • 没错。我的目标是松弛频道
  • @MaximGeerinck,我已经更新了我的答案以包含一个工作代码示例。您可能需要进行调整以适应您的模型,但这应该会让您继续前进。您也可以参考演示此概念的 13.core-bot 示例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多