Showing posts with label salesforce chatbot. Show all posts
Showing posts with label salesforce chatbot. Show all posts

Saturday, December 21, 2019

Create And Play With Your ChatBot!



Hello Passionate Learners,

Today, I will be discussing a very interesting topic of ChatBot. A lot of enhancements and
modification will be required if you are building it for your client or for your learning.
This blog will be a starter for many of you.

In this blog, you will be able to learn:

  1. How to create your chatbot
  2. How to add lightning Components in the Utility Bar
  3. How to use custom metadata in your daily work

Requirements:
  1. Lightning Components
  2. Apex Classes
  3. Custom Metadata

I will be starting with a basic example. Whenever you open a chatbot, the first thing
that comes to your mind is the greetings. So, in this example we will send a ‘Hello’ message
and in return we will receive a text message from the bot. 

Let's start by building our apex classes and Custom Metadata.

  1. Class Name:BotField
Description: This class will be used when you want to display object record’s fields
onto the chat window.
Example: for Account object, you want to display Name, Phone, Industry etc.

Code:
public class BotField {

    @AuraEnabled public String name { get;set; }
    @AuraEnabled public String value { get;set; }
    @AuraEnabled public String linkURL { get;set; }
    
    public BotField(String name, String value) {
        this.name = name;
        this.value = value;
    }
    
    public BotField(String name, String value, string linkURL) {
        this.name = name;
        this.value = value;
        this.linkURL = linkURL;
    }

}

  1. Class Name: BotRecord
Description: This used to display the records on the chat window.
Code:
public class BotRecord {

    @AuraEnabled 
    public List<BotField> fields { get;set; }
    
    public BotRecord(List<BotField> fields) {
        this.fields = fields;
    }

}

  1. Class Name: BotItem
Description: This class will return the helps topics which you can ask to the bot.
Code:
public class BotItem {

    @AuraEnabled public String name { get;set; }
    @AuraEnabled public String linkURL { get;set; }
    
    public BotItem(String name) {
        this.name = name;
    }
    
    public BotItem(String name, string linkURL) {
        this.name = name;
        this.linkURL = linkURL;
    }

}

  1. Class Name: BotMessage
Description:

Code:
public virtual class BotMessage {

    @AuraEnabled public String author { get;set; }
    @AuraEnabled public String messageText { get;set; }
    @AuraEnabled public List<BotRecord> records { get;set; }
    @AuraEnabled public List<BotItem> items { get;set; }
    @AuraEnabled public List<BotMessageButton> buttons { get;set; }
    @AuraEnabled public String imageURL { get;set; }

    public BotMessage() {
    }

    public BotMessage(String author, String messageText) {
        this.author = author;
        this.messageText = messageText;
    }

    public BotMessage(String author, String messageText, List<BotRecord> records) {
        this.author = author;
        this.messageText = messageText;
        this.records = records;
    }

    public BotMessage(String author, String messageText, List<BotItem> items) {
        this.author = author;
        this.messageText = messageText;
        this.items = items;
    }
}

  1. Class Name: BotResponse

Code:
public class BotResponse {

    @AuraEnabled public List<BotMessage> messages { get; set; }
    @AuraEnabled public Map<String, String> session { get; set; }
    
    public BotResponse() {
    }

    public BotResponse(BotMessage[] messages) {
        this.messages = messages;
    }

    public BotResponse(List<BotMessage> messages, Map<String, String> session) {
        this.messages = messages;
        this.session = session;
    }

    /**
     * Convenience constructor to create a response with a single message
     */
    public BotResponse(BotMessage message) {
        this.messages = new BotMessage[]{message};
    }

    /**
     * Convenience constructor to create a response with a single message
     */
    public BotResponse(BotMessage message, Map<String, String> session) {
        this.messages = new BotMessage[]{message};
        this.session = session;
    }
    
}

  1. Class Name: BotHandler

Code:
public interface BotHandler {
    
    BotResponse handle(String utterance, String[] params, Map<String, String> session);
    
}

  1. Class Name: BotController

