I am trying to understand how I can manage some aspect of state within fullfillment (DialogFlow's implementation where you can write code in JavaScript and it executes within a Google Cloud Function). First, I would assume that this implementation is stateless, but there must be a way to maintain some state without having to store the data in the database and then retrieve it on the next execution.
I would simply like to maintain the full history of the chat - the question asked by the user, and the response from the chatbot. I can see that I can get this information on every response (and call to the fullfillment) via:
console.log(JSON.stringify(request.body.queryResult.queryText));
console.log(JSON.stringify(request.body.queryResult.fulfillmentText));
Now that I have this information I just want to append it to some variable that is statefull. I have looked into setContext, context.set, app.data, and other functions/variables, but I can't seem to get it working because I'm not sure I understand how it should work.
In my code I have mostly the basic template. I don't think I can use a global variable so how can I store this state (fullConversation) between intent executions just for this user's conversation?
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
let query = JSON.stringify(request.body.queryResult.queryText);
let response = console.log(JSON.stringify(request.body.queryResult.fulfillmentText);
// here I want to retrieve the prior query/response and append it
// i.e., let fullConversation = fullConversation + query + response
}
function welcome(agent) {
agent.add(`Welcome to my agent!`);
}
function fallback(agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
function myNewHandler(agent) {
}
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
intentMap.set('myIntent',myNewHandler);
agent.handleRequest(intentMap);
});
UPDATE:
If I update my code with the code suggestion from #Prisoner I'm still having issues with just getting the context. I never get to my console.log(2). Do I need to move the agent.context.get code outside of the onRequest block??:
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
console.log(JSON.stringify(request.body.queryResult.queryText));
console.log(JSON.stringify(request.body.queryResult.fulfillmentText));
console.log("1");
// Get what was set before, or an empty array
const historyContext = agent.context.get('history');
console.log("2");
SECOND UPDATE:
The issue is related to a known issue solved here.
Just needed to update the dialogflow-fulfillment in package.json and everything worked.
You're on the right track. Global variables are definitely not the way to do it. And state can be maintained as part of a Context.
The app.data property is only available if you're using the actions-on-google library, which it does not look like you're using. Several of the APIs have also changed over time, and can be confusing. See this older answer for an examination of some of the options.
Since you're using the dialogflow-fulfillment library, you'll be using the agent.context (note the singular) object to add new contexts. For the context, you'll want to set a context parameter with the value that you want to store. Values need to be strings - so if you have something like an array, you probably want to convert it to a string using something like JSON.serialzie() and extract it with JSON.parse().
The code that gets the current context with your stored information, and then updates it with the latest values, might look something like this:
// Get what was set before, or an empty array
const historyContext = agent.context.get('history');
const historyString = (historyContext && historyContext.params && historyContext.params.history) || '[]';
const history = JSON.parse(historyString);
// Add the messages as a single object
history.push({
requestMessage,
responseMessage
});
// Save this as a context with a long lifespan
agent.context.set('history', 99, JSON.stringify(history));
Update
I would put this code in a function, and call this function before you return from each handler function you're in. I'm a little surprised that agent.context would be causing problems outside the handler - but since you don't seem to have any specific error, that's my best guess.
Related
How can I get the full URL of the page which is being serviced by a service worker's 'fetch' event?
The "self.location" property seems to only refer to the root URL of the site. For example, if page https://example.com/folder/pagename.html is performing a fetch which the service worker is intercepting, the service worker's 'self.location' property returns "https://example.com".
event.currentTarget.location and event.explicitOriginalTarget.location, event.originalTarget, and event.target all return the URL of the service worker .js file.
How can I get the full URL of the page that triggered the fetch event?
You've got two general approaches, depending on how involved you want to get:
Use the 'Referer' header info
If the request is for a subresource and includes a Referer header, then there's a decent chance that the value of that header is the URL of the page that made the request. (There are some caveats; read this background info to delve into that.)
From within a fetch handler, you can read the value of that header with the following:
self.addEventListener('fetch', event => {
const clientUrl = event.request.referrer;
if (clientUrl) {
// Do something...
}
});
Use the clientId value
Another approach is to use the clientId value that (might) be exposed on the FetchEvent, and then use clients.get(id) or loop through the output of clients.matchAll() to find the matching WindowClient. You could then read the url property of that WindowClient.
One caveat with this approach is that the methods which look up the WindowClient are all asynchronous, and return promises, so if you're somehow using the URL of the client window to determine whether or not you want to call event.respondWith(), you're out of luck (that decision needs to be made synchronously, when the FetchEvent handler is first invoked).
There's a combination of different things that need to be supported in order for this approach to work, and I'm not sure which browsers currently support everything I mentioned. I know Chrome 67 does, for instance (because I just tested it there), but you should check in other browsers if this functionality is important to you.
self.addEventListener('fetch', async event => {
const clientId = event.clientId;
if (clientId) {
if ('get' in clients) {
const client = await clients.get(clientId);
const clientUrl = client.url;
// Do something...
} else {
const allClients = await clients.matchAll({type: 'window'});
const filtered = allClients.filter(client => client.id === clientId);
if (filtered.length > 0) {
const clientUrl = filtered[0].url;
// Do something...
}
}
}
});
I'm currently trying to connect to the Artsy public API and I'm having some trouble. Please go easy on me, I'm new to NodeJS and SailsJS. I've created a file called api/services/Artsy.js and now I'm hung up on whether my approach (see below) is really the Sails way of doing things. That's one question. If not, what is the proper way to set up a basic call to an API in SailsJS? Or more specifically, to the Artsy API?
Issues that come to mind:
I need to wait until the token is retrieved before I can call an API method that requires it. Is this done via pubsub, or some other technique. Are there any practical examples that show how this works in SailsJS?
Can you use service calls inside of views? How? Does this even make sense? It seems like it might be useful for an API call.
Note: I've set up the file config/artsy.js that holds important variables. (see this below)
/api/services/Artsy.js
/**
*
* Artsy.js => in api/services
* #description A service that connects to the Artsy.net public API.
* #url https://developers.artsy.net/
**/
var request = require('superagent'),
traverson = require('traverson'),
xappToken;
module.exports = {
init: function(){
sails.log.info('----- Artsy API Initialized -----');
var clientID = sails.config.artsy.clientId,
clientSecret = sails.config.artsy.clientSecret,
apiTokenUrl = sails.config.artsy.apiTokenUrl;
try{
request
.post(apiTokenUrl)
.send({ client_id: clientID, client_secret: clientSecret })
.end(function(res) {
if (res) {
xappToken = res.body.token;
} else {
sails.log.error('api/services/Artsy.js:');
sails.log.error(res.text);
}
});
} catch(e) {
sails.log.error('api/services/Artsy.js:');
sails.log.error(e);
}
},
getArtistStatement: function(){
var api = traverson.jsonHal.from(sails.config.artsy.apiUrl);
var request = api.newRequest()
.follow('artist')
.withRequestOptions({
headers: {
'X-Xapp-Token': xappToken,
'Accept': 'application/vnd.artsy-v2+json'
}
})
.withTemplateParameters({ id: 'andy-warhol' })
.getResource(function(error, andyWarhol) {
console.log(andyWarhol.name + 'was born in ' + andyWarhol.birthday + ' in ' + andyWarhol.hometown);
});
}
};
/config/artsy.js
/**
* artsy.js
*
* #description :: This is the brains that allow the app to connect to artsy.
* #docs :: http://sailsjs.org/#!documentation/models
*/
module.exports.artsy = {
clientId: 'CLIENT_ID',
clientSecret: 'CLIENT_SECRET',
apiUrl: 'https://api.artsy.net/api',
apiTokenUrl: 'https://api.artsy.net/api/tokens/xapp_token'
};
If I understand your questions correctly so
That's one question. If not, what is the proper way to set up a basic call to an API in SailsJS?
Service is quite right place for such case.
And you can call your init method in config/bootstrap.js, or when user has log in or somewhere else.
I need to wait until the token is retrieved before I can call an API method that requires it. Is this done via pubsub
Yes, you can use pubsub, but it seems better to use promises. For example you can use this lib
https://github.com/istavros/vowjs
Can you use service calls inside of views? How? Does this even make sense? It seems like it might be useful for an API call.
I think yes, but it seems you should not want it. It looks betted when templates works with controller passed data and nothing else. So use service in controller (or mb model) an then pass results to template.
Scenario = I am slowly but surely wrapping my head around what is going on with Parse's cloud code features. I just need some help from those who would like to answer some short, relatively simple questions about what is going on in some sample cloud code functions.
The code I will use in this example is below
1) cloud code
Parse.Cloud.define('editUser', function(request, response) {
var userId = request.params.userId,
newColText = request.params.newColText;
var User = Parse.Object.extend('_User'),
user = new User({ objectId: userId });
user.set('new_col', newColText);
Parse.Cloud.useMasterKey();
user.save().then(function(user) {
response.success(user);
}, function(error) {
response.error(error)
});
});
2) called from iOS
[PFCloud callFunction:#"editUser" withParameters:#{
#"userId": #"someuseridhere",
#"newColText": #"new text!"
}];
This code was taken from here
Question 1 =
(request, response)
I am confused by what this is. Is this like typecasting in iOS where I am saying (in the iOS call) I want to pass an NSString into this function ("userId") and inside the cloud code function I'm going to call it "request"? Is that what's going on here?
Question 2 =
Parse.Object.extend('_User')
Is this grabbing the "User" class from the Parse database so that a "PFObject" of sorts can update it by creating a new "user" in the line below it?
Is this like a...
PFObject *userObject = [PFObject objectWithClassName:#"User"]?
Question 3 =
user.set('new_col', newColText)
This obviously 'sets' the values to be saved to the PFUser (~I think). I know that the "newColText" variable is the text that is to be set - but what is 'new_col'? Only thing I can think of is that this sets the name of a new column in the database of whatever type is being passed through the "request"?
Is this like a...
[[PFUser currentUser] setObject: forKey:]
Question 4 =
Parse.Cloud.useMasterKey()
Without getting too technical, is this basically all I have to type before I can edit a "User" object from another User?
Question 5 =
user.save().then(function(user) {
response.success(user);
}
Is this like a...
[user saveInBackgroundWithBlock:]?
and if so, is
function(error) {
response.error(error)
just setting what happens if there is an error in the saveInBackgroundWithBlock?
Please keep in mind, I know iOS - not JavaScript. So try to be as descriptive as possible to someone who understands the Apple realm.
Here's my take on your questions:
The request parameter is for you to access everything that is part of the request/call to your cloud function, it includes the parameters passed (request.params), the User that is authenticated on the client (request.user) and some other things you can learn about in the documentation. The response is for you to send information back to the calling code, you generally call response.success() or response.error() with an optional string/object/etc that gets included in the response, again documentation here.
That's a way of creating an instance of a User, which because it is a special internal class is named _User instead, same with _Role and _Installation. It is creating an instance of the user with an ID, not creating a new one (which wouldn't have an ID until saved). When you create an object this way you can "patch" it by just changing the properties you want updated.
Again, look at the documentation or an example, the first parameter is the column name (it will be created if it doesn't exist), the second value is what you want that column set to.
You have to do Parse.Cloud.useMasterKey() when you need to do something that the user logged into the client doesn't have permission to do. It means "ignore all security, I know what I'm doing".
You're seeing a promise chain, each step in the chain allows you to pass in a "success" handler and an optional "error" handler. There is some great documentation. It is super handy when you want to do a couple of things in order, e.g.
Sample code:
var post = new Parse.Object('Post');
var comment = new Parse.Object('Comment');
// assume we set a bunch of properties on the post and comment here
post.save().then(function() {
// we know the post is saved, so now we can reference it from our comment
comment.set('post', post);
// return the comment save promise, so we can keep chaining
return comment.save();
}).then(function() {
// success!
response.success();
}, function(error) {
// uh oh!
// this catches errors anywhere in the chain
response.error(error);
});
I'm pretty much at the same place as you are, but here are my thoughts:
No, these are the parameters received by the function. When something calls the editUser cloud function, you'll have those two objects to use: request & response. The request is basically what the iOS device sent to the server, and response is what the server will send to the iOS device.
Not quite that. It's like creating a subclass of _User.
Think of Parse objects types as a database table and it's instances as rows. The set will set (derp) the value of 'newColText' to the attribute/column 'new_col'.
Not sure, never used that function as I don't handle User objects. But might be that.
Pretty much that. But it's more sort of like (pseudo-code, mixing JS with Obj-C):
[user saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error){
if(error){
response.error(error); // mark the function as failed and return the error object to the iOS device
}
else{
response.success(user); // mark the function call as successful and return the user object to the iOS device
}
}];
I am trying to read the response headers 'name' and 'value'. The end goal is to compare them to some pre-set name and a value to see if they match.
Here is what I have so far, it's the function that run every time I get a response header.
var observer = require("observer-service");
observer.add("http-on-examine-response", onHttpRequest);
function onHttpRequest(subject, data)
{
console.log("request subject...." + subject);
console.log("request data...." + data);
}
The output is as follows:
request subject....[xpconnect wrapped nsISupports]
request data....null
I was hoping to know how to get the rest of the data out of the response.
Any help would be great, thanks.
The subject for http-on-examime-response implements nsIHttpChannel, among some other things. You may use .QueryInterface() or instanceof (which internally kinda uses QueryInteface, so that this works as well) to get to that interface.
const {Ci} = require("chrome");
if (subject instanceof Ci.nsIHttpChannel) {
console.log("content-type", subject.getResponseHeader("content-type"));
subject.visitResponseHeaders(function(header, value) {
console.log(header, value);
});
}
There are a couple of other questions around here going into more detail on how to use these notifications... Also, mxr can help a lot checkout out what interfaces there are, how it fits together and how one could use it (in particular the existing tests are great to see some uses for all kinds of stuff).
There is also the "nsITraceableChannel, Intercept HTTP Traffic" article going into more details, e.g. on how to use nsITraceableChannel to get the payload data from such a channel.
I'm making a temporary fake API and am trying to set up a simple request response script in node using express.js to achieve this. It's very strraightforward, A request comes in, is validated and, if valid, is merged with a .json template file and the result returned, thus giving the impression the user was successfully created.
app.post('/agent/user', function(req, res){
var responseTemplate = new jsonRequired('post_user');
var errorTemplate = new jsonRequired('post_user_error');
var payload = req.body;
var responseData;
var hasErrors = false;
console.log('Creating new user');
//Recursive Merge from http://stackoverflow.com/a/383245/284695
responseData = new mergeRecursive(responseTemplate,payload);
if(!payload.username){
hasErrors = true;
errorTemplate.errors.username.push('A username is required.');
}
if (hasErrors){
res.send(errorTemplate,422);
}else{
res.send(responseData,200);
}
});
The problem I'm having is that data is persisting between calls. So if I define a username and name[first] in 1 request and just a username in the 2nd one, both requests come back with the name[first] property.
I have a feeling it's something to do with js closures. Unfortunately, every tutorial I find seems to be about making closures, not avoiding them.
It should work like this:
The client POST's username=user1&name[first]=joe&name[last]=bloggs
The Server loads a json file containing a prepopulated user object: e.g.
{"username":"demo","name":{"first":"John","last":"Doe"}...}
mergeRecursive() merges the payload from the POST request over the template object and returns the new object as the POST response text.
The problem is that with every new request, the server is using the result of step 3 in step 2 instead of reloading the .json file.
That mergeRecursive function has the same caveat as jQuery.extend: it modifies the first object sent into it. In fact, you don't even need to use its return value.
You didn't show the code of jsonRequired function (it's not even clear why you've used new when invoking it), but it looks like this function doesn't create a new object each time it's called, instead fetching this object from some outer repository. Obviously, mergeRecursive modifications for it won't be lost after that function ends.
The solution is using your data object for merging. Like this:
var responseData = {};
...
mergeRecursive(responseData, responseTemplate);
mergeRecursive(responseData, payload);
Merging two objects will make this for you.
If your responseTemplate has parameter, which actual request did not have, then you will end up having it there.
Check definition of word merge ;)
While this doesn't resolve the issue I had, I have found a workaround using the cloneextend package available via npm:
$ npm install cloneextend
This allows me to use the following js:
var ce = require('cloneextend');
...
ce.extend(responseData, responseTemplate);
ce.extend(responseData, payload);