如何通过 Microsoft Bot Framework 在 React WebChat 中制作菜单?

How to make a menu in React WebChat by Microsoft Bot Framework?

React webchat 提供上传附件的按钮。

我没有使用默认的附件按钮,而是尝试用一个可以切换菜单的按钮来替换它。此菜单将提供用户可以发送的各种文件类型,类似于下图中的文件类型。

但我无法想出一种方法来修改附件按钮的功能,甚至无法更改它。

请帮忙。

从技术上讲,这是可行的,但它有几个警告。首先,您正在玩​​ DOM ,这在 React 环境中是非常不鼓励的。其次,这里发生了很多事情来完成这项工作,如果某些组件、功能或元素在幕后发生变化,这确实会让您在未来出错。这里使用了许多元素,但是它们没有正确的 ID 或者它们依赖于生成的 class 名称。为了隔离某些元素,您将需要通过分配的名称或角色来定位它们……这很脆弱。 (我现在可以告诉您,已经计划重新设计发送框以适应即将推出的功能。)

在我进入细节之前,如果你真的想正确地做到这一点,我建议你克隆 BotFramework-WebChat repo 并在后台进行你自己的更改。这样你就不会直接使用 DOM 了,如果操作得当,将会产生一个更稳定的环境。您有责任使您的版本与官方版本保持同步。无论采用哪种方式,您都需要对代码进行维护以保持最新和正常工作。


这应该会让你朝着你想要的方向前进。请注意,这主要集中在网络聊天代码上。除此之外,我没有构建真正的功能。即使是第一个“功能”按钮(见下文),虽然允许您 select 一个文件,但只发送 activity 中的文件名。获取和发送文件以及您的机器人如何响应 activity 超出了您的提问范围,不会涉及。

辅助函数 - 用于简化创建要使用的不同元素。

const createElement = ( elType, id, className, option ) => {
  const el = document.createElement( elType );
  switch ( el.localName ) {
  case 'button':
    if ( id ) {
      el.setAttribute( 'id', id );
      el.setAttribute( 'type', 'button' );
    }
    if ( className ) {
      el.classList.add( className );
    }
    return el;
  case 'div':
    if ( id ) {
      el.setAttribute( 'id', id );
    }
    if (className) {
      el.classList.add( className );
    }
    if (option) {
      const style = option.style;

      el.setAttribute( 'style', style );
    }
    return el;
  case 'img':
    if ( className ) {
      el.classList.add( className );
    }
    if ( option ) {
      const src = option;
      el.src = src;
    }
    return el;
  case 'input':
    if ( id ) {
      el.setAttribute( 'id', id );
    }
    if ( className ) {
      el.className = className;
    }
    if ( option ) {
      const inputName = option.name;
      const inputType = option.type;
      const style = option.style;

      el.setAttribute( 'name', inputName );
      el.setAttribute( 'type', inputType );
      el.setAttribute( 'style', style );
    }
    return el;
  case 'p':
    if ( option ) {
      const innerText = option;
      el.innerText = innerText;
    }
    return el;
  }
};

获取发送框 - 获取发送框并定位文件附件按钮。

const parent = document.querySelector( '.main' );
const attachmentButton = document.querySelector( '[title="Upload file"]' );
if ( attachmentButton ) {
  var attachmentSVG = attachmentButton.firstElementChild;
  var attachmentParent = attachmentButton.parentElement;
}
const child = parent.querySelectorAll( 'svg' );

创建菜单和“按钮”(输入) - 注意:第一个按钮 documentButton 的功能在于它允许您 select一份文件。其他五个按钮不起作用,这就是为什么它们的设计与第一个略有不同。 如上所述,在此示例中,selecting 文件仅发送文件名。

// Creates containers to hold menu and buttons
const menuContainer = createElement( 'div', 'menuContainer', undefined );
menuContainer.hidden = true;
const firstRow = createElement( 'div', 'firstRow' );
const secondRow = createElement( 'div', 'secondRow' );

const documentButton = createElement( 'div', 'documentBtn', 'menuItemSize', { style: 'display: none' } );
const inputFile = createElement( 'input', 'docFile', 'menuItemSize', { name: 'docFile', type: 'file', style: 'display: none' } );
const docImage = createElement( 'img', undefined, 'menuItemSize', './images/menuDoc.png' );
documentButton.appendChild( inputFile );
documentButton.appendChild( docImage );
const docLabel = createElement( 'p', undefined, undefined, 'Document' );
const docWrapper = createElement( 'div', undefined, 'menuItemWrapper' );
docWrapper.appendChild( documentButton );
docWrapper.appendChild( docImage );
docWrapper.appendChild( docLabel );
firstRow.appendChild( docWrapper );
menuContainer.appendChild( firstRow );

// Enables button, allows file selection, and "sends" file via postBack.
docImage.onclick = () => { inputFile.click() };
inputFile.onchange = ( e ) => { handleChange( e ) };

