Bot framework (v4) Оперативный выбор в карусели с использованием карт HeroCard не идет к следующему шагу - PullRequest
0 голосов
/ 27 января 2019

Я пытаюсь использовать HeroCards вместе с быстрым выбором в карусели.Таким образом, параметры, которые будут выбраны пользователем, отображаются в виде карт HeroCard.Как только пользователь нажимает на кнопку карты, он переходит к следующей функции водопада.

Вот рабочий пример в Bot Framework 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 (как предложено Кайлом Делани), похоже, имеет тот же результат (не переходите к следующему шагу), но я понял, что если удалить return из приглашения, как это:

return await step.prompt(PRODUCTS_CAROUSEL, `What is your name, human?`); TO 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);

Я вижу, что данные из моего состояния поступают должным образом: <- dАта в порядке, выбор: <- данные тоже в порядке </p>

Иногда я тоже получаю эту ошибку:

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 ]

0 голосов
/ 06 марта 2019

В основном Дрю Марш был прав.

Я просто хотел бы добавить некоторые другие детали, которые мне нужно было настроить, чтобы это работало.В случае, если кто-то еще сходит с ума, как я.Это может дать некоторые идеи.Все дело в том, как вы обрабатываете возврат вложенных диалогов.

Первое изменение.Мне пришлось преобразовать идентификатор приглашения 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()),
});

Еще одна проблема, которую я обнаружил, была в родительском диалоговом окне:

Iв основном пытался сделать это:

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;
}
0 голосов
/ 27 января 2019

Вы используете ChoicePrompt, но когда вы звоните prompt, вы проходите только через activity (карусель). ChoicePrompt попытается проверить входные данные в соответствии с набором вариантов, которые вы должны передать при вызове prompt. Поскольку вы этого не делаете, приглашение не распознает значение обратной ссылки как действительное, и с технической точки зрения вам нужно будет повторно выполнить карусель, чтобы сделать правильный выбор.

Исправление здесь должно состоять в том, чтобы вызвать приглашение с PromptOptions вместо простого Activity и установить choices PromptOptions в массив, который содержит все значения, которые вы ожидаете вернуть (например, одно и то же значение Вы установили для value кнопки возврата назад).

Это должно выглядеть примерно так:

Так как вы предоставляете UX с вашими картами, вы хотите установить ListStyle на ChoicePrompt на 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),
  });
...