Creating an Outlook Add-In with Sentiment Analysis

A few months ago, I was reading an email that had a snippy tone to it and several people didn’t take too kindly to it.  Despite the tone, I don’t believe that the person who composed the email intended it to come off that way.  It got me thinking that it would be cool to have an Outlook Add-In that would use the Sentiment Analysis api made available by the Azure Cognitive Services to score an email.  I finally got around to putting this together this weekend and this is a quick overview of the setup.

Setting up Sentiment Analysis in Azure

Start by logging into your Azure portal and searching for Cognitive Services

cognitive services search.PNG

On the cognitive services blade, click the Add button which will show the “AI + Machine Learning” blade where you can search for “Text Analytics”.

cognitive services - add.PNG

text analytics search.PNG

After you select Text Analytics, you can provide you service name, subscription, location, etc as shown below.

create form.PNG

Your new service will have some basic information for getting started.  The keys that your service will use, links to additional information and tutorials, etc.

text analytics - configure.PNG

There isn’t anything else to configure.  The keys needed are in the link under Step 1.  You can regenerate the keys if you need to but this is where you’ll get the keys that you need when you call the api.

step 1 - manage keys.PNG

Building the Outlook Add-In

For this demo, I’m using a circular progress bar sample by Anders Ingemann.  I start by creating a Outlook Add-In project in Visual Studio 2017.  I’ll show how I created an Add-In that is available in the composer and scores your email message.vs - outlook add-in.PNG

Similar to SharePoint Add-Ins, the Outlook Add-In creates 2 projects; one that creates a manifest and the other is a web application.

vs- project files.PNG

If you run the project without making any changes, you’ll notice that the Add-In is available when reading an email, but I want it to be visible on compose.  You configure this via the manifest file found in the first project.  Look for the following lines (I apologize for the code images.  Posting code was causing issues):

ReadXML.PNG

 

Replace the first line with:

composexml.PNG

Now, since this is just a demo that I was playing with, I didn’t bother to change the file names and I left the MessageRead.css, MessageRead.html, and MessageRead.js files as is.  If this were an actual project, I would probably create new files for Compose. Open MessageRead.html and find the div with id “content-main.”  This is where the main body of your Add-In is rendered from.  Using the sample from the link above with some minor tweaks, I came up with the following:

compose html.PNG

The main change is the div with the class “numbers”, where I removed the list of spans (1 for each available percentage) and replaced it with a span where I will pass in the score provided by the sentiment analysis results. I created a new styles.less file and added the following contents, straight from the link above with one minor change.  I removed the inset class which is no longer needed since I’m passing in the score.

<pre>/*@import&nbsp;url(http://fonts.googleapis.com/css?family=Lato:100,300,400,700,900,100italic,300italic,400italic,700italic,900italic);*/
 
.radial-progress&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;@circle-size:&nbsp;120px;
&nbsp;&nbsp;&nbsp;&nbsp;@circle-background:&nbsp;#d6dadc;
&nbsp;&nbsp;&nbsp;&nbsp;@circle-color:&nbsp;#97a71d;
&nbsp;&nbsp;&nbsp;&nbsp;@inset-size:&nbsp;90px;
&nbsp;&nbsp;&nbsp;&nbsp;@inset-color:&nbsp;#fbfbfb;
&nbsp;&nbsp;&nbsp;&nbsp;@transition-length:&nbsp;1s;
&nbsp;&nbsp;&nbsp;&nbsp;@shadow:&nbsp;6px&nbsp;6px&nbsp;10px&nbsp;rgba(0,0,0,0.2);
&nbsp;&nbsp;&nbsp;&nbsp;@percentage-color:&nbsp;#97a71d;
&nbsp;&nbsp;&nbsp;&nbsp;@percentage-font-size:&nbsp;22px;
&nbsp;&nbsp;&nbsp;&nbsp;@percentage-text-width:&nbsp;57px;
&nbsp;&nbsp;&nbsp;&nbsp;margin:&nbsp;50px;
&nbsp;&nbsp;&nbsp;&nbsp;width:&nbsp;@circle-size;
&nbsp;&nbsp;&nbsp;&nbsp;height:&nbsp;@circle-size;
&nbsp;&nbsp;&nbsp;&nbsp;background-color:&nbsp;@circle-background;
&nbsp;&nbsp;&nbsp;&nbsp;border-radius:&nbsp;50%;
&nbsp;&nbsp;&nbsp;&nbsp;.circle
 
{
&nbsp;&nbsp;&nbsp;&nbsp;.mask,&nbsp;.fill,&nbsp;.shadow
 
{
&nbsp;&nbsp;&nbsp;&nbsp;width:&nbsp;@circle-size;
&nbsp;&nbsp;&nbsp;&nbsp;height:&nbsp;@circle-size;
&nbsp;&nbsp;&nbsp;&nbsp;position:&nbsp;absolute;
&nbsp;&nbsp;&nbsp;&nbsp;border-radius:&nbsp;50%;
}
 
.shadow&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;box-shadow:&nbsp;@shadow&nbsp;inset;
}
 
