How to prevent duplicate action execution in Bixby? - bixby

I want to implement a capsule that does a calculation if the user provides the full input necessary for the calculation or asks the user for the necessary input if the user doesn't provide the full input with the very first request. Everything works if the user provides the full request. If the user doesn't provide the full request but Bixby needs more information, I run into some strange behavior where the Calculation is being called more than once and Bixby takes the necessary information for the Calculation from a result of another Calculation, it looks like in the debug graph.
To easier demonstrate my problem I've extended the dice sample capsule capsule-sample-dice and added numSides and numDice to the RollResultConcept, so that I can access the number of dice and sides in the result.
RollResult.model.bxb now looks like this:
structure (RollResultConcept) {
description (The result object produced by the RollDice action.)
property (sum) {
type (SumConcept)
min (Required)
max (One)
}
property (roll) {
description (The list of results for each dice roll.)
type (RollConcept)
min (Required)
max (Many)
}
// The two properties below have been added
property (numSides) {
description (The number of sides that the dice of this roll have.)
type (NumSidesConcept)
min (Required)
max (One)
}
property (numDice) {
description (The number of dice in this roll.)
type (NumDiceConcept)
min (Required)
max (One)
}
}
I've also added single-lines in RollResult.view.bxb so that the number of sides and dice are being shown to the user after a roll.
RollResult.view.bxb:
result-view {
match {
RollResultConcept (rollResult)
}
render {
layout {
section {
content {
single-line {
text {
style (Detail_M)
value ("Sum: #{value(rollResult.sum)}")
}
}
single-line {
text {
style (Detail_M)
value ("Rolls: #{value(rollResult.roll)}")
}
}
// The two single-line below have been added
single-line {
text {
style (Detail_M)
value ("Dice: #{value(rollResult.numDice)}")
}
}
single-line {
text {
style (Detail_M)
value ("Sides: #{value(rollResult.numSides)}")
}
}
}
}
}
}
}
Edit: I forgot to add the code that I changed in RollDice.js, see below:
RollDice.js
// RollDice
// Rolls a dice given a number of sides and a number of dice
// Main entry point
module.exports.function = function rollDice(numDice, numSides) {
var sum = 0;
var result = [];
for (var i = 0; i < numDice; i++) {
var roll = Math.ceil(Math.random() * numSides);
result.push(roll);
sum += roll;
}
// RollResult
return {
sum: sum, // required Sum
roll: result, // required list Roll
numSides: numSides, // required for numSides
numDice: numDice // required for numDice
}
}
End Edit
In the Simulator I now run the following query
intent {
goal: RollDice
value: NumDiceConcept(2)
}
which is missing the required NumSidesConcept.
Debug view shows the following graph, with NumSidesConcept missing (as expected).
I now run the following query in the simulator
intent {
goal: RollDice
value: NumDiceConcept(2)
value: NumSidesConcept(6)
}
which results in the following Graph in Debug view:
and it looks like to me that the Calculation is being done twice in order to get to the Result. I've already tried giving the feature { transient } to the models, but that didn't change anything. Can anybody tell me what's happening here? Am I not allowed to use the same primitive models in an output because they will be used by Bixby when trying to execute an action?

I tried modifying the code as you have but was unable to run the intent (successfully).
BEGIN EDIT
I added the additional lines in RollDice.js and was able to see the plan that you are seeing.
The reason for the double execution is that you ran the intents consecutively and Bixby derived the value of the NumSidesConcept that you did NOT specify in the first intent, from the second intent, and executed the first intent.
You can verify the above by providing a different set of values to NumSidesConcept and NumDiceConcept in each of the intents.
If you had given enough time between these two intents, then the result would be different. In your scenario, the first intent was waiting on a NumSidesConcept to be available, and as soon as the Planner found it (from the result of the second intent), the execution went through.
How can you avoid this? Make sure that you have an input-view for each of the inputs so Bixby can prompt the user for any values that did not come through the NL (or Aligned NL).
END EDIT
Here is another approach that will NOT require changing the RollResultConcept AND will work according to your expectations (of accessing the number of dice and sides in the result-view)
result-view {
match: RollResultConcept (rollResult) {
from-output: RollDice(action)
}
render {
layout {
section {
content {
single-line {
text {
style (Detail_M)
value ("Sum: #{value(rollResult.sum)}")
}
}
single-line {
text {
style (Detail_M)
value ("Rolls: #{value(rollResult.roll)}")
}
}
// The two single-line below have been added
single-line {
text {
style (Detail_M)
value ("Dice: #{value(action.numDice)}")
}
}
single-line {
text {
style (Detail_M)
value ("Sides: #{value(action.numSides)}")
}
}
}
}
}
}
}
Give it a shot and let us know if it works!

