问题
From what I see, in the nodejs framework, I notice you can't prompt a user within a dialogflow dialog and retrieve the that prompt within the same dialog function... I may be wrong about that. Either way, you can return prompt and retrieve it in the next dialog flow.
Is there a way to cut off the awaiting prompt for user input and end or resume an activity of the dialog?
I would like to cut the prompt off at a certain point, ideally on a time basis.
回答1:
This is possible thru using a combination of sending a proactive message, filtering via onMessage()
and onSendActivities
, and a dialog interruption.
First, in index.js, I setup an API for sending proactive messages.
server.get('/api/interrupt', async (req, res) => {
for (const conversationReference of Object.values(conversationReferences)) {
await adapter.continueConversation(conversationReference, async turnContext => {
var reply = { type: ActivityTypes.Message };
const yesBtn = { type: ActionTypes.PostBack, title: 'Yes', value: 'Yes' };
const noBtn = { type: ActionTypes.PostBack, title: 'No', value: 'No' };
const card = CardFactory.heroCard(
'Want to skip out and see Star Wars?',
null,
[ yesBtn, noBtn ]
);
reply.attachments = [ card ];
await turnContext.sendActivity(reply);
return { status: DialogTurnStatus.waiting };
});
}
res.setHeader('Content-Type', 'text/html');
res.writeHead(200);
res.write('<html><body><h1>Proactive messages have been sent.</h1></body></html>');
res.end();
});
In mainBot.js:
- I create a state property for setting whether the timer should be active or not. I also create an empty
timer
object. - I assign the timer state property to a variable (
activityTimer
) along with a default value (null
). - In
onMessage()
, I filter onchannelId
("directline") androle
("user"). - I filter on whether the user selected the 'Yes' option from the proactive message. If so, the dialog(s) are cancelled,
timerState
is set tofalse
, and the timer is cleared. This ensures the timer won't fire once the dialog is cancelled. New input from the user is required to start the dialog, again. - Any other user input sets the
timerState
totrue
and clears the timer. This ensures multiple timers do not stack when new user input is received by clearing any current timers and flagging a new timer to start. - Lastly, if
timerState
is set totrue
, thensetTimeout()
is called, assigned to thetimer
variable, with a timeout value of 5 seconds. At this point, an API call is made to the "interrupt" endpoint I created inindex.js
, which sends the proactive message, andtimerState
is set back tofalse
.
this.activityTimerAccessor = conversationState.createProperty('activityTimer');
let timer;
this.onMessage(async (context, next) => {
if (activity.channelId === 'directline') {
if (activity.from.role === 'user') {
let text = activity.text.toLowerCase();
switch (text === 'yes') {
case true:
if (activity.channelData.postBack) {
activityTimer.timerState = false;
}
if (timer) clearTimeout(timer);
break;
default:
activityTimer.timerState = true;
if (timer) clearTimeout(timer);
}
}
if (activityTimer.timerState === true) {
timer = await setTimeout(async () => {
await fetch('http://localhost:3978/api/interrupt');
}, 5000);
activityTimer.timerState = false;
}
}
}
In mainDialog.js, I extend cancelAndHelpDialog.js
which is where any interruptions are caught. If you are unfamiliar with interruptions, you can read about them in the docs here and reference a sample here.
const { CancelAndHelpDialog } = require( './cancelAndHelpDialog' );
const MAIN_WATERFALL_DIALOG = 'MainWaterfallDialog';
class MainDialog extends CancelAndHelpDialog {
constructor ( id, userState, conversationState ) {
super( id, userState, conversationState );
this.mainId = id;
this.userState = userState;
this.conversationState = conversationState;
this.addDialog( new AdaptiveCardDialog( ADAPTIVE_CARD_WATERFALL_DIALOG ) )
.addDialog( new WaterfallDialog( MAIN_WATERFALL_DIALOG, [
this.simpleSend.bind(this)
] ) ) ;
this.initialDialogId = MAIN_WATERFALL_DIALOG;
}
async simpleSend ( stepContext ) {
await stepContext.context.sendActivity('Looks like you said or did something...')
return await stepContext.next();
}
}
module.exports.MainDialog = MainDialog;
In cancelAndHelpDialog.js, I create a new ComponentDialog
which contains an "interrupt" method. In interrupt()
, I filter for the user selecting the "Yes" button from the proactive message indicating the user's wish to cancel the dialog(s). If pressed, I notify the user and cancel all dialogs.
class CancelAndHelpDialog extends ComponentDialog {
[...]
async interrupt(innerDc, next) {
const activity = innerDc.context.activity;
const text = innerDc.context.activity.text.toLowerCase();
if (activity.type === 'message' && activity.channelId === 'directline') {
if (text === 'yes' && activity.channelData.postBack === true) {
await innerDc.context.sendActivity("Cancelling...Let's go see a movie!!");
return await innerDc.cancelAllDialogs();
}
}
}
}
This should set you up going in the right direction. Obviously, alter to fit your needs. For instance, the proactive message doesn't have to take user input sending just a message that is picked up and triggers an interruption.
Hope of help!
来源:https://stackoverflow.com/questions/58964283/is-there-a-way-to-proactively-end-a-prompt-i-e-with-a-timer-or-event-from-the