.mask,&nbsp;.fill&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;-webkit-backface-visibility:&nbsp;hidden;
&nbsp;&nbsp;&nbsp;&nbsp;transition:&nbsp;-webkit-transform&nbsp;@transition-length;
&nbsp;&nbsp;&nbsp;&nbsp;transition:&nbsp;-ms-transform&nbsp;@transition-length;
&nbsp;&nbsp;&nbsp;&nbsp;transition:&nbsp;transform&nbsp;@transition-length;
&nbsp;&nbsp;&nbsp;&nbsp;border-radius:&nbsp;50%;
}
 
.mask&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;clip:&nbsp;rect(0px,&nbsp;@circle-size,&nbsp;@circle-size,&nbsp;@circle-size/2);
&nbsp;&nbsp;&nbsp;&nbsp;.fill
 
{
&nbsp;&nbsp;&nbsp;&nbsp;clip:&nbsp;rect(0px,&nbsp;@circle-size/2,&nbsp;@circle-size,&nbsp;0px);
&nbsp;&nbsp;&nbsp;&nbsp;background-color:&nbsp;@circle-color;
}
 
}
}
 
.inset&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;width:&nbsp;@inset-size;
&nbsp;&nbsp;&nbsp;&nbsp;height:&nbsp;@inset-size;
&nbsp;&nbsp;&nbsp;&nbsp;position:&nbsp;absolute;
&nbsp;&nbsp;&nbsp;&nbsp;margin-left:&nbsp;(@circle-size&nbsp;-&nbsp;@inset-size)/2;
&nbsp;&nbsp;&nbsp;&nbsp;margin-top:&nbsp;(@circle-size&nbsp;-&nbsp;@inset-size)/2;
&nbsp;&nbsp;&nbsp;&nbsp;background-color:&nbsp;@inset-color;
&nbsp;&nbsp;&nbsp;&nbsp;border-radius:&nbsp;50%;
&nbsp;&nbsp;&nbsp;&nbsp;box-shadow:&nbsp;@shadow;
&nbsp;&nbsp;&nbsp;&nbsp;.percentage
 
{
&nbsp;&nbsp;&nbsp;&nbsp;height:&nbsp;@percentage-font-size;
&nbsp;&nbsp;&nbsp;&nbsp;width:&nbsp;@percentage-text-width;
&nbsp;&nbsp;&nbsp;&nbsp;overflow:&nbsp;hidden;
&nbsp;&nbsp;&nbsp;&nbsp;position:&nbsp;absolute;
&nbsp;&nbsp;&nbsp;&nbsp;top:&nbsp;(@inset-size&nbsp;-&nbsp;@percentage-font-size)&nbsp;/&nbsp;2;
&nbsp;&nbsp;&nbsp;&nbsp;left:&nbsp;(@inset-size&nbsp;-&nbsp;@percentage-text-width)&nbsp;/&nbsp;2;
&nbsp;&nbsp;&nbsp;&nbsp;line-height:&nbsp;1;
&nbsp;&nbsp;&nbsp;&nbsp;.numbers
 
{
&nbsp;&nbsp;&nbsp;&nbsp;margin-top:&nbsp;-@percentage-font-size;
&nbsp;&nbsp;&nbsp;&nbsp;transition:&nbsp;width&nbsp;@transition-length;
&nbsp;&nbsp;&nbsp;&nbsp;span
 
{
&nbsp;&nbsp;&nbsp;&nbsp;width:&nbsp;@percentage-text-width;
&nbsp;&nbsp;&nbsp;&nbsp;display:&nbsp;inline-block;
&nbsp;&nbsp;&nbsp;&nbsp;vertical-align:&nbsp;top;
&nbsp;&nbsp;&nbsp;&nbsp;text-align:&nbsp;center;
&nbsp;&nbsp;&nbsp;&nbsp;font-weight:&nbsp;800;
&nbsp;&nbsp;&nbsp;&nbsp;font-size:&nbsp;@percentage-font-size;
&nbsp;&nbsp;&nbsp;&nbsp;font-family:&nbsp;"Lato",&nbsp;"Helvetica&nbsp;Neue",&nbsp;Helvetica,&nbsp;Arial,&nbsp;sans-serif;
&nbsp;&nbsp;&nbsp;&nbsp;color:&nbsp;@percentage-color;
}
 
}
}
}
 
@i:&nbsp;0;
@increment:&nbsp;180deg&nbsp;/&nbsp;100;
 