Related

Bixby: Audio player concept

I am showing artist to user and following up by asking if user wanted to listen the song of this artist.
Endpoint
action-endpoint (PlayArtist) {
accepted-inputs (artist_id) // here too yellow line (Missing required input `artistToPlay`)
local-endpoint (PlayArtist.js)
}
action-endpoint (BuildArtistAudioInfo) {
accepted-inputs (artist_id)
local-endpoint (BuildArtistAudioInfo.js)
}
BuildArtistAudioInfo.model.bxb
action (BuildArtistAudioInfo) {
type (Search)
description (Makes a meow info to play.)
collect{
input (artist_id){
type (ArtistID)
min (Optional)
max (One)
}
}
output (audioPlayer.AudioInfo)
}
Follow-up
followup {
prompt {
dialog (Would you like to play music for this artist)
on-confirm {
if (false) {
message (I see...)
} else {
intent {
goal: PlayArtist
value: $expr(singleArtistEvent.artistId)
}
}
}
on-deny {
message (OK friend.)
}
}
}
Now i need to pass this value to PlayArtist action file but after seeing your sample capsule i am confused where to pass the input.
PlayArtist Action file
action (PlayArtist) {
type (Search)
collect {
input (artist_id){
type (ArtistID)
min (Optional)
max (One)
}
computed-input (artistToPlay) {
description (Create the playlist to play)
type (audioPlayer.AudioInfo)
min (Required) max (One)
compute {
intent {
goal: BuildArtistAudioInfo
value:ArtistID(artist_id) // i am confused over here, it's giving error ()
}
}
//hidden
}
computed-input (play) {
description (By passing in the AudioInfo object to the PlayAudio action, we ask the client to play our sound.)
type (audioPlayer.Result)
compute {
intent {
goal: audioPlayer.PlayAudio
value: $expr(artistToPlay)
}
}
//hidden
}
}
output (Result)
}
Do i need to extra accept-input? also BuildArtistAudioInfo.js file has songs info, so i need to get the artist id here and proceed it to the api where I will get song info. please guide.
You need an input artistID in action model PlayArtist and action model BuildArtistAudioInfo, that's how the artistID pass to BuildArtistAudioInfo.js as an argument
In computed-input (artistToPlay), intent block should have a value field
Use value in intent instead of value-set, since it's single value, not a set
Make sure in endpoints.bxb you correctly link JS file to action model, that is accepted input and argument name must match.

How can I let the user give NL input when showing them an input-view?

