Search code examples
botframework

Create custom Web Chat for Microsoft Bot Framework


Is there a guide on how to create my own Web Chat (I don't want to be using/customizing the one that is in react). I would like to make it from scratch using Blazor or Angular, but I haven't found a good guide on how to do that.


Solution

  • No guides that I am aware of.

    It also depends on what you mean. If you are wanting to build the tool that would implement something like Web Chat, then I'd recommend installing different tools to use while referencing their respective SDKs.

    Regardless, you will need to have a method for capturing the incoming messages as well as posting messages back. This means you will need to utilize the BotFramework-DirectLineJS SDK which is what BotFramework-WebChat relies on. You will then need to consider how you will want to handle the different activity types and messages that could be sent by the bot or user. For instance, how to handle:

    • Messages:
      • Simple text messages
      • Messages with attachments: cards, adaptive cards, images, audio/speech, etc.
    • Events
    • ConversationUpdates, including membersAdded
    • Traces
    • Typing

    There are accessibility considerations, as well, for the hearing- and sight-impaired, etc., plus a host of other things to think of.

    Here is a custom chat I created some time ago. It does the absolute basic and is not pretty. In it I call a local token server I run for getting a valid Directline token.

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <link rel="icon" href="favicon.ico" type="image/x-icon" />
      <title>Custom Chat Using Direct Line</title>
      <script crossorigin="anonymous" src="https://unpkg.com/@babel/[email protected]/babel.min.js"></script>
      <script crossorigin="anonymous" integrity="sha384-7aeOL7r9BM1QyTIsoLYJYNsRLfVnQCtLmwgXlxteDNhJf0xUGj9PKP8F5w2Fx92/"
        src="https://unpkg.com/[email protected]/dist/directline.js"></script>
    </head>
    
    <body>
      <h2>Custom Chat Using Direct Line</h2>
      <div class="input-container">
        <input type="text" class="input-box" name="input-box" value="Hi">
        <button type="button" class="input-button">Send</button>
      </div>
      <div class="response-container">
      </div>
      <script type="text/babel" data-presets="es2015,react,stage-3">
        ( async function () {
          const { ConnectionStatus, DirectLine } = window.DirectLine;
    
          const renderStatus = {
            DISPLAY_ACTIVITIES: 'display',
            HIDE_ACTIVITIES: 'hide',
            RESET_VIEW: 'reset',
            MAINTAIN_VIEW: 'maintain'
          }
    
          let previousWatermark = 0;
          let currentWatermark = 0;
          let displayStatus = renderStatus.DISPLAY_ACTIVITIES;
          let viewStatus = renderStatus.MAINTAIN_VIEW;
          let responseHistory;
    
          // Custom 'token' server retrieves a Direct Line token
          // Server stores the Direct Line secret exchanging it for a token when requested
          const res = await fetch( 'http://localhost:3500/directline/conversations', { method: 'POST' } );
          const { token } = await res.json();
    
          var directLine = new DirectLine( {
            token: token
          } )
          
          // Function posts activity to Direct Line, when called
          const postActivity = ( dl, text ) => {
            dl.postActivity(
              {
                from: { id: 'abc123', name: 'JohnDoe' }, // required (from.name is optional)
                type: 'message',
                text: `${ text }`
              }
            )
              // As all message activities are logged below, there is no need to log the posted activity
              .subscribe(
                id => id,
                error => console.log( "Error posting activity", error )
              );
          }
    
          // Posts user message on button click
          const inputButton = document.querySelector( '.input-button' );
          const inputBox = document.querySelector( '.input-box' );
    
          inputButton.addEventListener( 'click', ( e ) => {
            e.preventDefault();
            const text = inputBox.value;
            postActivity( directLine, text );
          } );
    
          inputBox.onkeyup = ( e ) => {
            const keyCode = e ? ( e.which ? e.which : e.keyCode ) : event.keyCode;
            if ( keyCode === 13 ) {
              const text = inputBox.value;
              postActivity( directLine, text );
            }
          };
    
          // Updates UI with all response activity
          let responseContainer = document.querySelector( '.response-container' );
    
          const subscribeToActivities = (dl) => {
            dl.activity$
              .filter( activity => {
                return activity.type === 'message';
              } )
              .subscribe(
                activity => {
                  const text = activity.text;
    
                  if (!activity.attachments) {
                    const id = activity.from.id;
                    currentWatermark = Number(dl.watermark);
                    
                    if ( viewStatus === renderStatus.RESET_VIEW && currentWatermark <= previousWatermark && responseHistory.length > 0) {
                      displayStatus = renderStatus.HIDE_ACTIVITIES;
                      viewStatus = renderStatus.MAINTAIN_VIEW;
                    } 
        
                    else if ( displayStatus === renderStatus.DISPLAY_ACTIVITIES && currentWatermark >= previousWatermark ) {
        
                      switch ( id ) {
                        case 'botberg':
                          responseContainer.innerHTML += `<ul class="chat-list"><li>From Bot: ${ text } </li></ul>`;
                          displayStatus = renderStatus.HIDE_ACTIVITIES;
                          viewStatus = renderStatus.MAINTAIN_VIEW;
                          break;
                      }
                    }
                    else if ( displayStatus === renderStatus.HIDE_ACTIVITIES && currentWatermark >= previousWatermark ) {
                      switch ( id ) {
                        case 'botberg':
                          break;
                        default:
                          responseContainer.innerHTML += `<ul class="chat-list"><li>From User: ${ text } </li></ul>`;
                          displayStatus = renderStatus.DISPLAY_ACTIVITIES;
                          viewStatus = renderStatus.MAINTAIN_VIEW;
                      }
                    }
                  }
                  else {
                    responseContainer.innerHTML += `<ul class="chat-list"><li>From Bot: Client received unsuppported attachment type </li></ul>`;
                  }
                }
              );
          }
    
          subscribeToActivities(directLine);
    
          directLine.connectionStatus$
            .subscribe( async connectionStatus => {
              switch ( connectionStatus ) {
                case ConnectionStatus.Uninitialized:
                  console.log( 'CONNECTION_STATUS => UNINITIALIZED ', directLine );
                  break;
                case ConnectionStatus.Connecting:
                  console.log( 'CONNECTION_STATUS => CONNECTING ', directLine );
                  break;
                case ConnectionStatus.Online:
                  console.log( 'CONNECTION_STATUS => ONLINE ', directLine );
                  break;
                case ConnectionStatus.ExpiredToken:
                  console.log( 'CONNECTION_STATUS => EXPIRED TOKEN ', directLine );
                  break;
                case ConnectionStatus.FailedToConnect:
                  console.log( 'CONNECTION_STATUS => FAILED TO CONNECT ', directLine );
                  break;
                case ConnectionStatus.Ended:
                  console.log( 'CONNECTION_STATUS => ENDED ', directLine );
                  break;
              }
            } );
        } )()
    
      </script>
    </body>
    
    </html>
    

    enter image description here