Microsoft Bot Framework, Set User State From Javascript - javascript

I am attempting to proof of concept a simple support & ticketing bot using Microsoft Bot Framework v4 and Azure. I have successfully created a bot via the Bot Framework Composer and deployed it to Azure using a Web App Bot. I have configured and installed the BotFramework-WebChat component on my test site and successfully connected the bot to it. The BotFramework-WebChat component is installed via cdn.botframework.com. The test site is a single page application and the bot sits alongside this such that it can be accessed at the users convivence.
Everything is working as expected. I would like however to post certain contextual information to the Bot depending on the current browser state. For example, if a user is looking at product ABC when they converse with the bot I'd like the bot to know this. To achieve it I'd like to update the user state on the bot channel, preferably via vanilla JavaScript when the main application state changes. I'd like to do this seamlessly in the background and I do not want to rely on additional frameworks such as React (the Microsoft Documentation references React heavily and I unfortunately have no understanding of the particular product so find it difficult/impossible to follow).
My question is therefore in two parts. Is the above possible using the API provided by the BotFramwork-WebChat component and if so how would one go about doing so? If it is not possible I would also appreciate any assistance on alternative methods should such alternatives exist.

Answer to my own question, this is indeed possible with a minor modification to the example code used to create/render the webchat component.
First I've parameterised the DirectLine client creation. This client is key to posting to the channel once the WebChat component is set up:
var dl = window.WebChat.createDirectLine({ token: '#yourtokenhere#' });
window.WebChat.renderWebChat(
{
directLine: dl,
userID: '#yourid#',
username: '#yourusername#',
locale: 'en-US'
},
document.getElementById('#yourtargetelement')
);
Using the client I have then posted an event to the channel using the postActivity method:
dl.postActivity({
from: { id: '#youruserid#', name: '#yourusername#' },
type: 'event',
name: '#youreventname#',
value: {
// Put your parameters here
}
})
On the Composer side I have set up a trigger to activate on a "Custom Event" with the name #youreventname#. I can then parse out the parameters from the value property and set my user state.
I personally found the documentation for the Microsoft Direct Line JS protocol much easier to work with than going directly through the WebChat docs.
Thank you to #billoverton for the inspiration to use a custom event.

If you just want to capture information at the time the user renders the webchat session, you can do this by sending a custom event, which you'll also need to handle in your bot code. This specific code will not work if you are trying to create a single instance of the bot that persists across multiple pages, and you are wanting the page details sent every time, but the same concept should apply. This method sends the details only on the initial connection to the bot.
First you need to send an event from your bot rendering via custom store. Here is how I did it.
var store = window.WebChat.createStore({}, function(dispatch) { return function(next) { return function(action) {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch.dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
value: {
browserLanguage: navigator.languages[0],
page: window.location.href,
language: getCookie("LANGUAGE"),
country: getCookie("COUNTRYNAME")
}
}
});
}
return next(action);
}}});
Now your bot also needs to be set up to handle that event. Again, I stored the relevant details in "value" but you can design your object differently if you want. I store this in user state under userData.siteContext and then can use the information throughout the bot application. If you want a unique welcome message based on this information, you'll need to add that inside the if statement as well and add a condition that your welcome message in the onMembersAdded handler fires only for non-direct line channels.
if (context.activity.name && context.activity.name === 'webchat/join') {
const userData = await this.userDialogStateAccessor.get(context, {});
userData.siteContext = context.activity.value;
// siteContext specific welcome message if desired
await this.userState.saveChanges(context);
await this.conversationState.saveChanges(context);
}
If you need this information at other times than the original connection, you should be able to create another event from your page and set up an appropriate handler in the bot.

Related

How to isolate a Gunjs database?

I've been trying out GunJs for a couple of days now and I'm really enjoying it. As a starter project I've followed the Fireship chat dapp video aimed at building your own chat.
Here's the issue, now that I've finished the tutorial I would like to create my own chat. However, for some reason if I get a 'chat' node within my own app it seems to pick up on the same 'chat' node as the tutorial one that is online.
onMount(() => {
// Get Messages in large chat
db.get('chat')
.map()
.once(async (data, id) => {
if (data) {
// key for E2E - to do: change for web3
const key = '#foo';
var message = {
//transform the data
who: await db.user(data).get('alias'),
what: (await SEA.decrypt(data.what, key)) + '',
when: GUN.state.is(data, 'what'),
};
if (message.what) {
messages = [...messages.slice(-100), message]
}
}
})
})
This is also the case if I change the encryption key (then the messages just become undefined). Multiple questions arise from this:
Are graph node names unique within the whole of GunDb?
How do you handle conflicts where two gun-based apps call on the same node name?
Is this problem generally solved through filtering using 'header' props?
How do I make it pick up on only my data?
Even if I've read most of the docs, there seems to be something I'm missing in my comprehension of how the graph is generally seperated between apps. Any insight on how this works would be much appreciated.
Are graph node names unique within the whole of GunDb?
Yes.
How do you handle conflicts where two gun-based apps call on the same node name?
You don't. The expected result will be, they will overwrite each other.
Is this problem generally solved through filtering using 'header' props?
I don't think it's the right way to do it.
How do I make it pick up on only my data?
Use your own relay server.
Conclusion :
gunDB doesn't really care about the who fetch / put the data. If you want to protect your data, use your own relay server (not a public one), and put data in your user space. user space is readonly to the public, but read/write for the owner.

matrix-js-sdk setup and configuration

I am having some issues trying to connect to a matrix server using the matrix-js-sdk in a react app.
I have provided a simple code example below, and made sure that credentials are valid (login works) and that the environment variable containing the URL for the matrix client is set. I have signed into element in a browser and created two rooms for testing purposes, and was expecting these two rooms would be returned from matrixClient.getRooms(). However, this simply returns an empty array. With some further testing it seems like the asynchronous functions provided for fetching room, member and group ID's only, works as expected.
According to https://matrix.org/docs/guides/usage-of-the-matrix-js-sd these should be valid steps for setting up the matrix-js-sdk, however the sync is never executed either.
const matrixClient = sdk.createClient(
process.env.REACT_APP_MATRIX_CLIENT_URL!
);
await matrixClient.long("m.login.password", credentials);
matrixClient.once('sync', () => {
debugger; // Never hit
}
for (const room of matrixClient.getRooms()) {
debugger; // Never hit
}
I did manage to use the roomId's returned from await matrixClient.roomInitialSync(roomId, limit, callback), however this lead me to another issue where I can't figure out how to decrypt messages, as the events containing the messages sent in the room seems to be of type 'm.room.encrypted' instead of 'm.room.message'.
Does anyone have any good examples of working implementations for the matrix-js-sdk, or any other good resources for properly understanding how to put this all together? I need to be able to load rooms, persons, messages etc. and display these respectively in a ReactJS application.
It turns out I simply forgot to run startClient on the matrix client, resulting in it not fetching any data.

How to programmatically clear the content of the input field (sendbox) in the chat window | MS Bot framework | Directline bot

I'm using QnAMaker in the back end for my chat bot, which is running in direct line bot channel. In a situation i want to clear the contents in the input field, for that i've used the following simple JavaScript line
document.querySelector("[aria-label='Sendbox']").value ="";
It clears the content at that moment, however it appears again when we click inside the input field to type the next question. Hence the content is actually not cleared.
So suggest me a way with which i should programmatically clear the input field (sendbox) of the chat window permanently.
You might be interested in this answer: How to add AutoComplete/AutoSuggestion in Microsoft botframework webchat using React.js
Web Chat uses Redux which has a Redux store that can use
Redux middleware. Web chat has an action called
WEB_CHAT/SET_SEND_BOX that can be used to respond to what the
user types in the text input box like this:
const store = window.WebChat.createStore(
{},
store => next => action => {
if (action.type === 'WEB_CHAT/SET_SEND_BOX') {
const user_entered_text = action.payload.text;
// Use the text to query the Azure database and display suggestions
}
return next(action);
}
);
When the user clicks on a suggestion or presses the right key, you can
use that same action to change what's in the text input box like this:
store.dispatch({
type: 'WEB_CHAT/SET_SEND_BOX',
payload: {
text: user_selected_suggestion,
}
});
There are samples in the Web Chat repo that may help with using
Redux actions in Web Chat
You're trying to edit the contents of the send box without using the Redux store, and so Web Chat isn't aware of the changes you're trying to make. If you use the WEB_CHAT/SET_SEND_BOX action with empty text then you can clear the send box correctly.
The exact solution for the issue is the following piece of code.
function clearinput()
{
store.dispatch({
type: 'WEB_CHAT/SET_SEND_BOX',
payload: {
text: "",
}
});
document.querySelector("[aria-label='Sendbox']").value ="";
}
thanks Kyle Delaney for the detailed explanation, based on your input I've achieved it.

How can I make Dialogflow agent greet user if they have used the action before?

I'm using Actions On Google / Dialogflow, and I'm trying to make a function that will greet a user by their name if they've used the action before, and if not, will ask for their name. I have tried to map this to the "Welcome intent" through fulfillment, but whenever I try to run the action on the simulator, I get this error:
Error 206: Webhook Error
Which Initially would make sense if this was mapped to another intent, but I'm wondering if I'm getting this error because you can't have a fulfillment with the welcome intent?
Here's the code I'm using in the inline editor which may be the problem:
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request,response) => {
const agent = new WebhookClient({ request, response });
function welcome(conv) {
if (conv.user.last.seen) {
conv.ask(`Welcome back ${name}!`);
} else {
conv.ask('Welcome to The app! My name is Atlas, could I get your name?');
}}
let intentMap = new Map();
intentMap.set('Welcome Intent', welcome);
agent.handleRequest(intentMap);
How come this isn't working? Do I need to implement user login? Do I need to use a function that would write to a firestore databbase?
Thanks for the help or suggestions!
Let's clear a few things up to start:
You can have fulfillment with your welcome intent.
You do not need user login. Although using Google Sign In for Assistant can certainly be used, it doesn't fundamentally change your problem here.
You do not need to use a function that writes to the firestore database. Again, you could use it, but this doesn't change your problems.
The specific reason this isn't working is because the conv parameter in this case contains a Dialogflow WebhookClient rather than an actions-on-google Conversation object.
To get the Conversation object with the parameter you have, you can call conv.getConv(), which will give you an object that has a user parameter. So this may look something like
function welcome(conv) {
let aog = conv.getConv();
if (aog.user.last.seen) {
conv.ask(`Welcome back ${name}!`);
} else {
conv.ask('Welcome to The app! My name is Atlas, could I get your name?');
}}
There are, however, still some issues with this. Most notably, it isn't clear where name will come from. I assume you will get it out of the user store object, but you don't seem to have done this.
For anyone who comes across this question in the future and just wants a straight forward answer without having to search through ambiguous answers / documentation, here is what to do step by step:
note: I ended up using the Google Sign in method, but even if this isn't your goal, i'll post the link to the alternative method.
1) Import the actions on google module. What people / tutorials don't to show is you have to import the library like this (for user login):
const {
dialogflow,
Permission,
SignIn
} = require('actions-on-google')
instead of
const dialogflow = require('actions-on-google')
2) Use this code:
const app = dialogflow({
clientId: '<YOUR CLIENT ID from Actions on Google>',
});
app.intent('Start Signin', conv => {
conv.ask(new SignIn('To get your account details'));
});
app.intent('Get Signin', (conv, params, signin) => {
if (signin.status === 'OK') {
const payload = conv.user.profile.payload;
conv.ask(`Welcome back ${payload.name}. What do you want to do next?`);
} else {
conv.ask(`I won't be able to save your data, but what do you want to do next?`);
}
});
This function will ask the user for a login, and next time you invoke the intent, it will say "Welcome back name", because google automatically saves it.
Here's the link to the alternative method:

How to send custom channel data when using web chat client with bot framework?

I'm trying to pass custom data along with every message that gets sent to my bot. There seems to be a way to achieve this but the example code is incomplete and because I'm a n00b at Javascript I have no clue how to get this right :-)
Here's the script I am using to set everything up:
const botConnection = new BotChat.DirectLine({
secret: '#directLineSecret',
});
BotChat.App({
bot: bot,
botConnection: botConnection,
user: user,
resize: 'detect'
}, document.getElementById('bot'));
I am trying to apply what is shown here:
var dl = new BotChat.DirectLine({secret});
BotChat.App({botConnection: {
… dl,
postActivity: activity => dl.postActivity({
… activity,
channelData: // your data goes here
}),
// other Chat props
});
The code above seems to intercept all calls to postActivity and adds custom channel data. But it also contains these unfortunate "...". How would I have to change my initialisation code so that it will intercept calls and add some keys/values to the channelData object?
Found the answer via the Github page of the web chat client.
The "..." is not omitting any code but is Javascript spread syntax
The correct code:
BotChat.App({
botConnection:
{
...botConnection,
postActivity: activity => {
// Add whatever needs to be added.
activity.channelData.MyKey = "MyValue";
return botConnection.postActivity(activity)
}
},
bot: bot,
user: user,
resize: 'detect',
}, document.getElementById('bot'));

Categories

Resources