Move data in Waterfall-Dialog. Bot Framework SDK - node.js

I'm using Bot Framework SDK with nodejs to implement a disamibuation flow.
I want that if two intents predicted by Luis are close to each other, ask the user from which of them are the one they want. I have done the validator but, I have a problem with the flow.
It is a waterfall Dialog with 3 steps:
FirstStep: Calls Orchestrator and Luis to get intents and entities. It pass the data with return await step.next({...})
Disamiguation Step: Checks if it is necessary to disambiguate, and, in that case, prompts the options. If not, it pass the data like the first step.
Answer step: If it has a disambiguation flag in the data it receives in step.result, it prompts the answer acordingly with the user response. Elsewhere, it uses the data in step.result that comes from the first step.
The problem is that, when it prompts user to say the intent, I lost the data of the FirstStep since I cannot use step.next({...})
¿How can I maintain both the data from the first step and the user answer in the prompt?
Here are the basic code:
async firstStep(step) {
logger.info(`FinalAnswer Dialog: firstStep`);
let model_dispatch = await this.bot.get_intent_dispatch(step.context);
let result = await this.bot.dispatchToTopIntentAsync(step.context, model_dispatch.model)
// model_dispatch = orchestrator_model
// result = {topIntent: String, entities: Array, disamibiguation: Array}
return await step.next({ model_dispatch: model_dispatch, result: result})
}
async disambiguationStep(step) {
logger.info(`FinalAnswer Dialog: disambiguationStep`);
if (step.result.result.disambiguation) {
logger.info("We need to disambiguate")
let disambiguation_options = step.result.result.disambiguation
const message_text = "What do you need";
const data = [
{
"title": "TEXT",
"value": disambiguation_option[0]
},
{
"title": "TEXT",
"value": disambiguation_option[1]
},
]
let buttons = data.map(function (d) {
return {
type: ActionTypes.PostBack,
title: d.title,
value: d.value
}
});
const msg = MessageFactory.suggestedActions(buttons, message_text);
return await step.prompt(TEXT_PROMPT, { prompt: msg });
return step.next(step.result) //not working
}
else {
logger.info("We dont desambiguate")
return step.next(step.result)
}
}
async answerStep(step) {
logger.info(`FinalAnswer Dialog: answerStep`);
let model_dispatch = step.result.model_dispatch
let result = step.result.result
//Show answer
return await step.endDialog();
}

You can use the step dictionary to store your values. The complex dialogs sample on GitHub is excellent for demonstrating this. https://github.com/microsoft/BotBuilder-Samples/blob/main/samples/javascript_nodejs/43.complex-dialog/dialogs/topLevelDialog.js

You can save data in the context with whatever name you want:
step.values['nameProperty'] = {}
This will be accessible within the entire execution context of the waterfall dialog:
const data = step.values['nameProperty'] // {}

Related

microsoft bots to teams using nodejs fails with missing activityid when updating the same activity

