BotFramework-WebChat - адаптивная карта - PullRequest
1 голос
/ 27 марта 2020

Есть ли способ добавить событие Onchange в поле ввода адаптивной карты, отображаемое в веб-чате (версия V4). Пример изменения количественного значения (поле ввода адаптивной карты с номером типа) на экране оформления заказа должно обновить Общее значение (текстовое поле адаптивной карты)

Чтобы было проще ... На следующем рисунке я изменил номер в поле ввода должен обновляться в текстовом поле ниже. все должно происходить на стороне клиента чата Web4 Vact (React)

AdaptiveCard

Ниже приведены варианты, которые я попробовал, у меня нет кода для отправки здесь:

option1: Попытка добавить событие в поле ввода количества на карте, поступающей от бота с использованием промежуточного программного обеспечения, но не нашла возможность однозначно идентифицировать поле ввода для добавления события (можно увидеть несколько полей ввода на основе отсутствия элементов в card)

option2: создайте новую карту во внешнем интерфейсе на основе карты, полученной от бота, и добавьте события к этой новой карте. Можно ли прервать сообщение, отправляемое боту, и отправить карту из внешнего интерфейса?

option3: добавить кнопку обновления на карту, чтобы общая сумма была рассчитана в бэкэнде, а карта обновления передана в user

ниже - полезная нагрузка:

{
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.0",
    "type": "AdaptiveCard",
    "body": [
        {
            "type": "TextBlock",
            "text": "Output",
            "weight": "Bolder",
            "horizontalAlignment": "Center",
            "size": "Large",
            "id": "output",
            "color": "Good"
        },
        {
            "type": "Container",
            "items": [
                {
                    "$data": "{items}",
                    "type": "Container",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": " ",
                            "id": "line",
                            "spacing": "None"
                        },
                        {
                            "type": "Image",
                            "altText": "",
                            "id": "myimage",
                            "url": "{imgUrl}",
                            "spacing": "None",
                            "size": "Stretch",
                            "width": "1000px",
                            "height": "100px"
                        },
                        {
                            "type": "ColumnSet",
                            "id": "imgset",
                            "columns": [
                                {
                                    "type": "Column",
                                    "width": 50,
                                    "id": "desc",
                                    "items": [
                                        {
                                            "type": "TextBlock",
                                            "text": "{description}",
                                            "weight": "Bolder",
                                            "spacing": "None",
                                            "id": "desc",
                                            "wrap": true,
                                            "maxLines": 4
                                        }
                                    ],
                                    "spacing": "None"
                                }
                            ],
                            "spacing": "None"
                        },
                        {
                            "type": "ColumnSet",
                            "spacing": "None",
                            "columns": [
                                {
                                    "type": "Column",
                                    "width": 50,
                                    "id": "qty",
                                    "items": [
                                        {
                                            "type": "Input.Number",
                                            "placeholder": "Quantity",
                                            "id": "myquantity",
                                            "min": 0,
                                            "max": 100,
                                            "value": "{quantity}",
                                            "spacing": "None"
                                        }
                                    ],
                                    "horizontalAlignment": "Left",
                                    "verticalContentAlignment": "Center",
                                    "spacing": "None"
                                },
                                {
                                    "type": "Column",
                                    "id": "pricec",
                                    "items": [
                                        {
                                            "type": "TextBlock",
                                            "text": "{price}",
                                            "id": "pricet",
                                            "horizontalAlignment": "Right",
                                            "spacing": "None"
                                        }
                                    ],
                                    "verticalContentAlignment": "Center",
                                    "horizontalAlignment": "Right",
                                    "width": 50,
                                    "spacing": "None"
                                }
                            ],
                            "id": "qtypset"
                        },
                        {
                            "type": "ColumnSet",
                            "spacing": "None",
                            "columns": [
                                {
                                    "type": "Column",
                                    "width": 1,
                                    "items": [
                                        {
                                            "type": "TextBlock",
                                            "text": "Sub Total",
                                            "size": "Medium",
                                            "id": "subtotal00",
                                            "weight": "Bolder",
                                            "spacing": "None"
                                        }
                                    ],
                                    "id": "subtotal1",
                                    "spacing": "None"
                                },
                                {
                                    "type": "Column",
                                    "width": 1,
                                    "items": [
                                        {
                                            "type": "TextBlock",
                                            "horizontalAlignment": "Right",
                                            "text": "{subtotal}",
                                            "size": "Medium",
                                            "weight": "Bolder",
                                            "id": "subtotalt0",
                                            "color": "Accent",
                                            "spacing": "None"
                                        }
                                    ],
                                    "id": "subtotal200",
                                    "spacing": "None"
                                }
                            ],
                            "id": "colsetsubtot00"
                        }
                    ],
                    "id": "itemcontainer",
                    "style": "emphasis",
                    "spacing": "None"
                }
            ],
            "id": "rootcontainer",
            "style": "accent"
        },
        {
            "type": "ColumnSet",
            "id": "totalset",
            "columns": [
                {
                    "type": "Column",
                    "width": 50,
                    "id": "totalcolumn",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "Total",
                            "size": "Medium",
                            "isSubtle": true,
                            "weight": "Bolder",
                            "id": "total",
                            "color": "Dark"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": 50,
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "{total}",
                            "size": "Medium",
                            "id": "totaltext",
                            "horizontalAlignment": "Right",
                            "weight": "Bolder",
                            "color": "Accent"
                        }
                    ],
                    "id": "totalcol2"
                }
            ]
        }
    ],
    "id": "final"
}

