Bot框架(v4)使用HeroCard提示在轮播中选择提示,不进行下一步

时间:2019-01-27 00:54:22

标签: node.js typescript botframework prompt

我正在尝试在转盘中同时使用HeroCard和即时选择。因此,用户要选择的选项将显示为HeroCards。用户单击卡上的按钮后,应立即进入下一个瀑布功能。

这是bot框架v3中的一个有效示例。它确实按预期工作。

  const cards = (data || []).map(i => {
    return new builder.HeroCard(session)
      .title(`${i.productName} ${i.brandName}`)
      .subtitle(‘product details’)
      .text(‘Choose a product’)
      .images([builder.CardImage.create(session, i.image)])
      .buttons([builder.CardAction.postBack(session, `${i.id.toString()}`, ‘buy’)]);
  });

  const msg = new builder.Message(session);
  msg.attachmentLayout(builder.AttachmentLayout.carousel);
  msg.attachments(cards);

  builder.Prompts.choice(session, msg, data.map(i => `${i.id.toString()}`), {
    retryPrompt: msg,
  });

下面,我尝试对bot框架v4进行同样的操作,但是它不起作用。它永远不会进入我瀑布中的下一个功能。

如何对v4进行相同操作?

...

this.addDialog(new ChoicePrompt(PRODUCTS_CAROUSEL));

...

const productOptions: Partial<Activity> = MessageFactory.carousel(
  item.map((p: Product) =>
    CardFactory.heroCard(
      p.productName,
      ‘product details’,
      [p.image || ''],
      [
        {
          type: ActionTypes.PostBack,
          title: ‘buy’,
          value: p.id,
        },
      ],
    ),
  ),
  ‘Choose a product’,
);

return await step.prompt(PRODUCTS_CAROUSEL, productOptions);

...

更新:

按照完整的代码提供@Drew Marsh的建议

export class ProductSelectionDialog extends ComponentDialog {
  private selectedProducts: Product[] = [];
  private productResult: Product[][];
  private stateAccessor: StatePropertyAccessor<State>;

  static get Name() {
    return PRODUCT_SELECTION_DIALOG;
  }

  constructor(stateAccessor: StatePropertyAccessor<State>) {
    super(PRODUCT_SELECTION_DIALOG);

    if (!stateAccessor) {
      throw Error('Missing parameter.  stateAccessor is required');
    }

    this.stateAccessor = stateAccessor;

    const choicePrompt = new ChoicePrompt(PRODUCTS_CAROUSEL);
    choicePrompt.style = ListStyle.none;

    this.addDialog(
      new WaterfallDialog<State>(REVIEW_PRODUCT_OPTIONS_LOOP, [
        this.init.bind(this),
        this.selectionStep.bind(this),
        this.loopStep.bind(this),
      ]),
    );

    this.addDialog(choicePrompt);
  }

  private init = async (step: WaterfallStepContext<State>) => {
    const state = await this.stateAccessor.get(step.context);
    if (!this.productResult) this.productResult = state.search.productResult;
    return await step.next();
  };

  private selectionStep = async (step: WaterfallStepContext<State>) => {
    const item = this.productResult.shift();

    const productOptions: Partial<Activity> = MessageFactory.carousel(
      item.map((p: Product) =>
        CardFactory.heroCard(
          p.productName,
          'some text',
          [p.image || ''],
          [
            {
              type: ActionTypes.ImBack,
              title: 'buy',
              value: p.id,
            },
          ],
        ),
      ),
      'Choose a product',
    );

    return await step.prompt(PRODUCTS_CAROUSEL, {
      prompt: productOptions,
      choices: item.map((p: Product) => p.id),
    });
  };

  private loopStep = async (step: WaterfallStepContext<State>) => {
    console.log('step.result: ', step.result);
  };
}

以下父对话框:

...

this.addDialog(new ProductSelectionDialog(stateAccessor));

...

if (search.hasIncompletedProducts) await step.beginDialog(ProductSelectionDialog.Name);

...

return await step.next();

...

“我的机器人”对话结构

onTurn()
>>> await this.dialogContext.beginDialog(MainSearchDialog.Name) (LUIS)
>>>>>> await step.beginDialog(QuoteDialog.Name)
>>>>>>>>> await step.beginDialog(ProductSelectionDialog.Name)

更新