My code has a simple card carousel which has action button like below:
actions = [
{
"type": "Action.Submit",
"title": "Qualify",
"data": { "action" : "qualify_lead" }
},
{
"type": "Action.OpenUrl",
"title": "Retire",
"url": "{viewUrl}"
},
{
"type": "Action.ShowCard",
"title": "Add Note",
"card": this.noteCard(item.LeadId, "leads")
}
]
I am having a method to handle qualify_lead action like below
async qualifyLead(context:any){
console.log("in qualifyLead:" + JSON.stringify(context.activity))
await context.updateActivity(this.successCard('Lead is qualified'))
}
All I am doing on purpose is to replace entire carousel with a simple text message. But it fails with error:
Error: BotFrameworkAdapter.updateActivity(): missing activity.id
Where do i get this ?
I am using google firebase for this and the wrapper code is like below
const {
TurnContext,
TeamsActivityHandler,
CardFactory,
AttachmentLayoutTypes,
ActionTypes
} = require('botbuilder');
class TeamsConversationBot extends TeamsActivityHandler {
constructor() {
super();
this.leadState =
this.conversationState.createProperty('leadCarouselState');
this.onMessage(async (context:any, next:any) => {
TurnContext.removeRecipientMention(context.activity);
let msg = context.activity.text
const action = context.activity.value
let objNum = ''
let keyword = ''
if(msg === undefined && action === undefined)
msg = 'help'
else if(msg !== undefined){
msg = msg.trim().toLowerCase()
if(msg.indexOf("help") > -1)
msg = 'help'
else{
if(msg.startsWith('lead')){
msg = 'lead'
}
}
}
switch (msg) {
case 'lead':
await this.lead(context, userKey, platform)
break;
case 'qualify_lead':
await this.qualifyLead(context)
break;
}
await next();
});
}
I'm not sure exactly what this.successCard('Lead is qualified') does, but presumably it returns an Activity. To my knowledge, in order for this Activity to replace another one, you need to set it's Id property to match the previous message. That means that, when you send the previous message (i.e. the card), you need to capture the reference that's returned from the send method on the context (e.g. into bot state), and then use it for this new activity.
As I explained in my answer to your other question, you need to save the activity ID in bot state and then apply it to the update that you're sending. The Bot Framework can't update an activity unless you tell it which activity to update, and you do that using an activity ID.
This was the part that saves the ID:
const dict = await this.carouselState.get(turnContext, {});
dict[batchId] = {
[KEYACTIVITYID]: response.id,
[KEYLEADS]: leads
};
And this was the part that applies it to the updated activity:
const update = this.createCarousel(batchId, leads);
update.id = info[KEYACTIVITYID];

undefined parameter received in Bixby function

I'm trying to process an utterance in the format "Get News from Impeachment Sage" where Impeachment Sage corresponds to an enum of publication names. Bixby is successfully understanding the utterance and trying to call my goal (GetNewsByName) but the trained Value is not arriving at the function. (This is based off the user persistence data example).
The operative portion of the function is thus:
function getNewsByName(altBrainsNames) {
// const name = "Impeachment Sage" //hard coded for testing
const url = properties.get("config", "baseUrl") + "altbrains"
console.log("i got to restdb.js and the url is ", url);
console.log("AltBrainsNames is", altBrainsNames)
const query = {
apikey: properties.get("secret", "apiKey"),
q: "{\"" + "name" + "\":\"" + name + "\"}"
// q: "{}"
}
console.log("query", query)
const options = {
format: "json",
query: query,
cacheTime: 0
}
const response = http.getUrl(url, options)
if (response) {
const content1 = response
// const altBrainsData = response[0][properties.get("config", "altbrainsName")]
// altbrainsData.$id = response[0]["_id"]
console.log('content1', content1);
console.log('identifier', content1)
return content1
} else {
// Doesn't exist
console.log('doesnae exist');
return
}
}
What is happening here where the Value is not reaching the function?
The Action model is:
action (GetNewsByName) {
description ("Get news data from remote Content db by searching on AltBrain name")
type (Calculation)
output (Content)
collect {
input (altBrainsNames) {
type (AltBrainsNames)
min (Required) max (One) //this means js must catch error when multiple names offered
}
}
}
We resolved this offline, just wanted to follow up on the public channel to any fellow Bixby developers seeing this question posted. The function that calls 'getNewsByName' needs to receive the input parameter. Once populated, the action worked successfully.

Including dialogs or reusing dialogs from different file

I am trying to do begindialog from another another dialog js file. I am getting error.
<b>[onTurnError]: Error: DialogContext.beginDialog(): A dialog with an id of 'FollowUpDialog' wasn't found. </b>
this is dialog structure-
dialogs
orderstatus
orderstatus.js
index.js
FollowUp
followUp.js
index.js
i am trying to include FollowUp dialog in OrderStatus Dialog, similary i have other dialogs where i want to begin followup or orderstatus dialog. trying to reuse the dialogs.
One way to do use how we include the file in botjs amd to do adddialog same way i can include in otherfile. But it is redundant work. I am trying to avoid that. Can some one tell me better approach to include the dialog in different dialogs.
code:
Below code is from greeting.js
If you see line where i am doing begindialog.
return await step.beginDialog(ORDER_STATUS_DIALOG);
return await step.beginDialog(ENTITLEMENT_CHECK_DIALOG);
It is error. I am trying to include the dialog which is part of different JS files.
async step => {
if (step.context.activity.channelId == 'directline') {
const buttons = [{
type: ActionTypes.ImBack,
title: 'Repair Order Status',
value: symbolicString.ZEB_GR_STR_013
}, {
type: ActionTypes.ImBack,
title: 'Entitlement Check',
value: symbolicString.ZEB_GR_STR_014
}];
const card = CardFactory.heroCard('', undefined, buttons, {
text: symbolicString.ZEB_GR_STR_015
});
const reply = {type: ActivityTypes.Message};
reply.attachments = [card];
return await step.context.sendActivity(reply);
} else {
return await step.prompt(MENU_PROMPT, symbolicString.ZEB_GR_STR_028);
}
},
async step => {
if (step.context.activity.channelId != 'directline'){
console.log("step greeting dialog next step");
console.log(step);
console.log(step.context.activity);
if (step.context.activity.text == '1'){
return await step.beginDialog(ORDER_STATUS_DIALOG);
}else if (step.context.activity.text == '2'){
return await step.beginDialog(ENTITLEMENT_CHECK_DIALOG);
}
}
return await step.endDialog();
}
]));