const handleChange = ( e ) => { 
  const file = e.target.files[0];
  store.dispatch({
    type: 'WEB_CHAT/SEND_POST_BACK',
    payload: { 
      value: { file: file.name }
    }
  })
};

const cameraButton = createElement( 'div', 'cameraBtn', 'menuItemSize' );
const cameraImage = createElement( 'img', undefined, 'menuItemSize', './images/menuCamera.png' );
cameraButton.appendChild( cameraImage );
const cameraLabel = createElement( 'p', undefined, undefined, 'Camera' );
const cameraWrapper = createElement( 'div', undefined, 'menuItemWrapper' );
cameraWrapper.appendChild( cameraButton );
cameraWrapper.appendChild( cameraLabel );
firstRow.appendChild( cameraWrapper );
menuContainer.appendChild( firstRow );

const galleryButton = createElement( 'div', 'galleryBtn', 'menuItemSize' );
const galleryImage = createElement( 'img', undefined, 'menuItemSize', './images/menuGallery.png' );
galleryButton.appendChild( galleryImage);
const galleryLabel = createElement( 'p', undefined, undefined, 'Gallery' );
const galleryWrapper = createElement( 'div', undefined, 'menuItemWrapper' );
galleryWrapper.appendChild( galleryButton );
galleryWrapper.appendChild( galleryLabel );
firstRow.appendChild( galleryWrapper );
menuContainer.appendChild( firstRow );

const audioButton = createElement( 'div', 'audioBtn', 'menuItemSize' );
const audioImage = createElement( 'img', undefined, 'menuItemSize', './images/menuAudio.png' );
audioButton.appendChild( audioImage );
const audioLabel = createElement( 'p', undefined, undefined, 'Audio' );
const audioWrapper = createElement( 'div', undefined, 'menuItemWrapper' );
audioWrapper.appendChild( audioButton );
audioWrapper.appendChild( audioLabel );
secondRow.appendChild( audioWrapper );
menuContainer.appendChild( secondRow );

const locationButton = createElement( 'div', 'locationBtn', 'menuItemSize' );
const locationImage = createElement( 'img', undefined, 'menuItemSize', './images/menuLocation.png' );
locationButton.appendChild( locationImage );
const locationLabel = createElement( 'p', undefined, undefined, 'Location' );
const locationWrapper = createElement( 'div', undefined, 'menuItemWrapper' );
locationWrapper.appendChild( locationButton );
locationWrapper.appendChild( locationLabel );
secondRow.appendChild( locationWrapper );
menuContainer.appendChild( secondRow );

const contactButton = createElement( 'div', 'contactBtn', 'menuItemSize' );
const contactImage = createElement( 'img', undefined, 'menuItemSize', './images/menuContact.png' );
contactButton.appendChild( contactImage );
const contactLabel = createElement( 'p', undefined, undefined, 'Contact' );
const contactWrapper = createElement( 'div', undefined, 'menuItemWrapper' );
contactWrapper.appendChild( contactButton );
contactWrapper.appendChild( contactLabel );
secondRow.appendChild( contactWrapper );
menuContainer.appendChild( secondRow );

let transcriptWindow = document.querySelector( '[dir="ltr"]' );
let fosterParent = createElement( 'div' );
let menuButtonContainer = createElement( 'div', 'menuButtonContainer' );
let menuButton = createElement( 'div', 'menuButton' );
transcriptWindow.appendChild( menuContainer );

按钮功能

// Recreates file attachment button that, when clicked, opens the menu
menuButton.appendChild( attachmentSVG );
menuButtonContainer.appendChild( menuButton );
fosterParent.appendChild( menuButtonContainer );
const buttonDiv = createElement( 'div' );
buttonDiv.classList = attachmentButton.classList
buttonDiv.setAttribute( 'title', 'Upload file' );
buttonDiv.classList.remove( 'webchat__icon-button' );
attachmentButton.remove();
attachmentParent.appendChild( buttonDiv );

buttonDiv.innerHTML = fosterParent.innerHTML;

// Gets elements for use with event listeners
const menuBtnContainer = document.querySelector( '#menuButtonContainer' );
const menuItems = document.querySelectorAll( '.menuItemSize' );
const menuItemButtons = [];

menuItems.forEach(item => {
  if ( item.localName === 'div' ) {
    menuItemButtons.push( item )
  }
});

// Shows/hides menu on file attachment button click
const menu = document.querySelector( '#menuContainer' );
menuBtnContainer.addEventListener('click', ( e ) => {
  e.preventDefault();
  switch ( menu.hidden ) {
  case false:
    menu.hidden = true;
    break;
  case true:
    menu.hidden = false;
    break;
  }
  return false;
});

// Hides menu when menu button is clicked
menuItemButtons.map(value => {
  value.addEventListener('click', () => {
    switch ( value.id ) {
    case 'documentBtn':
      menu.hidden = true;
      break;
    }
    return false;
  })
});

希望得到帮助!