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

Bot framework (v4) Prompt choice in carousel using HeroCards not going to next step

我正在尝试使用 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 framework 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) {

您正在使用 ChoicePrompt,但是当您调用 prompt 时,您只是通过 activity(轮播)。 ChoicePrompt 将尝试根据您调用 prompt 时应该传入的一组选择来验证输入。因为你没有这样做,所以提示没有将 post 返回值识别为有效,技术上应该再次提示你使用轮播以做出有效选择。

这里的解决方法应该是使用 PromptOptions 而不是原始的 Activity 调用提示,并将 PromptOptionschoices 设置为包含所有内容的数组您期望返回的值(例如,您为 post 后退按钮的 value 设置的相同值)。

最后应该看起来像这样:

由于您要为卡片提供用户体验选项,因此您希望将 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),
  });

基本上德鲁·马什是对的。

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

第一次改动。我必须将 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();
}

然后在parent dialog的parent中最后修改:

之前是这样的:

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

await step.endDialog();

这又没有意义,因为我应该 returnbeginDialog 或 endDialog。已更改为:

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