.loop&nbsp;(@i)&nbsp;when&nbsp;(@i&nbsp;&lt;=&nbsp;100)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&amp;[data-progress="@{i}"]
 
{
&nbsp;&nbsp;&nbsp;&nbsp;.circle
 
{
&nbsp;&nbsp;&nbsp;&nbsp;.mask.full,&nbsp;.fill
 
{
&nbsp;&nbsp;&nbsp;&nbsp;-webkit-transform:&nbsp;rotate(@increment&nbsp;*&nbsp;@i);
&nbsp;&nbsp;&nbsp;&nbsp;-ms-transform:&nbsp;rotate(@increment&nbsp;*&nbsp;@i);
&nbsp;&nbsp;&nbsp;&nbsp;transform:&nbsp;rotate(@increment&nbsp;*&nbsp;@i);
}
 
.fill.fix&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;-webkit-transform:&nbsp;rotate(@increment&nbsp;*&nbsp;@i&nbsp;*&nbsp;2);
&nbsp;&nbsp;&nbsp;&nbsp;-ms-transform:&nbsp;rotate(@increment&nbsp;*&nbsp;@i&nbsp;*&nbsp;2);
&nbsp;&nbsp;&nbsp;&nbsp;transform:&nbsp;rotate(@increment&nbsp;*&nbsp;@i&nbsp;*&nbsp;2);
}
 
}
 
.inset&nbsp;.percentage&nbsp;.numbers&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;/*width:&nbsp;@i&nbsp;*&nbsp;@percentage-text-width&nbsp;+&nbsp;@percentage-text-width;*/
}
 
}
.loop(@i&nbsp;+&nbsp;1);
}
.loop(@i);
}
</pre>
<pre>

The javascript logic consists of just a few simple steps.  First, we get the message and pass it to the calculateSentiment function.  Then we construct the “document” that the sentiment analysis will score.  For more info on this document schema, visit the how-to page.  We then do an http post to your endpoint.  The Ocp-Apim-Subscription-Key is the key that we get from Azure that we saw at the beginning of this post.  Finally, we get the result if the post succeeds, and we pass it to the function that sets the chart value.

</pre>
<pre>(function&nbsp;()&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;'use&nbsp;strict';
&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;item;
 
&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;The&nbsp;Office&nbsp;initialize&nbsp;function&nbsp;must&nbsp;be&nbsp;run&nbsp;each&nbsp;time&nbsp;a&nbsp;new&nbsp;page&nbsp;is&nbsp;loaded.
&nbsp;&nbsp;&nbsp;&nbsp;Office.initialize&nbsp;=&nbsp;function&nbsp;(reason)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;item&nbsp;=&nbsp;Office.context.mailbox.item;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$(document).ready(function&nbsp;()&nbsp;{
 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;get&nbsp;the&nbsp;body&nbsp;of&nbsp;the&nbsp;message&nbsp;and&nbsp;calculate&nbsp;the&nbsp;score
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;item.body.getAsync('text',&nbsp;function&nbsp;(async)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;calculateSentiment(async.value)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});
&nbsp;&nbsp;&nbsp;&nbsp;};
 
&nbsp;&nbsp;&nbsp;&nbsp;function&nbsp;calculateSentiment(emailMessage)&nbsp;{
 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;build&nbsp;the&nbsp;document
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;documentsVal&nbsp;=&nbsp;[{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"language":&nbsp;"en",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"id":&nbsp;1,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"text":&nbsp;emailMessage
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}];
 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;documentsKey&nbsp;=&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"documents":&nbsp;documentsVal
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};
 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$.ajax({
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;type:&nbsp;'POST',
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;url:&nbsp;"https://eastus.api.cognitive.microsoft.com/text/analytics/v2.0/sentiment",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;headers:&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Ocp-Apim-Subscription-Key":&nbsp;"",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Content-Type":&nbsp;"application/json",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Accept":&nbsp;"application/json"
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;data:&nbsp;JSON.stringify(documentsKey,&nbsp;null,&nbsp;2),
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dataType:&nbsp;"json",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;success:&nbsp;function&nbsp;(result)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;documents&nbsp;=&nbsp;result.documents;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(var&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;documents.length;&nbsp;i++)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;msg&nbsp;=&nbsp;documents[i];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;score&nbsp;=&nbsp;msg.score;
 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;setChart(score)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;
 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;error:&nbsp;function&nbsp;(xhr,&nbsp;ajaxOptions,&nbsp;thrownError)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;console.log("Error&nbsp;statue:&nbsp;"&nbsp;+&nbsp;xhr.status);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;console.log("Thrown&nbsp;error:&nbsp;"&nbsp;+&nbsp;thrownError);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});
&nbsp;&nbsp;&nbsp;&nbsp;}
 
 
&nbsp;&nbsp;&nbsp;&nbsp;function&nbsp;setChart(pct)&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;window.randomize&nbsp;=&nbsp;function&nbsp;()&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$('.radial-progress').attr('data-progress',&nbsp;Math.floor(pct&nbsp;*&nbsp;100));
 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;displayVal&nbsp;=&nbsp;Math.round(pct&nbsp;*&nbsp;100)&nbsp;+&nbsp;'%';
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$('#app-pct').text(displayVal);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;setTimeout(window.randomize,&nbsp;200);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$('.radial-progress').click(window.randomize);
&nbsp;&nbsp;&nbsp;&nbsp;}
 
 
})();
 
 
 
</pre>
<pre>

The end result is as follows:

This slideshow requires JavaScript.

Integrating FAQ Bots with SharePoint

Everyday, we hear more and more about bots and I’ve had the opportunity to build several over the last few months.  They do offer some pretty cool possibilities around process automation and productivity.  Lately, I’ve been talking to people about creating an FAQ bot to free up people from answering common questions.

Creating an FAQ bot with QnA Maker

To get started, you’ll want to go to the QnA Maker site, where you will login with your Azure credentials, create, and setup your new bot.

Once you’re logged in, click the “Create a knowledge base” button at the top of the page.

qnaMaker

The next page is a simple 5 step process that will guide you through creating your knowledge base.

Create

 

 

Step 1: Click the “Create a QnA service” button which will take you to Azure to create your App Service.

step 1

Once in Azure, you’ll enter some basic information about your bot like the name, which needs to be unique, the type of pricing that you’ll want to use, resource group, site location, and other.  Note: Something odd happens after this step.  When the resource group, app service plan, and web app are created, the Web App Bot is missing.  In the next section, we’ll fix that.

azure - create

Step 2: Select the Azure ID that you want to use to create your bot, your subscription service, and the service name that you created in the previous step.  If it doesn’t appear in the Azure QnA service dropdown, then you likely need to click the “refresh this page” link above the dropdown lists.

step 2

Step 3: Provide a name for your knowledge base.

step 3

Step 4: This is where you point to the source of your knowledge base.  If you have an FAQ page somewhere, you can provide a link to the page.  In my case, I will be choosing to upload a word document that I created and stored on my local machine.

step 4

 

Step 5: Click the “Create your KB” button and wait for the knowledge base to be created from your source entered in step 4.

step 5

 

step 5 - saving

Once it’s done, you’ll see that your knowledge base has been created.  If you see any issues or need to make changes, you can do that here.

knowledge base

Once everything looks fine, click the Publish link in the menu bar and the Publish button on the next page.

qna publish

 

When everything is published, you’ll get the Success page which contains some information that you’ll want to keep handy.  Specifically, the guid in the 1st line which represents your bot Id, the host url on the 2nd line, and the guid on the 3rd line which is used for authorization.

published qna bot

 

Creating the Web App Bot in Azure

In the previous section, I mention that the Web App Bot is missing.  I don’t know if that’s intentional but either way, we need to introduce it.

Resource Group - missing bot app

At the top of your resources group, click the Add button and search for Web App Bot.

Add - Web App Bot

Fill out the form to create your bot.  You can decide what plan you want to use, but choose the existing resource group that was created by the QnA Maker and under Bot template, choose Question and Answer.

Create - Bot Service

It’ll take a few minutes to create the bot.  Once it’s done, you’ll see a few more entries in your resource group, and then it’s time to configure some settings.

Resources revisited

Click on the new Web App Bot that you just created, go to the App Settings, and update the 3 settings that are highlighted.  QnAKnowledgebaseId, QnAAuthKey, and QnAEndpointHostName.

add app keys

The 3 values can be found in the QnA Maker.  We saw it in the success page but you can view it anytime from the QnA Maker site.

published qna bot

Once entered, you can then go to the Test in Web Chat page to make sure that bot is working as expected.  If you visit this page before editing the 3 values previously mentioned, the bot will respond by telling you to enter those values.

bot - test results

When working, you can go to Channels.  This is where you can configure your bot to work in other apps like Skype and Teams but we are simply adding it to a SharePoint site so we can use the Web Chat that is already configured.  Click on the Edit button next to Web Chat.

bot channels

You’ll want to copy the text in the Embed Code section and replace the ‘Your_Secret_here’ text with one of the secret keys above it.  Clicking the Show button will let you see the key that you’ll insert into the iframe markup.

embed code

 

Adding the QnA bot to SharePoint

This is the easy part.  Go to your SharePoint site, edit the page, and insert the Embed webpart.

embed QnA

 

Edit the web part and insert the iframe markup that you copied from the Channels page in the final step of the previous section.  The web part will require you to insert a height and width to the markup.

embed editor part

Close the editor part, publish your page, and voilà!  You now have a simple bot that will use natural language and an FAQ list to answer those questions that you get ALL the time.

end result