Wednesday, May 30, 2018

Oracle Bot Cloud (IBCS): Custom UI

Problem Description: In this blog I would be going over various options to build a web UI for IBCS chat bot.

By default IBCS provides two sdks to build web UI for Chatbot. We can download those sdks from
http://www.oracle.com/technetwork/topics/cloud/downloads/mobile-suite-3636471.html under heading Bots Client SDK


We can follow link https://docs.oracle.com/en/cloud/paas/mobile-suite/use-chatbot/bot-channels.html#GUID-A0A40E26-54BA-4EDD-A4C5-95D498D6CF61 to find out how to use these SDKs.


Widget is very cool and rich but still at times you want to build your own custom UI instead of widget. To do that you can follow
https://docs.oracle.com/en/cloud/paas/mobile-suite/use-chatbot/bot-channels.html#GUID-78F6DD7E-5085-476B-AD03-1318D9107D39

In this blog I am trying to enhance that code to handle postback requests. Also this blog will help me in my next blog to achieve voice based conversation.

1. Add below code in html
HTML:
 <div id="no-display" style="display:none;"></div>
       <p>User ID: <span id="user-id"></span></p>
       <ul id="conversation"></ul>
    <input type="text" id="text-input" placeholder="text">

<script src="js/app.js"></script>


2. Add app.js inside js directory. It can have following code
!function(e,t,n,r){
    function s(){try{var e;if((e="string"==typeof this.response?JSON.parse(this.response):this.response).url){var n=t.getElementsByTagName("script")[0],r=t.createElement("script");r.async=!0,r.src=e.url,n.parentNode.insertBefore(r,n)}}catch(e){}}var o,p,a,i=[],c=[];e[n]={init:function(){o=arguments;var e={then:function(t){return c.push({type:"t",next:t}),e},catch:function(t){return c.push({type:"c",next:t}),e}};return e},on:function(){i.push(arguments)},render:function(){p=arguments},destroy:function(){a=arguments}},e.__onWebMessengerHostReady__=function(t){if(delete e.__onWebMessengerHostReady__,e[n]=t,o)for(var r=t.init.apply(t,o),s=0;s<c.length;s++){var u=c[s];r="t"===u.type?r.then(u.next):r.catch(u.next)}p&&t.render.apply(t,p),a&&t.destroy.apply(t,a);for(s=0;s<i.length;s++)t.on.apply(t,i[s])};var u=new XMLHttpRequest;u.addEventListener("load",s),u.open("GET",r+"/loader.json",!0),u.responseType="json",u.send()
}(window,document,"Bots", "<Your-Bot-sdk-url>");

var appId = '<Your app id>';

Bots.init({
        appId: appId, embedded: true
    }).then(function (res){
        console.log("init complete");

    });

Bots.render(document.getElementById('no-display'));


var inputElement = document.getElementById('text-input');
  inputElement.onkeyup = function(e) {
  if (e.key === 'Enter') {
    var totalMsg = Bots.getConversation().messages.length;
    if(Bots.getConversation().messages[totalMsg-1] && Bots.getConversation().messages[totalMsg-1].actions){
        
        var actions = Bots.getConversation().messages[totalMsg-1].actions.filter(function(action){
            return action.text === inputElement.value; //Improve it by performing case insensitive matching
        })
        if(actions){
            Bots.triggerPostback(actions[0]._id).then(function() {
                        inputElement.value = '';
                    });
        }
        
    }
    else{
       Bots.sendMessage(inputElement.value).then(function() {
                inputElement.value = '';
            }); 
    }
        
    }
   
  }


function displayUserMessage(message) {
    console.log(message);
            var conversationElement = document.getElementById('conversation');
            var messageElement = document.createElement('li');
            messageElement.innerText = message.name + ' says "' + message.text + '"';
            conversationElement.appendChild(messageElement);
        }

        function createButtonElement(action) {
            var btnElement = document.createElement('button');
            var btnTitle = document.createTextNode(action.text);
            btnElement.appendChild(btnTitle);
            btnElement.onclick = function(e){Bots.triggerPostback(action._id);};
            return btnElement;
        }

    function displayServerMessage(message) {
         console.log(message);
            var conversationElement = document.getElementById('conversation');
            var messageElement = document.createElement('li');
            var text = 'Server says "' + message.text + '"';
            messageElement.innerText = text;

            if(message.actions && message.actions.length > 0){
                var wrapperElement = document.createElement('div');
                for(var i = 0; i < message.actions.length; i++){
                    var action = message.actions[i];
                    var btnElement = createButtonElement(action);
                    wrapperElement.appendChild(btnElement);
                }
                messageElement.appendChild(wrapperElement);
                isPostBackRequired = true;
                lastPostBackServerMsg = message;
            }
            conversationElement.appendChild(messageElement);


        }
  // display new messages
  Bots.on('message:sent', displayUserMessage);

  Bots.on('message:received', displayServerMessage);



NOTE:
1. Bot-sdk-url is url of your sdk directory. If you copy bot-sdk inside js directory as bot-client-sdk-js and server is running on port 8000, your bot-sdk-url would be http://localhost:8000/js/bots-client-sdk-js
2. app-id mentioned in above code is given on channels page of IBCS (once you register a web channel)

3. Input element in which user types message is enhanced to handle postBack message of user.
4. displayUserMessage function adds user typed message in list
5. displayServerMessage function adds server message in list. It also create appropriate buttons if server wants user to select one value.

UI is very crude but it gives you complete control to decorate it.

Now we have a very basic UI ready. My idea is to enhance it further and add voice feature to it in my Next blog.

Thats all in this blog.