Code:
public with sharing class BotController {
    
    //wrapper class
    class HandlerMapping {
        public String handlerClassName;
        public Pattern utterancePattern;
        public HandlerMapping(String handlerClassName, String patternStr) {
            this.handlerClassName = handlerClassName;
            this.utterancePattern = Pattern.compile(patternStr);
        }
    }
    
    static List<HandlerMapping> handlerMappings;
    
    /*********************************
    
    description: method to get all the command in the bot command object.
    
    *********************************/
    static {
        List<Bot_Command__c> commands = [SELECT apex_class__c, pattern__c FROM
Bot_Command__c WHERE Active__c = True ORDER BY Name];
        System.debug(commands);
        List<HandlerMapping> mappings = new List<HandlerMapping>();
        for (Bot_Command__c command : commands) {
            mappings.add(new HandlerMapping(command.apex_class__c, command.pattern__c));
        }
        system.debug('mappings-->'+mappings);
        handlerMappings = mappings;
    }
    
    /*********************************
    
    description: method to match the text written and display the response from the bot command object.
    
    *********************************/
    @AuraEnabled
    public static BotResponse submit(String utterance, Map<String, String> session) {
        try{
            system.debug('utterance->'+utterance);
            system.debug('session1->'+session);
            if (session != null) {
            system.debug('session->'+session);
                String nextCommand = session.get('nextCommand');
                system.debug('nextCommand ->'+nextCommand );
                if (nextCommand != null) {
                    Type t = Type.forName('', nextCommand);
                    system.debug('Type T->'+t );
                    BotHandler h = (BotHandler)t.newInstance();
                    return h.handle(utterance, null, session);
                }
            }
            for (HandlerMapping mapping : BotController.handlerMappings) {
                system.debug('@@');
                Matcher utteranceMatcher = mapping.utterancePattern.matcher(utterance);
                if (utteranceMatcher.matches()) {
                    Type t = Type.forName('', mapping.handlerClassName);
                    system.debug('Type T->'+t );
                    BotHandler h = (BotHandler)t.newInstance();
                    List<String> params = new List<String>();
                    for (Integer i=1; i<=utteranceMatcher.groupCount(); i=i+1) {
                        params.add(utteranceMatcher.group(i).trim());
                    }
                    system.debug('ut'+utterance);
                    system.debug('para'+params);
                    return h.handle(utterance, params, session);
                }
            }
            return new BotResponse(new BotMessage('Bot', 'I don\'t know how to answer that'));
        }
        catch (Exception e) {
            System.debug(e);                
            return new BotResponse(new BotMessage('Bot', 'Oops, something went wrong invoking that command'));
        }
    }
}

  1. Class Name: BotMessageButton
Code:
public class BotMessageButton {

    @AuraEnabled public String label { get;set; }
    @AuraEnabled public String value { get;set; }
    
    public BotMessageButton(String label, String value) {
        this.label = label;
        this.value = value;
    }
    
}

Now the Class structure is all set. Make sure that all the classes are in place and saved.
In the later part, we will create Custom Metadata, Lightning component and the
Apex Class(apex class - specifically to our functionality). Functionality here means,
how to respond to the query entered by the user. If you say Hello to the Bot then how are
we going to reply to the same.
Here we will take the example, How to respond to Hello or Hi.

  1. Create an Object with name as Bot Command and create the fields as shown below:


  1. Create two records as shown in the screenshot below. Basically,
We have created two records. 1. To repond to ‘Hi’ 2. To respond to ‘Hello’.


  1. Now we will create the Custom Metadata and the fields as shown below.


  1. Create two records for each utterance done by User.
I have taken only two scenarios discussed in point 2. Please refer to the image below:


  1. Create the Apex Class with Name: HandlerHello

Code:
public with sharing class HandlerHello implements BotHandler {
    
    public BotResponse handle(String utterance, String[] params, Map<String, String> session) {
        //Integer randomNumber = Integer.valueof((Math.random() * sizeof the metadata fetch with classname));
        List<BotChat_Answers_Metadata__mdt> md = new List<BotChat_Answers_Metadata__mdt>();
        md= [select id,Class_Name__c,Response__c from BotChat_Answers_Metadata__mdt
where Class_Name__c='HandlerHello'];
        system.debug('md'+md);
        
        Integer randomNumber = Integer.valueof((Math.random() * md.size()));
        String resp='';
        if(md.size()>0){
            resp = md[randomNumber].Response__c;
        }
        return new BotResponse(new BotMessage('Bot', resp ));
    }
    
}

