-
-
Save AskYous/5f8da20bccc42ad6d33bce49b73d250a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { LuisRecognizer, LuisPredictionOptions } from "botbuilder-ai"; | |
import { ConversationState, TurnContext, StatePropertyAccessor, CardFactory, RecognizerResult } from "botbuilder"; | |
import { OAuthHelpers, LOGIN_PROMPT } from "./oauth-helpers"; | |
import { ActivityTypes } from "botbuilder"; | |
import { | |
DialogState, | |
DialogSet, | |
WaterfallDialog, | |
WaterfallStepContext, | |
WaterfallStep, | |
DialogTurnResult, | |
TextPrompt, | |
Prompt, | |
ActivityPrompt, | |
ConfirmPrompt, | |
ChoicePrompt | |
} from "botbuilder-dialogs"; | |
import { Activity } from "botframework-connector/lib/connectorApi/models/mappers"; | |
const CONNECTION_SETTINGS_NAME = "ms-graph"; | |
const RECEPIENT_PROMPT = "recepiant_prompt"; | |
/** | |
* A simple bot that responds to utterances with answers from the Language Understanding (LUIS) service. | |
* If an answer is not found for an utterance, the bot responds with help. | |
*/ | |
class LuisBot { | |
private luisRecognizer: LuisRecognizer; | |
private dialogState: StatePropertyAccessor<DialogState>; | |
private commandState: StatePropertyAccessor<any>; | |
private helpMessage: string; | |
private graphDialogId = "graphDialog"; | |
private dialogs: DialogSet; | |
/** | |
* The LuisBot constructor requires one argument (`application`) which is used to create an instance of `LuisRecognizer`. | |
* @param {LuisApplication} luisApplication The basic configuration needed to call LUIS. In this sample the configuration is retrieved from the .bot file. | |
* @param {LuisPredictionOptions} luisPredictionOptions (Optional) Contains additional settings for configuring calls to LUIS. | |
*/ | |
constructor( | |
application, | |
luisPredictionOptions: LuisPredictionOptions, | |
private conversationState: ConversationState | |
) { | |
this.luisRecognizer = new LuisRecognizer(application, luisPredictionOptions, true); | |
this.conversationState = conversationState; | |
this.dialogState = conversationState.createProperty("dialogState"); | |
this.commandState = conversationState.createProperty("commandState"); | |
this.helpMessage = | |
`You can type "send <recipient_email>" to send an email, "recent" to view recent unread mail,` + | |
` "me" to see information about your, or "help" to view the commands` + | |
` again. Any other text will display your token.`; | |
// @ts-ignore | |
this.dialogs = new DialogSet(this.dialogState); | |
this.dialogs.add(OAuthHelpers.prompt(CONNECTION_SETTINGS_NAME)); | |
this.dialogs.add( | |
new WaterfallDialog(this.graphDialogId, [this.promptStep.bind(this), this.processStep.bind(this)]) | |
); | |
// send email | |
this.dialogs.add(new TextPrompt("tp")); | |
this.dialogs.add(new TextPrompt("tp2")); | |
this.dialogs.add(new TextPrompt("tp3")); | |
this.dialogs.add( | |
new WaterfallDialog("send_email", [ | |
this.promptStep.bind(this), | |
async step => await step.prompt("tp", "Who do you want to email?"), | |
async step => await step.prompt("tp2", "What's the subject line?"), | |
async step => await step.prompt("tp3", "And what's the message?"), | |
async step => await OAuthHelpers.sendMail(step.context, step.result, step.result) | |
]) | |
); | |
} | |
/** | |
* WaterfallDialogStep to process the command sent by the user. | |
* @param { WaterfallStepContext } step WaterfallStepContext | |
*/ | |
public async processStep(step: WaterfallStepContext) { | |
// We do not need to store the token in the bot. When we need the token we can | |
// send another prompt. If the token is valid the user will not need to log back in. | |
// The token will be available in the Result property of the task. | |
const tokenResponse = step.result; | |
const luisResult = (await this.luisRecognizer.recognize(step.context)).luisResult; | |
const intent = luisResult.topScoringIntent.intent; // TODO: Check confidence level | |
// If the user is authenticated the bot can use the token to make API calls. | |
if (tokenResponse !== undefined) { | |
let parts = await this.commandState.get(step.context); | |
if (!parts) parts = step.context.activity.text; | |
switch (intent) { | |
case "me": | |
await OAuthHelpers.listMe(step.context, tokenResponse); | |
break; | |
case "sendEmail": | |
let recipient: string; | |
if (luisResult.entities.length) recipient = luisResult.entities[0].entity; | |
else { | |
const dc = await this.dialogs.createContext(step.context); | |
await dc.beginDialog("send_email"); | |
} | |
break; | |
case "getEmails": | |
await OAuthHelpers.listRecentMail(step.context, tokenResponse); | |
break; | |
default: | |
await step.context.sendActivity(`Your token is: ${tokenResponse.token}`); | |
} | |
} else { | |
// Ask the user to try logging in later as they are not logged in. | |
await step.context.sendActivity(`We couldn't log you in. Please try again later.`); | |
} | |
return await step.endDialog(); | |
} | |
/** | |
* Every conversation turn calls this method. | |
* @param {TurnContext} turnContext Contains all the data needed for processing the conversation turn. | |
*/ | |
public async onTurn(turnContext) { | |
const dc = await this.dialogs.createContext(turnContext); | |
switch (turnContext.activity.type) { | |
case ActivityTypes.Message: | |
await this.processInput(dc); | |
break; | |
case ActivityTypes.Event: | |
break; | |
case ActivityTypes.Invoke: | |
// This handles the Microsoft Teams Invoke Activity sent when magic code is not used. | |
// See: https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/authentication/auth-oauth-card#getting-started-with-oauthcard-in-teams | |
// Manifest Schema Here: https://docs.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema | |
// It also handles the Event Activity sent from The Emulator when the magic code is not used. | |
// See: https://blog.botframework.com/2018/08/28/testing-authentication-to-your-bot-using-the-bot-framework-emulator/ | |
// Sanity check the Activity type and channel Id. | |
if ( | |
turnContext.activity.type === ActivityTypes.Invoke && | |
turnContext.activity.channelId !== "msteams" | |
) { | |
throw new Error("The Invoke type is only valid on the MS Teams channel."); | |
} | |
await dc.continueDialog(); | |
if (!turnContext.responded) await dc.beginDialog(this.graphDialogId); | |
break; | |
case ActivityTypes.ConversationUpdate: | |
await this.sendWelcomeMessage(turnContext); | |
break; | |
default: | |
await turnContext.sendActivity(`[${turnContext.activity.type}]-type activity detected.`); | |
} | |
await this.conversationState.saveChanges(turnContext); | |
} | |
/** | |
* Creates a Hero Card that is sent as a welcome message to the user. | |
* @param {TurnContext} turnContext A TurnContext instance containing all the data needed for processing this conversation turn. | |
*/ | |
public async sendWelcomeMessage(turnContext: TurnContext) { | |
const activity = turnContext.activity; | |
if (activity && activity.membersAdded) { | |
const heroCard = CardFactory.heroCard( | |
"🎉 Welcome to FlexO! 🎉", | |
CardFactory.images([ | |
"https://chiefexecutive.net/wp-content/uploads/2018/02/GettyImages-870184586-compressor.jpg", | |
"https://botframeworksamples.blob.core.windows.net/samples/aadlogo.png" | |
]), | |
CardFactory.actions([ | |
{ | |
type: "imBack", | |
title: "Me", | |
value: "me", | |
channelData: undefined | |
}, | |
{ | |
type: "imBack", | |
title: "Recent", | |
value: "recent", | |
channelData: undefined | |
}, | |
{ | |
type: "imBack", | |
title: "View Token", | |
value: "view Token", | |
channelData: undefined | |
}, | |
{ | |
type: "imBack", | |
title: "Help", | |
value: "help", | |
channelData: undefined | |
}, | |
{ | |
type: "imBack", | |
title: "Signout", | |
value: "signout", | |
channelData: undefined | |
} | |
]) | |
); | |
for (const idx in activity.membersAdded) { | |
if (activity.membersAdded[idx].id !== activity.recipient.id) { | |
await turnContext.sendActivity({ attachments: [heroCard] }); | |
} | |
} | |
} | |
} | |
/** | |
* Processes input and route to the appropriate step. | |
* @param {DialogContext} dc DialogContext | |
* @param {RecognizerResult} luisResponse the response from luis | |
*/ | |
public async processInput(dc) { | |
switch (dc.context.activity.text.toLowerCase()) { | |
case "signout": | |
case "logout": | |
case "signoff": | |
case "logoff": | |
// The bot adapter encapsulates the authentication processes and sends | |
// activities to from the Bot Connector Service. | |
const botAdapter = dc.context.adapter; | |
await botAdapter.signOutUser(dc.context, CONNECTION_SETTINGS_NAME); | |
// Let the user know they are signed out. | |
await dc.context.sendActivity("You are now signed out."); | |
break; | |
case "help": | |
await dc.context.sendActivity(this.helpMessage); | |
break; | |
default: | |
// The user has input a command that has not been handled yet, | |
// begin the waterfall dialog to handle the input. | |
await dc.continueDialog(); | |
if (!dc.context.responded) { | |
await dc.beginDialog(this.graphDialogId); | |
} | |
} | |
} | |
/** | |
* WaterfallDialogStep for storing commands and beginning the OAuthPrompt. | |
* Saves the user's message as the command to execute if the message is not | |
* a magic code. | |
* @param {WaterfallStepContext} step WaterfallStepContext | |
*/ | |
public async promptStep(step) { | |
const activity = step.context.activity; | |
if (activity.type === ActivityTypes.Message && !/\d{6}/.test(activity.text)) { | |
await this.commandState.set(step.context, activity.text); | |
await this.conversationState.saveChanges(step.context); | |
} | |
return await step.beginDialog(LOGIN_PROMPT); | |
} | |
} | |
module.exports.LuisBot = LuisBot; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment