QnA Maker: How to count a specific answer in a session? - chatbot

I have QnA Maker chatbot. I want to do that: If bot gives the DefaultNoAnswer 3 times in a session, I want to show different DefaultNoAnswer. How can I count the DefaultNoAnswers in QnAMakerBaseDialog ?
ex:
Client: asdaaasd
Bot: Sorry, Could you phrase your question differently?
Client: dsjhdsgjdsa
Bot:Sorry, Could you phrase your question differently?
Client: aasdjhajds
Bot: Sorry, I couldn't get the question. Send an email for detailed information.

I find the best way to handle this is with a conversation state variable. I have my default message set up in my helper (i.e. I have a helper file that makes the call to QnA Maker, checks the confidence, and sends a default message in case of low confidence or no answer). If you are using a similar case, you can increment your state variable there. If you are using QnA Maker's default answer directly, you still need to do some check on every result before sending the response to user. I haven't used that method, but I would probably just check the result for the default answer and increment the variable accordingly.
Here is a sample for the first case. I am assuming here that you are already familiar with managing user and conversation state.
var qnaResult = await QnAServiceHelper.queryQnaService(query, oldState);
if (qnaResult[0].score > MINIMUM_SCORE) {
const conversationData = await this.dialogState.get(step.context, {});
conversationData.defaultAnswerCounter = 0;
await this.conversationState.saveChanges(step.context);
var outputActivity = MessageFactory.text(qnaResult[0].answer);
} else {
const conversationData = await this.dialogState.get(step.context, {});
conversationData.defaultAnswerCounter += 1;
if (conversationData.defaultAnswerCounter <= 2) {
var outputActivity = defaultAnswer;
} else {
var outputActivity = escalationAnswer;
}
await this.conversationState.saveChanges(step.context);
}

Related

appwrite list users search params

I am trying to use appwrite server sdk list users to get userid from an email.
The documentation says there is a search: option that can be used but no where does it say what the format of that String? is.
What is the format of the search: String? to only get a list of users whose email matches?
void main() { // Init SDK
Client client = Client();
Users users = Users(client);
client
.setEndpoint(endPoint) // Your API Endpoint
.setProject(projectID) // Your project ID
.setKey(apiKey) // Your secret API key
;
Future result = users.list(search: '<<<WHAT GOES HERE>>>');
}
:wave: Hello!
Thanks for bringing this question up, this is definitely not well documented, I'll note this down and try to make it clearer in the docs, but here's how you'd approach this in Dart:
final res = users.list(search: Query.equal('email',
'email#example.com'));
res.then((response) {
print(response.users[0].toMap());
}).catchError((error) {
print(error);
});
The Query object generates a query string, and works similar to how listDocument would work. The difference here is that it only takes a single query string instead of a list.

ASP.NET Core, where is FindByID?

I have an ASP.NET Core 2.0 application and I'm trying to attach a user to a model:
var user = _userManager.FindByIdAsync(Model.Author);
var promotion = new Promotion()
{
Title = Model.Title,
User = user //error here,
Created = DateTime.Now
};
The problem with this code is that I can't assign user to promotion.User as user is the result of an async operation. I'd prefer not to use FindByIdAsync but for some reason I can't find FindById.
UserManager contains only async API and FindByIdAsync actually returns Task<User> instead of User. So you need to make your code async also and use FindByIdAsync like this:
var user = await _userManager.FindByIdAsync(Model.Author); // will return the User
Only if it is not possible leave your code synchronous, e.g. by calling Result property of the Task which will cause your thread to block until the result is available
var user = _userManager.FindByIdAsync(Model.Author).Result;

Azure Mobile App Offline Sync - URL length limitations, batched pulls, queryId length