Botbuilder route action message to correct dialog

I am building a bot with the botbuilder framework using node, and I am now trying to use the CardAction.dialogAction:
builder.CardAction.dialogAction(session, 'help', 'topic:mytopic', 'Click me')
It generates a message that looks like this:
action?help=topic:mytopic
Now I need to route that action to the correct dialog to handle it, but I can't figure out where and how to do that. Seeing as this is a builtin feature, I figured there should be easy ways of doing this already?
Appreciate the help.
For a lack of any other options at this point I resorted to writing my own simple action recognizer. It is no beauty, but it does the trick.
function action_recognizer() {
return {
recognize: function (context, done) {
let intent = { score: 0.0 }
const text = context.message.text
if (text) {
const m = text.match(/action\?(\w+)=?(.+)/)
logger.debug(m)
if (m) {
switch (m[1]) {
case 'help':
intent = { score: 1.0, intent: 'help' }
if (m.length > 2) {
intent.entities = [{entity: m[2], type: 'custom_intent'}]
}
break
}
}
}
done(null, intent)
}
}
}
This will basically direct to my help:/ dialog. Which in turn will read the entities list (if custom_intent types are available).

Threading pattern: Chaining and looping

I need to use a WCF API to save data into a DB. Ordinarily, I'd use chaining, like the example below:
IClientBroker clientBroker = UIContext.CreateWcfInterface<IClientBroker>("Data/ClientBroker.svc");
clientBroker.BeginSetClientBusinessName(_client.ID, businessName, (result) =>
{
_client = ((IClientBroker)result.AsyncState).EndSetClientBusinessName(result);
clientBroker.BeginSetClientAddress(_client.ID, addressObservableCollection, postcodeZip, (result2) =>
{
_client = ((IClientBroker)result2.AsyncState).EndSetClientAddress(result2);
clientBroker.BeginSetClientTelephone(_client.ID, telephone, (result3) =>
{
_client = ((IClientBroker)result3.AsyncState).EndSetClientTelephone(result3);
clientBroker.BeginSetClientFax(_client.ID, fax, (result4) =>
{
_client = ((IClientBroker)result4.AsyncState).EndSetClientFax(result4);
if (customFields.Save(validationSummaryBridge))
{
CloseWindow(true, "ClientID=" + _client.ID.ToString());
}
else
{
validationSummary.Errors.Add(new ValidationSummaryItem("Failed to save Custom Fields"));
}
}, clientBroker);
}, clientBroker);
}, clientBroker);
}, clientBroker);
}
This gives me faux-synchronous behaviour which I need so exceptions are thrown in a timely fashion and I can react on validation events.
This doesn't map well, however, when I have a loop of fields to save. For example, what pattern would be best to save the following list of "Custom Fields", where each Custom Field must be saved using a single WCF call?
ICustomFieldsBroker customFieldsBroker = UIContext.CreateWcfInterface<ICustomFieldsBroker>("Data/CustomFieldsBroker.svc");
foreach (CustomField customField in _customFields)
{
string newValue=_customFieldControlDictionary[customField].CustomField.Value;
customFieldsBroker.BeginSetCustomFieldValueForItem(DataTypeID, DataItemID, customField.Key, newValue, (result) =>
{
((ICustomFieldsBroker)result.AsyncState).EndSetCustomFieldValueForItem(result);
}, customFieldsBroker);
}
In the above example, this would just set off, say, 5 requests to the WCF API/threads which would potentially return AFTER the form has closed. I need them to "line up", so I can list their status and return to the form.
Thanks very much.
Don't let the WCF distract you, but if you have any comments, do let me know. :)
This is the answer I was looking for:
http://www.netfxharmonics.com/2008/11/Understanding-WCF-Services-in-Silverlight-2#WCFSilverlightThreadWaiting

Resources