I've created an html file with embedded Watson Virtual Agent chat bot, code similar below, with WVA strictly using the building core capabilities:
IBMChat.init({
el: 'ibm_chat_root',
baseURL: 'https://api.ibm.com/virtualagent/run/api/v1',
botID: '',
XIBMClientID: '',
XIBMClientSecret: ''
});
What I noticed is if I run the WVA in Preview mode, and have input "pay bill", the WVA can come back with two piece response, with first:
Accessing your account information...
and second the make payment:
Your account balance is $42.01 due on 5/17/2017. What would you like to do? (More options coming soon!)
However, if I enter the same in my HTML chatbot, the response only comes back with the first part:
Accessing your account information...
and second part never comes out.
Does anyone else experience the same problem?
The version in the "Preview" mode has some mock "action" handlers setup. Obviously, not every one of you users would owe $42! In the sample code on the github, the mock action handlers are not setup. There are examples on how to subscribe to those action events with handlers here: https://github.com/watson-virtual-agents/chat-widget/tree/master/examples/basic-actions-example
As of 5/31/17 you can cover all the built in actions using the code snippet below...
const config = { instance: null };
const getUserProfileVariablesMap = {
'bill_amount': '42.01',
'payment_due_date': (() => {
const currentDate = new Date(new Date().getTime() + 24 * 60 * 60 * 1000);
return `${currentDate.getMonth() + 1}/${currentDate.getDate()}/${currentDate.getFullYear()}`;
})(),
'authorized_users': 'Bob Everyman and Jane Doe'
};
const getUserProfileVariables = (data) => {
const variables = data.message.action.args.variables;
variables.forEach(v => {
const value = getUserProfileVariablesMap[v];
(value) ? config.instance.profile.set(v, value) : config.instance.profile.set(v, '[sample data]');
});
config.instance.sendSilently('success');
};
const success = () => config.instance.sendSilently('success');
const agent = () => config.instance.receive('On your own site you would run code to connect to an agent now.');
const accountSettings = () => config.instance.receive('On your own site you would run code to open the Account Settings page now.');
function registerActions(instance) {
config.instance = instance;
instance.subscribe('action:getUserProfileVariables', getUserProfileVariables);
instance.subscribe('action:updateAddress', success);
instance.subscribe('action:updateUserName', success);
instance.subscribe('action:updatePhoneNumber', success);
instance.subscribe('action:updateEmail', success);
instance.subscribe('action:payBill', success);
instance.subscribe('action:sendPaymentReceipt', success);
instance.subscribe('action:agent', agent);
instance.subscribe('action:openAccountSettingsPage', accountSettings);
};
window.IBMChatActions = {
registerActions: registerActions
};
// window.IBMChatActions.registerActions(window.IBMChat);
On the Administrative Preview, you are getting fake code stubs that handle action requests from the agent.
When one of these actions are invoked, the widget will print the "Processing..." message and then invoke all registered subscribers for that action. It is up to these registered subscribers to continue the conversation flow by silently sending "success", "failure", or "cancel" back to the server.
For example, the agent might pass down the "payBill" action. You would want to call your payment gateway, determine if it was successful, and then notify the agent of the result:
IBMChat.init(/* Settings */);
IBMChat.subscribe('action:payBill', function() {
var data = {
amount: IBMChat.profile.get('amount'),
card: {
number: IBMChat.profile.get('cc_number'),
// ... other private card data
}
};
$.post('https://www.myserver.com/payment-gateway', data)
.done( function() {
IBMChat.sendSilently('success');
})
.fail( function() {
IBMChat.sendSilently('failure');
});
});
Actions Documentation
https://github.com/watson-virtual-agents/chat-widget/blob/master/docs/DOCS.md#actions
Related
I wanted to trigger a Jenkins job through the Jenkins API
we can do that by hitting the URL similar to "JENKINS_URL/job/JOBNAME/build"
I want to hit the API via Google action/Dialogflow.
Is there any tutorial available to do a similar process that I want to achieve?
You should take a look at the Dialogflow quotes sample, which shows how to make external API calls:
// Retrieve data from the external API.
app.intent('Default Welcome Intent', (conv) => {
// Note: Moving this fetch call outside of the app intent callback will
// cause it to become a global var (i.e. it's value will be cached across
// function executions).
return fetch(URL)
.then((response) => {
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
} else {
return response.json();
}
})
.then((json) => {
// Grab random quote data from JSON.
const data = json.data[Math.floor(Math.random() * json.data.length)];
const randomQuote =
data.quotes[Math.floor(Math.random() * data.quotes.length)];
conv.close(new SimpleResponse({
text: json.info,
speech: `${data.author}, from Google ` +
`Developer Relations once said... ${randomQuote}`,
}));
if (conv.screen) {
conv.close(new BasicCard({
text: randomQuote,
title: `${data.author} once said...`,
image: new Image({
url: BACKGROUND_IMAGE,
alt: 'DevRel Quote',
}),
}));
}
});
});
Amazon Lex FAQ's mention that we can send the parsed intent and slots back to the client, so that we can place the business logic in the client. But am unable to find anything clear on this in the Lex documentation.
My use case:
Send text/voice data to Amazon lex, lex then parses the intent and slots and sends back the JSON with intent, slot and context data back the client which requested it, rather than sending it to Lambda or other backend API endpoint.
Can anyone please point out the right way/configuration for this?
Regards
If I'm understanding you correctly, you want your client to receive the LexResponse and handle it within the client rather than by Lambda or backend API. If this is correct, you can try the following implementation of Lex-Audio.
// This will handle the event when the mic button is clicked on your UI.
scope.audioClick = function () {
// Cognito Credentials for Lex Runtime Service
AWS.config.credentials = new AWS.CognitoIdentityCredentials(
{ IdentityPoolId: Settings.AWSIdentityPool },
{ region: Settings.AWSRegion }
);
AWS.config.region = Settings.AWSRegion;
config = {
lexConfig: { botName: Settings.BotName }
};
conversation = new LexAudio.conversation(config, function (state) {
scope.$apply(function () {
if (state === "Passive") {
scope.placeholder = Settings.PlaceholderWithMic;
}
else {
scope.placeholder = state + "...";
}
});
}, chatbotSuccess
, function (error) {
audTextContent = error;
}, function (timeDomain, bufferLength) {
});
conversation.advanceConversation();
};
The success function which is called after Lex has responded is as follows:
chatbotSuccess = function (data) {
var intent = data.intent;
var slots = data.slots;
// Do what you need with this data
};
Hopefully that gives you some idea of what you need to do. If you need the reference for Lex-Audio, there's a great post about it on the Amazon Blog which you should go check out:
https://aws.amazon.com/blogs/machine-learning/capturing-voice-input-in-a-browser/
I need to make a POST request and send some data. I'm using the service worker sync to handle offline situation.
But is there a way to pass the POST data to the service worker, so it makes the same request again?
Cause apparently the current solution is to store requests in some client side storage and after client gets connection - get the requests info from the storage and then send them.
Any more elegant way?
PS: I thought about just making the service worker send message to the application code so it does the request again ... but unfortunately it doesn't know the exact client that registered the service worker :(
You can use fetch-sync
or i use postmessage to fix this problem, which i agree that indexedDB looks trouble.
first of all, i send the message from html.
// send message to serviceWorker
function sync (url, options) {
navigator.serviceWorker.controller.postMessage({type: 'sync', url, options})
}
i got this message in serviceworker, and then i store it.
const syncStore = {}
self.addEventListener('message', event => {
if(event.data.type === 'sync') {
// get a unique id to save the data
const id = uuid()
syncStore[id] = event.data
// register a sync and pass the id as tag for it to get the data
self.registration.sync.register(id)
}
console.log(event.data)
})
in the sync event, i got the data and fetch
self.addEventListener('sync', event => {
// get the data by tag
const {url, options} = syncStore[event.tag]
event.waitUntil(fetch(url, options))
})
it works well in my test, what's more you can delete the memory store after the fetch
what's more, you may want to send back the result to the page. i will do this in the same way by postmessage.
as now i have to communicate between each other, i will change the fucnction sync into this way
// use messagechannel to communicate
sendMessageToSw (msg) {
return new Promise((resolve, reject) => {
// Create a Message Channel
const msg_chan = new MessageChannel()
// Handler for recieving message reply from service worker
msg_chan.port1.onmessage = event => {
if(event.data.error) {
reject(event.data.error)
} else {
resolve(event.data)
}
}
navigator.serviceWorker.controller.postMessage(msg, [msg_chan.port2])
})
}
// send message to serviceWorker
// you can see that i add a parse argument
// this is use to tell the serviceworker how to parse our data
function sync (url, options, parse) {
return sendMessageToSw({type: 'sync', url, options, parse})
}
i also have to change the message event, so that i can pass the port to sync event
self.addEventListener('message', event => {
if(isObject(event.data)) {
if(event.data.type === 'sync') {
// in this way, you can decide your tag
const id = event.data.id || uuid()
// pass the port into the memory stor
syncStore[id] = Object.assign({port: event.ports[0]}, event.data)
self.registration.sync.register(id)
}
}
})
up to now, we can handle the sync event
self.addEventListener('sync', event => {
const {url, options, port, parse} = syncStore[event.tag] || {}
// delete the memory
delete syncStore[event.tag]
event.waitUntil(fetch(url, options)
.then(response => {
// clone response because it will fail to parse if it parse again
const copy = response.clone()
if(response.ok) {
// parse it as you like
copy[parse]()
.then(data => {
// when success postmessage back
port.postMessage(data)
})
} else {
port.postMessage({error: response.status})
}
})
.catch(error => {
port.postMessage({error: error.message})
})
)
})
At the end. you cannot use postmessage to send response directly.Because it's illegal.So you need to parse it, such as text, json, blob, etc. i think that's enough.
As you have mention that, you may want to open the window.
i advice that you can use serviceworker to send a notification.
self.addEventListener('push', function (event) {
const title = 'i am a fucking test'
const options = {
body: 'Yay it works.',
}
event.waitUntil(self.registration.showNotification(title, options))
})
self.addEventListener('notificationclick', function (event) {
event.notification.close()
event.waitUntil(
clients.openWindow('https://yoursite.com')
)
})
when the client click we can open the window.
To comunicate with the serviceworker I use a trick:
in the fetch eventlistener I put this:
self.addEventListener('fetch', event => {
if (event.request.url.includes("sw_messages.js")) {
var zib = "some data";
event.respondWith(new Response("window.msg=" + JSON.stringify(zib) + ";", {
headers: {
'Content-Type': 'application/javascript'
}
}));
}
return;
});
then, in the main html I just add:
<script src="sw_messages.js"></script>
as the page loads, global variable msg will contain (in this example) "some data".
I'm working in an implementation using SignalR and the Kendo Scheduler. When a new task is created (for exemple), the SchedulerDataSource transport send the connection hub id to the server as an additional parameter:
transport: {
read: { url: global.web_path + 'Home/Tasks' },
update: { url: global.web_path + 'Home/UpdateTask', type: 'PUT', contentType: 'application/json' },
create: { url: global.web_path + 'Home/CreateTask', type: 'POST', contentType: 'application/json' },
destroy: { url: global.web_path + 'Home/DeleteTask', type: 'DELETE', contentType: 'application/json' },
parameterMap: function (options, operation) {
if (operation == "destroy" && options.models) {
return JSON.stringify({ taskId: options.models[0].Id, callerId: $.connection.hub.id });
}
if (operation !== "read" && options.models) {
return JSON.stringify({ tasks: options.models, callerId: $.connection.hub.id });
}
}
},
The server do whatever it has to do, and send a notification to every other user, except de caller:
[HttpPost]
public JsonResult CreateTask(List<ScheduledEvent> tasks, string callerId)
{
...create task and other stuff
//broadcast the newly created object to everyone except caller
var hubContext = GlobalHost.ConnectionManager.GetHubContext<Notebooks.Hubs.SchedulerHub>();
hubContext.Clients.AllExcept(callerId).UpdateSchedule(task);
//return the object to caller
return Json(task);
}
Once the other clients receive a new task from the hub, it is added to the SchedulerDataSource:
hub.client.updateSchedule = function (scheduledEvent) {
schedulerDataSource.add(scheduledEvent);
}
Everything seems to work fine, and it really took me some time to realize this behavior: if a client have the scheduler window open, this window is closed once the schedulerDataSource is updated. This is expected or am I doing something wrong?
Edit: I just realized how old this question is, so you have probably moved on to other things by now, or the pushCreate method may not have existed back then.
I think this may be how it works, but it seems like it should be able to add those events behind the scenes without having to close the edit window. Have you tried the pushCreate method? If that doesn't work, since the add automatically closes the edit dialog, maybe when the events come in, if the dialog is open, you could store the new events, then add them when the user closes the edit dialog.
My answer is now even older ;) but I faced this very same issue today.
First, I'm quite sure this is indeed the expected behavior. You can see in the kendo sources the call of the close editor window method in the transport update and create methods of the scheduler.
Below is what I've done to bypass the issue .
The idea is as simple as to prevent the edit window to close when an appointment modification comes from another hub client.
Server-side : modify the hub methods (example with update method)
public MyAppointmentViewModel Update(MyAppointmentViewModel appointment)
{
if (!appointmentService.Update(appointment))
throw new InvalidOperationException("Something went wrong");
else
{
Clients.Others.PrepareBeforeAddOrUpdateSignal(appointment.Id);
Clients.Others.Update(appointment);
return appointment;
}
}
Here you see we inform every other clients (through PrepareBeforeAddOrUpdate) we're about to update an appintment.
Client-side now (in index.cshtml for instance)
schedulerHub.client.prepareBeforeAddOrUpdateSignal = function(id){ lastModifiedRdvId = id; };
schedulerHub.client.create = function(appointment){ lastModifiedRdvId = 0; }; /* reset the variable for next time */
schedulerHub.client.update = function(appointment){ lastModifiedRdvId = 0; }; /* reset the variable for next time */
function SchedulerEditor()
{
return $(".k-scheduler-edit-form").data("kendoWindow");
}
var eventBeforeChanges = null;
var lastModifiedRdvId = 0;
function onEditorClose(e) {
if (eventBeforeChanges != null) {
if (lastModifiedRdvId > 0 && eventBeforeChanges.Id != lastModifiedRdvId)
e.preventDefault();
else {
var editWin = SchedulerEditor(); /* unbind this callback and use default behavior again */
editWin.unbind('close', onEditorClose);
}
}}
function onEditRdv(e) {
var editWin = SchedulerEditor();
if (editWin != null) /*Bind the close event */
editWin.unbind('close', onEditorClose).bind('close', onEditorClose);
eventBeforeChanges = e.event;
/* continue onEditRdv */
}
you see here the close event is prevented when the appointment id is not the appointment id beeing updated by the current client.
And fortunately, preventing the close event prevents the annoying behavior of having a sort of ghost appointment after one has been changed by another hub client.
I'm sorry if I'm not clear or if the code isn't clear enough. I can provide more information if necessary.
Bye
i am trying to accomplish a two way communication request response in my firefox sidebar extension, i have a file named event.js this resides on the content side, i have another file called sidebar.js file which is residing in the xul. I am able to communicate from event.js to sidebar.js file using the dispatchEvent method. my event in turn raises a XMLHttpRequest in sidebar.js file which hits the server and sends back the response. Now, here i am unable to pass the response to the event.js file. I want the response to be accessed in the event.js file. Till now i have achieved only one way communication. Please help me in getting the two way communication.
Code is as follows:
// event.js file
// This event occurs on blur of the text box where i need to save the text into the server
function saveEvent() {
var element = document.getElementById("fetchData");
element.setAttribute("urlPath", "http://localhost:8080/event?Id=12");
element.setAttribute("jsonObj", convertToList);
element.setAttribute("methodType", "POST");
document.documentElement.appendChild(element);
var evt = document.createEvent("Events");
evt.initEvent("saveEvent", true, true);
element.dispatchEvent(evt);
//Fetching the response over here by adding the listener
document.addEventListener("dispatchedResponse", function (e) { MyExtension.responseListener(e); }, false, true);
}
var MyExtension = {
responseListener: function (evt) {
receivedResponse(evt.target.getAttribute("responseObject"));
}
}
function receivedResponse(event) {
alert('response: ' + event);
}
// sidebar.js file
window.addEventListener("load", function (event) {
var saveAjaxRequest = function (urlPath, jsonObj, methodType, evtTarget) {
var url = urlPath;
var request = Components.classes["#mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Components.interfaces.nsIXMLHttpRequest);
request.onload = function (aEvent) {
window.alert("Response Text: " + aEvent.target.responseText);
saveResponse = aEvent.target.responseText;
//here i am again trying to dispatch the response i got from the server back to the origin, but unable to pass it...
evtTarget.setAttribute("responseObject", saveResponse);
document.documentElement.appendChild(evtTarget);
var evt = document.createEvent("dispatchedRes"); // Error line "Operation is not supported" code: "9"
evt.initEvent("dispatchedResponse", true, false);
evtTarget.dispatchEvent(evt);
};
request.onerror = function (aEvent) {
window.alert("Error Status: " + aEvent.target.status);
};
//window.alert(methodType + " " + url);
request.open(methodType, url, true);
request.send(jsonObj);
};
this.onLoad = function () {
document.addEventListener("saveEvent", function (e) { MyExtension.saveListener(e); }, false, true);
}
var MyExtension =
{
saveListener: function (evt) {
saveAjaxRequest(evt.target.getAttribute("urlPath"), evt.target.getAttribute("jsonObj"), evt.target.getAttribute("methodType"), evt.originalTarget);
}
};
});
Why are you moving your fetchData element into the sidebar document? You should leave it where it is, otherwise your content code won't be able to receive the event. Also, use the content document to create the event. Finally, document.createEvent() parameter for custom events should be "Events". So the code after your //here i am again trying comment should look like:
evtTarget.setAttribute("responseObject", saveResponse);
var evt = evtTarget.ownerDocument.createEvent("Events");
evt.initEvent("dispatchedResponse", true, false);
evtTarget.dispatchEvent(evt);
Please note however that your code as you show it here is a huge security vulnerability - it allows any website to make any HTTP requests and get the result back, so it essentially disables same-origin policy. At the very least you need to check that the website talking to you is allowed to do it (e.g. it belongs to your server). But even then it stays a security risk because server response could be altered (e.g. by an attacker on a public WLAN) or your server could be hacked - and you would be giving an attacker access to sensitive data (for example he could trigger a request to mail.google.com and if the victim happens to be logged in he will be able to read all email data). So please make this less generic, only allow requests to some websites.