So, here we are getting all the metadata records with the class as
HandlerHello and then we will pick any of the response and display it on the UI.

Lightning Component

Now it's time to create the lightning which will be shown to user to chat.
I have taken the name as ‘Bot’ for the component.

  1. Copy and paste the cmp file
<aura:component implements="force:appHostable,
flexipage:availableForAllPageTypes,
flexipage:availableForRecordHome,force:hasRecordId,
forceCommunity:availableForAllPageTypes,force:lightningQuickAction" 
                access="global"
                controller="BotController" >
<aura:attribute name="messages" type="Object[]"/>
    <aura:attribute name="session" type="Array"/>
    <aura:attribute name="inputT" type="String"/>
    <aura:attribute name="height" type="String" default="500px"/>
    <aura:attribute name="URLfield" type="Boolean" default="false"/>
    <aura:attribute name="NonURLfield" type="Boolean" default="true"/>
    
   
    <div style="{#'height: '+v.height}" class="slds-p-vertical--x-small">
        <div aura:id="content" class="content">
            <aura:iteration items="{!v.messages}" var="message"> 
                <lightning:layout >
                    <lightning:layoutitem padding="around-small">
                        <lightning:icon iconName="{#message.author == 'Me' ? 'standard:avatar_loading' : 'standard:custom_notification'}" size="small"/>
                    </lightning:layoutitem>             
                
                    <lightning:layoutitem padding="around-small" flexibility="grow">
                        <p><strong>{#message.author}</strong></p>
                        <p class="slds-text-body">{#message.messageText}</p>
                            <!--<a href ="https://sank-dev-ed.lightning.force.com/lightning/o/Opportunity/home">Opp</a>-->
                     <aura:iteration items="{!message.records}" var="record">
                            <dl class="slds-list--horizontal slds-wrap">
                                <aura:iteration items="{!record.fields}" var="field">
                                    <dt class="slds-item--label slds-text-color--weak slds-truncate">{!field.name}:</dt>
                                    <dd class="slds-item--detail slds-truncate">
                                        <aura:if isTrue="{!field.linkURL}">
                                            <a href="{#field.linkURL}">{#field.value}</a>
                                            <aura:set attribute="else">
                                                {#field.value}
                                            </aura:set>
                                        </aura:if>
                                    </dd>
                                </aura:iteration>
                            </dl>
                        </aura:iteration>
                    </lightning:layoutitem>
                </lightning:layout>
            </aura:iteration>
            
            <div class="footer slds-form-element slds-p-horizontal--x-small">
                <div class="slds-form-element__control">
                    <!-- <input type="text" class="slds-input" placeholder="Ask Lightning Bot..." onkeypress="{!c.utteranceHandler}" /> -->
                    <lightning:input value ="{!v.inputT}" aura:id="inputText" class="inputText" label="Have a question?" placeholder="Ask Lightning Bot..."  />
                    <lightning:button variant="brand" label="Search" title="Brand action" onclick="{! c.utteranceHandler }" />
                </div>
            </div>
            
     </div>
    </div>
</aura:component>

  1. Copy and paste the Controller.js
({
utteranceHandler : function(component, event, helper) {
        debugger;
/*if (event.keyCode !== 13) {
            return;
        }*/
        
        var utterance = component.find("inputText").get("v.value");
        if($A.util.isEmpty(utterance)){
            //component.set("v.messages", "");
        }
        else{
            
            var messages = component.get("v.messages");
            messages.push({author: "Me", messageText: utterance});
            component.set("v.messages", messages);
            var msg = component.get("v.messages");
            helper.submit(component, utterance, component.get('v.session'), function(answer) {
                if (answer) {
                    console.log(answer);
                    component.set("v.session", answer.session);
                    Array.prototype.push.apply(messages, answer.messages);
component.set("v.messages", messages);
                    component.set("v.inputT","");
    
                }
            });
        }
}
})

  1. Copy and paste the Helper.js
({
submit : function(component, utterance, session, callback) {
        var action = component.get("c.submit");
action.setParams({
       "utterance": utterance,
            "session": session
     });

        action.setCallback(this, function(a) {
            var state = a.getState();
            if (state === "SUCCESS") {
                callback(a.getReturnValue());
            } else if (state === "INCOMPLETE") {

            } else if (state === "ERROR") {
                var errors = a.getError();
                console.log(errors);
            }
     });
     $A.enqueueAction(action);
    }
})

  1. Css
.THIS {
    position: relative;
box-sizing: border-box;
    overflow: hidden !important;
}

.THIS>.content {
position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 80px;
    overflow: scroll;
}

.THIS>.footer {
position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
}
.THIS .footer {
position: fixed;
    left: 50px;
    right: 70px;
    bottom: 0;
    margin-top:500px;
}
.THIS>.author {
    font-weight: bold;
}

.THIS dl {
    padding: 4px 0 4px 0;
    border-bottom: solid 1px #f4f6f9;
}

.THIS .list-item {
margin: 4px 0 4px 0;
    border-bottom: solid 1px #f4f6f9;
}

.THIS .slds-post {
    border-top: none !important;
}

.THIS .message-button {
    display: block;
    width:100%;
    margin-top: 4px;
    margin-left: 0;
}

.THIS .slds-form-element__label {
    display: none;
}

.THIS .slds-file-selector {
    margin-top: 4px;
    width: 100% !important;
}

.THIS .slds-file-selector__dropzone {
    width: 100% !important;
}
.THIS .inputText {
color: black;
}

  1. Renderer:
({
    rerender: function (component, helper) {
        this.superRerender();
        window.setTimeout(
     $A.getCallback(function() {
                if (component.isValid()) {
                    var el = component.find("content").getElement();
    el.scrollTop = el.scrollHeight;
                }
    }),200);
}
    
})

Adding Lightning component to the Utility Bar

  1. Search App Manager in the Setup and open it. It will be done in Lightning Experience.
  2. Select the App for which you want to add the component.
  3. On the left panel, click on the Utility Items as shown in the picture below:

  1. Click on Add and search for your component.
  2. Click save 
  3. Navigate to the Sales App.
  4. You will see a utility item on left bottom side:

  1. Click on the Bot Icon below and type Hi or Hello.
  2. If everything is fine, then you will see the response as shown in the image below:
    So Basically, we need to type what we have mentioned in the Invocation Pattern field
in the Bot Command record.


If you want to extend the functionality, then first you need to create a apex class for that functionality and then create a record in the bot command object.

For example ,we want to see the cases that are open.
As discussed earlier, create the apex class.
Sample Code:
public with sharing class HandlerMyOpenCases implements BotHandler {
    
    public BotResponse handle(String utterance, String[] params, Map<String, String> session) {
        system.debug('inside case');
        List<Case> cases = 
            [SELECT Id, CaseNumber, Subject, Status, Priority, Contact.Id, Contact.Name 
             FROM Case WHERE OwnerId =:UserInfo.getUserId() AND Status != 'Closed'];
        
        List<BotRecord> records = new List<BotRecord>();
        system.debug('inside case');
        for (Case c : cases) {
            List<BotField> fields = new List<BotField>();
            fields.add(new BotField('Case Number', c.CaseNumber, '#/sObject/' + c.Id + '/view'));
            fields.add(new BotField('Subject', c.Subject));
            fields.add(new BotField('Priority', c.Priority));
            fields.add(new BotField('Status', c.Status));
            fields.add(new BotField('Contact', c.Contact.Name, '#/sObject/' + c.Contact.Id + '/view'));
            records.add(new BotRecord(fields));
            system.debug('records-->'+records);
        }
        BotMessage message = new BotMessage('Bot', 'Here are your open cases:', records);
        return new BotResponse(message);
        
    }
    
}

Secondly, go and create the record in the Bot Command Object.



Once this is set up, let go check the bot UI.
Type Open Case and see the response:

Thank You!

Do let me know your views on this.


Other Posts:

Mostly Viewed