How can I pass collected values to a replanned action? - bixby

When the user says "read john 3:100", I have a ReadBibleVerse action that matches book:john, chapter:3, verse:100. The endpoint will return a 404, since there is no verse 100.
I want that action to capture the error and replan to a "read chapter" request, passing along book:john and chapter:3.
What I have...
action (ReadBibleVerse) {
collect {
input (book) {…}
input (chapter) { type (ChapterNum) … }
input (verse) {…}
}
output (Scripture) {
throws {
unknown-error {
on-catch {
replan {
dialog ("Unknown verse, trying the chapter.")
intent {
goal: Scripture
route: ReadBibleChapter
}}}}}}}
…what I get is "Unknown verse, trying the chapter. I need a book to continue."
I'm clearly hitting the error and, I believe, being "replanned" to ReadBibleChapter, but I'm also getting "I need a book to continue." because I need to explicitly pass along book and chapter?
I found intent.value, which appears to solve my problem, except I can't seem to find the correct format:
value: ChapterNum
value: ChapterNum (chapter)
value: [namespace].ChapterNum { $expr(chapter) }
more various nonsense
…

This should work value {$expr(chapter)}

Related

SwiftUI #MainActor loses global actor

I've got an ObservableObject class that I am calling to via #StateObject from my view. Inside that class is a function and the entire class is marked #MainActor. In my view there is a TextField with an onChange modifier that has a perform calling to that function via viewModel.funcname.
Xcode is complaining with an error of:
Converting function value of type '#MainActor (String) -> Void' to '(String) -> Void' loses global actor 'MainActor'
I've been researching this for hours now and have found little to nothing. Even Apple's own docs say to simply use await but that doesn't work, at least not the many ways I've tried it.
This is the code within my view:
TextField("", text: $viewModel.username)
.onChange(of: viewModel.username, perform: viewModel.editingChanged) // This is where the error occurs
This is the function in my class (remember that the entire class is marked #MainActor):
func editingChanged(_ value: String) {
let jsonFetchUserExistsURL = URL(string: "https://blah.com")
let jsonFetchUserExistsTask = jsonFetch(jsonFetchUserExistsURL, defaultValue: [UserExists]())
guard isNetworkActive else { loadingAlert = true; return }
Task {
jsonFetchUserExistsTask.sink (receiveCompletion: { completion in
switch completion {
case .failure:
self.loadingState = .failed
case .finished:
self.checkUser()
}
},
receiveValue: { loadedUserExists in
self.userExists = loadedUserExists
}).store(in: &requests)
}
}
}
I have tried modifying the onChange to read as follows:
.onChange(of: viewModel.username, perform: await viewModel.editingChanged)
.onChange(of: viewModel.username, perform: Task { await viewModel.editingChanged })
.onChange(of: viewModel.username, perform: Task.detached { await viewModel.editingChanged })
.onChange(of: viewModel.username, perform: DispatchQueue.main.async { viewModel.editingChanged })
The entire reason I marked the class #MainActor is because Xcode complained that the function wasn't running on the main thread. It compiled but froze after it complained a few times in the console.
Nothing I've tried seems to change anything. Hoping someone can shed some light on this.
See my comment on the question about whether or not the #MainActor strategy is the right way to address an underlying issue, but to directly address your compilation error, you can use this syntax, which compiles fine:
.onChange(of: viewModel.username) { viewModel.editingChanged($0) }

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 to prevent duplicate action execution in 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!

Reason for different API responses to request in node and chrome?

