Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Live agent handoff #128

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5a0e87d
updating live agent handoff sample
orsharab Apr 3, 2020
6e06065
Merge branch 'master' into orel_handoff
orsharab Apr 3, 2020
963dfa2
fixing scroll in agent webchat
orsharab Apr 5, 2020
4c642e4
fix request chat bot url params
orsharab Apr 5, 2020
190401a
adding agent authentication comment
orsharab Apr 5, 2020
e907032
aligning with master
orsharab Apr 5, 2020
37c6b35
allow app service deployment from live agent handoff branch (#55)
orsharab-zz Apr 5, 2020
10adfb3
Update/liva agent handoff (#65)
amir-microsoft Apr 8, 2020
53dab54
Update live agent handoff (#71)
amir-microsoft Apr 19, 2020
3da5290
Live agent handoff sync (#79)
amir-microsoft Jun 15, 2020
1655218
Merge branch 'master' into live_agent_sync_july
amir-microsoft Jul 20, 2020
9a2ac96
Merge pull request #81 from microsoft/live_agent_sync_july
amir-microsoft Jul 20, 2020
8491b27
Merge branch 'master' into live_agent_handoff
Aug 31, 2020
2122bc4
Merging master
Aug 31, 2020
9f96535
Merge pull request #85 from microsoft/live_agent_handoff_update
amir-microsoft Aug 31, 2020
b5c7785
Better inline link to relative image
arieschwartzman Sep 1, 2020
e7c7677
Merge pull request #87 from microsoft/arieschwartzman-patch-1
arieschwartzman Sep 1, 2020
ac11ed3
Merge pull request #88 from microsoft/master
amir-microsoft Sep 1, 2020
2a3d519
Merge pull request #91 from microsoft/master
amir-microsoft Sep 3, 2020
2c637b7
Merge pull request #99 from microsoft/master
alonitms Mar 10, 2021
d418b0c
Merge pull request #102 from microsoft/master
guy-microsoft May 11, 2021
99b2ab6
Merge pull request #107 from microsoft/master
orsharab-yy Jul 18, 2021
9a37913
Merge pull request #112 from microsoft/master
amir-microsoft Jan 4, 2022
66df97b
Update azuredeploy.json (#113) (#114)
amir-microsoft Jan 25, 2022
f6f1ff8
merging master (#119)
amir-microsoft Feb 24, 2022
97ca79a
Merge branch 'master' into master_to_live_agent
Feb 24, 2022
55e8320
merging master
Feb 24, 2022
b3605f5
Merge pull request #122 from microsoft/master_to_live_agent
amir-microsoft Feb 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ A simple web page that allows users to communicate with the [Azure Health Bot](h

1.Deploy the website:

[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fmicrosoft%2FHealthBotContainerSample%2Fmaster%2Fazuredeploy.json)
[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fmicrosoft%2FHealthBotContainerSample%2Flive_agent_handoff%2Fazuredeploy.json)

2.Set the following environment variables:

Expand Down Expand Up @@ -51,5 +51,19 @@ Pass your preferred geographic endpoint URI by setting the environment variable:

**Note:** If you are deploying the code sample using the "Deploy to Azure" option, you should add the above secrets to the application settings for your App Service.

## Agent webchat
If the agent webchat sample is also required, [switch to the live agent handoff branch](https://github.com/Microsoft/HealthBotContainerSample/tree/live_agent_handoff)
## Live agent handoff sample

The live agent handoff sample is wrapper around the standard webchat that is generally used by end users. This sample is intended for testing the handoff scenario that is built-in to your Health Bot instance.

To access the sample you should follow the deployment instructions and request the `/agent.html` path from your browser. This will load a dummy login page that illustrates the agent experience (you can provide any values to access the agent portal). Within the agent portal you can issue agent commands to interact with end users that are talking with your bot.

The wrapper adds to the server.js file an agent flagging function: `function isAgentAuthenticated(req)` which will serve the agent webchat if a `true` value is returned. You should implement custom logic in this function that returns a `true` value once your agent has been authenticated.

**IMPORTANT:**
The sample login page is for testing and demonstration purposes only. You MUST authenticate agent access in a production deployment of the agent webchat. The agent webchat provides access to sensitive end user information.

## Customizing the webchat

You can send programmed messages to the agent webchat by invoking the `function talk(message)`. In the sample we have added example buttons with issue some of the built-in agent commands.

[Learn more about agent webchat functionality](https://docs.microsoft.com/en-us/HealthBot/handoff)
2 changes: 1 addition & 1 deletion azuredeploy.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
},
"branch": {
"type": "string",
"defaultValue": "master",
"defaultValue": "live_agent_handoff",
"metadata": {
"description": "The branch of the GitHub repository to use."
}
Expand Down
48 changes: 48 additions & 0 deletions public/agent.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title>Health Bot</title>
<script src="https://cdn.botframework.com/botframework-webchat/latest/webchat-es5.gzip.js"></script>
<link href="stylesheets/style.css?v=5" rel="stylesheet" />
<link href="stylesheets/agent.css?v=5" rel="stylesheet" />
</head>

<body>
<h1> Agent Webchat</h1>
<h2 id="logon-title"> Login page </h2>

<form id="logon-form">
<label for="user-name">Enter your name (as it should appear in the chat)</label>
<br>
<input id="user-name" type="text">
<br><br>

<label for="user-id">Enter login ID here</label>
<br>
<input id="user-id" type="text">
<br><br>
<button id="submit-btn" type="submit">Log In</button>
</form>

<table id="webchat-container" class="invisible">
<tr>
<td style="width:150px; border-right: groove; vertical-align: top;">
<div class="agent-buttons hidden">
<button type="button" onclick="talk('list')"> List </button>
<button type="button" onclick="talk('connect')"> Connect </button>
<button type="button" onclick="talk('disconnect')"> End conversation </button>
<button type="button" onclick="talk('options')"> Options </button>
</div>
</td>
<td style="width:100%">
<div id="webchat" role="main" watermark="false"></div>
</td>
</tr>
</table>
<script src="index.js?v=5"></script>
<script src="agent.js"></script>
</body>

</html>
35 changes: 35 additions & 0 deletions public/agent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
var logon_form = document.getElementById('logon-form');
var logon_title = document.getElementById('logon-title');

var user_id = document.getElementById('user-id');
var user_name = document.getElementById('user-name');

logon_form.onsubmit = e => {
e.preventDefault();
logon_form.style.display = 'none';
logon_title.style.display = 'none';

document.querySelector(".agent-buttons").classList.toggle("hidden");
document.querySelector(".invisible").classList.toggle("invisible");

chatRequested({
userId: user_id.value,
userName: user_name.value,
agent: true
});
};

function talk(message) {
var input = document.querySelectorAll('[data-id]')[0];
var lastValue = input.value;
input.value = message;
var event = new CustomEvent('input', { bubbles: true });
event.simulated = true;
var tracker = input._valueTracker;
if (tracker) {
tracker.setValue(lastValue);
}
input.dispatchEvent(event);
var sendButton = document.querySelectorAll(".css-115fwte")[1];
sendButton.click();
}
34 changes: 22 additions & 12 deletions public/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const defaultLocale = 'en-US';

function requestChatBot(loc) {
function requestChatBot(info, loc) {
const params = new URLSearchParams(location.search);
const oReq = new XMLHttpRequest();
oReq.addEventListener("load", initBotConversation);
Expand All @@ -9,12 +9,22 @@ function requestChatBot(loc) {
if (loc) {
path += "&lat=" + loc.lat + "&long=" + loc.long;
}
if (params.has('userId')) {
path += "&userId=" + params.get('userId');

const userId = (info && info.userId) || (params.has('userId') ? params.get('userId') : undefined);
if (userId) {
path += "&userId=" + userId;
}

const userName = (info && info.userName) || (params.has('userName') ? params.get('userName') : undefined);
if (userName) {
path += "&userName=" + userName;
}
if (params.has('userName')) {
path += "&userName=" + params.get('userName');

if (info && info.agent) {
path += "&agent=true";
}


oReq.open("POST", path);
oReq.send();
}
Expand All @@ -31,17 +41,17 @@ function extractLocale(localeParam) {
}
}

function chatRequested() {
function chatRequested(info) {
const params = new URLSearchParams(location.search);
if (params.has('shareLocation')) {
getUserLocation(requestChatBot);
getUserLocation(info, requestChatBot);
}
else {
requestChatBot();
requestChatBot(info);
}
}

function getUserLocation(callback) {
function getUserLocation(info, callback) {
navigator.geolocation.getCurrentPosition(
function(position) {
var latitude = position.coords.latitude;
Expand All @@ -50,12 +60,12 @@ function getUserLocation(callback) {
lat: latitude,
long: longitude
}
callback(location);
callback(info, location);
},
function(error) {
// user declined to share location
console.log("location error:" + error.message);
callback();
callback(info);
});
}

Expand Down Expand Up @@ -134,7 +144,7 @@ function initBotConversation() {
else if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
if (action.payload && action.payload.activity && action.payload.activity.type === "event" && action.payload.activity.name === "ShareLocationEvent") {
// share
getUserLocation(function (location) {
getUserLocation(null, function (location) {
store.dispatch({
type: 'WEB_CHAT/SEND_POST_BACK',
payload: { value: JSON.stringify(location) }
Expand Down
103 changes: 103 additions & 0 deletions public/stylesheets/agent.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@

body{
font-family: calibri;
}


h1 {
background-color: grey;
color: white;
border: solid gray 1px;
font-weight: 500;
font-size: 20px;

margin-top: 0;
margin-bottom: 0;

line-height:30px;
padding-left: 5px;

position: relative;
top:0;
left: 0;
z-index: 100000;
}

#webchat-container {
height: calc(100% - 32px);
}

#webchat {
height: calc(100vh - 36px);
}

#logon-title{
font-weight: 700;

margin-top: 20px;
margin-left: 20px;
}

input {
width: 300px;
height: 20px;
margin-left: 20px;
}

button {
width: 130px;
height: 50px;
background-color: grey;

color: white;
font-size: 16px;
border: none;
outline: none !important;
margin-bottom: 20px;
}

#submit-btn {
margin-left: 20px;
}

.hidden{
display: none;
}

label {
margin-left: 20px;
}

#botContainer{
height: 100%;
}


#logon-form{
margin-top: 10px;
}

div.wc-header{
visibility: hidden;
}

.agent-buttons {
width: 130px;
padding: 10px;
}

table tr td {
padding: 0;
margin: 0;


}

table {
border: none;
outline: none;
}

.invisible {
visibility: hidden
}
8 changes: 8 additions & 0 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ function isUserAuthenticated(){
return true;
}

function isAgentAuthenticated(req) {
// add here the logic to verify the agent is authenticated
return Boolean(req.query.agent);
}

const appConfig = {
isHealthy : false,
options : {
Expand Down Expand Up @@ -102,6 +107,9 @@ app.post('/chatBot', async function(req, res) {
if (req.query.lat && req.query.long) {
response['location'] = {lat: req.query.lat, long: req.query.long};
}
if (isAgentAuthenticated(req)) {
response['isAgent'] = true;
}
response['directLineURI'] = DIRECTLINE_ENDPOINT_URI;
const jwtToken = jwt.sign(response, APP_SECRET);
res.send(jwtToken);
Expand Down