Schematically, I spend my time doing things looking like the following.
public async Task<ItemA> GetItemsA(object someParams)
{
var res = new List<ItemA>();
var listOfItemAIds = await GetIdsFromServerAsync(someParams);
var tableAQuery = _tableA.Where(x => listOfItemAIds.Contains(x.Id));
await _tableA.PullAsync(null, tableAQuery);
var itemsA= await tableAQuery.ToListAsync();
var listOfItemBIds = itemsA.Select(x => x.bId).ToList();
await _tableB.PullAsync(null, _tableB.Where(x => listOfItemBIds .Contains(x.Id));
foreach(var itemA in itemsA)
{
itemA.ItemB = await _tableB.LookupAsync(itemA.itemBId);
}
return res;
}
There are several problems with that:
listOfTableAIds.Contains(x.Id) leads to errors due to URL length limitations
As a cannot represent the content of listOfItemAIds or listOfItemBIds with a queryId of less than 50 chars, I end up pulling data that I might already have
It's a shame that all my pulls are not batched into a single server call
I could directly get all I need from a single server query but then I wouldn't benefit from the Azure Mobile Sync framework
Any suggestions on how to improve that sequence?
I would suggest that you refine things so that you query is simpler. You may overall pull more data, but then that data will be available within tableB. For instance, you may want to say "where itemBId is not null".

AngularJS DOM update for loop progress

I have a number of AJAX calls that need to be run for every entry in an array, I'm trying to supply some visual feedback on the progress of the loop through the array, the model is being updated correctly but i'm not seeing anything updated in the view trying to call $digest in the loop has no effect on the DOM.
I've tried adding $apply to the function in the inner loop but I'm still seeing no change.
$scope.UploadEntry = function(item){
var oDBGet = new htmldb_Get(null,
$v('pFlowId'),
"APPLICATION_PROCESS=UploadTargetDates",
$v('pFlowStepId'));
oDBGet.add('EX_TRD',$scope.Ext.TRDDate.val);
oDBGet.add('EX_MAX_TRD',$scope.Ext.MaxTRDDate.val);
oDBGet.add('EX_READ',Ext.ReadDownloadCheck);
oDBGet.get();
};
$scope.ShowUploadModal = true;
$scope.UploadDone = 0;
for(i in submissionList)
{
$scope.UploadEntry(submissionList[i]);
$scope.UploadDone += 1;
}
$scope.ShowUploadModal = false;
But the view:
<div class="UploadModal" ng-show="ShowUploadModal">
Uploading entries: {{UploadDone}} complete
</div>
Never seen as the entries are uploaded, but does show at the end of the loop if I take the $scope.ShowUploadModal = false; out from the end of the loop.
UPDATE
After discussion, author states that the http request is synchronous. The problem is still about "sychronity", but a little bit tricker. Take a look at Angular digest concepts. It basically runs all watchers, expressions (binds) and process all $evalAsync over and over until there is no change in the watches result and expressions anymore. Just after this the DOM is updated.
So, the problem is that all your sync request are being resolved prior to the end of the digest cycle, and the DOM render will only happens after the digest cycle finishes processing.
The simplest way to solve your problem, as you state you can't change API to call async, is to ensure your requests are asynchronous, grab their promises and only hide uploadModal when all of them had been completed (this can be achieved with promises API, read promises API and $timeout). Like this:
var loadingPromises = [];
$scope.ShowUploadModal = true;
$scope.UploadDone = 0;
for(i in submissionList) {
loadingPromises.push($timeout((function(index) {
return function() {
$scope.UploadEntry(submissionList[index]);
$scope.UploadDone += 1;
};
})(i), 0));
}
$q.all(loadingPromises).then(function() {
$scope.ShowUPloadMOdel = false;
});
Note the closure I created to make sure the correct index is passed to the request, and you need to inject $q service to your controller. Although this is going to solve your problem, you should create a service and move your loading logic there, returning a promise (change your $scope references to parameters):
app.service('yourLoaderService', function($timeout) {
this.load = function(url) {
return $timeout(function() {
var oDBGet = new htmldb_Get(null,
$v('pFlowId'),
"APPLICATION_PROCESS=UploadTargetDates",
$v('pFlowStepId'));
oDBGet.add('EX_TRD',$scope.Ext.TRDDate.val);
oDBGet.add('EX_MAX_TRD',$scope.Ext.MaxTRDDate.val);
oDBGet.add('EX_READ',Ext.ReadDownloadCheck);
oDBGet.get();
}, 0);
};
});
And your controller:
var loadingPromises = [];
$scope.ShowUploadModal = true;
$scope.UploadDone = 0;
for(i in submissionList) {
var promise = yourLoaderService.load(submissionList[i]).then(function() {
$scope.UploadDone++;
});
loadingPromises.push(promise);
}
$q.all(loadingPromises).then(function() {
$scope.ShowUPloadMOdel = false;
});
First answer
This is a conception error. You seem to come from a synchronous server side background, maybe Python, I don't know. If this is your real code, then the problem is that your code is completely synchronous. You are showing upload model, looping all the entries and hiding the model and this entire code happens in milliseconds (or less) all before the DOM gets rendered even once.
This happens because when you ask for the upload, Javascript doesn't hang on the uploading process, it just asks and keep going. You can find something about async programming here, here, here and here.
You have to hook up your uploaded count in the upload callbacks. I don't know what you are using to upload, but your $scope.uploadEntry shall return a promise, then you wait it to be done and update the count.
$scope.ShowUploadModal = true;
$scope.UploadDone = 0;
for(i in submissionList)
{
$scope.UploadEntry(submissionList[i]).then(function() {
$scope.UploadDone += 1;
scope.ShowUploadModal = $scope.UploadDone !== submissionList.length;
});
}
If you're using $http for the uypload job, just return it returns, as it is already a promise and change .then(funciton per .success(function. If not, this is going to be a little more complicated, and you need to read the Angular docs on promises.
Just a side note, you should take a look at Javascript naming convetions. Javascript normally assume cammelCase variables, not PascalCase. Here's David Crackford's convetion.

setting up script to include google docs form data in email notification

I've setup a form using googledocs. I just want to have the actual data entered into the form emailed to me, as opposed to the generic response advising that the form has been completed.
I have no skill or experience with code etc, but was sure i could get this sorted. I've spent hours+hours and haven't had any luck.
My form is really basic.it has 5 fields. 4 of which are just text responses, and one multiple choice.
I found this tute online (http://www.labnol.org/internet/google-docs-email-form/20884/) which i think sums up what i'm trying to do, but have not been able to get it to work.
from this site i entered the following code:
function sendFormByEmail(e)
{
var email = "reports.mckeir#gmail.com";
var subject = "Google Docs Form Submitted";
var s = SpreadsheetApp.getActiveSheet();
var headers = s.getRange(1,1,1,s.getLastColumn()).getValues()[0];
var message = "";
for(var i in headers)
message += headers[i] + ' = '+ e.namedValues[headers[i]].toString() + "\n\n";
MailApp.sendEmail(email, subject, message);
}
To this, i get the following response: ->
Your script, Contact Us Form Mailer, has recently failed to finish successfully. A summary of the failure(s) is shown below. To configure the triggers for this script, or change your setting for receiving future failure notifications, click here.
The script is used by the document 100% Club.
Details:
Start Function Error Message Trigger End
12/3/12 11:06 PM sendFormByEmail TypeError: Cannot call method "toString" of undefined. (line 12) formSubmit 12/3/12 11:06 PM
Is anyone able to help shed some light on this for me? I'm guessing i'm not including some data neeeded, but i honestly have no clue.
Workaround http://www.labnol.org/internet/google-docs-email-form/20884/
You have to setup app script to forward the data as email.
I'll point to the comment above that solved it for me: https://stackoverflow.com/a/14576983/134335
I took that post a step further:
I removed the normal notification. The app script makes that generic text redundant and useless now
I modified the script to actually parse the results and build the response accordingly.
function sendFormByEmail(e)
{
var toEmail = "changeme";
var name = "";
var email = "";
// Optional but change the following variable
// to have a custom subject for Google Docs emails
var subject = "Google Docs Form Submitted";
var message = "";
// The variable e holds all the form values in an array.
// Loop through the array and append values to the body.
var s = SpreadsheetApp.getActiveSheet();
var headers = s.getRange(1,1,1,s.getLastColumn()).getValues()[0];
// Credit to Henrique Abreu for fixing the sort order
for(var i in headers) {
if (headers[i] = "Name") {
name = e.namedValues[headers[i]].toString();
}
if (headers[i] = "Email") {
email = e.namedValues[headers[i]].toString();
}
if (headers[i] = "Subject") {
subject = e.namedValues[headers[i]].toString();
}
if (headers[i] = "Message") {
message = e.namedValues[headers[i]].toString();
}
}
// See https://developers.google.com/apps-script/reference/mail/mail-app#sendEmail(String,String,String,Object)
var mailOptions = {
name: name,
replyTo: email,
};
// This is the MailApp service of Google Apps Script
// that sends the email. You can also use GmailApp here.
MailApp.sendEmail(toEmail, subject, message, mailOptions);
// Watch the following video for details
// http://youtu.be/z6klwUxRwQI
// By Amit Agarwal - www.labnol.org
}
The script utilized in the example is extremely generic but very resilient to change because the message is built as a key/value pair of the form fields submitted.
If you use my script you'll have to tweak the for loop if statements to match your fields verbatim. You'll also want to edit the toEmail variable.
Thanks again for the question and answers. I was about to ditch Google Forms as the generic response was never enough for what I was trying to do.
Lastly, in response to the actual problem above "toString of undefined" specifically means one of the form fields was submitted as blank. If I had to guess, I would say the author only used this for forms where all the fields were required or a quick undefined check would've been put in place.
Something like the following would work:
for(var i in headers) {
var formValue = e.namedValues[headers[i]];
var formValueText = "";
if (typeof(formValue) != "undefined") {
formValueText = formValue.toString();
}
message += headers[i] + ' = '+ formvalueText + "\n\n";
}
I haven't tested this precisely but it's a pretty standard way of making sure the object is defined before trying methods like toString() that clearly won't work.
This would also explain Jon Fila's answer. The script blindly assumes all of the header rows in the response are sent by the form. If any of the fields aren't required or the spreadsheet has fields that are no longer in the form, you'll get a lot of undefined objects.
The script could've been coded better but I won't fault the author as it was clearly meant to be a proof of concept only. The fact that they mention the replyTo correction but don't give any examples on implementing it made it perfectly clear.
If this is a Google Form, do you have any extra columns in your spreadsheet that are not on the form? If you delete those extra columns then it started working for me.
You don't need to use a script. Simply go to Tools >> Notification Rules on your Google Spreadsheet. There you can change the settings to receive an email with your desired information every time the document is changed.