开发者问题收集

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

2019-01-27
3451

我尝试在轮播中使用 HeroCards 和提示选项。因此,用户要选择的选项将显示为 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)

更新

TextPromt 替换 ChoicePrompt (如 Kyle Delaney 所建议)似乎会产生相同的结果(不进入下一步),但我意识到如果从提示中删除 return ,如下所示:

return await step.prompt(PRODUCTS_CAROUSEL, `你叫什么名字,人类?`); TO await step.prompt(PRODUCTS_CAROUSEL, `你叫什么名字,人类?`);

它确实有效,但是当我返回带有 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) {
2个回答

您正在使用 ChoicePrompt ,但是当您调用 prompt 时,您只会通过一个 activity (轮播)。 ChoicePrompt 将尝试根据您在调用 prompt 时应该传递的一组选项来验证输入。由于您没有这样做,提示无法将回发值识别为有效值,从技术上讲,应该再次使用轮播提示您做出有效选择。

此处的修复方法是使用 PromptOptions 而不是原始 Activity 调用提示,并将 PromptOptionschoices 设置为包含所有您期望返回的值的数组(例如,您为回发按钮的 value 设置的值相同)。

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

由于您正在使用卡片提供选择 UX,因此您需要将 ChoicePrompt 上的 ListStyle 设置为 none

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

this.addDialog(productsPrompt);

然后,设置可用的 choices 针对特定提示:

return await step.prompt(PRODUCTS_CAROUSEL, {
      prompt: productOptions,
      choices: items.map((p: Product) => p.id),
  });
Drew Marsh
2019-01-27

基本上,Drew Marsh 是对的。

我只想添加一些其他细节,我必须调整这些细节才能使其正常工作。以防其他人像我一样发疯。它可以提供一些见解。这完全取决于您如何处理嵌套对话框的返回。

第一个更改。我必须将 Choice 提示的标识符转换为字符串:

        {
          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;
}
Thiago C. S Ventura
2019-03-06