ChoicePrompt替换为TextPromt(由Kyle Delaney建议)似乎具有相同的结果(不进行下一步),但我意识到,如果从return中删除return await step.prompt(PRODUCTS_CAROUSEL, `What is your name, human?`);这样的提示:

await step.prompt(PRODUCTS_CAROUSEL, `What is your name, human?`);ChoicePrompt

它确实可以工作,但是当我使用return而不使用await step.prompt(PRODUCTS_CAROUSEL, { prompt: productOptions, choices: item.map((p: Product) => p.id), }); 返回原始代码时,如下所示:

error:  TypeError: Cannot read property 'length' of undefined
    at values.sort (/xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/choices/findValues.js:84:48)
    at Array.sort (native)
    at Object.findValues (/xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/choices/findValues.js:84:25)
    at Object.findChoices (/xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/choices/findChoices.js:58:25)
    at Object.recognizeChoices (/xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/choices/recognizeChoices.js:75:33)
    at ChoicePrompt.<anonymous> (/xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/prompts/choicePrompt.js:62:39)
    at Generator.next (<anonymous>)
    at /xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/prompts/choicePrompt.js:7:71
    at new Promise (<anonymous>)
    at __awaiter (/xxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/prompts/choicePrompt.js:3:12)

我在框架中遇到另一个错误:

    // Sort values in descending order by length so that the longest value is searched over first.
    const list = values.sort((a, b) => b.value.length - a.value.length);

这是一行:

error:  TypeError: Cannot read property 'status' of undefined
    at ProductSelectionDialog.<anonymous> (/xxxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/componentDialog.js:92:28)
    at Generator.next (<anonymous>)
    at fulfilled (/xxxx/Workspace/temp/13.basic-bot/node_modules/botbuilder-dialogs/lib/componentDialog.js:4:58)
    at <anonymous>
    at process._tickDomainCallback (internal/process/next_tick.js:228:7)

我可以看到来自我的状态的数据正在正常传输 提示:<-数据正常 选择:<-数据也可以

有时我也会收到此错误:

            // Check for end of inner dialog
            if (turnResult.status !== dialog_1.DialogTurnStatus.waiting) {

此行

{{1}}

2 个答案:

答案 0 :(得分:1)

您使用的是ChoicePrompt,但是当您致电prompt时,您只会通过activity(轮播)。 ChoicePrompt将尝试根据调用prompt时应传递的一组选择来验证输入。由于您未执行此操作,因此提示无法识别回发值是有效的,并且从技术上讲,应该再次提示您轮播以做出有效选择。

此处的解决方法应该是使用PromptOptions而不是原始的Activity来调用提示符,并将choices的{​​{1}}设置为包含所有值的数组您期望返回(例如,您为“后退”按钮的PromptOptions设置的值相同)。

这应该最终看起来像这样:

由于您要在卡片上提供选项UX,因此需要将value上的ListStyle设置为ChoicePrompt

none

然后,为特定的提示设置可用的const productsPrompt = new ChoicePrompt(PRODUCTS_CAROUSEL); productsPrompt.style = ListStyle.none; this.addDialog(productsPrompt);

choices

答案 1 :(得分:0)

基本上Drew Marsh是对的。

我只想添加一些其他细节,我需要对其进行调整以使其起作用。万一有人像我一样疯狂。它可以提供一些见解。这与如何处理嵌套对话框的返回有关。

第一次更改。我必须将“选择”提示的标识符转换为字符串:

        {
          type: ActionTypes.PostBack,
          title: 'buy',
          value: p.id.toString(),
        },

return await step.prompt(PRODUCTS_CAROUSEL, {
  prompt: productOptions,
  choices: item.map((p: Product) => p.id.toString()),
});

我发现的另一个问题是在父对话框中:

我基本上是想这样做:

if (search.hasIncompletedProducts) await step.beginDialog(ProductSelectionDialog.Name);
return await step.next();

毫无意义,然后我将其更改为:

if (search.hasIncompletedProducts) {
  return await step.beginDialog(ProductSelectionDialog.Name);
} else {
  return await step.next();
}

然后是父级对话框的父级中的最终更改:

之前是这样的:

switch (step.result) {
  case ESearchOptions.OPT1:
    await step.beginDialog(OPT1Dialog.Name);
    break;
  default:
    break;
}

await step.endDialog();

这又没有意义,因为我应该返回beginDialog或endDialog。更改为:

switch (step.result) {
  case ESearchOptions.OPT1:
    return await step.beginDialog(OPT1Dialog.Name);
  default:
    break;
}