Я использую приведенный ниже пример в качестве отправной точки https://github.com/microsoft/BotFramework-WebChat/tree/master/samples/04.api/e.piping-to-redux

webchat. js:

import React from 'react';

import ReactWebChat, { createDirectLine, createStore } from 'botframework-webchat';
import directLineDisconnect from 'botframework-webchat-core/lib/actions/disconnect';
import dispatchIncomingActivityMiddleware from './dispatchIncomingActivityMiddleware';
import uuid from 'uuid';

export default class extends React.Component {
  constructor(props) {
    super(props);

    this.store = createStore({}, dispatchIncomingActivityMiddleware(props.appDispatch, this));
    this.activityMiddleware = this.setActivityMiddleware();
    this.attachmentMiddleware = this.setAttachmentMiddleware();

    this.state = {};

  }

  componentDidMount() {
    this.fetchToken();
    this.setSendBox();
  }

  componentWillUnmount(){

  }

  async fetchToken() {
    const myHeaders = new Headers();
    const userDetails = uuid.v4();
    myHeaders.append('Authorization', 'Bearer ' + 'mytoken'); 
    myHeaders.append('Content-type', 'application/json');
    const res = await fetch('https://directline.botframework.com/v3/directline/tokens/generate', { 
                        body: JSON.stringify({ user: { id: userDetails, name: userDetails }}),
                        method: 'POST', headers: myHeaders });
    const { token } = await res.json();
    console.log("My Token: " + token);
    this.setState(() => ({
      directLine: createDirectLine({ token })
    }));
  }

  setActivityMiddleware(){
    return () => next => card => {
      return children => (
        <div
          className={card.activity.attachments && (card.activity.attachments[0].content.id === "output") ? card.activity.attachments && card.activity.attachments[0].content.id : ''}
        >
          {next(card)(children)}
        </div>
      );
    };

  }


  setAttachmentMiddleware(){
    return () => next => ({ card, activity, attachment: baseAttachment }) => {
      let attachment = baseAttachment;
      if (baseAttachment.content.body){
      switch (baseAttachment.content.body[0].id) {
        case 'review':                   
         for (let i = 0; i < attachment.content.body[1].items.length; i++) {
         attachment.content.body[1].items[i].items[3].columns[0].items[0].value = baseAttachment.content.body[1].items[i].items[3].columns[0].items[0].value.toString();
                                                                           } //for loop
         break;

         default:
           break;
        }
    }
    return next({ card, activity, attachment });
    };

  }

