How to pass one more parameter other than option from the intent showing the carousel to the intent handling the carousel? - dialogflow-es

I am using an intent to first present the carousel to the user.
When the user clicks on one of the options in the carousel, in the handler intent I get the key of the carousel item that the user selected.
Example of carousel intent,
app.intent('search', async (conv,params) => {
conv.ask(`Choose one item`,new Carousel({
title :`Search results`,
items : carouselItems,
}));
});
Example of the handler intent,
app.intent('handle_carousel', async (conv,params,option) => {
const key = parseInt(option);
});
However, along with the key of the option selected I also want to pass another integer from the carousel intent to the handler intent.
This other integer is different for each option. You can think of the other integer as an ID, it's unique for each option.
How can I achieve that?

You have a few approaches for passing additional data that should be associated with each key.
The first is, as you note in your answer, storing that mapping in a table that is stored as part of session data (either using conv.data or a Dialogflow context).
Another is to encode that data as part of the key that you include with each option, and then decode the key when you get it back.
So, for example, you could make the key a result of an encode function like
function encodeOptionKey( key, otherValue ){
return `${key}:${otherValue}`
}
and then decode it with a function such as
function decodeOptionKey( option ){
const [key,otherValue] = option.split(':');
return {
key,
otherValue
}
}
and call this from your handler with something like
app.intent('handle_carousel', async (conv,params,option) => {
const {key, otherValue} = decodeOptionKey( option );
// ...
});

I created a map of the keys of various carousel options and the corresponding parameter I wanted to pass, saved that map in conv.data.store, which is the conversation storage provided by actions-on-google. Then I used that map to get the parameter from the carousel key that was being passed to the handler intent.
For example in the carousel intent :
let map = {
keyofcarousel : option,
other_parameter : otherparam,
};
conv.data.store = map;
Then call conv.data.store in the handler intent.

Related

How to use Parameters to modify Context in Google Dialogflow