I have a capsule that calculates something based on user input. The user needs to tell my capsule an originating country (FromCountryConcept), destination country (ToCountryConcept), and a text (LetterContentConcept). Since the country concepts are enum, the input-view for those are simple selection-of. For the input-view for the text I use a textarea. All of the code is below and available on github in this repository: SendLetter-Bixby
When the user uses the Bixby Views to give the required input to Bixby everything works as expected.
How can I let the user provide input to the shown input-view using (spoken or typed) NL input?
My action SendLetter.model.bxb looks like this:
action (SendLetter) {
description (Sends a Letter from one country to another and calculates the cost based on the letter content length.)
type (Calculation)
collect {
input (fromCountry) {
type (FromCountryConcept)
min (Required)
max (One)
default-init {
intent {
goal: FromCountryConcept
value-set: FromCountryConcept {
FromCountryConcept(Germany)
FromCountryConcept(South Korea)
FromCountryConcept(USA)
FromCountryConcept(Spain)
FromCountryConcept(Austria)
FromCountryConcept(France)
}
}
}
}
input (toCountry) {
type (ToCountryConcept)
min (Required)
max (One)
default-init {
intent {
goal: ToCountryConcept
value-set: ToCountryConcept {
ToCountryConcept(Austria)
ToCountryConcept(South Korea)
ToCountryConcept(USA)
ToCountryConcept(Spain)
ToCountryConcept(Germany)
ToCountryConcept(France)
}
}
}
}
input (letterContent) {
type (LetterContentConcept)
min (Required)
max (One)
}
}
output (SendLetterResponseConcept)
}
The input-view for the country concepts FromCountry_Input.view.bxb looks like this (ToCountry_Input.view.bxb is equivalent):
input-view {
match: FromCountryConcept(this)
message {
template ("Select the country this letter will be sent from")
}
render {
selection-of (this) {
where-each (fromCountry) {
// default-layout used
}
}
}
}
The input-view for the text I want the user to be able to input is in LetterContent_Input.view.bxb:
input-view {
match: LetterContentConcept(this)
message {
template ("Write the content of the letter.")
}
render {
form {
on-submit {
goal: LetterContentConcept
value {
viv.core.FormElement(letterContent)
}
}
elements {
textarea {
id (letterContent)
label ("Letter Content")
type (LetterContentConcept)
value ("#{value(this)}")
}
}
}
}
}
You're at a prompting moment, so you need to add prompt training.
This will allow the user to use NL to respond to your prompt.
In the training tab, it looks like this:
https://bixbydevelopers.com/dev/docs/dev-guide/developers/training.intro-training#add-training-examples-for-prompts

How do I train on time when I already know the date?

I'm looking to filter by a time interval. For example, from 9am to noon. It is unclear how to create that functionality in the training and action concept. For example, I currently have this:
You should train it with DateTimeExpression and use the DateTimeInterval component to get the actual difference
action (TestDateTimeInterval) {
type(Search)
description (__DESCRIPTION__)
collect {
input (dateTimeExpression) {
type (time.DateTimeExpression)
min (Optional) max (One)
}
}
output (core.Integer)
}
Action Javascript
module.exports.function = function testDateTimeInterval (dateTimeExpression) {
var dates = require ('dates')
var whenStart;
var whenEnd;
if (dateTimeExpression.dateTimeInterval) {
whenStart = dates.ZonedDateTime.of(
dateTimeExpression.dateTimeInterval.start.date.year,
dateTimeExpression.dateTimeInterval.start.date.month,
dateTimeExpression.dateTimeInterval.start.date.day,
dateTimeExpression.dateTimeInterval.start.time.hour,
dateTimeExpression.dateTimeInterval.start.time.minute,
dateTimeExpression.dateTimeInterval.start.time.second);
whenEnd = dates.ZonedDateTime.of(
dateTimeExpression.dateTimeInterval.end.date.year,
dateTimeExpression.dateTimeInterval.end.date.month,
dateTimeExpression.dateTimeInterval.end.date.day,
dateTimeExpression.dateTimeInterval.end.time.hour,
dateTimeExpression.dateTimeInterval.end.time.minute,
dateTimeExpression.dateTimeInterval.end.time.second);
// If you intend to return the difference between the number of hours
return dateTimeExpression.dateTimeInterval.end.time.hour - dateTimeExpression.dateTimeInterval.start.time.hour
}
return -1;
}

Bixby: How do I set an initial-value to self BirthdayInfo in a date-picker within a input-view?

