【问题标题】:Bot framework (v4) Prompt choice in carousel using HeroCards not going to next stepBot framework (v4) 使用 HeroCards 在轮播中提示选择不会进入下一步
【发布时间】:2023-03-14 20:01:01
【问题描述】:

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

更新

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) {

【问题讨论】:

  • 你说它永远不会进入下一步。这是否意味着您的机器人在每一轮都继续发送相同的提示?问题是否与您的状态存储有关,而不是您的选择提示?请通过将您的选择提示替换为简单的文本提示并查看您的瀑布是否可以继续下一步来测试这一点。
  • 刚刚试过这样:return await step.prompt(PRODUCTS_CAROUSEL, 'question ?');问题仍然存在。
  • "...每回合的提示相同?"没有。
  • "问题可能与您的状态存储有关,而不是您的选择提示?"不确定。我可以正常访问我所在州的数据。所以,我不知道。
  • 刚刚在我的onturn上添加了一些日志,在选择选择或简单提示时似乎是日志记录.dialogcontext.context.respondeda alkate diallturnturnstatus.empty,但对话框永远不会结束。我希望它可以帮助。

标签: node.js typescript botframework prompt


【解决方案1】:

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

这里的解决方法应该是使用PromptOptions 而不是仅仅使用原始Activity 调用prompt,并将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),
  });

【讨论】:

  • 确实有道理。我会尽力让你知道。谢谢
  • 刚刚更新了我尝试使用您的建议的问题。我收到问题中描述的错误。当我再次使用以前的方法时,我可以在轮播中看到 HeroCards,但不会继续进行下一个瀑布功能,如您所知。
  • 刚刚尝试使用“toString()”作为 ID。选择: item.map((p: Product) => p.id.toString()) 并且不再收到错误但仍然不是预期的行为。现在它在我的 HeroCards 下方将 ID 显示为选项,甚至单击它也会执行与 HeroCards 相同的行为。
  • 啊,抱歉,您需要在 ChoicePrompt 上设置 ListStyle.none。我会更新我的答案。
  • 啊,好的...你可以尝试将按钮上的ActionTypes.PostBack 更改为ImBack 吗?我很确定会这样做,但是您需要确定您是否可以使用该 UX。 ChoicePrompt 不处理回发 IIRC。
【解决方案2】:

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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多