We are running an experiment, where I need to manipulate the dialog flow responses based on the participant's ID. My thought is there is a way to set the Output Context based on a parameter value.
For example, we have a prompt that asks for the participant's id. This matches an intent with a "participantID" parameter. Now what I would like to do is set the output context to be the value of the "participantID" parameter. Then I could set the input context to be a specific "participantID".
Any parameters set on an intent will be kept in the output context and recoverable in posterior follow-up intents within the lifespan of such context.
Through the Dialogflow UI you can reference them in the initial response as $<parameter-name>.
For example, assuming your parameter name is id, the response can be: Welcome player $id,....
For values in the context in posterior follow-up intents use #<context-name>.<parameter-name>.
For example, in a second follow-up which has parameter answer and input context id-followup use Your answer has been $answer, if you are not player #id-followup.id please let me know your actual id.
If you need to use the Fulfillment Webhook I recommend a structure like the one illustrated here, i.e. a structure like:
//The user inputs a participant ID (name of the parameter is id)
function answers(agent){
const id = agent.parameters.id;
agent.add(`So your id is ${id}`);
agent.add(`Any extra questions...`);
agent.setContext({
name: 'question',
lifespan: 5, // Amount of follow-up intents this context will be kept
parameters: {'cid': id},
});
}
// Next functions that use the participant id as input context
function answers2(agent){
// Get the context and from it extract the id value
const cont = agent.getContext('question');
const particular_id = cont.parameters.cid;
// The rest of your code
Some documentation you may find useful includes Input and output contexts, Parameter reference,...
Depending on what version of dialogflow-fulfillment you are using, I'm talking about 0.6.1 here. The syntax for setting context is:
agent.context.set({
name: "ctx_name",
lifespan: 5,
parameters: {'cid': id},
});

How to combine ChoicePrompt and TextPrompt in the same step

I am using Microsoft Bot Framework V4 in node.js. In a step of a Dialog we need to combine buttons using the ChoicePrompt object but also the TextPrompt. In case the user clicks the buttons the suggested actions will be triggered, and if the user writes plain text, we handle the action using LUIS and certain intents. The problem is combining both actions.
I have tried to avoid re-prompting when using the ChoicePrompt, but I couldn't manage. Also I look for other prompts that directly could combine buttons and text but it seems there's not any.
First I declare the objects I am using in the prompt:
class ExampleDialog extends LogoutDialog {
constructor(userState, logger) {
super(EXAMPLE_DIALOG);
this.addDialog(new TextPrompt(TEXT_PROMPT));
this.addDialog(new ChoicePrompt(CHOICE_PROMPT));
Second, in the steps, I use the prompts declared before:
async firstStep(step) {
const promptOptions = {
prompt: 'Text to prompt',
retryPrompt: 'Retry text prompt',
choices: ChoiceFactory.toChoices(['option1', 'option2', 'option3'])
};
const promptAction = await step.prompt(A_PROMPT_ID, promptOptions);
return promptAction;
}
async secondStep(step) {
const thePreviousStepResult = step.result.values
}
Text prompts are the way to go when you want to accept any string. Remember that you can include any activity as the prompt property of your prompt options, and that activity can contain attachments, suggested actions, etc. You can see in the source code that a choice prompt just calls Prompt.appendChoices which uses ChoiceFactory to generate the buttons for its activities. You can do the same thing:
const promptOptions = {
prompt: ChoiceFactory.forChannel(step.context, ['option1', 'option2', 'option3'], 'Text to prompt')
// You can also include a retry prompt if you like,
// but there's no need to include the choices property in a text prompt
};
const promptAction = await step.prompt(TEXT_PROMPT, promptOptions);
return promptAction;

BotFramework: Start a Form from a Dialog using Intents

Regarding the Microsoft Bot Framework, we all know the samples given by Microsoft. Those samples, however, normally have "one single purpose", that is, the Pizzabot is only for ordering Pizzas, and so on.
Thing is, I was hoping on creating a more complex Bot that actually answers a series of things. For this I am creating a "lobby" dialog where all the messages go, using this MessageController:
return await Conversation.SendAsync(message, () => new LobbyDialog());
On that "Lobby" dialog I have a series of LUIS intents for different things, and since it picks the Task based on the intent, it works nicely.
However, for more complex operations, I was hoping on using the FormFlow mechanism so I can have forms like in the PizzaBot sample. The problem is that all of the "form bots" that are sampled always use this message controller type:
return Chain.From(() => new PizzaOrderDialog(BuildForm)
And in the same MessagesController stablishes the builder flow, like this:
var builder = new FormBuilder<PizzaOrder>();
ActiveDelegate<PizzaOrder> isBYO = (pizza) => pizza.Kind == PizzaOptions.BYOPizza;
ActiveDelegate<PizzaOrder> isSignature = (pizza) => pizza.Kind == PizzaOptions.SignaturePizza;
ActiveDelegate<PizzaOrder> isGourmet = (pizza) => pizza.Kind == PizzaOptions.GourmetDelitePizza;
ActiveDelegate<PizzaOrder> isStuffed = (pizza) => pizza.Kind == PizzaOptions.StuffedPizza;
return builder
// .Field(nameof(PizzaOrder.Choice))
.Field(nameof(PizzaOrder.Size))
.Field(nameof(PizzaOrder.Kind))
.Field("BYO.Crust", isBYO)
.Field("BYO.Sauce", isBYO)
.Field("BYO.Toppings", isBYO)
.Field(nameof(PizzaOrder.GourmetDelite), isGourmet)
.Field(nameof(PizzaOrder.Signature), isSignature)
.Field(nameof(PizzaOrder.Stuffed), isStuffed)
.AddRemainingFields()
.Confirm("Would you like a {Size}, {BYO.Crust} crust, {BYO.Sauce}, {BYO.Toppings} pizza?", isBYO)
.Confirm("Would you like a {Size}, {&Signature} {Signature} pizza?", isSignature, dependencies: new string[] { "Size", "Kind", "Signature" })
.Confirm("Would you like a {Size}, {&GourmetDelite} {GourmetDelite} pizza?", isGourmet)
.Confirm("Would you like a {Size}, {&Stuffed} {Stuffed} pizza?", isStuffed)
.Build()
;
My big question here is, is it possible to start the conversation with the MessagesController that I used and then in the LobbyDialog, use an Intent that fires a Form and returns it? That is, start a flow from a dialog? Or is better to use DialogChains for that?
Because, from what I tried, it appears that I can ONLY do forms if they are called from teh MessagesController class with the methods I described, that is, how Microsoft sampled it in the Pizzabot.
I appreciate any help or input on the matter. Thanks for your time.
Sure you can! Instantiating a form from a dialog is a pretty common scenario. To accomplish that you can do the following inside the LUIS intent method:
var form = new FormDialog<YourFormModel>(
<ExistingModel>,
<TheMethodThatBuildTheForm>,
FormOptions.PromptInStart,
result.Entities);
context.Call(form, <ResumeAfterCallback>);
using the PizzaBot sample, it should looks like:
var form = new FormDialog<PizzaOrder>(
null,
BuildForm,
FormOptions.PromptInStart,
result.Entities);
context.Call(form, <ResumeAfterCallback>);
In the ResumeAfterCallback you will usually get the result of the form, catch exceptions and perform a context.Wait so the dialog can keep receiving messages. Below a quick example:
private async Task ResumeAfterCallback(IDialogContext context,
IAwaitable<PizzaOrder> result)
{
try
{
var pizzaOrder = await result;
// do something with the pizzaOrder
context.Wait(this.MessageReceived);
}
catch (FormCanceledException<PizzaOrder> e)
{
string reply;
if (e.InnerException == null)
{
reply = "You have canceled the operation. What would you like to do next?";
}
else
{
reply = $"Oops! Something went wrong :(. Technical Details: {e.InnerException.Message}";
}
await context.PostAsync(reply);
context.Wait(this.MessageReceived);
}
}

Youtube Search for Videos in given Channel only

How can (a user) SEARCH FOR OUR VIDEOS BY KEYWORDS (Public Videos -- OUR Channel Only..)..
We were basing our approach in https://developers.google.com/youtube/v3/code_samples/javascript#search_by_keyword
We want to use API KEY for Authorization.. and Access is via browser.. (JS)
So when the user enters KEYWORDS in a Search box the CONTEXT (where to search) should already be the given Channel XXXX (its Public Videos)..
What would be the API/methods to use and with what params..??
The search endpoint is the correct one to use, passing the keywords as the 'q' parameter and the channel as the 'channelId' parameter. So, using the sample function you linked to, you might try something like this:
function search() {
var q = $('#query').val();
var request = gapi.client.youtube.search.list({
q: q,
part: 'snippet',
channelId: {whatever your ID is}
});

Appending Text to the body field in Drupal

I am trying to append a string to the body field of a CCK node after it has been submitted or edited. However, I'm having trouble working with the body field in the form alter. My initial attempt was to modify the body field in the submit handler by using the .operator to append a string to the body field.
//Calling this submit function to add string to body.
function appendToBody_submit_function($form, &$form_state) {
$form_state['values']['body'] = array('0' => array('value' => $form['#body'])) . $stringToAppend;
}
However, I can't get this to work, and I'm not sure it's the right way. I am new to Drupal, Can someone point me in the right direction? Should I be using node_api for this?
I assume that you add your custom submit callback to the forms #submit array via hook_form_alter().
If you add it before any other entry in that array (as opposed to just appending it), your callback should be called before the standard submit function. That way, all you need to do is adjust the $form_state['values']['body'] content 'in place', and it will be picked up (and subsequently saved) on further processing by the standard submit callback implicitly:
/**
* Implementation of hook_form_alter()
*/
function yourModule_form_alter(&$form, $form_state, $form_id) {
// Is this a node edit form?
if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) {
// Yes, add custom submit handler *before* already existing ones
array_unshift($form['#submit'], 'appendToBody_submit_function');
}
}
// Custom submit function to add string to body.
function appendToBody_submit_function($form, &$form_state) {
$form_state['values']['body'] = $form_state['values']['body'] . $stringToAppend;
}
I recommend installing the Devel module so you can easily print out the contents of $form_state by placing dpm($form_state); in your method body. I usually start with that to make sure the values are where/what I expect.
// Replace "hook" in the function name with the name of your module.
function hook_submit($form, &$form_state) {
// dpm($form_state); // Debug code to view the contents of $form_state.
$body = $form_state['values']['body'] . ' new string to append';
// Place code to save this data to your database here.
}

Resources