What additional steps do I need to take set the date-picker's initial-value to the user's birthday using the viv.self library? Is this the best place to handle this? Currently I am setting the default to 30 Years Prior.
render {
date-picker {
// Default Date -30 Years (viv.self Birthday Option)
initial-value ("subtractDuration(now().date, 'P30Y')")
restrictions {
// allow selection 80 Years Ago
min-allowed ("subtractDuration(now().date, 'P80Y')")
// to allow selection 18 Years Ago
max-allowed ("subtractDuration(now().date, 'P18Y')")
}
}
}
You can use a match-pattern to achieve this.
Birthday is a part of the viv.contact library but is not available in the viv.self library.
In your Action, create an additional attribute of type time.Date (or a concept with role-of of time.Date) to hold the Birthday.
Heres how the code would look like.
action (GetBirthDate) {
type(Constructor)
description (Collect the date selected by a user)
collect {
input (usersBirthday) {
type (BirthDate)
min (Required) max (One)
default-init {
intent {
// TO-DO Get the birthday with an intent
}
}
}
// This input will hold the value entered by the user
computed-input (birthDate) {
type (BirthDate)
min (Required) max (One)
compute {
intent {
goal: BirthDate
}
}
}
} output (BirthDate)
}
The BirthDate concept used in the code above looks like this
structure (BirthDate) {
description (__DESCRIPTION__)
role-of (time.Date)
}
Your input-view will look like this. This defines a match-pattern that is invoked whenever we need an input view for BirthDate and that BirthDate is functioning as an input back to the action.
Checkout match patterns here: https://bixbydevelopers.com/dev/docs/dev-guide/developers/customizing-plan.match-patterns
input-view {
match: BirthDate (this) {
to-input {
GetBirthDate (action)
}
}
render {
date-picker {
initial-value (action.usersBirthday)
restrictions {
// allow selection 80 Years Ago
min-allowed ("subtractDuration(now().date, 'P80Y')")
// to allow selection 18 Years Ago
max-allowed ("subtractDuration(now().date, 'P18Y')")
}
}
}
}

protractor: filter until finding first valid element

I am doing e2e testing on a site that contains a table which I need to iterate "until" finding one that doesn't fail when I click on it.
I tried it using filter and it is working:
this.selectValidRow = function () {
return Rows.filter(function (row, idx) {
row.click();
showRowPage.click();
return errorMessage.isDisplayed().then(function (displayed) {
if (!displayed) {
rowsPage.click(); // go back to rows Page, all the rows
return true;
}
});
}).first().click();
};
The problem here is that it is iterating all available rows, and I only need the first one that is valid (that doesn't show an errorMessage).
The problem with my current approach is that it is taking too long, as my current table could contain hundreds of rows.
Is it possible to filter (or a different method) and stop iterating when first valid occurrence appears?, or could someone come up with a better approach?
If you prefer a non-protractor approach of handling this situation, I would suggest async.whilst. async is a very popular module and its highly likely that your application is using it. I wrote below code here in the editor, but it should work, you can customize it based on your needs. Hopefully you get an idea of what I'm doing here.
var found = false, count = 0;
async.whilst(function iterator() {
return !found && count < Rows.length;
}, function search(callback) {
Rows[count].click();
showRowPage.click();
errorMessage.isDisplayed().then(function (displayed) {
if (!displayed) {
rowsPage.click(); // go back to rows Page, all the rows
found = true; //break the loop
callback(null, Rows[count]); //all good, lets get out of here
} else {
count = count + 1;
callback(null); //continue looking
}
});
}, function aboutToExit(err, rowIwant) {
if(err) {
//if search sent an error here;
}
if(!found) {
//row was not found;
}
//otherwise as you were doing
rowIwant.click();
});
You are right, filter() and other built-in Protractor "functional programming" methods would not solve the "stop iterating when first valid occurrence appears" case. You need the "take some elements while some condition evaluates to true" (like the itertools.takewhile() in Python world).
Fortunately, you can extend ElementArrayFinder (preferably in onPrepare()) and add the takewhile() method:
Take elements while a condition evaluates to true (extending ElementArrayFinder)
Note that I've proposed it to be built-in, but the feature request is still open:
Add takewhile() method to ElementArrayFinder

Resources