BotFramework-WebChat - 自适应卡片

Posted

技术标签:

【中文标题】BotFramework-WebChat - 自适应卡片【英文标题】:BotFramework-WebChat - Adaptive Card 【发布时间】:2020-07-07 16:37:06 【问题描述】:

有没有办法将 Onchange 事件添加到在 webchat 中呈现的自适应卡片输入字段(版本 V4)。在结帐屏幕中更改数量值(类型编号的自适应卡输入字段)的示例应更新总值(自适应卡文本字段)

为了简单起见....在下图中,一旦我更改了输入框中的数字,它应该在下面的文本框中更新。一切都应该发生在网络聊天 V4(React) 客户端

以下是我尝试过的选项,这里没有要提交的代码:

option1:尝试使用中间件将事件添加到来自机器人的卡片中的数量输入字段,但无法找到唯一标识添加事件的输入字段的选项(可以根据 no of 看到多个输入字段卡片中的物品)

选项2:根据来自bot的卡片在前端创建一张新卡片,并将事件添加到该新卡片中。是否可以中断发送到机器人的消息并从前端发送卡片?

选项3:在卡片上添加更新按钮,以便在后端计算总数并提交更新卡片给用户

下面是有效载荷:


    "$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);
    ;
  

【问题讨论】:

欢迎来到 Stack Overflow。您使用的是 React 网络聊天还是 javascript 网络聊天?您能否提供您的自适应卡以及任何相关代码并说明您迄今为止所做的尝试? 我不确定您的编辑是否是为了回应我的问题,但您仍然没有回答。我仍然需要您的自适应卡片(应该是 JSON,而不是屏幕截图)以及任何相关代码以及您迄今为止尝试过的内容的说明。请查看this helpful document。另外,请记住,您需要留下实际评论,让我知道您已进行了编辑,否则我将不会收到通知。 感谢您的回复。我已经更新了帖子。如果您需要更多信息,请告诉我 您能否让我看看您用于呈现网络聊天的一些代码,并告诉我它是在 html 脚本标签中还是在单独的 JavaScript 文件中?我正在寻找答案,但我希望能够在您当前的设置之后对其进行建模。 更新了代码块。由于我是 bot 框架的新手,因此尝试使用所有可用选项。 【参考方案1】:

首先要了解的是,网络聊天使用Adaptive Cards JavaScript SDK,作为 npm 包提供。网络聊天主要使用 SDK 的开箱即用呈现功能,但它改变的一件重要事情是如何处理操作。如果不提供 customized handler,提交操作将不会发送到机器人。

adaptiveCard.onExecuteAction = handleExecuteAction;

这就是应用程序应该如何使用自适应卡片的方式。虽然大部分功能都是在 SDK 端处理的,但应用程序需要做一些事情才能使自适应卡片适用于该特定应用程序。虽然您可以看到 Web Chat 将函数分配给特定 Adaptive Card 实例的 onExecuteAction“事件”属性,但也有 onExecuteAction 的静态对应物,可以像这样访问:

AdaptiveCard.onExecuteAction = handleExecuteAction;

使用静态事件将为所有自适应卡片应用一个处理程序,而不仅仅是一个,但它会被应用于特定实例的任何处理程序覆盖。我告诉你这个的原因是因为有many more static events,并且有一些特别对你的情况有用:

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 实例上的事件的另一种方法是侦听特定元素(如输入)上的事件。所以我的示例将使用静态onParseElement 事件来获取它需要的元素,然后将onValueChanged 事件用于它找到的特定输入实例。

在为处理程序编写代码之前,我们需要想出一种方法让代码知道哪些元素用于操作数和计算结果。例如,您可以让代码组合卡片(或容器)中的每个输入,并将结果放在找到的最后一个文本块中。对于我的示例,我提出了代码可以使用的命名模式。有两个关键字,“total”和“price”,代码在每个元素 ID 中查找它们。我想澄清一下,这个模式是完全任意的,如果你愿意,你可以做一些不同的事情。这是我的示例卡片:


  "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_”开头并在其后有一些标识符的 ID。您要相加的数量以相同的标识符开头,您要与每个数量相乘的价格与数量具有相同的 ID,但后缀为“_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 类的这种更改将自动应用于 Web Chat 导入的包中的 AdaptiveCard 类,因为它是同一个包中的同一个类。但是,Web Chat 现在允许您提供自己的 Adaptive Cards 包作为属性,因此您可以确保 Web Chat 将包与您的特殊事件处理程序一起使用:

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

【讨论】:

以上是关于BotFramework-WebChat - 自适应卡片的主要内容,如果未能解决你的问题,请参考以下文章

贝宝自适应付款?

jquery 如何让图片自适应大小

响应式设计与自适应设计

js中怎么使用showModalDialog,弹出一个自适应大小窗口????

自适应数据库

UWP UserControl 不会自适应大小