  setSendBox() {

    this.store.dispatch({
      type: 'WEB_CHAT/SET_SEND_BOX',
      payload: { text: 'sample:redux-middleware' }
    });
/*

    this.store.dispatch({
      type: 'WEB_CHAT/SEND_EVENT',
      payload: { name: 'membersAdded',
                 value: { language: window.navigator.language }
               }  
    }); */
  }


  render() {
    return this.state.directLine ? (
      <ReactWebChat
        activityMiddleware={this.activityMiddleware}
        attachmentMiddleware={this.attachmentMiddleware}
        directLine={this.state.directLine}
        store={this.store}
        styleOptions={{
          backgroundColor: 'Transparent',
          hideUploadButton: true
        }}
      />
    ) : (
      <div>Connecting to bot&hellip;</div>
    );
  }

}

dispatchIncomingActivityMiddleware. js:

export default function(dispatch, thisvariable) {
    return () => next => action => {
      if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
        const { activity } = action.payload;


        if (activity.from.role === 'bot'){
        var inputBox=document.getElementsByClassName("css-eycyw2");
        if (inputBox.length > 0){
          inputBox[inputBox.length - 1].style.display='block';
        }
                                          }

      }


      if ((action.type === 'WEB_CHAT/SEND_POST_BACK') || (action.type === 'WEB_CHAT/SEND_MESSAGE')) { 
        var inputBox=document.getElementsByClassName("css-eycyw2");
        if (inputBox.length > 0){
          inputBox[inputBox.length - 1].style.display='none';
          dispatch(setInputVisibility(true));
        }
      }

      return next(action);
    };
  }

1 Ответ

0 голосов
/ 05 мая 2020

Первое, что нужно понять, это то, что в Web Chat используются Adaptive Cards JavaScript SDK , доступные в виде пакета npm. Веб-чат в основном использует готовые функции рендеринга SDK, но одна важная вещь, которую он меняет, - это то, как обрабатываются действия. Без предоставления настроенного обработчика действия по отправке не будут отправлены боту.

adaptiveCard.onExecuteAction = handleExecuteAction;

Именно так приложения должны использовать адаптивные карты. Хотя большая часть функциональности обрабатывается на стороне SDK, приложению необходимо сделать несколько вещей, чтобы адаптивные карты работали для этого конкретного приложения c. Хотя вы можете видеть, что веб-чат назначает функцию свойству onExecuteAction "event" определенного экземпляра c Adaptive Card, есть также stati c аналог onExecuteAction, к которому можно получить доступ следующим образом:

AdaptiveCard.onExecuteAction = handleExecuteAction;

При использовании события stati c будет применен обработчик для всех адаптивных карт вместо одной, но он будет переопределен любыми обработчиками, примененными к указанным c экземплярам. Причина, по которой я вам об этом говорю, заключается в том, что есть много других событий c событий , и есть несколько, в частности, которые будут полезны для вашей ситуации:

static onAnchorClicked: (element: CardElement, anchor: HTMLAnchorElement) => boolean = null;
static onExecuteAction: (action: Action) => void = null;
static onElementVisibilityChanged: (element: CardElement) => void = null;
static onImageLoaded: (image: Image) => void = null;
static onInlineCardExpanded: (action: ShowCardAction, isExpanded: boolean) => void = null;
static onInputValueChanged: (input: Input) => void = null;
static onParseElement: (element: CardElement, json: any, errors?: Array<HostConfig.IValidationError>) => void = null;
static onParseAction: (element: Action, json: any, errors?: Array<HostConfig.IValidationError>) => void = null;
static onParseError: (error: HostConfig.IValidationError) => void = null;
static onProcessMarkdown: (text: string, result: IMarkdownProcessingResult) => void = null;

Возможно, вы сможете найти решение, использующее событие onInputValueChanged, которое срабатывает каждый раз при изменении любого входа в карту. Ваш обработчик может искать на карте другие элементы, которые ему нужно использовать в качестве операндов для своего вычисления, и ему также нужно будет искать на карте элемент, который будет отображать результат. Вместо того, чтобы выполнять всю эту работу каждый раз, когда вводится символ, я предпочитаю решение, которое сначала ищет карточку в элементах, которые она будет использовать при вычислении. Альтернативой прослушиванию событий в классе Adaptive Card или экземпляре Adaptive Card является прослушивание событий в определенных элементах, таких как входы. Поэтому мой пример будет использовать событие stati c onParseElement для получения необходимых элементов, а затем использовать событие onValueChanged для указанных c входных найденных им экземпляров.

Перед написанием кода для обработчик, нам нужно найти способ, чтобы код знал, какие элементы использовать для операндов и результат вычисления. Например, вы можете просто сделать так, чтобы код объединял каждый ввод в карточке (или в контейнере) и помещал результат в последний найденный текстовый блок. Для моего примера я предложил схему именования, которую может использовать код. Есть два ключевых слова, «итого» и «цена», и код ищет их в каждом идентификаторе элемента. Я хочу прояснить, что эта схема абсолютно произвольна и что вы можете сделать что-то другое, если хотите. Вот мой пример карты:

{
  "type": "AdaptiveCard",
  "version": "1.0",
  "body": [
    {
      "type": "TextBlock",
      "text": "$10.00",
      "id": "foo_a_price"
    },
    {
      "type": "Input.Text",
      "id": "foo_a"
    },
    {
      "type": "TextBlock",
      "text": "$2.00",
      "id": "foo_b_price"
    },
    {
      "type": "Input.Text",
      "id": "foo_b"
    },
    {
      "type": "TextBlock",
      "text": "total",
      "id": "total_foo"
    }
  ],
  "actions": [
    {
      "type": "Action.Submit",
      "title": "Submit"
    }
  ]
}

Из этого вы можете догадаться, что идея состоит в том, чтобы один текстовый блок имел идентификатор, который начинается с «total_» и имеет некоторый идентификатор после него. Количества, которые вы хотите сложить, начинаются с того же идентификатора, а цена, которую вы хотите умножить на каждое количество, имеет тот же идентификатор, что и количество, но с суффиксом "_price". Я рекомендую использовать числовые вводы вместо ввода текста, но этот пример показывает, что текст все еще работает. И вот код для моего примера приложения, которое читает схему:

import * as adaptiveCardsPackage from 'adaptivecards';

adaptiveCardsPackage.AdaptiveCard.onParseElement = element => {
  const PREFIX_TOTAL = 'total_';
  const SUFFIX_PRICE = '_price';

  if (element.id && element.id.startsWith(PREFIX_TOTAL)) {
    const itemPrefix = element.id.slice(PREFIX_TOTAL.length);
    const card = element.getRootElement();
    const inputs = card.getAllInputs().filter(input => input.id.startsWith(itemPrefix));
    const products = {};

    for (const input of inputs) {
      const priceElement = card.getElementById(input.id + SUFFIX_PRICE);
      const price = Number(priceElement.text.replace(/[^0-9.-]+/g, '')) || 0;

      // `sender` will be the same as `input`.
      // You could capture the input const instead of using the argument,
      // but I'm demonstrating that you don't need to.
      input.onValueChanged = sender => {
        const quantity = Number(sender.value) || 0;

        products[sender.id] = price * quantity;

        const sum = Object.values(products).reduce((a, b) => a + b);

        element.setText("$" + sum.toFixed(2));
        element.renderedElement.replaceWith(element.render());
      };
    }
  }
};

У меня есть основания полагать, что это изменение класса AdaptiveCard будет автоматически применено к классу AdaptiveCard в пакете этот веб-чат импортирует, так как это тот же класс в том же пакете. Однако теперь Web Chat позволяет вам предоставлять собственный пакет Adaptive Cards в качестве свойства, поэтому вы можете убедиться, что Web Chat использует пакет с вашим специальным обработчиком событий:

<ReactWebChat
  directLine={createDirectLine({secretOrToken})}
  adaptiveCardsPackage={adaptiveCardsPackage}
/>

Self-updating card

...