So I have a bunch of tracks from Spotify's API and I want their genres (which Spotify doesn't give) so for every track I make an API call to Last FM to get their top tags. Now this works for most tracks, I have to match the track name and artist as strings to last fm:
Here's my problem:
I do like this (pseudo:ish code):
let promises = spotifyTracks
.map(track => rp({url: "http://lastfmapi.com/?artist="+track.artist+"?track="+track.name })
.then(response => {
track.genre = response.genre;
return track;
})
);
return Promise.all(promises).then(() => console.log('done!'));
Using request promise.
Now there a few tracks that currrently baffles me. Like 10 in 600. I get a response from lastFM saying:
{ error: 6, message: 'Track not found', links: [] }
To double check I printed the url used:
"http://lastfmapi.com/?artist="+track.artist+"?track="+track.name
Inside the then-call along with the response.
Now if I copied that url from my output and pasted it right into my chrome-browsers address-bar, the API finds the track!?!??!
the actual example
http://ws.audioscrobbler.com/2.0/?method=track.gettoptags&artist=pugh+rogefeldt&track=små+lätta+moln&autocorrect=1&api_key=141bed9ffc180dd9b07ac93b7e3b56d7&format=json
When it is called in my node-code I get
{ error: 6, message: 'Track not found', links: [] }
when called in the chrome address bar I get
{"toptags": {
"tag":
[
{
"count":100,
"name":"swedish",
"url":"https://www.last.fm/tag/swedish"
},
{
"count":100,
"name":"singer-songwriter",
"url":"https://www.last.fm/tag/singer-songwriter"
},
...
],
"#attr":{
"artist":"Pugh Rogefeldt",
"track":"Små lätta moln"
}
}
}
Anyone got any idea what could be the reason behind this discrepancy?
Chrome address bar will encode the string into URL for you, which will make your actual example become
method=track.gettoptags&artist=pugh+rogefeldt&track=sm%C3%A5+l%C3%A4tta+moln&autocorrect=1&api_key=141bed9ffc180dd9b07ac93b7e3b56d7&format=json
You should do the same thing in your node-code with encodeURIComponent

Protractor: Is it possible to check if an element doesn't contain certain text?

On the page that I am testing, a user can have a single currency or multiple currencies (i.e EUR and USD)the currency/currencies will appear in the same div at the top of the page.
If a user has multiple currencies, a tab for each currency will appear further down the page, if a user has only one currency, no tabs will appear (as there is no need for the user to switch tabs).
I am able to test multi currency users by checking to see if the text contained in the header matches the text contained in the currencies tabs.
However, as no tabs appear for a single currency, I'm not sure how to test this.
For example, if I have only a 'EUR' currency, is there a way to do something like
if element(by.className("currencies"))contains 'EUR'
&& doesn't contain 'USD' && doesn't contain 'GBP'
expect element(by.className("tabs").toDisplay.toBeFalsy()
This is the code for the page object file
this.checkCurrency = function(currency) {
var checkBalance = element(by.className("balances"));
checkBalance.getText().then(function (text) {
if (text.indexOf("GBP" && "EUR")>= 0) {
expect(element.all(by.linkText("GBP")).isDisplayed()).toBeTruthy();
console.log("EUR GBP buyer");
}
else if (text.indexOf("GBP" && "USD")>= 0) {
expect(element.all(by.linkText('USD')).isDisplayed()).toBeTruthy();
console.log("USD GBP buyer");
}
else
{
console.log("false");
}
});
};
From your description I'm not quite sure where the failure is. In general you want to keep this kind of logic out of your page object. Your test should understand what state the page should be in and call different functions. I know that's not always possible, but it works out so much better if you can. Here is some general condition advise that should help.
You can catch the success state and a failed state of a promise. Most people use the pass function, but forget about the fail function.
promise.then(passFunction, failFunction)
You can use this in several different ways. If you realize that almost everything in protractor is returning a promise.
Example:
element(by.className("currencies")).getText()
.then(
function(text) {
//check on something
},function(error){
//don't check on something
if(someCondition) {
throw error;
} else {
//the test continues
}
});
You can even do it with and expect
expect(element(by.className("currencies")).getText()).not.toContain("EUR")
.then(
function(passed) {
//check on something
},function(failed){
//don't check on something
if(someCondition) {
throw failed;
} else {
//the test continues
}
});
Or a simple findElement
element(by.className("currencies"))
.then(
function(element) {
//check on something
},function(error){
//don't check on something
if(someCondition) {
throw failed;
} else {
//the test continues
}
});

Resources