diff --git a/.vscode/launch.json b/.vscode/launch.json index f4ec0ee4..3bddf9dd 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -63,6 +63,32 @@ "runtimeExecutable": null, "env": {} }, + { + "name": "Launch botbuilder sample", + "type": "node", + "program": "${workspaceRoot}/client samples/botbuilder/index.js", + "stopOnEntry": false, + "args": [ + "--nolazy" + ], + "cwd": "${workspaceRoot}/client samples/botbuilder", + "runtimeExecutable": null, + "env": {} + }, + { + "name": "Launch ContosoFlowers sample", + "type": "node", + "program": "${workspaceRoot}/client samples/contosoflowers/app.js", + "stopOnEntry": false, + "args": [ + "--nolazy" + ], + "cwd": "${workspaceRoot}/client samples/contosoflowers", + "runtimeExecutable": null, + "env": { + "BING_MAPS_KEY":"K8LDhHtIMTPZO2lkv0RQ~obPXA8ITMZ2m8JanyFpxqg~Aix9KcaOiNfYAEfw3wIjvWXfmK6zMF9anDvj5u44vvPbgow2Ns_EsIsUz4pcmrSa" + } + }, { // Name of configuration; appears in the launch configuration drop down menu. "name": "Launch w/TypeScript", diff --git a/.vscode/settings.json b/.vscode/settings.json index d14ae151..bd0de230 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,4 +2,8 @@ { "editor.fontSize": 15, + "search.exclude": { + "**/node_modules": false, + "**/bower_components": true + } } \ No newline at end of file diff --git a/Plugins/Vorlon/.gitignore b/Plugins/Vorlon/.gitignore index 7c6c6151..b163553d 100644 --- a/Plugins/Vorlon/.gitignore +++ b/Plugins/Vorlon/.gitignore @@ -13,4 +13,5 @@ !plugins/nodejs/** !plugins/nodejs/**/*.css !plugins/nodejs/**/*.js -!plugins/nodejs/dom-timeline.js \ No newline at end of file +!plugins/nodejs/dom-timeline.js +!plugins/botFrameworkInspector/**/*.css \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/botFrameworkInspector/botbuilder.d.ts b/Plugins/Vorlon/plugins/botFrameworkInspector/botbuilder.d.ts new file mode 100644 index 00000000..792b96d7 --- /dev/null +++ b/Plugins/Vorlon/plugins/botFrameworkInspector/botbuilder.d.ts @@ -0,0 +1,2405 @@ +//============================================================================= +// +// INTERFACES +// +//============================================================================= + +/** + * An event received from or being sent to a source. + */ +export interface IEvent { + /** Defines type of event. Should be 'message' for an IMessage. */ + type: string; + + /** SDK thats processing the event. Will always be 'botbuilder'. */ + agent: string; + + /** The original source of the event (i.e. 'facebook', 'skype', 'slack', etc.) */ + source: string; + + /** The original event in the sources native schema. For outgoing messages can be used to pass source specific event data like custom attachments. */ + sourceEvent: any; + + /** Address routing information for the event. Save this field to external storage somewhere to later compose a proactive message to the user. */ + address: IAddress; + + /** + * For incoming messages this is the user that sent the message. By default this is a copy of [address.user](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iaddress.html#user) but you can configure your bot with a + * [lookupUser](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iuniversalbotsettings.html#lookupuser) function that lets map the incoming user to an internal user id. + */ + user: IIdentity; +} + +/** The Properties of a conversation have changed. */ +export interface IConversationUpdate extends IEvent { + /** Array of members added to the conversation. */ + membersAdded?: IIdentity[]; + + /** Array of members removed from the conversation. */ + membersRemoved?: IIdentity[]; + + /** The conversations new topic name. */ + topicName?: string; + + /** If true then history was disclosed. */ + historyDisclosed?: boolean; +} + +/** A user has updated their contact list. */ +export interface IContactRelationUpdate extends IEvent { + /** The action taken. Valid values are "add" or "remove". */ + action: string; +} + +/** + * A chat message sent between a User and a Bot. Messages from the bot to the user come in two flavors: + * + * * __reactive messages__ are messages sent from the Bot to the User as a reply to an incoming message from the user. + * * __proactive messages__ are messages sent from the Bot to the User in response to some external event like an alarm triggering. + * + * In the reactive case the you should copy the [address](#address) field from the incoming message to the outgoing message (if you use the [Message]( /en-us/node/builder/chat-reference/classes/_botbuilder_d_.message.html) builder class and initialize it with the + * [session](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session.html) this will happen automatically) and then set the [text](#text) or [attachments](#attachments). For proactive messages you’ll need save the [address](#address) from the incoming message to + * an external storage somewhere. You can then later pass this in to [UniversalBot.beginDialog()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.universalbot.html#begindialog) or copy it to an outgoing message passed to + * [UniversalBot.send()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.universalbot.html#send). + * + * Composing a message to the user using the incoming address object will by default send a reply to the user in the context of the current conversation. Some channels allow for the starting of new conversations with the user. To start a new proactive conversation with the user simply delete + * the [conversation](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iaddress.html#conversation) field from the address object before composing the outgoing message. + */ +export interface IMessage extends IEvent { + /** Timestamp of message given by chat service for incoming messages. */ + timestamp: string; + + /** Text to be displayed by as fall-back and as short description of the message content in e.g. list of recent conversations. */ + summary: string; + + /** Message text. */ + text: string; + + /** Identified language of the message text if known. */ + textLocale: string; + + /** For incoming messages contains attachments like images sent from the user. For outgoing messages contains objects like cards or images to send to the user. */ + attachments: IAttachment[]; + + /** Structured objects passed to the bot or user. */ + entities: any[]; + + /** Format of text fields. The default value is 'markdown'. */ + textFormat: string; + + /** Hint for how clients should layout multiple attachments. The default value is 'list'. */ + attachmentLayout: string; +} + +/** Implemented by classes that can be converted into a message. */ +export interface IIsMessage { + /** Returns the JSON object for the message. */ + toMessage(): IMessage; +} + +/** Represents a user, bot, or conversation. */ +export interface IIdentity { + /** Channel specific ID for this identity. */ + id: string; + + /** Friendly name for this identity. */ + name?: string; + + /** If true the identity is a group. Typically only found on conversation identities. */ + isGroup?: boolean; +} + +/** + * Address routing information for a [message](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.imessage.html#address). + * Addresses are bidirectional meaning they can be used to address both incoming and outgoing messages. They're also connector specific meaning that + * [connectors](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iconnector.html) are free to add their own fields. + */ +export interface IAddress { + /** Unique identifier for channel. */ + channelId: string; + + /** User that sent or should receive the message. */ + user: IIdentity; + + /** Bot that either received or is sending the message. */ + bot: IIdentity; + + /** + * Represents the current conversation and tracks where replies should be routed to. + * Can be deleted to start a new conversation with a [user](#user) on channels that support new conversations. + */ + conversation?: IIdentity; +} + +/** Chat connector specific address. */ +export interface IChatConnectorAddress extends IAddress { + /** Incoming Message ID. */ + id?: string; + + /** Specifies the URL to post messages back. */ + serviceUrl?: string; +} + +/** + * Many messaging channels provide the ability to attach richer objects. Bot Builder lets you express these attachments in a cross channel way and [connectors](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iconnector.html) will do their best to render the + * attachments using the channels native constructs. If you desire more control over the channels rendering of a message you can use [IEvent.sourceEvent](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ievent.html#sourceevent) to provide attachments using + * the channels native schema. The types of attachments that can be sent varies by channel but these are the basic types: + * + * * __Media and Files:__ Basic files can be sent by setting [contentType](#contenttype) to the MIME type of the file and then passing a link to the file in [contentUrl](#contenturl). + * * __Cards and Keyboards:__ A rich set of visual cards and custom keyboards can by setting [contentType](#contenttype) to the cards type and then passing the JSON for the card in [content](#content). If you use one of the rich card builder classes like + * [HeroCard](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.herocard.html) the attachment will automatically filled in for you. + */ +export interface IAttachment { + /** MIME type string which describes type of attachment. */ + contentType: string; + + /** (Optional) object structure of attachment. */ + content?: any; + + /** (Optional) reference to location of attachment content. */ + contentUrl?: string; +} + +/** Implemented by classes that can be converted into an attachment. */ +export interface IIsAttachment { + /** Returns the JSON object for the attachment. */ + toAttachment(): IAttachment; +} + +/** Displays a signin card and button to the user. Some channels may choose to render this as a text prompt and link to click. */ +export interface ISigninCard { + /** Title of the Card. */ + title: string; + + /** Sign in action. */ + buttons: ICardAction[]; +} + +/** + * Displays a card to the user using either a smaller thumbnail layout or larger hero layout (the attachments [contentType](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iattachment.html#contenttype) determines which). + * All of the cards fields are optional so this card can be used to specify things like a keyboard on certain channels. Some channels may choose to render a lower fidelity version of the card or use an alternate representation. + */ +export interface IThumbnailCard { + /** Title of the Card. */ + title?: string; + + /** Subtitle appears just below Title field, differs from Title in font styling only. */ + subtitle?: string; + + /** Text field appears just below subtitle, differs from Subtitle in font styling only. */ + text?: string; + + /** Messaging supports all media formats: audio, video, images and thumbnails as well to optimize content download. */ + images?: ICardImage[]; + + /** This action will be activated when user taps on the card. Not all channels support tap actions and some channels may choose to render the tap action as the titles link. */ + tap?: ICardAction; + + /** Set of actions applicable to the current card. Not all channels support buttons or cards with buttons. Some channels may choose to render the buttons using a custom keyboard. */ + buttons?: ICardAction[]; +} + +/** Displays a rich receipt to a user for something they've either bought or are planning to buy. */ +export interface IReceiptCard { + /** Title of the Card. */ + title: string; + + /** Array of receipt items. */ + items: IReceiptItem[]; + + /** Array of additional facts to display to user (shipping charges and such.) Not all facts will be displayed on all channels. */ + facts: IFact[]; + + /** This action will be activated when user taps on the card. Not all channels support tap actions. */ + tap: ICardAction; + + /** Total amount of money paid (or should be paid.) */ + total: string; + + /** Total amount of TAX paid (or should be paid.) */ + tax: string; + + /** Total amount of VAT paid (or should be paid.) */ + vat: string; + + /** Set of actions applicable to the current card. Not all channels support buttons and the number of allowed buttons varies by channel. */ + buttons: ICardAction[]; +} + +/** An individual item within a [receipt](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ireceiptcard.html). */ +export interface IReceiptItem { + /** Title of the item. */ + title: string; + + /** Subtitle appears just below Title field, differs from Title in font styling only. On some channels may be combined with the [title](#title) or [text](#text). */ + subtitle: string; + + /** Text field appears just below subtitle, differs from Subtitle in font styling only. */ + text: string; + + /** Image to display on the card. Some channels may either send the image as a seperate message or simply include a link to the image. */ + image: ICardImage; + + /** Amount with currency. */ + price: string; + + /** Number of items of given kind. */ + quantity: string; + + /** This action will be activated when user taps on the Item bubble. Not all channels support tap actions. */ + tap: ICardAction; +} + +/** Implemented by classes that can be converted into a receipt item. */ +export interface IIsReceiptItem { + /** Returns the JSON object for the receipt item. */ + toItem(): IReceiptItem; +} + +/** The action that should be performed when a card, button, or image is tapped. */ +export interface ICardAction { + /** Defines the type of action implemented by this button. Not all action types are supported by all channels. */ + type: string; + + /** Text description for button actions. */ + title?: string; + + /** Parameter for Action. Content of this property depends on Action type. */ + value: string; + + /** (Optional) Picture to display for button actions. Not all channels support button images. */ + image?: string; +} + +/** Implemented by classes that can be converted into a card action. */ +export interface IIsCardAction { + /** Returns the JSON object for the card attachment. */ + toAction(): ICardAction; +} + +/** An image on a card. */ +export interface ICardImage { + /** Thumbnail image for major content property. */ + url: string; + + /** Image description intended for screen readers. Not all channels will support alt text. */ + alt: string; + + /** Action assigned to specific Attachment. E.g. navigate to specific URL or play/open media content. Not all channels will support tap actions. */ + tap: ICardAction; +} + +/** Implemented by classes that can be converted into a card image. */ +export interface IIsCardImage { + /** Returns the JSON object for the card image. */ + toImage(): ICardImage; +} + +/** A fact displayed on a card like a [receipt](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ireceiptcard.html). */ +export interface IFact { + /** Display name of the fact. */ + key: string; + + /** Display value of the fact. */ + value: string; +} + +/** Implemented by classes that can be converted into a fact. */ +export interface IIsFact { + /** Returns the JSON object for the fact. */ + toFact(): IFact; +} + +/** Settings used to initialize an ILocalizer implementation. */ +interface IDefaultLocalizerSettings { + /** The path to the parent of the bot's locale directory */ + botLocalePath?: string; + + /** The default locale of the bot */ + defaultLocale?: string; +} + +/** Plugin for localizing messages sent to the user by a bot. */ +export interface ILocalizer { + /** + * Loads the localied table for the supplied locale, and call's the supplied callback once the load is complete. + * @param locale The locale to load. + * @param callback callback that is called once the supplied locale has been loaded, or an error if the load fails. + */ + load(locale: string, callback: (err: Error) => void): void; + + /** + * Loads a localized string for the specified language. + * @param locale Desired locale of the string to return. + * @param msgid String to use as a key in the localized string table. Typically this will just be the english version of the string. + * @param namespace (Optional) namespace for the msgid keys. + */ + trygettext(locale: string, msgid: string, namespace?: string): string; + + /** + * Loads a localized string for the specified language. + * @param locale Desired locale of the string to return. + * @param msgid String to use as a key in the localized string table. Typically this will just be the english version of the string. + * @param namespace (Optional) namespace for the msgid keys. + */ + gettext(locale: string, msgid: string, namespace?: string): string; + + /** + * Loads the plural form of a localized string for the specified language. + * @param locale Desired locale of the string to return. + * @param msgid Singular form of the string to use as a key in the localized string table. + * @param msgid_plural Plural form of the string to use as a key in the localized string table. + * @param count Count to use when determining whether the singular or plural form of the string should be used. + * @param namespace (Optional) namespace for the msgid and msgid_plural keys. + */ + ngettext(locale: string, msgid: string, msgid_plural: string, count: number, namespace?: string): string; +} + +/** Persisted session state used to track a conversations dialog stack. */ +export interface ISessionState { + /** Dialog stack for the current session. */ + callstack: IDialogState[]; + + /** Timestamp of when the session was last accessed. */ + lastAccess: number; + + /** Version number of the current callstack. */ + version: number; +} + +/** An entry on the sessions dialog stack. */ +export interface IDialogState { + /** ID of the dialog. */ + id: string; + + /** Persisted state for the dialog. */ + state: any; +} + +/** + * Results returned by a child dialog to its parent via a call to session.endDialog(). + */ +export interface IDialogResult { + /** The reason why the current dialog is being resumed. Defaults to {ResumeReason.completed} */ + resumed?: ResumeReason; + + /** ID of the child dialog thats ending. */ + childId?: string; + + /** If an error occured the child dialog can return the error to the parent. */ + error?: Error; + + /** The users response. */ + response?: T; +} + +/** Context of the received message passed to the Dialog.recognize() method. */ +export interface IRecognizeContext { + /** Message that was received. */ + message: IMessage; + + /** The users preferred locale for the message. */ + locale: string; + + /** If true the Dialog is the active dialog on the callstack. */ + activeDialog: boolean; + + /** Data persisted for the current dialog. */ + dialogData: any; +} + +/** Results from a call to a recognize() function. The implementation is free to add any additional properties to the result. */ +export interface IRecognizeResult { + /** Confidence that the users utterance was understood on a scale from 0.0 - 1.0. */ + score: number; +} + +/** Options passed when binding a dialog action handler. */ +export interface IDialogActionOptions { + /** (Optional) regular expression that should be matched against the users utterance to trigger an action. If this is ommitted the action can only be invoked from a button. */ + matches?: RegExp; + + /** Minimum score needed to trigger the action using the value of [expression](#expression). The default value is 0.1. */ + intentThreshold?: number; + + /** (Optional) arguments to pass to the dialog spawned when the action is triggered. */ + dialogArgs?: any; +} + +/** Results for a recognized dialog action. */ +export interface IRecognizeActionResult extends IRecognizeResult { + /** Named dialog action that was matched. */ + action?: string; + + /** A regular expression that was matched. */ + expression?: RegExp; + + /** The results of the [expression](#expression) that was matched. matched[0] will be the text that was matched and matched[1...n] is the result of capture groups. */ + matched?: string[]; + + /** Optional data passed as part of the action binding. */ + data?: string; + + /** ID of the dialog the action is bound to. */ + dialogId?: string; + + /** Index on the dialog stack of the dialog the action is bound to. */ + dialogIndex?: number; +} + +/** Options passed to built-in prompts. */ +export interface IPromptOptions { + /** + * (Optional) retry prompt to send if the users response isn't understood. Default is to just + * reprompt with the configured [defaultRetryPrompt](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ipromptsoptions.html#defaultretryprompt) + * plus the original prompt. + * + * Note that if the original prompt is an _IMessage_ the retry prompt will be sent as a seperate + * message followed by the original message. If the retryPrompt is also an _IMessage_ it will + * instead be sent in place of the original message. + * * _{string}_ - Initial message to send the user. + * * _{string[]}_ - Array of possible messages to send user. One will be chosen at random. + * * _{IMessage}_ - Initial message to send the user. Message can contain attachments. + * * _{IIsMessage}_ - Instance of the [Message](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.message.html) builder class. + */ + retryPrompt?: string|string[]|IMessage|IIsMessage; + + /** (Optional) maximum number of times to reprompt the user. By default the user will be reprompted indefinitely. */ + maxRetries?: number; + + /** (Optional) reference date when recognizing times. Date expressed in ticks using Date.getTime(). */ + refDate?: number; + + /** (Optional) type of list to render for PromptType.choice. Default value is ListStyle.auto. */ + listStyle?: ListStyle; + + /** (Optional) flag used to control the reprompting of a user after a dialog started by an action ends. The default value is true. */ + promptAfterAction?: boolean; + + /** (Optional) namespace to use when localizing a passed in prompt. */ + localizationNamespace?: string; +} + +/** Arguments passed to the built-in prompts beginDialog() call. */ +export interface IPromptArgs extends IPromptOptions { + /** Type of prompt invoked. */ + promptType: PromptType; + + /** + * Initial message to send to user. + * * _{string}_ - Initial message to send the user. + * * _{string[]}_ - Array of possible messages to send user. One will be chosen at random. + * * _{IMessage}_ - Initial message to send the user. Message can contain attachments. + * * _{IIsMessage}_ - Instance of the [Message](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.message.html) builder class. + */ + prompt: string|string[]|IMessage|IIsMessage; + + /** Enum values for a choice prompt. */ + enumsValues?: string[]; +} + +/** Dialog result returned by a system prompt. */ +export interface IPromptResult extends IDialogResult { + /** Type of prompt completing. */ + promptType?: PromptType; +} + +/** Result returned from an IPromptRecognizer. */ +export interface IPromptRecognizerResult extends IPromptResult { + /** Returned from a prompt recognizer to indicate that a parent dialog handled (or captured) the utterance. */ + handled?: boolean; +} + +/** Strongly typed Text Prompt Result. */ +export interface IPromptTextResult extends IPromptResult { } + +/** Strongly typed Number Prompt Result. */ +export interface IPromptNumberResult extends IPromptResult { } + +/** Strongly typed Confirm Prompt Result. */ +export interface IPromptConfirmResult extends IPromptResult { } + +/** Strongly typed Choice Prompt Result. */ +export interface IPromptChoiceResult extends IPromptResult { } + +/** Strongly typed Time Prompt Result. */ +export interface IPromptTimeResult extends IPromptResult { } + +/** Strongly typed Attachment Prompt Result. */ +export interface IPromptAttachmentResult extends IPromptResult { } + +/** Plugin for recognizing prompt responses received by a user. */ +export interface IPromptRecognizer { + /** + * Attempts to match a users reponse to a given prompt. + * @param args Arguments passed to the recognizer including that language, text, and prompt choices. + * @param callback Function to invoke with the result of the recognition attempt. + * @param callback.result Returns the result of the recognition attempt. + */ + recognize(args: IPromptRecognizerArgs, callback: (result: IPromptRecognizerResult) => void): void; +} + +/** Arguments passed to the IPromptRecognizer.recognize() method.*/ +export interface IPromptRecognizerArgs { + /** Type of prompt being responded to. */ + promptType: PromptType; + + /** Text of the users response to the prompt. */ + text: string; + + /** Language of the text if known. */ + language?: string; + + /** For choice prompts the list of possible choices. */ + enumValues?: string[]; + + /** (Optional) reference date when recognizing times. */ + refDate?: number; +} + +/** Global configuration options for the Prompts dialog. */ +export interface IPromptsOptions { + /** Replaces the default recognizer (SimplePromptRecognizer) used to recognize prompt replies. */ + recognizer?: IPromptRecognizer +} + +/** A recognized intent. */ +export interface IIntent { + /** Intent that was recognized. */ + intent: string; + + /** Confidence on a scale from 0.0 - 1.0 that the proper intent was recognized. */ + score: number; +} + +/** A recognized entity. */ +export interface IEntity { + /** Type of entity that was recognized. */ + type: string; + + /** Value of the recognized entity. */ + entity: string; + + /** Start position of entity within text utterance. */ + startIndex?: number; + + /** End position of entity within text utterance. */ + endIndex?: number; + + /** Confidence on a scale from 0.0 - 1.0 that the proper entity was recognized. */ + score?: number; +} + +/** Options used to configure an [IntentDialog](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.intentdialog.html). */ +export interface IIntentDialogOptions { + /** Minimum score needed to trigger the recognition of an intent. The default value is 0.1. */ + intentThreshold?: number; + + /** Controls the dialogs processing of incomming user utterances. The default is RecognizeMode.onBeginIfRoot. The default prior to v3.2 was RecognizeMode.onBegin. */ + recognizeMode?: RecognizeMode; + + /** The order in which the configured [recognizers](#recognizers) should be evaluated. The default order is parallel. */ + recognizeOrder?: RecognizeOrder; + + /** (Optional) list of intent recognizers to run the users utterance through. */ + recognizers?: IIntentRecognizer[]; + + /** Maximum number of recognizers to evaluate at one time when [recognizerOrder](#recognizerorder) is parallel. */ + processLimit?: number; +} + +/** export interface implemented by intent recognizers like the LuisRecognizer class. */ +export interface IIntentRecognizer { + /** Attempts to match a users text utterance to an intent. */ + recognize(context: IRecognizeContext, callback: (err: Error, result: IIntentRecognizerResult) => void): void; +} + +/** Results returned by an intent recognizer. */ +export interface IIntentRecognizerResult extends IRecognizeResult { + /** Top intent that was matched. */ + intent: string; + + /** A regular expression that was matched. */ + expression?: RegExp; + + /** The results of the [expression](#expression) that was matched. matched[0] will be the text that was matched and matched[1...n] is the result of capture groups. */ + matched?: string[]; + + /** Full list of intents that were matched. */ + intents?: IIntent[]; + + /** List of entities recognized. */ + entities?: IEntity[]; +} + +/** Options passed to the constructor of a session. */ +export interface ISessionOptions { + /** Function to invoke when the sessions state is saved. */ + onSave: (done: (err: Error) => void) => void; + + /** Function to invoke when a batch of messages are sent. */ + onSend: (messages: IMessage[], done: (err: Error) => void) => void; + + /** The bots root library of dialogs. */ + library: Library; + + /** The localizer to use for the session. */ + localizer: ILocalizer; + + /** Array of session middleware to execute prior to each request. */ + middleware: ISessionMiddleware[]; + + /** Unique ID of the dialog to use when starting a new conversation with a user. */ + dialogId: string; + + /** (Optional) arguments to pass to the conversations initial dialog. */ + dialogArgs?: any; + + /** (Optional) time to allow between each message sent as a batch. The default value is 250ms. */ + autoBatchDelay?: number; + + /** Default error message to send users when a dialog error occurs. */ + dialogErrorMessage?: string|string[]|IMessage|IIsMessage; + + /** Global actions registered for the bot. */ + actions?: ActionSet; +} + +/** result returnd from a call to EntityRecognizer.findBestMatch() or EntityRecognizer.findAllMatches(). */ +export interface IFindMatchResult { + /** Index of the matched value. */ + index: number; + + /** Value that was matched. */ + entity: string; + + /** Confidence score on a scale from 0.0 - 1.0 that an value matched the users utterance. */ + score: number; +} + +/** Context object passed to IBotStorage calls. */ +export interface IBotStorageContext { + /** (Optional) ID of the user being persisted. If missing __userData__ won't be persisted. */ + userId?: string; + + /** (Optional) ID of the conversation being persisted. If missing __conversationData__ and __privateConversationData__ won't be persisted. */ + conversationId?: string; + + /** (Optional) Address of the message received by the bot. */ + address?: IAddress; + + /** If true IBotStorage should persist __userData__. */ + persistUserData: boolean; + + /** If true IBotStorage should persist __conversationData__. */ + persistConversationData: boolean; +} + +/** Data values persisted to IBotStorage. */ +export interface IBotStorageData { + /** The bots data about a user. This data is global across all of the users conversations. */ + userData?: any; + + /** The bots shared data for a conversation. This data is visible to every user within the conversation. */ + conversationData?: any; + + /** + * The bots private data for a conversation. This data is only visible to the given user within the conversation. + * The session stores its session state using privateConversationData so it should always be persisted. + */ + privateConversationData?: any; +} + +/** Replacable storage system used by UniversalBot. */ +export interface IBotStorage { + /** Reads in data from storage. */ + getData(context: IBotStorageContext, callback: (err: Error, data: IBotStorageData) => void): void; + + /** Writes out data to storage. */ + saveData(context: IBotStorageContext, data: IBotStorageData, callback?: (err: Error) => void): void; +} + +/** Options used to initialize a ChatConnector instance. */ +export interface IChatConnectorSettings { + /** The bots App ID assigned in the Bot Framework portal. */ + appId?: string; + + /** The bots App Password assigned in the Bot Framework Portal. */ + appPassword?: string; + + /** If true the bots userData, privateConversationData, and conversationData will be gzipped prior to writing to storage. */ + gzipData?: boolean; +} + +/** Options used to initialize a UniversalBot instance. */ +export interface IUniversalBotSettings { + /** (Optional) dialog to launch when a user initiates a new conversation with a bot. Default value is '/'. */ + defaultDialogId?: string; + + /** (Optional) arguments to pass to the initial dialog for a conversation. */ + defaultDialogArgs?: any; + + /** (Optional) settings used to configure the frameworks built in default localizer. */ + localizerSettings?: IDefaultLocalizerSettings; + + /** (Optional) function used to map the user ID for an incoming message to another user ID. This can be used to implement user account linking. */ + lookupUser?: (address: IAddress, done: (err: Error, user: IIdentity) => void) => void; + + /** (Optional) maximum number of async options to conduct in parallel. */ + processLimit?: number; + + /** (Optional) time to allow between each message sent as a batch. The default value is 150ms. */ + autoBatchDelay?: number; + + /** (Optional) storage system to use for storing user & conversation data. */ + storage?: IBotStorage; + + /** (optional) if true userData will be persisted. The default value is true. */ + persistUserData?: boolean; + + /** (Optional) if true shared conversationData will be persisted. The default value is false. */ + persistConversationData?: boolean; + + /** (Optional) message to send the user should an unexpected error occur during a conversation. A default message is provided. */ + dialogErrorMessage?: string|string[]|IMessage|IIsMessage; +} + +/** Implemented by connector plugins for the UniversalBot. */ +export interface IConnector { + /** Called by the UniversalBot at registration time to register a handler for receiving incoming events from a channel. */ + onEvent(handler: (events: IEvent[], callback?: (err: Error) => void) => void): void; + + /** Called by the UniversalBot to deliver outgoing messages to a user. */ + send(messages: IMessage[], callback: (err: Error) => void): void; + + /** Called when a UniversalBot wants to start a new proactive conversation with a user. The connector should return a properly formated __address__ object with a populated __conversation__ field. */ + startConversation(address: IAddress, callback: (err: Error, address?: IAddress) => void): void; +} + +/** Function signature for a piece of middleware that hooks the 'receive' or 'send' events. */ +export interface IEventMiddleware { + (event: IEvent, next: Function): void; +} + +/** Function signature for a piece of middleware that hooks the 'botbuilder' event. */ +export interface ISessionMiddleware { + (session: Session, next: Function): void; +} + +/** + * Map of middleware hooks that can be registered in a call to __UniversalBot.use()__. + */ +export interface IMiddlewareMap { + /** Called in series when an incoming event is received. */ + receive?: IEventMiddleware|IEventMiddleware[]; + + /** Called in series before an outgoing event is sent. */ + send?: IEventMiddleware|IEventMiddleware[]; + + /** Called in series once an incoming message has been bound to a session. Executed after [receive](#receive) middleware. */ + botbuilder?: ISessionMiddleware|ISessionMiddleware[]; +} + +/** + * Signature for functions passed as steps to [DialogAction.waterfall()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.dialogaction.html#waterfall). + * + * Waterfalls let you prompt a user for information using a sequence of questions. Each step of the + * waterfall can either execute one of the built-in [Prompts](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.prompts.html), + * start a new dialog by calling [session.beginDialog()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session.html#begindialog), + * advance to the next step of the waterfall manually using `skip()`, or terminate the waterfall. + * + * When either a dialog or built-in prompt is called from a waterfall step, the results from that + * dialog or prompt will be passed via the `results` parameter to the next step of the waterfall. + * Users can say things like "nevermind" to cancel the built-in prompts so you should guard against + * that by at least checking for [results.response](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogresult.html#response) + * before proceeding. A more detailed explination of why the waterfall is being continued can be + * determined by looking at the [code](/en-us/node/builder/chat-reference/enums/_botbuilder_d_.resumereason.html) + * returned for [results.resumed](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogresult.html#resumed). + * + * You can manually advance to the next step of the waterfall using the `skip()` function passed + * in. Calling `skip({ response: "some text" })` with an [IDialogResult](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogresult.html) + * lets you more accurately mimic the results from a built-in prompt and can simplify your overall + * waterfall logic. + * + * You can terminate a waterfall early by either falling through every step of the waterfall using + * calls to `skip()` or simply not starting another prompt or dialog. + * + * __note:__ Waterfalls have a hidden last step which will automatically end the current dialog if + * if you call a prompt or dialog from the last step. This is useful where you have a deep stack of + * dialogs and want a call to [session.endDialog()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session.html#enddialog) + * from the last child on the stack to end the entire stack. The close of the last child will trigger + * all of its parents to move to this hidden step which will cascade the close all the way up the stack. + * This is typically a desired behaviour but if you want to avoid it or stop it somewhere in the + * middle you'll need to add a step to the end of your waterfall that either does nothing or calls + * something liek [session.send()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session.html#send) + * which isn't going to advance the waterfall forward. + * @example + *

+ * var bot = new builder.BotConnectorBot();
+ * bot.add('/', [
+ *     function (session) {
+ *         builder.Prompts.text(session, "Hi! What's your name?");
+ *     },
+ *     function (session, results) {
+ *         if (results && results.response) {
+ *             // User answered question.
+ *             session.send("Hello %s.", results.response);
+ *         } else {
+ *             // User said nevermind.
+ *             session.send("OK. Goodbye.");
+ *         }
+ *     }
+ * ]);
+ * 
+ */ +export interface IDialogWaterfallStep { + /** + * @param session Session object for the current conversation. + * @param result + * * __result:__ _{any}_ - For the first step of the waterfall this will be `null` or the value of any arguments passed to the handler. + * * __result:__ _{IDialogResult}_ - For subsequent waterfall steps this will be the result of the prompt or dialog called in the previous step. + * @param skip Fuction used to manually skip to the next step of the waterfall. + * @param skip.results (Optional) results to pass to the next waterfall step. This lets you more accurately mimic the results returned from a prompt or dialog. + */ + (session: Session, result?: any | IDialogResult, skip?: (results?: IDialogResult) => void): any; +} + +/** A per/local mapping of LUIS service url's to use for a LuisRecognizer. */ +export interface ILuisModelMap { + [local: string]: string; +} + +/** A per/source mapping of custom event data to send. */ +export interface ISourceEventMap { + [source: string]: any; +} + +/** Options passed to Middleware.dialogVersion(). */ +export interface IDialogVersionOptions { + /** Current major.minor version for the bots dialogs. Major version increments result in existing conversations between the bot and user being restarted. */ + version: number; + + /** Optional message to send the user when their conversation is ended due to a version number change. A default message is provided. */ + message?: string|string[]|IMessage|IIsMessage; + + /** Optional regular expression to listen for to manually detect a request to reset the users session state. */ + resetCommand?: RegExp; +} + +/** Options passed to Middleware.firstRun(). */ +export interface IFirstRunOptions { + /** Current major.minor version for the bots first run experience. Major version increments result in redirecting users to [dialogId](#dialogid) and minor increments redirect users to [upgradeDialogId](#upgradedialogid). */ + version: number; + + /** Dialog to redirect users to when the major [version](#version) changes. */ + dialogId: string; + + /** (Optional) args to pass to [dialogId](#dialogid). */ + dialogArgs?: any; + + /** (Optional) dialog to redirect users to when the minor [version](#version) changes. Useful for minor Terms of Use changes. */ + upgradeDialogId?: string; + + /** (Optional) args to pass to [upgradeDialogId](#upgradedialogid). */ + upgradeDialogArgs?: string; +} + +//============================================================================= +// +// ENUMS +// +//============================================================================= + +/** Reason codes for why a dialog was resumed. */ +export enum ResumeReason { + /** The user completed the child dialog and a result was returned. */ + completed, + + /** The user did not complete the child dialog for some reason. They may have exceeded maxRetries or canceled. */ + notCompleted, + + /** The dialog was canceled in response to some user initiated action. */ + canceled, + + /** The user requested to return to the previous step in a dialog flow. */ + back, + + /** The user requested to skip the current step of a dialog flow. */ + forward +} + +/** Order in which an [IntentDialogs](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.intentdialog.html) recognizers should be evaluated. */ +export enum RecognizeOrder { + /** All recognizers will be evaluated in parallel. */ + parallel, + + /** Recognizers will be evaluated in series. Any recognizer that returns a score of 1.0 will prevent the evaluation of the remaining recognizers. */ + series +} + +/** Controls an [IntentDialogs](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.intentdialog.html) processing of the users text utterances. */ +export enum RecognizeMode { + /** Process text utterances whenever the dialog is first loaded through a call to session.beginDialog() and anytime a reply from the user is received. This was the default behaviour prior to version 3.2. */ + onBegin, + + /** Processes text utterances anytime a reply is received but only when the dialog is first loaded if it's the root dialog. This is the default behaviour as of 3.2. */ + onBeginIfRoot, + + /** Only process text utterances when a reply is received. */ + onReply +} + +/** + * Type of prompt invoked. + */ +export enum PromptType { + /** The user is prompted for a string of text. */ + text, + + /** The user is prompted to enter a number. */ + number, + + /** The user is prompted to confirm an action with a yes/no response. */ + confirm, + + /** The user is prompted to select from a list of choices. */ + choice, + + /** The user is prompted to enter a time. */ + time, + + /** The user is prompted to upload an attachment. */ + attachment +} + +/** Type of list to render for PromptType.choice prompt. */ +export enum ListStyle { + /** No list is rendered. This is used when the list is included as part of the prompt. */ + none, + + /** Choices are rendered as an inline list of the form "1. red, 2. green, or 3. blue". */ + inline, + + /** Choices are rendered as a numbered list. */ + list, + + /** Choices are rendered as buttons for channels that support buttons. For other channels they will be rendered as text. */ + button, + + /** The style is selected automatically based on the channel and number of options. */ + auto +} + +/** Identifies the type of text being sent in a message. */ +export var TextFormat: { + /** Text fields should be treated as plain text. */ + plain: string; + + /** Text fields may contain markdown formatting information. */ + markdown: string; + + /** Text fields may contain xml formatting information. */ + xml: string; +}; + +/** Identities how the client should render attachments for a message. */ +export var AttachmentLayout: { + /** Attachments should be rendred as a list. */ + list: string; + + /** Attachments should be rendered as a carousel. */ + carousel: string; +}; + + +//============================================================================= +// +// CLASSES +// +//============================================================================= + +/** + * Manages the bots conversation with a user. + */ +export class Session { + /** + * Registers an event listener. + * @param event Name of the event. Event types: + * - __error:__ An error occured. Passes a JavaScript `Error` object. + * @param listener Function to invoke. + * @param listener.data The data for the event. Consult the list above for specific types of data you can expect to receive. + */ + on(event: string, listener: (data: any) => void): void; + + /** + * Creates an instance of the session. + * @param options Sessions configuration options. + */ + constructor(options: ISessionOptions); + + /** + * Dispatches a message for processing. The session will call any installed middleware before + * the message to the active dialog for processing. + * @param sessionState The current session state. If _null_ a new conversation will be started beginning with the configured [dialogId](#dialogid). + * @param message The message to dispatch. + */ + dispatch(sessionState: ISessionState, message: IMessage): Session; + + /** The bots root library of dialogs. */ + library: Library; + + /** Sessions current state information. */ + sessionState: ISessionState; + + /** The message received from the user. For bot originated messages this may only contain the "to" & "from" fields. */ + message: IMessage; + + /** Data for the user that's persisted across all conversations with the bot. */ + userData: any; + + /** Shared conversation data that's visible to all members of the conversation. */ + conversationData: any; + + /** Private conversation data that's only visible to the user. */ + privateConversationData: any; + + /** Data that's only visible to the current dialog. */ + dialogData: any; + + /** The localizer (if available) to use. */ + localizer:ILocalizer ; + + /** + * Signals that an error occured. The bot will signal the error via an on('error', err) event. + * @param err Error that occured. + */ + error(err: Error): Session; + + /** + * Returns the preferred locale when no parameters are supplied, otherwise sets the preferred locale. + * @param locale (Optional) the locale to use for localizing messages. + * @param callback (Optional) function called when the localization table has been loaded for the supplied locale. + */ + preferredLocale(locale?: string, callback?: (err: Error) => void): string; + + /** + * Loads a localized string for the messages language. If arguments are passed the localized string + * will be treated as a template and formatted using [sprintf-js](https://github.com/alexei/sprintf.js) (see their docs for details.) + * @param msgid String to use as a key in the localized string table. Typically this will just be the english version of the string. + * @param args (Optional) arguments used to format the final output string. + */ + gettext(msgid: string, ...args: any[]): string; + + /** + * Loads the plural form of a localized string for the messages language. The output string will be formatted to + * include the count by replacing %d in the string with the count. + * @param msgid Singular form of the string to use as a key in the localized string table. Use %d to specify where the count should go. + * @param msgid_plural Plural form of the string to use as a key in the localized string table. Use %d to specify where the count should go. + * @param count Count to use when determining whether the singular or plural form of the string should be used. + */ + ngettext(msgid: string, msgid_plural: string, count: number): string; + + /** Triggers saving of changes made to [dialogData](#dialogdata), [userData](#userdata), [conversationdata](#conversationdata), or [privateConversationData'(#privateconversationdata). */ + save(): Session; + + /** + * Sends a message to the user. + * @param message + * * __message:__ _{string}_ - Text of the message to send. The message will be localized using the sessions configured localizer. If arguments are passed in the message will be formatted using [sprintf-js](https://github.com/alexei/sprintf.js). + * * __message:__ _{string[]}_ - The sent message will be chosen at random from the array. + * * __message:__ _{IMessage|IIsMessage}_ - Message to send. + * @param args (Optional) arguments used to format the final output text when __message__ is a _{string|string[]}_. + */ + send(message: string|string[]|IMessage|IIsMessage, ...args: any[]): Session; + + /** + * Sends the user an indication that the bot is typing. For long running operations this should be called every few seconds. + */ + sendTyping(): Session; + + /** + * Returns true if a message has been sent for this session. + */ + messageSent(): boolean; + + /** + * Passes control of the conversation to a new dialog. The current dialog will be suspended + * until the child dialog completes. Once the child ends the current dialog will receive a + * call to [dialogResumed()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.dialog.html#dialogresumed) + * where it can inspect any results returned from the child. + * @param id Unique ID of the dialog to start. + * @param args (Optional) arguments to pass to the dialogs [begin()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.dialog.html#begin) method. + */ + beginDialog(id: string, args?: T): Session; + + /** + * Ends the current dialog and starts a new one its place. The parent dialog will not be + * resumed until the new dialog completes. + * @param id Unique ID of the dialog to start. + * @param args (Optional) arguments to pass to the dialogs [begin()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.dialog.html#begin) method. + */ + replaceDialog(id: string, args?: T): Session; + + /** + * Ends the current conversation and optionally sends a message to the user. + * @param message (Optional) + * * __message:__ _{string}_ - Text of the message to send. The message will be localized using the sessions configured localizer. If arguments are passed in the message will be formatted using [sprintf-js](https://github.com/alexei/sprintf.js). + * * __message:__ _{string[]}_ - The sent message will be chosen at random from the array. + * * __message:__ _{IMessage|IIsMessage}_ - Message to send. + * @param args (Optional) arguments used to format the final output text when __message__ is a _{string|string[]}_. + */ + endConversation(message?: string|string[]|IMessage|IIsMessage, ...args: any[]): Session; + + /** + * Ends the current dialog and optionally sends a message to the user. The parent will be resumed with an [IDialogResult.resumed](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogresult.html#resumed) + * reason of [completed](/en-us/node/builder/chat-reference/enums/_botbuilder_d_.resumereason.html#completed). + * @param message (Optional) + * * __message:__ _{string}_ - Text of the message to send. The message will be localized using the sessions configured localizer. If arguments are passed in the message will be formatted using [sprintf-js](https://github.com/alexei/sprintf.js). + * * __message:__ _{string[]}_ - The sent message will be chosen at random from the array. + * * __message:__ _{IMessage|IIsMessage}_ - Message to send. + * @param args (Optional) arguments used to format the final output text when __message__ is a _{string|string[]}_. + */ + endDialog(message?: string|string[]|IMessage|IIsMessage, ...args: any[]): Session; + + /** + * Ends the current dialog and optionally returns a result to the dialogs parent. + */ + endDialogWithResult(result?: IDialogResult): Session; + + /** + * Cancels an existing dialog and optionally starts a new one it its place. Unlike [endDialog()](#enddialog) + * and [replaceDialog()](#replacedialog) which affect the current dialog, this method lets you end a + * parent dialog anywhere on the stack. The parent of the canceled dialog will be continued as if the + * dialog had called endDialog(). A special [ResumeReason.canceled](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.resumereason#canceled) + * will be returned to indicate that the dialog was canceled. + * @param dialogId + * * __dialogId:__ _{string}_ - ID of the dialog to end. If multiple occurences of the dialog exist on the dialog stack, the last occurance will be canceled. + * * __dialogId:__ _{number}_ - Index of the dialog on the stack to cancel. This is the preferred way to cancel a dialog from an action handler as it ensures that the correct instance is canceled. + * @param replaceWithId (Optional) specifies an ID to start in the canceled dialogs place. This prevents the dialogs parent from being resumed. + * @param replaceWithArgs (Optional) arguments to pass to the new dialog. + */ + cancelDialog(dialogId: string|number, replaceWithId?: string, replaceWithArgs?: any): Session; + + /** + * Clears the sessions callstack and restarts the conversation with the configured dialogId. + * @param dialogId (Optional) ID of the dialog to start. + * @param dialogArgs (Optional) arguments to pass to the dialogs [begin()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.dialog.html#begin) method. + */ + reset(dialogId?: string, dialogArgs?: any): Session; + + /** Returns true if the session has been reset. */ + isReset(): boolean; + + /** + * Immediately ends the current batch and delivers any queued up messages. + * @param callback (Optional) function called when the batch was either successfully delievered or failed for some reason. + */ + sendBatch(callback?: (err: Error) => void): void; +} + +/** + * Message builder class that simplifies building complex messages with attachments. + */ +export class Message implements IIsMessage { + + /** + * Creates a new Message builder. + * @param session (Optional) will be used to populate the messages address and localize any text. + */ + constructor(session?: Session); + + /** Language of the message. */ + textLocale(locale: string): Message; + + /** Format of text fields. */ + textFormat(style: string): Message; + + /** Sets the message text. */ + text(text: string|string[], ...args: any[]): Message; + + /** Conditionally set this message text given a specified count. */ + ntext(msg: string|string[], msg_plural: string|string[], count: number): Message; + + /** Composes a complex and randomized reply to the user. */ + compose(prompts: string[][], ...args: any[]): Message; + + /** Text to be displayed by as fall-back and as short description of the message content in e.g. list of recent conversations. */ + summary(text: string|string[], ...args: any[]): Message; + + /** Hint for how clients should layout multiple attachments. The default value is 'list'. */ + attachmentLayout(style: string): Message; + + /** Cards or images to send to the user. */ + attachments(list: IAttachment[]|IIsAttachment[]): Message; + + /** + * Adds an attachment to the message. See [IAttachment](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iattachment.html) for examples. + * @param attachment The attachment to add. + */ + addAttachment(attachment: IAttachment|IIsAttachment): Message; + + /** Structured objects passed to the bot or user. */ + entities(list: Object[]): Message; + + /** Adds an entity to the message. */ + addEntity(obj: Object): Message; + + /** Address routing information for the message. Save this field to external storage somewhere to later compose a proactive message to the user. */ + address(adr: IAddress): Message; + + /** Timestamp of the message. If called will default the timestamp to (now). */ + timestamp(time?: string): Message; + + /** Message in original/native format of the channel for incoming messages. */ + originalEvent(event: any): Message; + + /** For outgoing messages can be used to pass source specific event data like custom attachments. */ + sourceEvent(map: ISourceEventMap): Message; + + /** Returns the JSON for the message. */ + toMessage(): IMessage; + + /** __DEPRECATED__ use [local()](#local) instead. */ + setLanguage(language: string): Message; + + /** __DEPRECATED__ use [text()](#text) instead. */ + setText(session: Session, prompt: string|string[], ...args: any[]): Message; + + /** __DEPRECATED__ use [ntext()](#ntext) instead. */ + setNText(session: Session, msg: string, msg_plural: string, count: number): Message; + + /** __DEPRECATED__ use [compose()](#compose) instead. */ + composePrompt(session: Session, prompts: string[][], ...args: any[]): Message; + + /** __DEPRECATED__ use [sourceEvent()](#sourceevent) instead. */ + setChannelData(data: any): Message; + + /** + * Selects a prompt at random. + * @param prompts Array of prompts to choose from. When prompts is type _string_ the prompt will simply be returned unmodified. + */ + static randomPrompt(prompts: string|string[]): string; + + /** + * Combines an array of prompts into a single localized prompt and then optionally fills the + * prompts template slots with the passed in arguments. + * @param session Session object used to localize the individual prompt parts. + * @param prompts Array of prompt lists. Each entry in the array is another array of prompts + * which will be chosen at random. The combined output text will be space delimited. + * @param args (Optional) array of arguments used to format the output text when the prompt is a template. + */ + static composePrompt(session: Session, prompts: string[][], args?: any[]): string; +} + +/** Builder class to simplify adding actions to a card. */ +export class CardAction implements IIsCardAction { + + /** + * Creates a new CardAction. + * @param session (Optional) will be used to localize any text. + */ + constructor(session?: Session); + + /** Type of card action. */ + type(t: string): CardAction; + + /** Title of the action. For buttons this will be the label of the button. For tap actions this may be used for accesibility purposes or shown on hover. */ + title(text: string|string[], ...args: any[]): CardAction; + + /** The actions value. */ + value(v: string): CardAction; + + /** For buttons an image to include next to the buttons label. Not supported by all channels. */ + image(url: string): CardAction; + + /** Returns the JSON for the action. */ + toAction(): ICardAction; + + /** + * Places a call to a phone number. The should include country code in +44/+1 format for Skype calls. + * @param session (Optional) Current session object for the conversation. If specified will be used to localize titles. + */ + static call(session: Session, number: string, title?: string|string[]): CardAction; + + /** + * Opens the specified URL. + * @param session (Optional) Current session object for the conversation. If specified will be used to localize titles. + */ + static openUrl(session: Session, url: string, title?: string|string[]): CardAction; + + /** + * Sends a message to the bot for processing in a way that's visible to all members of the conversation. For some channels this may get mapped to a [postBack](#postback). + * @param session (Optional) Current session object for the conversation. If specified will be used to localize titles. + */ + static imBack(session: Session, msg: string, title?: string|string[]): CardAction; + + /** + * Sends a message to the bot for processing in a way that's hidden from all members of the conversation. For some channels this may get mapped to a [imBack](#imback). + * @param session (Optional) Current session object for the conversation. If specified will be used to localize titles. + */ + static postBack(session: Session, msg: string, title?: string|string[]): CardAction; + + /** + * Plays the specified audio file to the user. Not currently supported for Skype. + * @param session (Optional) Current session object for the conversation. If specified will be used to localize titles. + */ + static playAudio(session: Session, url: string, title?: string|string[]): CardAction; + + /** + * Plays the specified video to the user. Not currently supported for Skype. + * @param session (Optional) Current session object for the conversation. If specified will be used to localize titles. + */ + static playVideo(session: Session, url: string, title?: string|string[]): CardAction; + + /** + * Opens the specified image in a native image viewer. For Skype only valid as a tap action on a CardImage. + * @param session (Optional) Current session object for the conversation. If specified will be used to localize titles. + */ + static showImage(session: Session, url: string, title?: string|string[]): CardAction; + + /** + * Downloads the specified file to the users device. Not currently supported for Skype. + * @param session (Optional) Current session object for the conversation. If specified will be used to localize titles. + */ + static downloadFile(session: Session, url: string, title?: string|string[]): CardAction; + + /** + * Binds a button or tap action to a named action registered for a dialog or globally off the bot. + * + * Can be used anywhere a [postBack](#postback) is valid. You may also statically bind a button + * to an action for something like Facebooks [Persistent Menus](https://developers.facebook.com/docs/messenger-platform/thread-settings/persistent-menu). + * The payload for the button should be `action?` for actions without data or + * `action?=` for actions with data. + * @param session (Optional) Current session object for the conversation. If specified will be used to localize titles. + * @param action Name of the action to invoke when tapped. + * @param data (Optional) data to pass to the action when invoked. The [IRecognizeActionResult.data](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.irecognizeactionresult#data) + * property can be used to access this data. If using [beginDialogAction()](dlg./en-us/node/builder/chat-reference/classes/_botbuilder_d_.dialog#begindialogaction) this value will be passed + * as part of the dialogs initial arguments. + * @param title (Optional) title to assign when binding the action to a button. + */ + static dialogAction(session: Session, action: string, data?: string, title?: string|string[]): CardAction; +} + +/** Builder class to simplify adding images to a card. */ +export class CardImage implements IIsCardImage { + + /** + * Creates a new CardImage. + * @param session (Optional) will be used to localize any text. + */ + constructor(session?: Session); + + /** URL of the image to display. */ + url(u: string): CardImage; + + /** Alternate text of the image to use for accessibility pourposes. */ + alt(text: string|string[], ...args: any[]): CardImage; + + /** Action to take when the image is tapped. */ + tap(action: ICardAction|IIsCardAction): CardImage; + + /** Returns the JSON for the image. */ + toImage(): ICardImage; + + /** Creates a new CardImage for a given url. */ + static create(session: Session, url: string): CardImage; +} + +/** Card builder class that simplifies building thumbnail cards. */ +export class ThumbnailCard implements IIsAttachment { + + /** + * Creates a new ThumbnailCard. + * @param session (Optional) will be used to localize any text. + */ + constructor(session?: Session); + + /** Title of the Card. */ + title(text: string|string[], ...args: any[]): ThumbnailCard; + + /** Subtitle appears just below Title field, differs from Title in font styling only. */ + subtitle(text: string|string[], ...args: any[]): ThumbnailCard; + + /** Text field appears just below subtitle, differs from Subtitle in font styling only. */ + text(text: string|string[], ...args: any[]): ThumbnailCard; + + /** Messaging supports all media formats: audio, video, images and thumbnails as well to optimize content download. */ + images(list: ICardImage[]|IIsCardImage[]): ThumbnailCard; + + /** Set of actions applicable to the current card. Not all channels support buttons or cards with buttons. Some channels may choose to render the buttons using a custom keyboard. */ + buttons(list: ICardAction[]|IIsCardAction[]): ThumbnailCard; + + /** This action will be activated when user taps on the card. Not all channels support tap actions and some channels may choose to render the tap action as the titles link. */ + tap(action: ICardAction|IIsCardAction): ThumbnailCard; + + /** Returns the JSON for the card, */ + toAttachment(): IAttachment; +} + +/** Card builder class that simplifies building hero cards. Hero cards contain the same information as a thumbnail card, just with a larger more pronounced layout for the cards images. */ +export class HeroCard extends ThumbnailCard { + + /** + * Creates a new HeroCard. + * @param session (Optional) will be used to localize any text. + */ + constructor(session?: Session); +} + +/** Card builder class that simplifies building signin cards. */ +export class SigninCard implements IIsAttachment { + + /** + * Creates a new SigninCard. + * @param session (Optional) will be used to localize any text. + */ + constructor(session?: Session); + + /** Title of the Card. */ + text(prompts: string|string[], ...args: any[]): SigninCard; + + /** Signin button label and link. */ + button(title: string|string[], url: string): SigninCard; + + /** Returns the JSON for the card, */ + toAttachment(): IAttachment; +} + +/** Card builder class that simplifies building receipt cards. */ +export class ReceiptCard implements IIsAttachment { + + /** + * Creates a new ReceiptCard. + * @param session (Optional) will be used to localize any text. + */ + constructor(session?: Session); + + /** Title of the Card. */ + title(text: string|string[], ...args: any[]): ReceiptCard; + + /** Array of receipt items. */ + items(list: IReceiptItem[]|IIsReceiptItem[]): ReceiptCard; + + /** Array of additional facts to display to user (shipping charges and such.) Not all facts will be displayed on all channels. */ + facts(list: IFact[]|IIsFact[]): ReceiptCard; + + /** This action will be activated when user taps on the card. Not all channels support tap actions. */ + tap(action: ICardAction|IIsCardAction): ReceiptCard; + + /** Total amount of money paid (or should be paid.) */ + total(v: string): ReceiptCard; + + /** Total amount of TAX paid (or should be paid.) */ + tax(v: string): ReceiptCard; + + /** Total amount of VAT paid (or should be paid.) */ + vat(v: string): ReceiptCard; + + /** Set of actions applicable to the current card. Not all channels support buttons and the number of allowed buttons varies by channel. */ + buttons(list: ICardAction[]|IIsCardAction[]): ReceiptCard; + + /** Returns the JSON for the card. */ + toAttachment(): IAttachment; +} + +/** Builder class to simplify adding items to a receipt card. */ +export class ReceiptItem implements IIsReceiptItem { + + /** + * Creates a new ReceiptItem. + * @param session (Optional) will be used to localize any text. + */ + constructor(session?: Session); + + /** Title of the item. */ + title(text: string|string[], ...args: any[]): ReceiptItem; + + /** Subtitle appears just below Title field, differs from Title in font styling only. On some channels may be combined with the [title](#title) or [text](#text). */ + subtitle(text: string|string[], ...args: any[]): ReceiptItem; + + /** Text field appears just below subtitle, differs from Subtitle in font styling only. */ + text(text: string|string[], ...args: any[]): ReceiptItem; + + /** Image to display on the card. Some channels may either send the image as a seperate message or simply include a link to the image. */ + image(img: ICardImage|IIsCardImage): ReceiptItem; + + /** Amount with currency. */ + price(v: string): ReceiptItem; + + /** Number of items of given kind. */ + quantity(v: string): ReceiptItem; + + /** This action will be activated when user taps on the Item bubble. Not all channels support tap actions. */ + tap(action: ICardAction|IIsCardAction): ReceiptItem; + + /** Returns the JSON for the item. */ + toItem(): IReceiptItem; + + /** Creates a new ReceiptItem. */ + static create(session: Session, price: string, title?: string|string[]): ReceiptItem; +} + +/** Builder class to simplify creating a list of facts for a card like a receipt. */ +export class Fact implements IIsFact { + + /** + * Creates a new Fact. + * @param session (Optional) will be used to localize any text. + */ + constructor(session?: Session); + + /** Display name of the fact. */ + key(text: string|string[], ...args: any[]): Fact; + + /** Display value of the fact. */ + value(v: string): Fact; + + /** Returns the JSON for the fact. */ + toFact(): IFact; + + /** Creates a new Fact. */ + static create(session: Session, value: string, key?: string|string[]): Fact; +} + + +/** + * Implement support for named actions which can be bound to a dialog to handle global utterances from the user like "help" or + * "cancel". Actions get pushed onto and off of the dialog stack as part of dialogs so these listeners can + * come into and out of scope as the conversation progresses. You can also bind named to actions to buttons + * which let your bot respond to button clicks on cards that have maybe scrolled off the screen. + */ +export class ActionSet { + /** + * Called to recognize any actions triggered by the users utterance. + * @param message The message received from the user. + * @param callback Function to invoke with the results of the recognition. The top scoring action, if any, will be returned. + */ + recognizeAction(message: IMessage, callback: (err: Error, result: IRecognizeActionResult) => void): void; + + /** + * Invokes an action that had the highest confidence score for the utterance. + * @param session Session object for the current conversation. + * @param recognizeResult Results returned from call to [recognizeAction()](#recognizeaction). + */ + invokeAction(session: Session, recognizeResult: IRecognizeActionResult): void; +} + +/** + * Base class for all dialogs. Dialogs are the core component of the BotBuilder + * framework. Bots use Dialogs to manage arbitrarily complex conversations with + * a user. + */ +export abstract class Dialog extends ActionSet { + /** + * Called when a new dialog session is being started. + * @param session Session object for the current conversation. + * @param args (Optional) arguments passed to the dialog by its parent. + */ + begin(session: Session, args?: T): void; + + /** + * Called when a new reply message has been received from a user. + * + * Derived classes should implement this to process the message received from the user. + * @param session Session object for the current conversation. + * @param recognizeResult Results returned from a prior call to the dialogs [recognize()](#recognize) method. + */ + abstract replyReceived(session: Session, recognizeResult: IRecognizeResult): void; + + /** + * A child dialog has ended and the current one is being resumed. + * @param session Session object for the current conversation. + * @param result Result returned by the child dialog. + */ + dialogResumed(session: Session, result: IDialogResult): void; + + /** + * Parses the users utterance and assigns a score from 0.0 - 1.0 indicating how confident the + * dialog is that it understood the users utterance. This method is always called for the active + * dialog on the stack. A score of 1.0 will indicate a perfect match and terminate any further + * recognition. + * + * When the score is less than 1.0, every dialog on the stack will have its + * [recognizeAction()](#recognizeaction) method called as well to see if there are any named + * actions bound to the dialog that better matches the users utterance. Global actions registered + * at the bot level will also be evaluated. If the dialog has a score higher then any bound actions, + * the dialogs [replyReceived()](#replyreceived) method will be called with the result object + * returned from the recognize() call. This lets the dialog pass additional data collected during + * the recognize phase to the replyReceived() method for handling. + * + * Should there be an action with a higher score then the dialog the action will be invoked instead + * of the dialogs replyReceived() method. The dialog will stay on the stack and may be resumed + * at some point should the action invoke a new dialog so dialogs should prepare for unexpected calls + * to [dialogResumed()](#dialogresumed). + * @param context The context of the request. + * @param callback Function to invoke with the recognition results. + */ + recognize(context: IRecognizeContext, callback: (err: Error, result: IRecognizeResult) => void): void; + + /** + * Binds an action to the dialog that will cancel the dialog anytime its triggered. When canceled, the + * dialogs parent will be resumed with a [resumed](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogresult#resumed) code indicating that it was [canceled](/en-us/node/builder/chat-reference/enums/_botbuilder_d_.resumereason#canceled). + * @param name Unique name to assign the action. + * @param msg (Optional) message to send the user prior to canceling the dialog. + * @param options (Optional) options used to configure the action. If [matches](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogactionoptions#matches) is specified the action will listen + * for the user to say a word or phrase that triggers the action, otherwise the action needs to be bound to a button using [CardAction.dialogAction()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.cardaction#dialogaction) + * to trigger the action. + */ + cancelAction(name: string, msg?: string|string[]|IMessage|IIsMessage, options?: IDialogActionOptions): Dialog; + + /** + * Binds an action to the dialog that will cause the dialog to be reloaded anytime its triggered. This is + * useful to implement logic that handle user utterances like "start over". + * @param name Unique name to assign the action. + * @param msg (Optional) message to send the user prior to reloading the dialog. + * @param options (Optional) options used to configure the action. If [matches](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogactionoptions#matches) is specified the action will listen + * for the user to say a word or phrase that triggers the action, otherwise the action needs to be bound to a button using [CardAction.dialogAction()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.cardaction#dialogaction) + * to trigger the action. You can also use [dialogArgs](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogactionoptions#dialogargs) to pass additional params to the dialog when reloaded. + */ + reloadAction(name: string, msg?: string|string[]|IMessage|IIsMessage, options?: IDialogActionOptions): Dialog; + + /** + * Binds an action to the dialog that will start another dialog anytime its triggered. The new + * dialog will be pushed onto the stack so it does not automatically end the current task. The + * current task will be continued once the new dialog ends. The built-in prompts will automatically + * re-prompt the user once this happens but that behaviour can be disabled by setting the [promptAfterAction](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ipromptoptions#promptafteraction) + * flag when calling a built-in prompt. + * @param name Unique name to assign the action. + * @param id ID of the dialog to start. + * @param options (Optional) options used to configure the action. If [matches](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogactionoptions#matches) is specified the action will listen + * for the user to say a word or phrase that triggers the action, otherwise the action needs to be bound to a button using [CardAction.dialogAction()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.cardaction#dialogaction) + * to trigger the action. You can also use [dialogArgs](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogactionoptions#dialogargs) to pass additional params to the dialog being started. + */ + beginDialogAction(name: string, id: string, options?: IDialogActionOptions): Dialog; + + /** + * Binds an action that will end the conversation with the user when triggered. + * @param name Unique name to assign the action. + * @param msg (Optional) message to send the user prior to ending the conversation. + * @param options (Optional) options used to configure the action. If [matches](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogactionoptions#matches) is specified the action will listen + * for the user to say a word or phrase that triggers the action, otherwise the action needs to be bound to a button using [CardAction.dialogAction()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.cardaction#dialogaction) + * to trigger the action. + */ + endConversationAction(name: string, msg?: string|string[]|IMessage|IIsMessage, options?: IDialogActionOptions): Dialog; +} + +/** + * Dialog actions offer static shortcuts to implementing common actions. They also implement support for + * named actions which can be bound to a dialog to handle global utterances from the user like "help" or + * "cancel". Actions get pushed onto and off of the dialog stack as part of dialogs so these listeners can + * come into and out of scope as the conversation progresses. You can also bind named to actions to buttons + * which let your bot respond to button clicks on cards that have maybe scrolled off the screen. + */ +export class DialogAction { + /** + * Returns a closure that will send a simple text message to the user. + * @param msg Text of the message to send. The message will be localized using the sessions configured [localizer](#localizer). If arguments are passed in the message will be formatted using [sprintf-js](https://github.com/alexei/sprintf.js) (see the docs for details.) + * @param args (Optional) arguments used to format the final output string. + */ + static send(msg: string, ...args: any[]): IDialogWaterfallStep; + + /** + * Returns a closure that will passes control of the conversation to a new dialog. + * @param id Unique ID of the dialog to start. + * @param args (Optional) arguments to pass to the dialogs begin() method. + */ + static beginDialog(id: string, args?: T): IDialogWaterfallStep; + + /** + * Returns a closure that will end the current dialog. + * @param result (Optional) results to pass to the parent dialog. + */ + static endDialog(result?: any): IDialogWaterfallStep; + + /** + * Returns a closure that wraps a built-in prompt with validation logic. The closure should be used + * to define a new dialog for the prompt using bot.add('/myPrompt', builder.DialogAction.) + * @param promptType Type of built-in prompt to validate. + * @param validator Function used to validate the response. Should return true if the response is valid. + * @param validator.response The users [IDialogResult.response](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogresult.html#response) returned by the built-in prompt. + * @example + *

+     * var bot = new builder.BotConnectorBot();
+     * bot.add('/', [
+     *     function (session) {
+     *         session.beginDialog('/meaningOfLife', { prompt: "What's the meaning of life?" });
+     *     },
+     *     function (session, results) {
+     *         if (results.response) {
+     *             session.send("That's correct! The meaning of life is 42.");
+     *         } else {
+     *             session.send("Sorry you couldn't figure it out. Everyone knows that the meaning of life is 42.");
+     *         }
+     *     }
+     * ]);
+     * bot.add('/meaningOfLife', builder.DialogAction.validatedPrompt(builder.PromptType.text, function (response) {
+     *     return response === '42';
+     * }));
+     * 
+ */ + static validatedPrompt(promptType: PromptType, validator: (response: any) => boolean): Dialog; +} + + +/** + * A library of related dialogs used for routing purposes. Libraries can be chained together to enable + * the development of complex bots. The [UniversalBot](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.universalbot.html) + * class is itself a Library that forms the root of this chain. + * + * Libraries of reusable parts can be developed by creating a new Library instance and adding dialogs + * just as you would to a bot. Your library should have a unique name that corresponds to either your + * libraries website or NPM module name. Bots can then reuse your library by simply adding your parts + * Library instance to their bot using [UniversalBot.library()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.universalbot.html#library). + * If your library itself depends on other libraries you should add them to your library as a dependency + * using [Library.library()](#library). You can easily manage multiple versions of your library by + * adding a version number to your library name. + * + * To invoke dialogs within your library bots will need to call [session.beginDialog()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session.html#begindialog) + * with a fully qualified dialog id in the form of ':'. You'll typically hide + * this from the devloper by exposing a function from their module that starts the dialog for them. + * So calling something like `myLib.someDialog(session, { arg: '' });` would end up calling + * `session.beginDialog('myLib:someDialog', args);` under the covers. + * + * Its worth noting that dialogs are always invoked within the current dialog so once your within + * a dialog from your library you don't need to prefix every beginDialog() call your with your + * libraries name. Its only when crossing from one library context to another that you need to + * include the library name prefix. + */ +export class Library { + /** + * The libraries unique namespace. This is used to issolate the libraries dialogs and localized + * prompts. + */ + name: string; + + /** + * Creates a new instance of the library. + * @param name Unique namespace for the library. + */ + constructor(name: string); + + /** + * Gets or sets the path to the libraries "/locale/" folder containing its localized prompts. + * The prompts for the library should be stored in a "/locale//.json" file + * under this path where "" representes the 2-3 digit language tage for the locale and + * "" is a filename matching the libraries namespace. + * @param path (Optional) path to the libraries "/locale/" folder. If specified this will update the libraries path. + */ + localePath(path?: string): string; + + /** + * Registers or returns a dialog from the library. + * @param id Unique ID of the dialog being regsitered or retrieved. + * @param dialog (Optional) dialog or waterfall to register. + * * __dialog:__ _{Dialog}_ - Dialog to add. + * * __dialog:__ _{IDialogWaterfallStep[]}_ - Waterfall of steps to execute. See [IDialogWaterfallStep](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogwaterfallstep.html) for details. + * * __dialog:__ _{IDialogWaterfallStep}_ - Single step waterfall. Calling a built-in prompt or starting a new dialog will result in the current dialog ending upon completion of the child prompt/dialog. + */ + dialog(id: string, dialog?: Dialog|IDialogWaterfallStep[]|IDialogWaterfallStep): Dialog; + + /** + * Registers or returns a library dependency. + * @param lib + * * __lib:__ _{Library}_ - Library to register as a dependency. + * * __lib:__ _{string}_ - Unique name of the library to lookup. All dependencies will be searched as well. + */ + library(lib: Library|string): Library; + + /** + * Searches the library and all of its dependencies for a specific dialog. Returns the dialog + * if found, otherwise null. + * @param libName Name of the library containing the dialog. + * @param dialogId Unique ID of the dialog within the library. + */ + findDialog(libName: string, dialogId: string): Dialog; + + /** + * Enumerates all of the libraries child libraries. The caller should take appropriate steps to + * avoid circular references when enumerating the hierarchy. Maintaining a map of visited + * libraries should be enough. + * @param callback Iterator function to call with each child. + * @param callback.library The current child. + */ + forEachLibrary(callback: (library: Library) => void): void; +} + +/** + * Built in built-in prompts that can be called from any dialog. + */ +export class Prompts extends Dialog { + /** + * Processes messages received from the user. Called by the dialog system. + * @param session Session object for the current conversation. + * @param (Optional) recognition results returned from a prior call to the dialogs [recognize()](#recognize) method. + */ + replyReceived(session: Session, recognizeResult?: IRecognizeResult): void; + + /** + * Updates global options for the Prompts dialog. + * @param options Options to set. + */ + static configure(options: IPromptsOptions): void; + + /** + * Captures from the user a raw string of text. + * @param session Session object for the current conversation. + * @param prompt + * * __prompt:__ _{string}_ - Initial message to send the user. + * * __prompt:__ _{string[]}_ - Array of possible messages to send user. One will be chosen at random. + * * __prompt:__ _{IMessage|IIsMessage}_ - Initial message to send the user. Message can contain attachments. + * @param options (Optional) parameters to control the behaviour of the prompt. + */ + static text(session: Session, prompt: string|string[]|IMessage|IIsMessage, options?: IPromptOptions): void; + + /** + * Prompts the user to enter a number. + * @param session Session object for the current conversation. + * @param prompt + * * __prompt:__ _{string}_ - Initial message to send the user. + * * __prompt:__ _{string[]}_ - Array of possible messages to send user. One will be chosen at random. + * * __prompt:__ _{IMessage|IIsMessage}_ - Initial message to send the user. Message can contain attachments. + * @param options (Optional) parameters to control the behaviour of the prompt. + */ + static number(session: Session, prompt: string|string[]|IMessage|IIsMessage, options?: IPromptOptions): void; + + /** + * Prompts the user to confirm an action with a yes/no response. + * @param session Session object for the current conversation. + * @param prompt + * * __prompt:__ _{string}_ - Initial message to send the user. + * * __prompt:__ _{string[]}_ - Array of possible messages to send user. One will be chosen at random. + * * __prompt:__ _{IMessage|IIsMessage}_ - Initial message to send the user. Message can contain attachments. + * @param options (Optional) parameters to control the behaviour of the prompt. + */ + static confirm(session: Session, prompt: string|string[]|IMessage|IIsMessage, options?: IPromptOptions): void; + + /** + * Prompts the user to choose from a list of options. + * @param session Session object for the current conversation. + * @param prompt + * * __prompt:__ _{string}_ - Initial message to send the user. + * * __prompt:__ _{string[]}_ - Array of possible messages to send user. One will be chosen at random. + * * __prompt:__ _{IMessage|IIsMessage}_ - Initial message to send the user. Message can contain attachments. Any [listStyle](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ipromptoptions.html#liststyle) options will be ignored. + * @param choices + * * __choices:__ _{string}_ - List of choices as a pipe ('|') delimted string. + * * __choices:__ _{Object}_ - List of choices expressed as an Object map. The objects field names will be used to build the list of values. + * * __choices:__ _{string[]}_ - List of choices as an array of strings. + * @param options (Optional) parameters to control the behaviour of the prompt. + */ + static choice(session: Session, prompt: string|string[]|IMessage|IIsMessage, choices: string|Object|string[], options?: IPromptOptions): void; + + /** + * Prompts the user to enter a time. + * @param session Session object for the current conversation. + * @param prompt + * * __prompt:__ _{string}_ - Initial message to send the user. + * * __prompt:__ _{string[]}_ - Array of possible messages to send user. One will be chosen at random. + * * __prompt:__ _{IMessage|IIsMessage}_ - Initial message to send the user. Message can contain attachments. + * @param options (Optional) parameters to control the behaviour of the prompt. + */ + static time(session: Session, prompt: string|string[]|IMessage|IIsMessage, options?: IPromptOptions): void; + + /** + * Prompts the user to upload a file attachment. + * @param session Session object for the current conversation. + * @param prompt + * * __prompt:__ _{string}_ - Initial message to send the user. + * * __prompt:__ _{string[]}_ - Array of possible messages to send user. One will be chosen at random. + * * __prompt:__ _{IMessage|IIsMessage}_ - Initial message to send the user. Message can contain attachments. + * @param options (Optional) parameters to control the behaviour of the prompt. + */ + static attachment(session: Session, prompt: string|string[]|IMessage|IIsMessage, options?: IPromptOptions): void; +} + +/** + * Implements a simple pattern based recognizer for parsing the built-in prompts. Derived classes can + * inherit from SimplePromptRecognizer and override the recognize() method to change the recognition + * of one or more prompt types. + */ +export class SimplePromptRecognizer implements IPromptRecognizer { + /** + * Attempts to match a users reponse to a given prompt. + * @param args Arguments passed to the recognizer including that language, text, and prompt choices. + * @param callback Function to invoke with the result of the recognition attempt. + */ + recognize(args: IPromptRecognizerArgs, callback: (result: IPromptResult) => void): void; +} + +/** Identifies a users intent and optionally extracts entities from a users utterance. */ +export class IntentDialog extends Dialog { + /** + * Constructs a new instance of an IntentDialog. + * @param options (Optional) options used to initialize the dialog. + */ + constructor(options?: IIntentDialogOptions); + + /** + * Processes messages received from the user. Called by the dialog system. + * @param session Session object for the current conversation. + * @param (Optional) recognition results returned from a prior call to the dialogs [recognize()](#recognize) method. + */ + replyReceived(session: Session, recognizeResult?: IRecognizeResult): void; + + /** + * Called when the dialog is first loaded after a call to [session.beginDialog()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session#begindialog). This gives the bot an opportunity to process arguments passed to the dialog. Handlers should always call the `next()` function to continue execution of the dialogs main logic. + * @param handler Function to invoke when the dialog begins. + * @param handler.session Session object for the current conversation. + * @param handler.args Arguments passed to the dialog in the `session.beginDialog()` call. + * @param handler.next Function to call when handler is finished to continue execution of the dialog. + */ + onBegin(handler: (session: Session, args: any, next: () => void) => void): IntentDialog; + + /** + * Invokes a handler when a given intent is detected in the users utterance. + * + * > __NOTE:__ The full details of the match, including the list of intents & entities detected, will be passed to the [args](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iintentrecognizerresult) of the first waterfall step or dialog that's started. + * @param intent + * * __intent:__ _{RegExp}_ - A regular expression that will be evaluated to detect the users intent. + * * __intent:__ _{string}_ - A named intent returned by an [IIntentRecognizer](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iintentrecognizer) plugin that will be used to match the users intent. + * @param dialogId + * * __dialogId:__ _{string} - The ID of a dialog to begin when the intent is matched. + * * __dialogId:__ _{IDialogWaterfallStep[]}_ - Waterfall of steps to execute when the intent is matched. + * * __dialogId:__ _{IDialogWaterfallStep}_ - Single step waterfall to execute when the intent is matched. Calling a built-in prompt or starting a new dialog will result in the current dialog ending upon completion of the child prompt/dialog. + * @param dialogArgs (Optional) arguments to pass the dialog that started when `dialogId` is a _{string}_. + */ + matches(intent: RegExp|string, dialogId: string|IDialogWaterfallStep[]|IDialogWaterfallStep, dialogArgs?: any): IntentDialog; + + /** + * Invokes a handler when any of the given intents are detected in the users utterance. + * + * > __NOTE:__ The full details of the match, including the list of intents & entities detected, will be passed to the [args](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iintentrecognizerresult) of the first waterfall step or dialog that's started. + * @param intent + * * __intent:__ _{RegExp[]}_ - Array of regular expressions that will be evaluated to detect the users intent. + * * __intent:__ _{string[]}_ - Array of named intents returned by an [IIntentRecognizer](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iintentrecognizer) plugin that will be used to match the users intent. + * @param dialogId + * * __dialogId:__ _{string} - The ID of a dialog to begin when the intent is matched. + * * __dialogId:__ _{IDialogWaterfallStep[]}_ - Waterfall of steps to execute when the intent is matched. + * * __dialogId:__ _{IDialogWaterfallStep}_ - Single step waterfall to execute when the intent is matched. Calling a built-in prompt or starting a new dialog will result in the current dialog ending upon completion of the child prompt/dialog. + * @param dialogArgs (Optional) arguments to pass the dialog that started when `dialogId` is a _{string}_. + */ + matchesAny(intent: RegExp[]|string[], dialogId: string|IDialogWaterfallStep[]|IDialogWaterfallStep, dialogArgs?: any): IntentDialog; + + /** + * The default handler to invoke when there are no handlers that match the users intent. + * + * > __NOTE:__ The full details of the recognition attempt, including the list of intents & entities detected, will be passed to the [args](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iintentrecognizerresult) of the first waterfall step or dialog that's started. + * @param dialogId + * * __dialogId:__ _{string} - The ID of a dialog to begin. + * * __dialogId:__ _{IDialogWaterfallStep[]}_ - Waterfall of steps to execute. + * * __dialogId:__ _{IDialogWaterfallStep}_ - Single step waterfall to execute. Calling a built-in prompt or starting a new dialog will result in the current dialog ending upon completion of the child prompt/dialog. + * @param dialogArgs (Optional) arguments to pass the dialog that started when `dialogId` is a _{string}_. + */ + onDefault(dialogId: string|IDialogWaterfallStep[]|IDialogWaterfallStep, dialogArgs?: any): IntentDialog; + + /** + * Adds a new recognizer plugin to the intent dialog. + * @param plugin The recognizer to add. + */ + recognizer(plugin: IIntentRecognizer): IntentDialog; +} + +/** + * Routes incoming messages to a LUIS app hosted on http://luis.ai for intent recognition. + * Once a messages intent has been recognized it will rerouted to a registered intent handler, along + * with any entities, for further processing. + */ +export class LuisRecognizer implements IIntentRecognizer { + /** + * Constructs a new instance of a LUIS recognizer. + * @param models Either an individual LUIS model used for all utterances or a map of per/local models conditionally used depending on the local of the utterance. + */ + constructor(models: string|ILuisModelMap); + + /** Called by the IntentDialog to perform the actual recognition. */ + public recognize(context: IRecognizeContext, callback: (err: Error, result: IIntentRecognizerResult) => void): void; + + /** + * Calls LUIS to recognizing intents & entities in a users utterance. + * @param utterance The text to pass to LUIS for recognition. + * @param serviceUri URI for LUIS App hosted on http://luis.ai. + * @param callback Callback to invoke with the results of the intent recognition step. + * @param callback.err Error that occured during the recognition step. + * @param callback.intents List of intents that were recognized. + * @param callback.entities List of entities that were recognized. + */ + static recognize(utterance: string, modelUrl: string, callback: (err: Error, intents?: IIntent[], entities?: IEntity[]) => void): void; +} + +/** + * Utility class used to parse & resolve common entities like datetimes received from LUIS. + */ +export class EntityRecognizer { + /** + * Searches for the first occurance of a specific entity type within a set. + * @param entities Set of entities to search over. + * @param type Type of entity to find. + */ + static findEntity(entities: IEntity[], type: string): IEntity; + + /** + * Finds all occurrences of a specific entity type within a set. + * @param entities Set of entities to search over. + * @param type Type of entity to find. + */ + static findAllEntities(entities: IEntity[], type: string): IEntity[]; + + /** + * Parses a date from either a users text utterance or a set of entities. + * @param value + * * __value:__ _{string}_ - Text utterance to parse. The utterance is parsed using the [Chrono](http://wanasit.github.io/pages/chrono/) library. + * * __value:__ _{IEntity[]}_ - Set of entities to resolve. + * @returns A valid Date object if the user spoke a time otherwise _null_. + */ + static parseTime(value: string | IEntity[]): Date; + + /** + * Calculates a Date from a set of datetime entities. + * @param entities List of entities to extract date from. + * @returns The successfully calculated Date or _null_ if a date couldn't be determined. + */ + static resolveTime(entities: IEntity[]): Date; + + /** + * Recognizes a time from a users utterance. The utterance is parsed using the [Chrono](http://wanasit.github.io/pages/chrono/) library. + * @param utterance Text utterance to parse. + * @param refDate (Optional) reference date used to calculate the final date. + * @returns An entity containing the resolved date if successful or _null_ if a date couldn't be determined. + */ + static recognizeTime(utterance: string, refDate?: Date): IEntity; + + /** + * Parses a number from either a users text utterance or a set of entities. + * @param value + * * __value:__ _{string}_ - Text utterance to parse. + * * __value:__ _{IEntity[]}_ - Set of entities to resolve. + * @returns A valid number otherwise _Number.NaN_. + */ + static parseNumber(value: string | IEntity[]): number; + + /** + * Parses a boolean from a users utterance. + * @param value Text utterance to parse. + * @returns A valid boolean otherwise _undefined_. + */ + static parseBoolean(value: string): boolean; + + /** + * Finds the best match for a users utterance given a list of choices. + * @param choices + * * __choices:__ _{string}_ - Pipe ('|') delimited list of values to compare against the users utterance. + * * __choices:__ _{Object}_ - Object used to generate the list of choices. The objects field names will be used to build the list of choices. + * * __choices:__ _{string[]}_ - Array of strings to compare against the users utterance. + * @param utterance Text utterance to parse. + * @param threshold (Optional) minimum score needed for a match to be considered. The default value is 0.6. + */ + static findBestMatch(choices: string | Object | string[], utterance: string, threshold?: number): IFindMatchResult; + + /** + * Finds all possible matches for a users utterance given a list of choices. + * @param choices + * * __choices:__ _{string}_ - Pipe ('|') delimited list of values to compare against the users utterance. + * * __choices:__ _{Object}_ - Object used to generate the list of choices. The objects field names will be used to build the list of choices. + * * __choices:__ _{string[]}_ - Array of strings to compare against the users utterance. + * @param utterance Text utterance to parse. + * @param threshold (Optional) minimum score needed for a match to be considered. The default value is 0.6. + */ + static findAllMatches(choices: string | Object | string[], utterance: string, threshold?: number): IFindMatchResult[]; + + /** + * Converts a set of choices into an expanded array. + * @param choices + * * __choices:__ _{string}_ - Pipe ('|') delimited list of values. + * * __choices:__ _{Object}_ - Object used to generate the list of choices. The objects field names will be used to build the list of choices. + * * __choices:__ _{string[]}_ - Array of strings. This will just be echoed back as the output. + */ + static expandChoices(choices: string | Object | string[]): string[]; +} + +/** + * Allows for the creation of custom dialogs that are based on a simple closure. This is useful for + * cases where you want a dynamic conversation flow or you have a situation that just doesn’t map + * very well to using a waterfall. The things to keep in mind: + * * Your dialogs closure is can get called in two different contexts that you potentially need to + * test for. It will get called as expected when the user send your dialog a message but if you + * call another prompt or dialog from your closure it will get called a second time with the + * results from the prompt/dialog. You can typically test for this second case by checking for the + * existant of an `args.resumed` property. It's important to avoid getting yourself into an + * infinite loop which can be easy to do. + * * Unlike a waterfall your dialog will not automatically end. It will remain the active dialog + * until you call [session.endDialog()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session.html#enddialog). + */ +export class SimpleDialog extends Dialog { + /** + * Creates a new custom dialog based on a simple closure. + * @param handler The function closure for your dialog. + * @param handler.session Session object for the current conversation. + * @param handler.args + * * __args:__ _{any}_ - For the first call to the handler this will be either `null` or the value of any arguments passed to [Session.beginDialog()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session.html#begindialog). + * * __args:__ _{IDialogResult}_ - If the handler takes an action that results in a new dialog being started those results will be returned via subsequent calls to the handler. + */ + constructor(handler: (session: Session, args?: any | IDialogResult) => void); + + /** + * Processes messages received from the user. Called by the dialog system. + * @param session Session object for the current conversation. + */ + replyReceived(session: Session): void; +} + +/** Default in memory storage implementation for storing user & session state data. */ +export class MemoryBotStorage implements IBotStorage { + /** Returns data from memmory for the given context. */ + getData(context: IBotStorageContext, callback: (err: Error, data: IBotStorageData) => void): void; + + /** Saves data to memory for the given context. */ + saveData(context: IBotStorageContext, data: IBotStorageData, callback?: (err: Error) => void): void; + + /** Deletes in-memory data for the given context. */ + deleteData(context: IBotStorageContext): void; +} + +/** Manages your bots conversations with users across multiple channels. */ +export class UniversalBot { + + /** + * Creates a new instance of the UniversalBot. + * @param connector (Optional) the default connector to use for requests. If there's not a more specific connector registered for a channel then this connector will be used./** + * @param settings (Optional) settings to configure the bot with. + */ + constructor(connector?: IConnector, settings?: IUniversalBotSettings); + + /** + * Registers an event listener. The bot will emit its own events as it process incoming and outgoing messages. It will also forward activity related events emitted from the connector, giving you one place to listen for all activity from your bot. The flow of events from the bot is as follows: + * + * #### Message Received + * When the bot receives a new message it will emit the following events in order: + * + * > lookupUser -> receive -> incoming -> getStorageData -> routing + * + * Any [receive middleware](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.imiddlewaremap#receive) that's been installed will be executed between the 'receive' and 'incoming' events. After the 'routing' event is emmited any + * [botbuilder middleware](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.imiddlewaremap#botbuilder) will be executed prior to dispatching the message to the bots active dialog. + * + * #### Connector Activity Received + * Connectors can emit activity events to signal things like a user is typing or that they friended a bot. These activities get routed through middleware like messages but they are not routed through the bots dialog system. They are only ever emitted as events. + * + * The flow of connector events is: + * + * > lookupUser -> receive -> (activity) + * + * #### Message sent + * Bots can send multiple messages so the session will batch up all outgoing message and then save the bots current state before delivering the sent messages. You'll see a single 'saveStorageData' event emitted and then for every outgoing message in the batch you'll see the following + * sequence of events: + * + * > send -> outgoing + * + * Any [send middleware](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.imiddlewaremap#send) that's been installed will be executed between the 'send' and 'outgoing' events. + * + * @param event Name of the event. Bot and connector specific event types: + * #### Bot Events + * - __error:__ An error occured. Passed a JavaScript `Error` object. + * - __lookupUser:__ The user is for an address is about to be looked up. Passed an [IAddress](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iaddress.html) object. + * - __receive:__ An incoming message has been received. Passed an [IEvent](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ievent.html) object. + * - __incoming:__ An incoming message has been received and processed by middleware. Passed an [IMessage](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.imessage.html) object. + * - __routing:__ An incoming message has been bound to a session and is about to be routed through any session middleware and then dispatched to the active dialog for processing. Passed a [Session](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session.html) object. + * - __send:__ An outgoing message is about to be sent to middleware for processing. Passed an [IMessage](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.imessage.html) object. + * - __getStorageData:__ The sessions persisted state data is being loaded from storage. Passed an [IBotStorageContext](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ibotstoragecontext.html) object. + * - __saveStorageData:__ The sessions persisted state data is being written to storage. Passed an [IBotStorageContext](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ibotstoragecontext.html) object. + * + * #### ChatConnector Events + * - __conversationUpdate:__ Your bot was added to a conversation or other conversation metadata changed. Passed an [IConversationUpdate](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iconversationupdate.html) object. + * - __contactRelationUpdate:__ The bot was added to or removed from a user's contact list. Passed an [IContactRelationUpdate](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.icontactrelationupdate.html) object. + * - __typing:__ The user or bot on the other end of the conversation is typing. Passed an [IEvent](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ievent.html) object. + * + * @param listener Function to invoke. + * @param listener.data The data for the event. Consult the list above for specific types of data you can expect to receive. + */ + on(event: string, listener: (data: any) => void): void; + + /** + * Sets a setting on the bot. + * @param name Name of the property to set. Valid names are properties on [IUniversalBotSettings](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iuniversalbotsettings.html). + * @param value The value to assign to the setting. + */ + set(name: string, value: any): UniversalBot; + + /** + * Returns the current value of a setting. + * @param name Name of the property to return. Valid names are properties on [IUniversalBotSettings](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iuniversalbotsettings.html). + */ + get(name: string): any; + + /** + * Registers or returns a connector for a specific channel. + * @param channelId Unique ID of the channel. Use a channelId of '*' to reference the default connector. + * @param connector (Optional) connector to register. If ommited the connector for __channelId__ will be returned. + */ + connector(channelId: string, connector?: IConnector): IConnector; + + /** + * Registers or returns a dialog for the bot. + * @param id Unique ID of the dialog being regsitered or retrieved. + * @param dialog (Optional) dialog or waterfall to register. + * * __dialog:__ _{Dialog}_ - Dialog to add. + * * __dialog:__ _{IDialogWaterfallStep[]}_ - Waterfall of steps to execute. See [IDialogWaterfallStep](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogwaterfallstep.html) for details. + * * __dialog:__ _{IDialogWaterfallStep}_ - Single step waterfall. Calling a built-in prompt or starting a new dialog will result in the current dialog ending upon completion of the child prompt/dialog. + */ + dialog(id: string, dialog?: Dialog|IDialogWaterfallStep[]|IDialogWaterfallStep): Dialog; + + /** + * Registers or returns a library dependency. + * @param lib + * * __lib:__ _{Library}_ - Library to register as a dependency. + * * __lib:__ _{string}_ - Unique name of the library to lookup. All dependencies will be searched as well. + */ + library(lib: Library|string): Library; + + /** + * Installs middleware for the bot. Middleware lets you intercept incoming and outgoing events/messages. + * @param args One or more sets of middleware hooks to install. + */ + use(...args: IMiddlewareMap[]): UniversalBot; + + /** + * Called when a new event is received. This can be called manually to mimic the bot receiving a message from the user. + * @param events Event or (array of events) received. + * @param done (Optional) function to invoke once the operation is completed. + */ + receive(events: IEvent|IEvent[], done?: (err: Error) => void): void; + + /** + * Proactively starts a new dialog with the user. Any current conversation between the bot and user will be replaced with a new dialog stack. + * @param address Address of the user to start a new conversation with. This should be saved during a previous conversation with the user. Any existing conversation or dialog will be immediately terminated. + * @param dialogId ID of the dialog to begin. + * @param dialogArgs (Optional) arguments to pass to dialog. + * @param done (Optional) function to invoke once the operation is completed. + */ + beginDialog(address: IAddress, dialogId: string, dialogArgs?: any, done?: (err: Error) => void): void; + + /** + * Sends a message to the user without disrupting the current conversations dialog stack. + * @param messages The message (or array of messages) to send the user. + * @param done (Optional) function to invoke once the operation is completed. + */ + send(messages: IIsMessage|IMessage|IMessage[], done?: (err: Error) => void): void; + + /** + * Returns information about when the last turn between the user and a bot occured. This can be called + * before [beginDialog](#begindialog) to determine if the user is currently in a conversation with the + * bot. + * @param address Address of the user to lookup. This should be saved during a previous conversation with the user. + * @param callback Function to invoke with the results of the query. + */ + isInConversation(address: IAddress, callback: (err: Error, lastAccess: Date) => void): void; + + /** + * Registers a global action that will start another dialog anytime its triggered. The new + * dialog will be pushed onto the stack so it does not automatically end any current task. The + * current task will be continued once the new dialog ends. The built-in prompts will automatically + * re-prompt the user once this happens but that behaviour can be disabled by setting the [promptAfterAction](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ipromptoptions#promptafteraction) + * flag when calling a built-in prompt. + * @param name Unique name to assign the action. + * @param id ID of the dialog to start. + * @param options (Optional) options used to configure the action. If [matches](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogactionoptions#matches) is specified the action will listen + * for the user to say a word or phrase that triggers the action, otherwise the action needs to be bound to a button using [CardAction.dialogAction()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.cardaction#dialogaction) + * to trigger the action. You can also use [dialogArgs](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogactionoptions#dialogargs) to pass additional params to the dialog being started. + */ + beginDialogAction(name: string, id: string, options?: IDialogActionOptions): Dialog; + + /** + * Registers a global action that will end the conversation with the user when triggered. + * @param name Unique name to assign the action. + * @param msg (Optional) message to send the user prior to ending the conversation. + * @param options (Optional) options used to configure the action. If [matches](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogactionoptions#matches) is specified the action will listen + * for the user to say a word or phrase that triggers the action, otherwise the action needs to be bound to a button using [CardAction.dialogAction()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.cardaction#dialogaction) + * to trigger the action. + */ + endConversationAction(name: string, msg?: string|string[]|IMessage|IIsMessage, options?: IDialogActionOptions): Dialog; +} + +/** Connects a UniversalBot to multiple channels via the Bot Framework. */ +export class ChatConnector implements IConnector, IBotStorage { + + /** + * Creates a new instnace of the ChatConnector. + * @param settings (Optional) config params that let you specify the bots App ID & Password you were assigned in the Bot Frameworks developer portal. + */ + constructor(settings?: IChatConnectorSettings); + + /** Registers an Express or Restify style hook to listen for new messages. */ + listen(): (req: any, res: any) => void; + + /** Called by the UniversalBot at registration time to register a handler for receiving incoming events from a channel. */ + onEvent(handler: (events: IEvent[], callback?: (err: Error) => void) => void): void; + + /** Called by the UniversalBot to deliver outgoing messages to a user. */ + send(messages: IMessage[], done: (err: Error) => void): void; + + /** Called when a UniversalBot wants to start a new proactive conversation with a user. The connector should return a properly formated __address__ object with a populated __conversation__ field. */ + startConversation(address: IAddress, done: (err: Error, address?: IAddress) => void): void; + + /** Reads in data from the Bot Frameworks state service. */ + getData(context: IBotStorageContext, callback: (err: Error, data: IBotStorageData) => void): void; + + /** Writes out data to the Bot Frameworks state service. */ + saveData(context: IBotStorageContext, data: IBotStorageData, callback?: (err: Error) => void): void; +} + +/** Connects a UniversalBot to the command line via a console window. */ +export class ConsoleConnector implements IConnector { + /** Starts the connector listening to stdIn. */ + listen(): ConsoleConnector; + + /** Sends a message through the connector. */ + processMessage(line: string): ConsoleConnector; + + /** Called by the UniversalBot at registration time to register a handler for receiving incoming events from a channel. */ + onEvent(handler: (events: IEvent[], callback?: (err: Error) => void) => void): void; + + /** Called by the UniversalBot to deliver outgoing messages to a user. */ + send(messages: IMessage[], callback: (err: Error, conversationId?: string) => void): void; + + /** Called when a UniversalBot wants to start a new proactive conversation with a user. The connector should return a properly formated __address__ object with a populated __conversation__ field. */ + startConversation(address: IAddress, callback: (err: Error, address?: IAddress) => void): void; +} + +export class Middleware { + /** + * Installs a piece of middleware that manages the versioning of a bots dialogs. + * @param options Settings to configure the bahviour of the installed middleware. + */ + static dialogVersion(options: IDialogVersionOptions): IMiddlewareMap; + + /** + * Adds a first run experience to a bot. The middleware uses Session.userData to store the latest version of the first run dialog the user has been through. Incrementing the version number can force users to run back through either the full or a partial first run dialog. + * @param options Settings to configure the bahviour of the installed middleware. + */ + static firstRun(options: IFirstRunOptions): IMiddlewareMap; + + /** + * Installs a piece of middleware that will always send an initial typing indication to the user. + * This is useful because it lets you send the typing indication before any LUIS models are called. + * The typing indicator will only stay valid for a few seconds so if you're performing any long running + * operations you may want to send an additional typing indicator using [session.sendTyping](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session#sendtyping). + */ + static sendTyping(): IMiddlewareMap; +} + +/** __DEPRECATED__ use an [IntentDialog](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.intentdialog) with a [LuisRecognizer](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.luisrecognizer) instead. */ +export class LuisDialog extends Dialog { + replyReceived(session: Session, recognizeResult?: IRecognizeResult): void; +} + +/** __DEPRECATED__ use an [IntentDialog](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.intentdialog) instead. */ +export class CommandDialog extends Dialog { + replyReceived(session: Session, recognizeResult?: IRecognizeResult): void; +} + +/** __DEPRECATED__ use [UniversalBot](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.universalbot) and a [ChatConnector](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.chatconnector) instead. */ +export class BotConnectorBot extends Dialog { + replyReceived(session: Session, recognizeResult?: IRecognizeResult): void; +} + +/** __DEPRECATED__ use [UniversalBot](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.universalbot) and a [ConsoleConnector](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.consoleconnector) instead. */ +export class TextBot extends Dialog { + replyReceived(session: Session, recognizeResult?: IRecognizeResult): void; +} \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/botFrameworkInspector/control.css b/Plugins/Vorlon/plugins/botFrameworkInspector/control.css new file mode 100644 index 00000000..cce198e8 --- /dev/null +++ b/Plugins/Vorlon/plugins/botFrameworkInspector/control.css @@ -0,0 +1,210 @@ +body { + margin: 0; +} + +#splitter { + display: flex; + flex-wrap: nowrap; + font-family: "Menlo", "Consolas", "Lucida Console", Courier, monospace; + font-size: 0.9em; + background-color: lightgray; + height:100%; +} + + + +.hidden{ + visibility: collapse; + display: none!important; +} + +#left { + flex-grow: 3; + height: 100%; + max-width: 50%; +} + +#left .placeholder { + align-content: center; + height: 100%; + color: slategray; + text-align: center; + margin-top: 100px; +} + + +#left #dialogs { + display:flex; + flex-direction: column; + overflow: scroll; + overflow-x: hidden; + height: 30%; + margin: 5px; + margin-bottom: 5px; + border: 1px solid black; +} + + + + +#left .dialog { + display:flex; + flex-direction: row; + line-height: 25px; + border-bottom: 1px solid lightgray; + flex-shrink: 0; +} + +#left .dialog-id { + background-color: #3E3D3D; + color: white; + padding-left: 10px; + margin: 0; + padding: 5px; + width: 200px; + height:25px; +} + +#left .dialog-detail { + flex-grow: 4; + display: flex; + flex-direction: row; + background-color: #ccc; + color: darkslategray; + padding-left: 20px; +} + +#left .dialog-detail { + margin:0; + padding: 5px; +} + +#left .dialog-detail p { + margin:0; + margin-left:5px; + margin-right:5px; +} + +#left .dialog-detail .waterfall-steps { + display:flex; + flex-grow: 2; +} + +#left .dialog-detail .waterfall-step { + background-color: dimgray; + color: white; + text-align: center; + flex-grow: 1; + margin-left: 10px; + max-width: 40px; +} + +#left #dialogstacks { + display:flex; + flex-direction: column; + overflow: scroll; + overflow-x: hidden; + flex-grow: 4; + margin: 5px; + margin-top: 5px; + border: 1px solid black; + height: calc(70% - 40px); +} + +#left #dialogstacks label { + color: white; +} + +#left #dialogstacks .user-entry { + display:flex; + flex-direction: column; + border-left: 5px solid #3E3D3D; + flex-shrink: 0; +} + +#left #dialogstacks .user-entry .entry { + background-color: #3E3D3D; + flex-grow: 1; + color: white; + line-height: 100%; + margin: 0; + padding: 10px; + padding-left: 0; +} + +#left #dialogstacks .user-entry .stacks { + display: flex; + flex-direction: column; + flex-grow: 5; + background-color: #ccc; +} + +#left #dialogstacks .user-entry .stack { + display: flex; + flex-direction: column; + padding: 4px 5px 4px 5px; + border-top: 1px solid #3E3D3D; +} + +#left #dialogstacks .user-entry .dialogs-in-stack { + display: flex; + flex-direction: row; + align-items: center; +} + +#left #dialogstacks .user-entry .data { + border: 1px solid darkslategray; + background-color: #eee; + padding: 5px; + margin: 5px 5px 0px 0px; +} + +#left #dialogstacks .user-entry .data-hidden { + visibility: collapse; + display: none!important; +} + +#left #dialogstacks .user-entry .stack .dialog-in-stack { + background-color: dimgray; + color: white; + text-align: center; + height:25px; + line-height: 25px; + padding-left: 10px; + padding-right: 10px; +} + +#left #dialogstacks .user-entry .stack p { + margin: 0; +} + +#left #dialogstacks .user-entry .stack .dialogs-in-stack .line { + height: 0px; + width: 20px; + border-bottom: 1px solid black; + align-self: center; +} + +#right { + height: 100%; + overflow: hidden; + flex-grow: 5; +} + +.scroll { + background: #3E3D3D; + overflow: scroll; +} +.scroll::-webkit-scrollbar { + width: 12px; +} + +.scroll::-webkit-scrollbar-track { + background: #3E3D3D; +} + +.scroll::-webkit-scrollbar-thumb { + border-radius: 10px; + -webkit-box-shadow: inset 0 0 6px rgba(256,256,256,1); +} +​ \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/botFrameworkInspector/control.html b/Plugins/Vorlon/plugins/botFrameworkInspector/control.html new file mode 100644 index 00000000..7e4055e9 --- /dev/null +++ b/Plugins/Vorlon/plugins/botFrameworkInspector/control.html @@ -0,0 +1,272 @@ + + + + + + + +
+
+
+ +
Nothing to show here (yet!).
+ +
+ +
+ +
Nothing to show here (yet!).
Start interacting with your bot.
+ +
+
+ +
+ + diff --git a/Plugins/Vorlon/plugins/botFrameworkInspector/cytoscape-dagre.js b/Plugins/Vorlon/plugins/botFrameworkInspector/cytoscape-dagre.js new file mode 100644 index 00000000..c93288b6 --- /dev/null +++ b/Plugins/Vorlon/plugins/botFrameworkInspector/cytoscape-dagre.js @@ -0,0 +1,192 @@ +;(function(){ 'use strict'; + + // registers the extension on a cytoscape lib ref + var register = function( cytoscape, dagre ){ + if( !cytoscape || !dagre ){ return; } // can't register if cytoscape unspecified + + var isFunction = function(o){ return typeof o === 'function'; }; + + // default layout options + var defaults = { + // dagre algo options, uses default value on undefined + nodeSep: undefined, // the separation between adjacent nodes in the same rank + edgeSep: undefined, // the separation between adjacent edges in the same rank + rankSep: undefined, // the separation between adjacent nodes in the same rank + rankDir: undefined, // 'TB' for top to bottom flow, 'LR' for left to right + minLen: function( edge ){ return 1; }, // number of ranks to keep between the source and target of the edge + edgeWeight: function( edge ){ return 1; }, // higher weight edges are generally made shorter and straighter than lower weight edges + + // general layout options + fit: true, // whether to fit to viewport + padding: 30, // fit padding + animate: false, // whether to transition the node positions + animationDuration: 500, // duration of animation in ms if enabled + animationEasing: undefined, // easing of animation if enabled + boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } + ready: function(){}, // on layoutready + stop: function(){} // on layoutstop + }; + + // constructor + // options : object containing layout options + function DagreLayout( options ){ + var opts = this.options = {}; + for( var i in defaults ){ opts[i] = defaults[i]; } + for( var i in options ){ opts[i] = options[i]; } + } + + // runs the layout + DagreLayout.prototype.run = function(){ + var options = this.options; + var layout = this; + + var cy = options.cy; // cy is automatically populated for us in the constructor + var eles = options.eles; + + var getVal = function( ele, val ){ + return isFunction(val) ? val.apply( ele, [ ele ] ) : val; + }; + + var bb = options.boundingBox || { x1: 0, y1: 0, w: cy.width(), h: cy.height() }; + if( bb.x2 === undefined ){ bb.x2 = bb.x1 + bb.w; } + if( bb.w === undefined ){ bb.w = bb.x2 - bb.x1; } + if( bb.y2 === undefined ){ bb.y2 = bb.y1 + bb.h; } + if( bb.h === undefined ){ bb.h = bb.y2 - bb.y1; } + + var g = new dagre.graphlib.Graph({ + multigraph: true, + compound: true + }); + + var gObj = {}; + var setGObj = function( name, val ){ + if( val != null ){ + gObj[ name ] = val; + } + }; + + setGObj( 'nodesep', options.nodeSep ); + setGObj( 'edgesep', options.edgeSep ); + setGObj( 'ranksep', options.rankSep ); + setGObj( 'rankdir', options.rankDir ); + + g.setGraph( gObj ); + + g.setDefaultEdgeLabel(function() { return {}; }); + g.setDefaultNodeLabel(function() { return {}; }); + + // add nodes to dagre + var nodes = eles.nodes(); + for( var i = 0; i < nodes.length; i++ ){ + var node = nodes[i]; + var nbb = node.boundingBox(); + + g.setNode( node.id(), { + width: nbb.w, + height: nbb.h, + name: node.id() + } ); + + // console.log( g.node(node.id()) ); + } + + // set compound parents + for( var i = 0; i < nodes.length; i++ ){ + var node = nodes[i]; + + if( node.isChild() ){ + g.setParent( node.id(), node.parent().id() ); + } + } + + // add edges to dagre + var edges = eles.edges().stdFilter(function( edge ){ + return !edge.source().isParent() && !edge.target().isParent(); // dagre can't handle edges on compound nodes + }); + for( var i = 0; i < edges.length; i++ ){ + var edge = edges[i]; + + g.setEdge( edge.source().id(), edge.target().id(), { + minlen: getVal( edge, options.minLen ), + weight: getVal( edge, options.edgeWeight ), + name: edge.id() + }, edge.id() ); + + // console.log( g.edge(edge.source().id(), edge.target().id(), edge.id()) ); + } + + dagre.layout( g ); + + var gNodeIds = g.nodes(); + for( var i = 0; i < gNodeIds.length; i++ ){ + var id = gNodeIds[i]; + var n = g.node( id ); + + cy.getElementById(id).scratch().dagre = n; + } + + var dagreBB; + + if( options.boundingBox ){ + dagreBB = { x1: Infinity, x2: -Infinity, y1: Infinity, y2: -Infinity }; + nodes.forEach(function( node ){ + var dModel = node.scratch().dagre; + + dagreBB.x1 = Math.min( dagreBB.x1, dModel.x ); + dagreBB.x2 = Math.max( dagreBB.x2, dModel.x ); + + dagreBB.y1 = Math.min( dagreBB.y1, dModel.y ); + dagreBB.y2 = Math.max( dagreBB.y2, dModel.y ); + }); + + dagreBB.w = dagreBB.x2 - dagreBB.x1; + dagreBB.h = dagreBB.y2 - dagreBB.y1; + } else { + dagreBB = bb; + } + + var constrainPos = function( p ){ + if( options.boundingBox ){ + var xPct = (p.x - dagreBB.x1) / dagreBB.w; + var yPct = (p.y - dagreBB.y1) / dagreBB.h; + + return { + x: bb.x1 + xPct * bb.w, + y: bb.y1 + yPct * bb.h + }; + } else { + return p; + } + }; + + nodes.layoutPositions(layout, options, function(){ + var dModel = this.scratch().dagre; + + return constrainPos({ + x: dModel.x, + y: dModel.y + }); + }); + + return this; // chaining + }; + + cytoscape('layout', 'dagre', DagreLayout); + + }; + + if( typeof module !== 'undefined' && module.exports ){ // expose as a commonjs module + module.exports = register; + } + + if( typeof define !== 'undefined' && define.amd ){ // expose as an amd/requirejs module + define('cytoscape-dagre', function(){ + return register; + }); + } + + if( typeof cytoscape !== 'undefined' && typeof dagre !== 'undefined' ){ // expose to global cytoscape (i.e. window.cytoscape) + register( cytoscape, dagre ); + } + +})(); diff --git a/Plugins/Vorlon/plugins/botFrameworkInspector/cytoscape.min.js b/Plugins/Vorlon/plugins/botFrameworkInspector/cytoscape.min.js new file mode 100644 index 00000000..bd2c76cf --- /dev/null +++ b/Plugins/Vorlon/plugins/botFrameworkInspector/cytoscape.min.js @@ -0,0 +1,62 @@ +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.cytoscape=e()}}(function(){var define,module,exports;return function e(t,r,n){function i(o,s){if(!r[o]){if(!t[o]){var l="function"==typeof require&&require;if(!s&&l)return l(o,!0);if(a)return a(o,!0);var u=new Error("Cannot find module '"+o+"'");throw u.code="MODULE_NOT_FOUND",u}var c=r[o]={exports:{}};t[o][0].call(c.exports,function(e){var r=t[o][1][e];return i(r?r:e)},c,c.exports,e,t,r,n)}return r[o].exports}for(var a="function"==typeof require&&require,o=0;oa&&(n=a,r=i)}return r}},a=this._private.cy;if(null!=e&&null!=e.root){var o=n.string(e.root)?this.filter(e.root)[0]:e.root[0];if(null!=e.goal){var s=n.string(e.goal)?this.filter(e.goal)[0]:e.goal[0];if(null!=e.heuristic&&n.fn(e.heuristic))var l=e.heuristic;else var l=function(){return 0};if(null!=e.weight&&n.fn(e.weight))var u=e.weight;else var u=function(e){return 1};if(null!=e.directed)var c=e.directed;else var c=!1;var d=[],h=[o.id()],p={},f={},v={},g={};v[o.id()]=0,g[o.id()]=l(o);for(var y=this.edges().stdFilter(function(e){return!e.isLoop()}),m=this.nodes(),b=0;h.length>0;){var x=i(h,g),w=a.getElementById(h[x]);if(b++,w.id()==s.id()){var E=r(o.id(),s.id(),p,[]);return E.reverse(),{found:!0,distance:v[w.id()],path:t.spawn(E),steps:b}}d.push(w.id()),h.splice(x,1);var _=w.connectedEdges();c&&(_=_.stdFilter(function(e){return e.data("source")===w.id()})),_=_.intersect(y);for(var P=0;P<_.length;P++){var S=_[P],k=S.connectedNodes().stdFilter(function(e){return e.id()!==w.id()}).intersect(m);if(-1==d.indexOf(k.id())){var T=v[w.id()]+u.apply(S,[S]);-1!=h.indexOf(k.id())?Th;h++)d[u[h].id()]=h;for(var p=[],f=[],v=[],h=0;c>h;h++)u[h].id()===o.id()?p[h]=0:p[h]=1/0,f[h]=void 0;for(var g=!1,h=1;c>h;h++){g=!1;for(var y=0;yh;h++)E.push(u[h].id());var _={distanceTo:function(e){if(n.string(e))var t=s.filter(e)[0].id();else var t=e.id();return p[d[t]]},pathTo:function(e){var r=function(e,t,r,n,i,a){for(;;){if(i.push(s.getElementById(n[r])),i.push(a[r]),t===r)return i;var o=e[r];if("undefined"==typeof o)return;r=o}};if(n.string(e))var i=s.filter(e)[0].id();else var i=e.id();var a=[],l=r(f,d[o.id()],d[i],E,a,v);return null!=l&&l.reverse(),t.spawn(l)},hasNegativeWeightCycle:!1};return _}}};t.exports=a},{"../../is":83,"../../util":100}],5:[function(e,t,r){"use strict";var n=e("../../is"),i=e("../../heap"),a={betweennessCentrality:function(e){e=e||{};var t,r;n.fn(e.weight)?(r=e.weight,t=!0):t=!1;for(var a=null!=e.directed?e.directed:!1,o=this._private.cy,s=this.nodes(),l={},u={},c=0,d={set:function(e,t){u[e]=t,t>c&&(c=t)},get:function(e){return u[e]}},h=0;h0?S.edgesTo(P)[0]:P.edgesTo(S)[0];var k=r.apply(_,[_]);P=P.id(),x[P]>x[p]+k&&(x[P]=x[p]+k,w.nodes.indexOf(P)<0?w.push(P):w.updateItem(P),b[P]=0,m[P]=[]),x[P]==x[p]+k&&(b[P]=b[P]+b[p],m[P].push(p))}else for(var E=0;E0;)for(var P=y.pop(),E=0;Ea&&(a=u),i[o[l].id()]=u}return{closeness:function(e){if(0==a)return 0;if(n.string(e))var e=t.filter(e)[0].id();else var e=e.id();return i[e]/a}}},closenessCentrality:function(e){if(e=e||{},null!=e.root){if(n.string(e.root))var t=this.filter(e.root)[0];else var t=e.root[0];if(null!=e.weight&&n.fn(e.weight))var r=e.weight;else var r=function(){return 1};if(null!=e.directed&&n.bool(e.directed))var i=e.directed;else var i=!1;var a=e.harmonic;void 0===a&&(a=!0);for(var o=this.dijkstra({root:t,weight:r,directed:i}),s=0,l=this.nodes(),u=0;ud;d++){var h=a[d],p=this.degreeCentrality(i.extend({},e,{root:h}));ud;d++){var h=a[d],p=this.degreeCentrality(i.extend({},e,{root:h}));vu||!i)&&(o=u,i=l)}return{edge:i,dist:o}};v.size()>0;){var b=v.pop(),x=p(b),w=b.id();if(c[w]=x,x===Math.Infinite)break;for(var E=b.neighborhood().intersect(h),g=0;g0)for(r.unshift(t);u[i.id()];){var a=u[i.id()];r.unshift(a.edge),r.unshift(a.node),i=a.node}return o.collection(r)}}}};t.exports=a},{"../../heap":81,"../../is":83}],10:[function(e,t,r){"use strict";var n=e("../../is"),i={floydWarshall:function(e){e=e||{};var t=this.cy();if(null!=e.weight&&n.fn(e.weight))var r=e.weight;else var r=function(e){return 1};if(null!=e.directed)var i=e.directed;else var i=!1;for(var a=this.edges().stdFilter(function(e){return!e.isLoop()}),o=this.nodes(),s=o.length,l={},u=0;s>u;u++)l[o[u].id()]=u;for(var c=[],u=0;s>u;u++){for(var d=new Array(s),h=0;s>h;h++)u==h?d[h]=0:d[h]=1/0;c.push(d)}var p=[],f=[],v=function(e){for(var t=0;s>t;t++){for(var r=new Array(s),n=0;s>n;n++)r[n]=void 0;e.push(r)}};v(p),v(f);for(var u=0;um&&(c[g][y]=m,p[g][y]=y,f[g][y]=a[u])}if(!i)for(var u=0;um&&(c[g][y]=m,p[g][y]=y,f[g][y]=a[u])}for(var b=0;s>b;b++)for(var u=0;s>u;u++)for(var h=0;s>h;h++)c[u][b]+c[b][h]u;u++)x.push(o[u].id());var w={distance:function(e,r){if(n.string(e))var i=t.filter(e)[0].id();else var i=e.id();if(n.string(r))var a=t.filter(r)[0].id();else var a=r.id();return c[l[i]][l[a]]},path:function(e,r){var i=function(e,r,n,i,a){if(e===r)return t.getElementById(i[e]);if(void 0!==n[e][r]){for(var o=[t.getElementById(i[e])],s=e;e!==r;){s=e,e=n[e][r];var l=a[s][e];o.push(l),o.push(t.getElementById(i[e]))}return o}};if(n.string(e))var a=t.filter(e)[0].id();else var a=e.id();if(n.string(r))var o=t.filter(r)[0].id();else var o=r.id();var s=i(l[a],l[o],p,x,f);return t.collection(s)}};return w}};t.exports=i},{"../../is":83}],11:[function(e,t,r){"use strict";var n=e("../../util"),i={};[e("./bfs-dfs"),e("./dijkstra"),e("./kruskal"),e("./a-star"),e("./floyd-warshall"),e("./bellman-ford"),e("./kerger-stein"),e("./page-rank"),e("./degree-centrality"),e("./closeness-centrality"),e("./betweenness-centrality")].forEach(function(e){n.extend(i,e)}),t.exports=i},{"../../util":100,"./a-star":3,"./bellman-ford":4,"./betweenness-centrality":5,"./bfs-dfs":6,"./closeness-centrality":7,"./degree-centrality":8,"./dijkstra":9,"./floyd-warshall":10,"./kerger-stein":12,"./kruskal":13,"./page-rank":14}],12:[function(e,t,r){"use strict";var n=e("../../util"),i={kargerStein:function(e){var t=this;e=e||{};var r=function(e,t,r){for(var n=r[e],i=n[1],a=n[2],o=t[i],s=t[a],l=r.filter(function(e){return t[e[1]]===o&&t[e[2]]===s?!1:t[e[1]]!==s||t[e[2]]!==o}),u=0;u=n)return t;var o=Math.floor(Math.random()*t.length),s=r(o,e,t);return i(e,s,n-1,a)},a=this._private.cy,o=this.edges().stdFilter(function(e){return!e.isLoop()}),s=this.nodes(),l=s.length,u=o.length,c=Math.ceil(Math.pow(Math.log(l)/Math.LN2,2)),d=Math.floor(l/Math.sqrt(2));if(2>l)return void n.error("At least 2 nodes are required for Karger-Stein algorithm");for(var h={},p=0;l>p;p++)h[s[p].id()]=p;for(var f=[],p=0;u>p;p++){var v=o[p];f.push([p,h[v.source().id()],h[v.target().id()]])}for(var g,y=1/0,m=[],p=0;l>p;p++)m.push(p);for(var b=0;c>=b;b++){var x=m.slice(0),w=i(x,f,l,d),E=x.slice(0),_=i(x,w,d,2),P=i(E,w,d,2);_.length<=P.length&&_.lengthn;n++)r+=e[n];for(var n=0;t>n;n++)e[n]=e[n]/r};if(null!=e&&null!=e.dampingFactor)var r=e.dampingFactor;else var r=.8;if(null!=e&&null!=e.precision)var i=e.precision;else var i=1e-6;if(null!=e&&null!=e.iterations)var a=e.iterations;else var a=200;if(null!=e&&null!=e.weight&&n.fn(e.weight))var o=e.weight;else var o=function(e){return 1};for(var s=this._private.cy,l=this.edges().stdFilter(function(e){return!e.isLoop()}),u=this.nodes(),c=u.length,d=l.length,h={},p=0;c>p;p++)h[u[p].id()]=p;for(var f=[],v=[],g=(1-r)/c,p=0;c>p;p++){for(var y=[],m=0;c>m;m++)y.push(0);f.push(y),v.push(0)}for(var p=0;d>p;p++){var b=l[p],x=h[b.source().id()],w=h[b.target().id()],E=o.apply(b,[b]);f[w][x]+=E,v[x]+=E}for(var _=1/c+g,m=0;c>m;m++)if(0===v[m])for(var p=0;c>p;p++)f[p][m]=_;else for(var p=0;c>p;p++)f[p][m]=f[p][m]/v[m]+g;for(var P,S=[],k=[],p=0;c>p;p++)S.push(1),k.push(0);for(var T=0;a>T;T++){for(var D=k.slice(0),p=0;c>p;p++)for(var m=0;c>m;m++)D[p]+=f[p][m]*S[m];t(D),P=S,S=D;for(var C=0,p=0;c>p;p++)C+=Math.pow(P[p]-S[p],2);if(i>C)break}var M={rank:function(e){if(n.string(e))var t=s.filter(e)[0].id();else var t=e.id();return S[h[t]]}};return M}};t.exports=i},{"../../is":83}],15:[function(e,t,r){"use strict";var n=e("../define"),i={animate:n.animate(),animation:n.animation(),animated:n.animated(),clearQueue:n.clearQueue(),delay:n.delay(),delayAnimation:n.delayAnimation(),stop:n.stop()};t.exports=i},{"../define":44}],16:[function(e,t,r){"use strict";var n=e("../util"),i={classes:function(e){e=(e||"").match(/\S+/g)||[];for(var t=this,r=[],i={},a=0;a0&&this.spawn(r).updateStyle().trigger("class"),t},addClass:function(e){return this.toggleClass(e,!0)},hasClass:function(e){var t=this[0];return!(null==t||!t._private.classes[e])},toggleClass:function(e,t){for(var r=e.match(/\S+/g)||[],n=this,i=[],a=0,o=n.length;o>a;a++)for(var s=n[a],l=!1,u=0;u0&&this.spawn(i).updateStyle().trigger("class"),n},removeClass:function(e){return this.toggleClass(e,!1)},flashClass:function(e,t){var r=this;if(null==t)t=250;else if(0===t)return r;return r.addClass(e),setTimeout(function(){r.removeClass(e)},t),r}};t.exports=i},{"../util":100}],17:[function(e,t,r){"use strict";var n={allAre:function(e){return this.filter(e).length===this.length},is:function(e){return this.filter(e).length>0},some:function(e,t){for(var r=0;r0},allAreNeighbors:function(e){return e=this.cy().collection(e),this.neighborhood().intersect(e).length===e.length}};n.allAreNeighbours=n.allAreNeighbors,t.exports=n},{}],18:[function(e,t,r){"use strict";var n={parent:function(e){for(var t=[],r=this._private.cy,n=0;n0&&t.push(a)}return this.spawn(t,{unique:!0}).filter(e)},parents:function(e){for(var t=[],r=this.parent();r.nonempty();){for(var n=0;ne}),maxDegree:i("degree",function(e,t){return e>t}),minIndegree:i("indegree",function(e,t){return t>e}),maxIndegree:i("indegree",function(e,t){return e>t}),minOutdegree:i("outdegree",function(e,t){return t>e}),maxOutdegree:i("outdegree",function(e,t){return e>t})}),a.extend(o,{totalDegree:function(e){for(var t=0,r=this.nodes(),n=0;n0?this.add(s):this;t?l.trigger("position"):l.rtrigger("position")}return this},silentPositions:function(e){return this.positions(e,!0)},renderedPosition:function(e,t){var r=this[0],n=this.cy(),i=n.zoom(),a=n.pan(),s=o.plainObject(e)?e:void 0,l=void 0!==s||void 0!==t&&o.string(e);if(r&&r.isNode()){if(!l){var u=r._private.position;return s={x:u.x*i+a.x,y:u.y*i+a.y},void 0===e?s:s[e]}for(var c=0;c0,d=c;c&&(u=u[0]);var h=d?u._private.position:{x:0,y:0};return i={x:l.x-h.x,y:l.y-h.y},void 0===e?i:i[e]}for(var p=0;p0,d=c;c&&(u=u[0]);var h=d?u._private.position:{x:0,y:0};void 0!==t?r._private.position[e]=t+h[e]:void 0!==i&&(r._private.position={x:i.x+h.x,y:i.y+h.y})}this.rtrigger("position")}else if(!a)return;return this},renderedBoundingBox:function(e){var t=this.boundingBox(e),r=this.cy(),n=r.zoom(),i=r.pan(),a=t.x1*n+i.x,o=t.x2*n+i.x,s=t.y1*n+i.y,l=t.y2*n+i.y;return{x1:a,x2:o,y1:s,y2:l,w:o-a,h:l-s}},updateCompoundBounds:function(){function e(e){if(e.isParent()){var t=e._private,n=e.children(),i="include"===e.pstyle("compound-sizing-wrt-labels").value,a=n.boundingBox({includeLabels:i,includeShadows:!1,includeOverlays:!1,useCache:!1}),o=t.position;t.autoWidth=a.w,o.x=(a.x1+a.x2)/2,t.autoHeight=a.h,o.y=(a.y1+a.y2)/2,r.push(e)}}var t=this.cy();if(!t.styleEnabled()||!t.hasCompoundNodes())return t.collection();for(var r=[],n=this;n.nonempty();){for(var i=0;ie.x2?n:e.x2,e.y1=re.y2?i:e.y2)},d=function(e,t){return c(e,t.x1,t.y1,t.x2,t.y2)},h=function(e,t,r){return s.getPrefixedProperty(e,t,r)},p=function(e,t,r,n){var i,a,o=t._private,s=o.rstyle,l=s.arrowWidth/2,u=t.pstyle(r+"-arrow-shape").value;"none"!==u&&("source"===r?(i=s.srcX,a=s.srcY):"target"===r?(i=s.tgtX,a=s.tgtY):(i=s.midX,a=s.midY),c(e,i-l,a-l,i+l,a+l))},f=function(e,t,r,n){var i;i=r?r+"-":"";var a=t._private,o=a.rstyle,s=t.pstyle(i+"label").strValue;if(s){var l,u,d,p,f=t.pstyle("text-halign"),v=t.pstyle("text-valign"),g=h(o,"labelWidth",r),y=h(o,"labelHeight",r),m=h(o,"labelX",r),b=h(o,"labelY",r),x=t.pstyle(i+"text-margin-x").pfValue,w=t.pstyle(i+"text-margin-y").pfValue,E=t.isEdge(),_=t.pstyle(i+"text-rotation"),P=t.pstyle("text-shadow-blur").pfValue/2,S=t.pstyle("text-shadow-offset-x").pfValue,k=t.pstyle("text-shadow-offset-y").pfValue,T=t.pstyle("text-shadow-opacity").value,D=t.pstyle("text-outline-width").pfValue,C=t.pstyle("text-border-width").pfValue,M=C/2,N=y,B=g,z=B/2,I=N/2;if(E)l=m-z,u=m+z,d=b-I,p=b+I;else{switch(f.value){case"left":l=m-B,u=m;break;case"center":l=m-z,u=m+z;break;case"right":l=m,u=m+B}switch(v.value){case"top":d=b-N,p=b;break;case"center":d=b-I,p=b+I;break;case"bottom":d=b,p=b+N}}var L=E&&"autorotate"===_.strValue,O=null!=_.pfValue&&0!==_.pfValue;if(L||O){var A=L?h(a.rstyle,"labelAngle",r):_.pfValue,R=Math.cos(A),q=Math.sin(A),V=function(e,t){return e-=m,t-=b,{x:e*R-t*q+m,y:e*q+t*R+b}},F=V(l,d),j=V(l,p),X=V(u,d),Y=V(u,p);l=Math.min(F.x,j.x,X.x,Y.x),u=Math.max(F.x,j.x,X.x,Y.x),d=Math.min(F.y,j.y,X.y,Y.y),p=Math.max(F.y,j.y,X.y,Y.y)}l+=x-Math.max(D,M),u+=x+Math.max(D,M),d+=w-Math.max(D,M),p+=w+Math.max(D,M),c(e,l,d,u,p),n.includeShadows&&T>0&&(l+=-P+S,u+=+P+S,d+=-P+k,p+=+P+k,c(e,l,d,u,p))}return e},v=function(e,t){var r,n,i,a,o,s,d=e._private.cy,h=d._private,v=h.styleEnabled,g={x1:1/0,y1:1/0,x2:-(1/0),y2:-(1/0)},y=e._private,m=v?e.pstyle("display").value:"element",b=e.isNode(),x=e.isEdge(),w="none"!==m;if(w){var E=0,_=0;v&&t.includeOverlays&&(E=e.pstyle("overlay-opacity").value,0!==E&&(_=e.pstyle("overlay-padding").value));var P=0,S=0;if(v&&(P=e.pstyle("width").pfValue,S=P/2),b&&t.includeNodes){var k=y.position;o=k.x,s=k.y;var P=e.outerWidth(),T=P/2,D=e.outerHeight(),C=D/2;r=o-T-_,n=o+T+_,i=s-C-_,a=s+C+_,c(g,r,i,n,a)}else if(x&&t.includeEdges){var M=y.rstyle||{};if(v&&(r=Math.min(M.srcX,M.midX,M.tgtX),n=Math.max(M.srcX,M.midX,M.tgtX),i=Math.min(M.srcY,M.midY,M.tgtY),a=Math.max(M.srcY,M.midY,M.tgtY),r-=S,n+=S,i-=S,a+=S,c(g,r,i,n,a)),v&&"haystack"===e.pstyle("curve-style").strValue){var N=M.haystackPts;if(r=N[0].x,i=N[0].y,n=N[1].x,a=N[1].y,r>n){var B=r;r=n,n=B}if(i>a){var B=i;i=a,a=B}c(g,r-S,i-S,n+S,a+S)}else{for(var z=M.bezierPts||M.linePts||[],I=0;In){var B=r;r=n,n=B}if(i>a){var B=i;i=a,a=B}r-=S,n+=S,i-=S,a+=S,c(g,r,i,n,a)}}}if(v){if(r=g.x1,n=g.x2,i=g.y1,a=g.y2,t.includeShadows&&e.pstyle("shadow-opacity").value>0){var j=e.pstyle("shadow-blur").pfValue/2,X=e.pstyle("shadow-offset-x").pfValue,Y=e.pstyle("shadow-offset-y").pfValue;c(g,r-j+X,i-j+Y,n+j+X,a+j+Y)}c(g,r-_,i-_,n+_,a+_)}v&&t.includeEdges&&x&&(p(g,e,"mid-source",t),p(g,e,"mid-target",t),p(g,e,"source",t),p(g,e,"target",t)),v&&t.includeLabels&&(f(g,e,null,t),x&&(f(g,e,"source",t),f(g,e,"target",t)))}return g.x1=u(g.x1),g.y1=u(g.y1),g.x2=u(g.x2),g.y2=u(g.y2),g.w=u(g.x2-g.x1),g.h=u(g.y2-g.y1),g.w>0&&g.h>0&&w&&l.expandBoundingBox(g,1),g},g=function(e){return e?"t":"f"},y=function(e){var t="";return t+=g(e.incudeNodes),t+=g(e.includeEdges),t+=g(e.includeLabels),t+=g(e.includeShadows),t+=g(e.includeOverlays)},m=function(e,t){var r,n=e._private,i=e.cy().headless(),a=t===b?x:y(t);return t.useCache&&!i&&n.bbCache&&n.bbCache[a]?r=n.bbCache[a]:(r=v(e,t),i||(n.bbCache=n.bbCache||{},n.bbCache[a]=r)),r},b={includeNodes:!0,includeEdges:!0,includeLabels:!0,includeShadows:!0,includeOverlays:!0,useCache:!0},x=y(b);i.recalculateRenderedStyle=function(e){var t=this.cy(),r=t.renderer(),n=t.styleEnabled();return r&&n&&r.recalculateRenderedStyle(this,e),this},i.boundingBox=function(e){if(1===this.length&&this[0]._private.bbCache&&(void 0===e||void 0===e.useCache||e.useCache===!0))return void 0===e&&(e=b),m(this[0],e);var t={x1:1/0,y1:1/0,x2:-(1/0),y2:-(1/0)};e=e||s.staticEmptyObject();var r={includeNodes:s["default"](e.includeNodes,b.includeNodes),includeEdges:s["default"](e.includeEdges,b.includeEdges),includeLabels:s["default"](e.includeLabels,b.includeLabels),includeShadows:s["default"](e.includeShadows,b.includeShadows),includeOverlays:s["default"](e.includeOverlays,b.includeOverlays),useCache:s["default"](e.useCache,b.useCache) +},n=this,i=n.cy(),a=i.styleEnabled();a&&this.recalculateRenderedStyle(r.useCache);for(var o=0;od;d++){var p=c[d];p&&""!==p&&(a._private.classes[p]=!0)}(t.style||t.css)&&e.style().applyBypass(this,t.style||t.css),(void 0===r||r)&&this.restore()};t.exports=a},{"../is":83,"../util":100}],23:[function(e,t,r){"use strict";var n=e("../define"),i={on:n.on(),one:n.on({unbindSelfOnTrigger:!0}),once:n.on({unbindAllBindersOnTrigger:!0}),off:n.off(),trigger:n.trigger(),rtrigger:function(e,t){return 0!==this.length?(this.cy().notify({type:e,eles:this}),this.trigger(e,t),this):void 0}};n.eventAliasesOn(i),t.exports=i},{"../define":44}],24:[function(e,t,r){"use strict";var n=e("../is"),i=e("../selector"),a={nodes:function(e){return this.filter(function(e,t){return t.isNode()}).filter(e)},edges:function(e){return this.filter(function(e,t){return t.isEdge()}).filter(e)},filter:function(e){if(void 0===e)return this;if(n.string(e)||n.elementOrCollection(e))return i(e).filter(this);if(n.fn(e)){for(var t=[],r=0;r1&&!i){var a=this.length-1,o=this[a],s=o._private.data.id;this[a]=void 0,this[n]=o,t.indexes[s]=n}return this.length--,this},unmerge:function(e){var t=this._private.cy;if(!e)return this;if(e&&n.string(e)){var r=e;e=t.mutableElements().filter(r)}for(var i=0;in&&(n=s,r=o)}return{value:n,ele:r}},min:function(e,t){for(var r,n=1/0,i=this,a=0;as&&(n=s,r=o)}return{value:n,ele:r}}},o=a;o.u=o["|"]=o["+"]=o.union=o.or=o.add,o["\\"]=o["!"]=o["-"]=o.difference=o.relativeComplement=o.subtract=o.not,o.n=o["&"]=o["."]=o.and=o.intersection=o.intersect,o["^"]=o["(+)"]=o["(-)"]=o.symmetricDifference=o.symdiff=o.xor,o.fnFilter=o.filterFn=o.stdFilter,o.complement=o.abscomp=o.absoluteComplement,t.exports=a},{"../is":83,"../selector":87}],25:[function(e,t,r){"use strict";var n={isNode:function(){return"nodes"===this.group()},isEdge:function(){return"edges"===this.group()},isLoop:function(){return this.isEdge()&&this.source().id()===this.target().id()},isSimple:function(){return this.isEdge()&&this.source().id()!==this.target().id()},group:function(){var e=this[0];return e?e._private.group:void 0}};t.exports=n},{}],26:[function(e,t,r){"use strict";var n=e("../util"),i=e("../is"),a=e("./element"),o={generate:function(e,t,r){for(var i=null!=r?r:n.uuid();e.hasElementWithId(i);)i=n.uuid();return i}},s=function(e,t,r){if(void 0===e||!i.core(e))return void n.error("A collection must have a reference to the core");var s={},l={},u=!1;if(t){if(t.length>0&&i.plainObject(t[0])&&!i.element(t[0])){u=!0;for(var c=[],d={},h=0,p=t.length;p>h;h++){var f=t[h];null==f.data&&(f.data={});var v=f.data;if(null==v.id)v.id=o.generate(e,f);else if(e.hasElementWithId(v.id)||d[v.id])continue;var g=new a(e,f,!1);c.push(g),d[v.id]=!0}t=c}}else t=[];this.length=0;for(var h=0,p=t.length;p>h;h++){var y=t[h];if(y){var m=y._private.data.id;(!r||r.unique&&!s[m])&&(s[m]=y,l[m]=this.length,this[this.length]=y,this.length++)}}this._private={cy:e,ids:s,indexes:l},u&&this.restore()},l=a.prototype=s.prototype;l.instanceString=function(){return"collection"},l.spawn=function(e,t,r){return i.core(e)||(r=t,t=e,e=this.cy()),new s(e,t,r)},l.spawnSelf=function(){return this.spawn(this)},l.cy=function(){return this._private.cy},l.element=function(){return this[0]},l.collection=function(){return i.collection(this)?this:new s(this._private.cy,[this])},l.unique=function(){return new s(this._private.cy,this,{unique:!0})},l.hasElementWithId=function(e){return!!this._private.ids[e]},l.getElementById=function(e){var t=this._private.cy,r=this._private.ids[e];return r?r:new s(t)},l.poolIndex=function(){var e=this._private.cy,t=e._private.elements,r=this._private.data.id;return t._private.indexes[r]},l.json=function(e){var t=this.element(),r=this.cy();if(null==t&&e)return this;if(null!=t){var a=t._private;if(i.plainObject(e)){r.startBatch(),e.data&&t.data(e.data),e.position&&t.position(e.position);var o=function(r,n,i){var o=e[r];null!=o&&o!==a[r]&&(o?t[n]():t[i]())};return o("removed","remove","restore"),o("selected","select","unselect"),o("selectable","selectify","unselectify"),o("locked","lock","unlock"),o("grabbable","grabify","ungrabify"),null!=e.classes&&t.classes(e.classes),r.endBatch(),this}if(void 0===e){var s={data:n.copy(a.data),position:n.copy(a.position),group:a.group,removed:a.removed,selected:a.selected,selectable:a.selectable,locked:a.locked,grabbable:a.grabbable,classes:null};return s.classes=Object.keys(a.classes).filter(function(e){return a.classes[e]}).join(" "),s}}},l.jsons=function(){for(var e=[],t=0;td;d++){var p=t[d];p.removed()&&(p.isNode()?u.push(p):c.push(p))}l=u.concat(c);var d,f=function(){l.splice(d,1),d--};for(d=0;dP;P++){var S=w[P],k=g[S];i.number(k)&&(k=g[S]=""+g[S]),null==k||""===k?(n.error("Can not create edge `"+y+"` with unspecified "+S),_=!0):r.hasElementWithId(k)||(n.error("Can not create edge `"+y+"` with nonexistant "+S+" `"+k+"`"),_=!0)}if(_){f();continue}var T=r.getElementById(g.source),D=r.getElementById(g.target);T._private.edges.push(x),D._private.edges.push(x),x._private.source=T,x._private.target=D}v.ids={},v.ids[y]=p,v.indexes={},v.indexes[y]=p,v.removed=!1,r.addToPool(p)}for(var d=0;d0){for(var I=new s(r,l),d=0;df;f++){var g=u[f];i(g)}var y=[];y.ids={},p.removeFromPool(d);for(var f=0;f0&&(e&&this.cy().notify({type:"remove",eles:E}),E.trigger("remove"));for(var f=0;fe&&(e=n+e),0>t&&(t=n+t);for(var i=e;i>=0&&t>i&&n>i;i++)r.push(this[i]);return this.spawn(r)},size:function(){return this.length},eq:function(e){return this[e]||this.spawn()},first:function(){return this[0]||this.spawn()},last:function(){return this[this.length-1]||this.spawn()},empty:function(){return 0===this.length},nonempty:function(){return!this.empty()},sort:function(e){if(!n.fn(e))return this;var t=this.toArray().sort(e);return this.spawn(t)},sortByZIndex:function(){return this.sort(i)},zDepth:function(){var e=this[0];if(e){var t=e._private,r=t.group;if("nodes"===r){var n=t.data.parent?e.parents().size():0;return e.isParent()?n:Number.MAX_VALUE}var i=t.source,a=t.target,o=i.zDepth(),s=a.zDepth();return Math.max(o,s,0)}}};t.exports=a},{"../is":83,"./zsort":32}],28:[function(e,t,r){"use strict";var n=e("../is"),i=e("../util"),a=e("../promise"),o={layoutPositions:function(e,t,r){var i=this.nodes(),o=this.cy();if(e.trigger({type:"layoutstart",layout:e}),e.animations=[],t.animate){for(var s=0;s0?this.add(n):this;return e?i.rtrigger("style"):i.trigger("style"),this},parsedStyle:function(e){var t=this[0];if(t.cy().styleEnabled())return t?t._private.style[e]||t.cy().style().getDefaultProperty(e):void 0},renderedStyle:function(e){var t=this.cy();if(!t.styleEnabled())return this;var r=this[0];if(r){var n=r.cy().style().getRenderedStyle(r);return void 0===e?n:n[e]}},style:function(e,t){var r=this.cy();if(!r.styleEnabled())return this;var i=!1,a=r.style();if(n.plainObject(e)){var o=e;a.applyBypass(this,o,i);var s=this.updateCompoundBounds(),l=s.length>0?this.add(s):this;l.rtrigger("style")}else if(n.string(e)){if(void 0===t){var u=this[0];return u?a.getStylePropertyValue(u,e):void 0}a.applyBypass(this,e,t,i);var s=this.updateCompoundBounds(),l=s.length>0?this.add(s):this;l.rtrigger("style")}else if(void 0===e){var u=this[0];return u?a.getRawStyle(u):void 0}return this},removeStyle:function(e){var t=this.cy();if(!t.styleEnabled())return this;var r=!1,n=t.style(),i=this;if(void 0===e)for(var a=0;a0?this.add(s):this;return l.rtrigger("style"),this},show:function(){return this.css("display","element"),this},hide:function(){return this.css("display","none"),this},visible:function(){var e=this.cy();if(!e.styleEnabled())return!0;var t=this[0],r=e.hasCompoundNodes();if(t){if("visible"!==t.pstyle("visibility").value||"element"!==t.pstyle("display").value||0===t.pstyle("width").pfValue)return!1;if("nodes"===t._private.group){if(0===t.pstyle("height").pfValue)return!1;if(!r)return!0;var n=t._private.data.parent?t.parents():null;if(n)for(var i=0;i0&&t.push(c[0]),t.push(s[0])}return this.spawn(t,{unique:!0}).filter(e)},"neighborhood"),closedNeighborhood:function(e){return this.neighborhood().add(this).filter(e)},openNeighborhood:function(e){return this.neighborhood(e)}}),l.neighbourhood=l.neighborhood,l.closedNeighbourhood=l.closedNeighborhood,l.openNeighbourhood=l.openNeighborhood,o.extend(l,{source:u(function(e){var t,r=this[0];return r&&(t=r._private.source||r.cy().collection()),t&&e?t.filter(e):t},"source"),target:u(function(e){var t,r=this[0];return r&&(t=r._private.target||r.cy().collection()),t&&e?t.filter(e):t},"target"),sources:n({attr:"source"}),targets:n({attr:"target"})}),o.extend(l,{edgesWith:u(i(),"edgesWith",!0),edgesTo:u(i({thisIsSrc:!0}),"edgesTo",!0)}),o.extend(l,{connectedEdges:u(function(e){for(var t=[],r=this,n=0;n0);return i.map(function(e){var t=e.connectedEdges().stdFilter(function(t){return e.anySame(t.source())&&e.anySame(t.target())});return e.union(t)})}}),t.exports=l},{"../is":83,"../util":100}],32:[function(e,t,r){"use strict";var n=function(e,t){var r=e.cy(),n=e.pstyle("z-index").value-t.pstyle("z-index").value,i=0,a=0,o=r.hasCompoundNodes(),s=e.isNode(),l=!s,u=t.isNode(),c=!u;o&&(i=e.zDepth(),a=t.zDepth());var d=i-a,h=0===d;return h?s&&c?1:l&&u?-1:0===n?e.poolIndex()-t.poolIndex():n:d};t.exports=n},{}],33:[function(e,t,r){"use strict";var n=e("../is"),i=e("../util"),a=e("../collection"),o=e("../collection/element"),s={add:function(e){var t,r=this;if(n.elementOrCollection(e)){var s=e;if(s._private.cy===r)t=s.restore();else{for(var l=[],u=0;uu;u++){var f=h[u],v=d[f];if(n.array(v))for(var g=0,y=v.length;y>g;g++){var m=i.extend({group:f},v[g]);l.push(m)}}t=new a(r,l)}else{var m=e;t=new o(r,m).collection()}return t},remove:function(e){if(n.elementOrCollection(e));else if(n.string(e)){var t=e;e=this.$(t)}return e.remove()},load:function(e,t,r){var a=this;a.notifications(!1);var o=a.mutableElements();o.length>0&&o.remove(),null!=e&&(n.plainObject(e)||n.array(e))&&a.add(e),a.one("layoutready",function(e){a.notifications(!0),a.trigger(e),a.notify({type:"load",eles:a.mutableElements()}),a.one("load",t),a.trigger("load")}).one("layoutstop",function(){a.one("done",r),a.trigger("done")});var s=i.extend({},a._private.options.layout);return s.eles=a.elements(),a.layout(s),this}};t.exports=s},{"../collection":26,"../collection/element":22,"../is":83,"../util":100}],34:[function(e,t,r){"use strict";var n=e("../define"),i=e("../util"),a=e("../is"),o={animate:n.animate(),animation:n.animation(),animated:n.animated(),clearQueue:n.clearQueue(),delay:n.delay(),delayAnimation:n.delayAnimation(),stop:n.stop(),addToAnimationPool:function(e){var t=this;t.styleEnabled()&&t._private.aniEles.merge(e)},stopAnimationLoop:function(){this._private.animationsRunning=!1},startAnimationLoop:function(){function e(){c._private.animationsRunning&&i.requestAnimationFrame(function(r){t(r),e()})}function t(e){function t(t,i){var s=t._private,l=s.animation.current,u=s.animation.queue,c=!1;if(0===l.length){var d=u.shift();d&&l.push(d)}for(var h=function(e){for(var t=e.length-1;t>=0;t--){var r=e[t];r()}e.splice(0,e.length)},p=l.length-1;p>=0;p--){var f=l[p],v=f._private;v.stopped?(l.splice(p,1),v.hooked=!1,v.playing=!1,v.started=!1,h(v.frames)):(v.playing||v.applying)&&(v.playing&&v.applying&&(v.applying=!1),v.started||r(t,f,e),n(t,f,e,i),a.fn(v.step)&&v.step.call(t,e),v.applying&&(v.applying=!1),h(v.frames),f.completed()&&(l.splice(p,1),v.hooked=!1,v.playing=!1,v.started=!1,h(v.completes)),c=!0)}return i||0!==l.length||0!==u.length||o.push(t),c}for(var i=c._private.aniEles,o=[],s=!1,l=0;l0){var p=i.updateCompoundBounds().spawnSelf().merge(i);c.notify({type:"draw",eles:p})}else c.notify({type:"draw"});i.unmerge(o),c.trigger("step")}function r(e,t,r){var n=a.core(e),i=!n,o=e,s=c._private.style,l=t._private;if(i){var u=o._private.position;l.startPosition=l.startPosition||{x:u.x,y:u.y},l.startStyle=l.startStyle||s.getAnimationStartStyle(o,l.style)}if(n){var d=c._private.pan;l.startPan=l.startPan||{x:d.x,y:d.y},l.startZoom=null!=l.startZoom?l.startZoom:c._private.zoom}l.started=!0,l.startTime=r-l.progress*l.duration}function n(e,t,r,n){var i=c._private.style,s=!n,l=e._private,d=t._private,h=d.easing,f=d.startTime;if(!d.easingImpl)if(null==h)d.easingImpl=p.linear;else{var v;if(a.string(h)){var g=i.parse("transition-timing-function",h);v=g.value}else v=h;var y,m;a.string(v)?(y=v,m=[]):(y=v[1],m=v.slice(2).map(function(e){return+e})),m.length>0?("spring"===y&&m.push(d.duration),d.easingImpl=p[y].apply(null,m)):d.easingImpl=p[y]}var b,x=d.easingImpl;if(b=0===d.duration?1:(r-f)/d.duration,d.applying&&(b=d.progress),0>b?b=0:b>1&&(b=1),null==d.delay){var w=d.startPosition,E=d.position,_=l.position;E&&s&&(o(w.x,E.x)&&(_.x=u(w.x,E.x,b,x)),o(w.y,E.y)&&(_.y=u(w.y,E.y,b,x)),e.trigger("position"));var P=d.startPan,S=d.pan,k=l.pan,T=null!=S&&n;T&&(o(P.x,S.x)&&(k.x=u(P.x,S.x,b,x)),o(P.y,S.y)&&(k.y=u(P.y,S.y,b,x)),e.trigger("pan"));var D=d.startZoom,C=d.zoom,M=null!=C&&n;M&&(o(D,C)&&(l.zoom=u(D,C,b,x)),e.trigger("zoom")),(T||M)&&e.trigger("viewport");var N=d.style;if(N&&N.length>0&&s){for(var B=0;Br?r=0:r>1&&(r=1);var i,o;if(i=null!=e.pfValue||null!=e.value?null!=e.pfValue?e.pfValue:e.value:e,o=null!=t.pfValue||null!=t.value?null!=t.pfValue?t.pfValue:t.value:t,a.number(i)&&a.number(o))return n(i,o,r);if(a.array(i)&&a.array(o)){for(var s=[],l=0;ld&&Math.abs(s.v)>d))break;return a?function(e){return u[e*(u.length-1)|0]}:c}}(),p={linear:function(e,t,r){return e+(t-e)*r},ease:l(.25,.1,.25,1),"ease-in":l(.42,0,1,1),"ease-out":l(0,0,.58,1),"ease-in-out":l(.42,0,.58,1),"ease-in-sine":l(.47,0,.745,.715),"ease-out-sine":l(.39,.575,.565,1),"ease-in-out-sine":l(.445,.05,.55,.95),"ease-in-quad":l(.55,.085,.68,.53),"ease-out-quad":l(.25,.46,.45,.94),"ease-in-out-quad":l(.455,.03,.515,.955),"ease-in-cubic":l(.55,.055,.675,.19),"ease-out-cubic":l(.215,.61,.355,1),"ease-in-out-cubic":l(.645,.045,.355,1),"ease-in-quart":l(.895,.03,.685,.22),"ease-out-quart":l(.165,.84,.44,1),"ease-in-out-quart":l(.77,0,.175,1),"ease-in-quint":l(.755,.05,.855,.06),"ease-out-quint":l(.23,1,.32,1),"ease-in-out-quint":l(.86,0,.07,1),"ease-in-expo":l(.95,.05,.795,.035),"ease-out-expo":l(.19,1,.22,1),"ease-in-out-expo":l(1,0,0,1),"ease-in-circ":l(.6,.04,.98,.335),"ease-out-circ":l(.075,.82,.165,1),"ease-in-out-circ":l(.785,.135,.15,.86),spring:function(e,t,r){if(0===r)return p.linear;var n=h(e,t,r);return function(e,t,r){return e+(t-e)*n(r)}},"cubic-bezier":function(e,t,r,n){return l(e,t,r,n)}}}}};t.exports=o},{"../define":44,"../is":83,"../util":100}],35:[function(e,t,r){"use strict";var n=e("../define"),i={on:n.on(),one:n.on({unbindSelfOnTrigger:!0}),once:n.on({unbindAllBindersOnTrigger:!0}),off:n.off(),trigger:n.trigger()};n.eventAliasesOn(i),t.exports=i},{"../define":44}],36:[function(e,t,r){"use strict";var n={png:function(e){var t=this._private.renderer;return e=e||{},t.png(e)},jpg:function(e){var t=this._private.renderer;return e=e||{},e.bg=e.bg||"#fff",t.jpg(e)}};n.jpeg=n.jpg,t.exports=n},{}],37:[function(e,t,r){"use strict";var n=e("../window"),i=e("../util"),a=e("../collection"),o=e("../is"),s=e("../promise"),l=e("../define"),u=function(e){var t=this;e=i.extend({},e);var r=e.container;r&&!o.htmlElement(r)&&o.htmlElement(r[0])&&(r=r[0]);var l=r?r._cyreg:null;l=l||{},l&&l.cy&&(l.cy.destroy(),l={});var u=l.readies=l.readies||[];r&&(r._cyreg=l),l.cy=t;var c=void 0!==n&&void 0!==r&&!e.headless,d=e;d.layout=i.extend({name:c?"grid":"null"},d.layout),d.renderer=i.extend({name:c?"canvas":"null"},d.renderer);var h=function(e,t,r){return void 0!==t?t:void 0!==r?r:e},p=this._private={container:r,ready:!1,initrender:!1,options:d,elements:new a(this),listeners:[],aniEles:new a(this),scratch:{},layout:null,renderer:null,notificationsEnabled:!0,minZoom:1e-50,maxZoom:1e50,zoomingEnabled:h(!0,d.zoomingEnabled),userZoomingEnabled:h(!0,d.userZoomingEnabled),panningEnabled:h(!0,d.panningEnabled),userPanningEnabled:h(!0,d.userPanningEnabled),boxSelectionEnabled:h(!0,d.boxSelectionEnabled),autolock:h(!1,d.autolock,d.autolockNodes),autoungrabify:h(!1,d.autoungrabify,d.autoungrabifyNodes),autounselectify:h(!1,d.autounselectify),styleEnabled:void 0===d.styleEnabled?c:d.styleEnabled,zoom:o.number(d.zoom)?d.zoom:1,pan:{x:o.plainObject(d.pan)&&o.number(d.pan.x)?d.pan.x:0,y:o.plainObject(d.pan)&&o.number(d.pan.y)?d.pan.y:0},animation:{current:[],queue:[]},hasCompoundNodes:!1},f=d.selectionType;void 0===f||"additive"!==f&&"single"!==f?p.selectionType="single":p.selectionType=f,o.number(d.minZoom)&&o.number(d.maxZoom)&&d.minZoom0?d.wheelSensitivity:1,motionBlur:void 0===d.motionBlur?!1:d.motionBlur,motionBlurOpacity:void 0===d.motionBlurOpacity?.05:d.motionBlurOpacity,pixelRatio:o.number(d.pixelRatio)&&d.pixelRatio>0?d.pixelRatio:void 0,desktopTapThreshold:void 0===d.desktopTapThreshold?4:d.desktopTapThreshold,touchTapThreshold:void 0===d.touchTapThreshold?8:d.touchTapThreshold},d.renderer)),v([d.style,d.elements],function(e){var r=e[0],n=e[1];p.styleEnabled&&t.setStyle(r),d.initrender&&(t.on("initrender",d.initrender),t.on("initrender",function(){p.initrender=!0})),t.load(n,function(){t.startAnimationLoop(),p.ready=!0,o.fn(d.ready)&&t.on("ready",d.ready);for(var e=0;e0;)t.removeChild(t.childNodes[0]);e._private.renderer=null},onRender:function(e){return this.on("render",e)},offRender:function(e){return this.off("render",e)}};i.invalidateDimensions=i.resize,t.exports=i},{"../util":100}],41:[function(e,t,r){"use strict";var n=e("../is"),i=e("../collection"),a={collection:function(e,t){return n.string(e)?this.$(e):n.elementOrCollection(e)?e.collection():n.array(e)?new i(this,e,t):new i(this)},nodes:function(e){var t=this.$(function(){return this.isNode()});return e?t.filter(e):t},edges:function(e){var t=this.$(function(){return this.isEdge()});return e?t.filter(e):t},$:function(e){var t=this._private.elements;return e?t.filter(e):t.spawnSelf()},mutableElements:function(){return this._private.elements}};a.elements=a.filter=a.$,t.exports=a},{"../collection":26,"../is":83}],42:[function(e,t,r){"use strict";var n=e("../is"),i=e("../style"),a={style:function(e){if(e){var t=this.setStyle(e);t.update()}return this._private.style},setStyle:function(e){var t=this._private;return n.stylesheet(e)?t.style=e.generateStyle(this):n.array(e)?t.style=i.fromJson(this,e):n.string(e)?t.style=i.fromString(this,e):t.style=i(this),t.style}};t.exports=a},{"../is":83,"../style":92}],43:[function(e,t,r){"use strict";var n=e("../is"),i={autolock:function(e){return void 0===e?this._private.autolock:(this._private.autolock=!!e,this)},autoungrabify:function(e){return void 0===e?this._private.autoungrabify:(this._private.autoungrabify=!!e,this)},autounselectify:function(e){return void 0===e?this._private.autounselectify:(this._private.autounselectify=!!e,this)},panningEnabled:function(e){return void 0===e?this._private.panningEnabled:(this._private.panningEnabled=!!e,this)},userPanningEnabled:function(e){return void 0===e?this._private.userPanningEnabled:(this._private.userPanningEnabled=!!e,this)},zoomingEnabled:function(e){return void 0===e?this._private.zoomingEnabled:(this._private.zoomingEnabled=!!e,this)},userZoomingEnabled:function(e){return void 0===e?this._private.userZoomingEnabled:(this._private.userZoomingEnabled=!!e,this)},boxSelectionEnabled:function(e){return void 0===e?this._private.boxSelectionEnabled:(this._private.boxSelectionEnabled=!!e,this)},pan:function(){var e,t,r,i,a,o=arguments,s=this._private.pan;switch(o.length){case 0:return s;case 1:if(n.string(o[0]))return e=o[0],s[e];if(n.plainObject(o[0])){if(!this._private.panningEnabled)return this;r=o[0],i=r.x,a=r.y,n.number(i)&&(s.x=i),n.number(a)&&(s.y=a),this.trigger("pan viewport")}break;case 2:if(!this._private.panningEnabled)return this;e=o[0],t=o[1],"x"!==e&&"y"!==e||!n.number(t)||(s[e]=t),this.trigger("pan viewport")}return this.notify({type:"viewport"}),this},panBy:function(e){var t,r,i,a,o,s=arguments,l=this._private.pan;if(!this._private.panningEnabled)return this;switch(s.length){case 1:n.plainObject(s[0])&&(i=s[0],a=i.x,o=i.y,n.number(a)&&(l.x+=a),n.number(o)&&(l.y+=o),this.trigger("pan viewport"));break;case 2:t=s[0],r=s[1],"x"!==t&&"y"!==t||!n.number(r)||(l[t]+=r),this.trigger("pan viewport")}return this.notify({type:"viewport"}),this},fit:function(e,t){var r=this.getFitViewport(e,t);if(r){var n=this._private;n.zoom=r.zoom,n.pan=r.pan,this.trigger("pan zoom viewport"),this.notify({type:"viewport"})}return this},getFitViewport:function(e,t){if(n.number(e)&&void 0===t&&(t=e,e=void 0),this._private.panningEnabled&&this._private.zoomingEnabled){var r;if(n.string(e)){var i=e;e=this.$(i)}else if(n.boundingBox(e)){var a=e;r={x1:a.x1,y1:a.y1,x2:a.x2,y2:a.y2},r.w=r.x2-r.x1,r.h=r.y2-r.y1}else n.elementOrCollection(e)||(e=this.mutableElements());r=r||e.boundingBox();var o,s=this.width(),l=this.height();if(t=n.number(t)?t:0,!isNaN(s)&&!isNaN(l)&&s>0&&l>0&&!isNaN(r.w)&&!isNaN(r.h)&&r.w>0&&r.h>0){o=Math.min((s-2*t)/r.w,(l-2*t)/r.h),o=o>this._private.maxZoom?this._private.maxZoom:o,o=othis._private.maxZoom?this._private.maxZoom:r,r=rt.maxZoom||!t.zoomingEnabled?o=!0:(t.zoom=l,a.push("zoom"))}if(i&&(!o||!e.cancelOnFailedZoom)&&t.panningEnabled){var u=e.pan;n.number(u.x)&&(t.pan.x=u.x,s=!1),n.number(u.y)&&(t.pan.y=u.y,s=!1),s||a.push("pan")}return a.length>0&&(a.push("viewport"),this.trigger(a.join(" ")),this.notify({type:"viewport"})),this},center:function(e){var t=this.getCenterPan(e);return t&&(this._private.pan=t,this.trigger("pan viewport"),this.notify({type:"viewport"})),this},getCenterPan:function(e,t){if(this._private.panningEnabled){if(n.string(e)){var r=e;e=this.mutableElements().filter(r)}else n.elementOrCollection(e)||(e=this.mutableElements());var i=e.boundingBox(),a=this.width(),o=this.height();t=void 0===t?this._private.zoom:t;var s={x:(a-t*(i.x1+i.x2))/2,y:(o-t*(i.y1+i.y2))/2};return s}},reset:function(){return this._private.panningEnabled&&this._private.zoomingEnabled?(this.viewport({pan:{x:0,y:0},zoom:1}),this):this},invalidateSize:function(){this._private.sizeCache=null},size:function(){var e=this._private,t=e.container;return e.sizeCache=e.sizeCache||(t?{width:t.clientWidth,height:t.clientHeight}:{width:1,height:1})},width:function(){return this.size().width},height:function(){return this.size().height},extent:function(){var e=this._private.pan,t=this._private.zoom,r=this.renderedExtent(),n={x1:(r.x1-e.x)/t,x2:(r.x2-e.x)/t,y1:(r.y1-e.y)/t,y2:(r.y2-e.y)/t};return n.w=n.x2-n.x1,n.h=n.y2-n.y1,n},renderedExtent:function(){var e=this.width(),t=this.height();return{x1:0,y1:0,x2:e,y2:t,w:e,h:t}}};i.centre=i.center,i.autolockNodes=i.autolock,i.autoungrabifyNodes=i.autoungrabify,t.exports=i},{"../is":83}],44:[function(e,t,r){"use strict";var n=e("./util"),i=e("./is"),a=e("./selector"),o=e("./promise"),s=e("./event"),l=e("./animation"),u={data:function(e){var t={field:"data",bindingEvent:"data",allowBinding:!1,allowSetting:!1,allowGetting:!1,settingEvent:"data",settingTriggersEvent:!1,triggerFnName:"trigger",immutableKeys:{},updateStyle:!1,onSet:function(e){},canSet:function(e){return!0}};return e=n.extend({},t,e),function(t,r){var n=e,a=this,o=void 0!==a.length,s=o?a:[a],l=o?a[0]:a;if(i.string(t)){if(n.allowGetting&&void 0===r){var u;return l&&(u=l._private[n.field][t]),u}if(n.allowSetting&&void 0!==r){var c=!n.immutableKeys[t];if(c){for(var d=0,h=s.length;h>d;d++)n.canSet(s[d])&&(s[d]._private[n.field][t]=r);n.updateStyle&&a.updateStyle(),n.onSet(a),n.settingTriggersEvent&&a[n.triggerFnName](n.settingEvent)}}}else if(n.allowSetting&&i.plainObject(t)){for(var p,f,v=t,g=Object.keys(v),d=0;du;u++){var c=s[u];if(!i.emptyString(c)){var d=!r.immutableKeys[c];if(d)for(var h=0,p=o.length;p>h;h++)o[h]._private[r.field][c]=void 0}}r.triggerEvent&&n[r.triggerFnName](r.event)}else if(void 0===t){for(var h=0,p=o.length;p>h;h++)for(var f=o[h]._private[r.field],s=Object.keys(f),u=0;u0:void 0}},clearQueue:function(e){var t={};return e=n.extend({},t,e),function(){var e=this,t=void 0!==e.length,r=t?e:[e],n=this._private.cy||this;if(!n.styleEnabled())return this;for(var i=0;i0;){var g=n.collection();i.bfs({roots:v[0],visit:function(e,t,r,n,i){g=g.add(r)},directed:!1}),v=v.not(g),f.push(g)}e=n.collection();for(var d=0;dD;){for(var C=k.shift(),M=C.neighborhood().nodes(),N=!1,d=0;dd;d++)for(var B=x[d],R=B.length,q=0;R>q;q++){var p=B[q],V=p._private.scratch.breadthfirst,F=I(p);F&&(V.intEle=F,A.push(p))}for(var d=0;dx.length-1;)x.push([]);x[X].push(p),V.depth=X,V.index=x[X].length-1}z()}var Y=0;if(r.avoidOverlap){for(var d=0;dc||0===t)&&(n+=u/d,i++)}return i=Math.max(1,i),n/=i,0===i&&(n=void 0),Z[e.id()]=n,n},Q=function(e,t){var r=G(e),n=G(t);return r-n},K=0;3>K;K++){for(var d=0;d0&&x[0].length<=3?c/2:0),h=2*Math.PI/x[i].length*a;return 0===i&&1===x[0].length&&(d=1),{x:ee.x+d*Math.cos(h),y:ee.y+d*Math.sin(h)}}return{x:ee.x+(a+1-(o+1)/2)*s,y:(i+1)*l}}var p={x:ee.x+(a+1-(o+1)/2)*s,y:(i+1)*l};return t?p:p},re={},d=x.length-1;d>=0;d--)for(var B=x[d],q=0;q1&&t.avoidOverlap){p*=1.75;var b=Math.cos(h)-Math.cos(0),x=Math.sin(h)-Math.sin(0),w=Math.sqrt(p*p/(b*b+x*x));l=Math.max(w,l)}var E=function(e,r){var n=t.startAngle+e*h*(i?1:-1),a=l*Math.cos(n),o=l*Math.sin(n),s={x:c.x+a,y:c.y+o};return s};return s.layoutPositions(this,t,E),this},t.exports=n},{"../../is":83,"../../math":85,"../../util":100}],50:[function(e,t,r){"use strict";function n(e){this.options=i.extend({},o,e)}var i=e("../../util"),a=e("../../math"),o={fit:!0,padding:30,startAngle:1.5*Math.PI,sweep:void 0,clockwise:!0,equidistant:!1,minNodeSpacing:10,boundingBox:void 0,avoidOverlap:!0,height:void 0,width:void 0,concentric:function(e){return e.degree()},levelWidth:function(e){return e.maxDegree()/4},animate:!1,animationDuration:500,animationEasing:void 0,ready:void 0,stop:void 0};n.prototype.run=function(){for(var e=this.options,t=e,r=void 0!==t.counterclockwise?!t.counterclockwise:t.clockwise,n=e.cy,i=t.eles,o=i.nodes().not(":parent"),s=a.makeBoundingBox(t.boundingBox?t.boundingBox:{x1:0,y1:0,w:n.width(),h:n.height()}),l={x:s.x1+s.w/2,y:s.y1+s.h/2},u=[],c=t.startAngle,d=0,h=0;h0){var x=Math.abs(m[0].value-b.value);x>=g&&(m=[],y.push(m))}m.push(b)}var w=d+t.minNodeSpacing;if(!t.avoidOverlap){var E=y.length>0&&y[0].length>1,_=Math.min(s.w,s.h)/2-w,P=_/(y.length+E?1:0);w=Math.min(w,P)}for(var S=0,h=0;h1&&t.avoidOverlap){var C=Math.cos(D)-Math.cos(0),M=Math.sin(D)-Math.sin(0),N=Math.sqrt(w*w/(C*C+M*M));S=Math.max(N,S)}k.r=S,S+=w}if(t.equidistant){for(var B=0,S=0,h=0;ha;a++)for(var o=e.layoutNodes[e.idToIndex[n[a]]],l=a+1;i>l;l++){var u=e.layoutNodes[e.idToIndex[n[l]]];s(o,u,e,t)}},s=function(e,t,r,n){var i=e.cmptId,a=t.cmptId;if(i===a||r.isCompound){var o=t.positionX-e.positionX,s=t.positionY-e.positionY;if(0!==o||0!==s){var c=l(e,t,o,s);if(c>0)var d=n.nodeOverlap*c,h=Math.sqrt(o*o+s*s),p=d*o/h,f=d*s/h;else var v=u(e,o,s),g=u(t,-1*o,-1*s),y=g.x-v.x,m=g.y-v.y,b=y*y+m*m,h=Math.sqrt(b),d=(e.nodeRepulsion+t.nodeRepulsion)/b,p=d*y/h,f=d*m/h;e.isLocked||(e.offsetX-=p,e.offsetY-=f),t.isLocked||(t.offsetX+=p,t.offsetY+=f)}}},l=function(e,t,r,n){if(r>0)var i=e.maxX-t.minX;else var i=t.maxX-e.minX;if(n>0)var a=e.maxY-t.minY;else var a=t.maxY-e.minY;return i>=0&&a>=0?Math.sqrt(i*i+a*a):0},u=function(e,t,r){var n=e.positionX,i=e.positionY,a=e.height||1,o=e.width||1,s=r/t,l=a/o,u={};do{if(0===t&&r>0){u.x=n,u.y=i+a/2;break}if(0===t&&0>r){u.x=n,u.y=i+a/2;break}if(t>0&&s>=-1*l&&l>=s){u.x=n+o/2,u.y=i+o*r/2/t;break}if(0>t&&s>=-1*l&&l>=s){u.x=n-o/2,u.y=i-o*r/2/t;break}if(r>0&&(-1*l>=s||s>=l)){u.x=n+a*t/2/r,u.y=i+a/2;break}if(0>r&&(-1*l>=s||s>=l)){u.x=n-a*t/2/r,u.y=i-a/2;break}}while(!1);return u},c=function(e,t){for(var r=0;rc;c++){var d=e.layoutNodes[e.idToIndex[i[c]]];if(!d.isLocked){var h=o-d.positionX,p=s-d.positionY,f=Math.sqrt(h*h+p*p);if(f>r){var v=t.gravity*h/f,g=t.gravity*p/f;d.offsetX+=v,d.offsetY+=g}}}}},h=function(e,t){var r=[],n=0,i=-1;for(r.push.apply(r,e.graphSet[0]),i+=e.graphSet[0].length;i>=n;){var a=r[n++],o=e.idToIndex[a],s=e.layoutNodes[o],l=s.children;if(0r)var i={x:r*e/n,y:r*t/n};else var i={x:e,y:t};return i},v=function(e,t){var r=e.parentId;if(null!=r){var n=t.layoutNodes[t.idToIndex[r]],i=!1;return(null==n.maxX||e.maxX+n.padRight>n.maxX)&&(n.maxX=e.maxX+n.padRight,i=!0),(null==n.minX||e.minX-n.padLeftn.maxY)&&(n.maxY=e.maxY+n.padBottom,i=!0),(null==n.minY||e.minY-n.padTopy&&(f+=g+t.componentSpacing,p=0,v=0,g=0)}}},y=function(e){return i?!1:(a(r,n,e),r.temperature=r.temperature*n.coolingFactor,!(r.temperature=b;){var _=m[b++],P=a.idToIndex[_],f=a.layoutNodes[P],S=f.children;if(S.length>0){a.graphSet.push(S);for(var c=0;cn.count?0:n.graph},h=function(e,t,r,n){var i=n.graphSet[r];if(-1s){var v=d(),g=h();(v-1)*g>=s?d(v-1):(g-1)*v>=s&&h(g-1)}else for(;s>c*u;){var v=d(),g=h();(g+1)*v>=s?h(g+1):d(v+1)}var y=o.w/c,m=o.h/u;if(t.condense&&(y=0,m=0),t.avoidOverlap)for(var b=0;b=c&&(M=0,C++)},B={},b=0;b=o&&s>=e&&t>=l&&u>=t;return c},o=function(e,t,r,n,i){var a=e*Math.cos(n)-t*Math.sin(n),o=e*Math.sin(n)+t*Math.cos(n),s=a*r,l=o*r,u=s+i.x,c=l+i.y;return{x:u,y:c}},s=function(e,t,r,n){for(var i=[],a=0;at))if(d){if(d.pstyle("z-index").value===e.pstyle("z-index").value)for(var r=0;r(l=i.sqdistToFiniteLine(e,t,_[P],_[P+1],_[P+2],_[P+3])))return a(n,l),!0}else if("bezier"===c.edgeType||"multibezier"===c.edgeType||"self"===c.edgeType||"compound"===c.edgeType)for(var _=c.allpts,P=0;P+5(l=i.sqdistToQuadraticBezier(e,t,_[P],_[P+1],_[P+2],_[P+3],_[P+4],_[P+5])))return a(n,l),!0;if(w&&E())for(var y=y||o.source,x=x||o.target,S=n.pstyle("width").pfValue,k=p.getArrowWidth(S),T=[{name:"source",x:c.arrowStartX,y:c.arrowStartY,angle:c.srcArrowAngle},{name:"target",x:c.arrowEndX,y:c.arrowEndY,angle:c.tgtArrowAngle},{name:"mid-source",x:c.midX,y:c.midY,angle:c.midsrcArrowAngle},{name:"mid-target",x:c.midX,y:c.midY,angle:c.midtgtArrowAngle}],P=0;P0&&(s(y),s(x))}}function u(e,t,r){return o.getPrefixedProperty(e,t,r)}function c(r,n){var o,s=r._private,l=w;o=n?n+"-":"";var c=r.pstyle(o+"label").value,d="yes"===r.pstyle("text-events").strValue;if(d&&c){var h=s.rstyle,p=r.pstyle("text-border-width").pfValue,f=u(h,"labelWidth",n)+p/2+2*l,v=u(h,"labelHeight",n)+p/2+2*l,g=u(h,"labelX",n),y=u(h,"labelY",n),m=u(s.rscratch,"labelAngle",n),b=g-f/2,x=g+f/2,E=y-v/2,_=y+v/2;if(m){var P=Math.cos(m),S=Math.sin(m),k=function(e,t){return e-=g,t-=y,{x:e*P-t*S+g,y:e*S+t*P+y}},T=k(b,E),D=k(b,_),C=k(x,E),M=k(x,_),N=[T.x,T.y,C.x,C.y,M.x,M.y,D.x,D.y];if(i.pointInsidePolygonPoints(e,t,N))return a(r),!0}else{var B={w:f,h:v,x1:b,x2:x,y1:E,y2:_};if(i.inBoundingBox(B,e,t))return a(r),!0}}}for(var d,h,p=this,f=this,v=f.getCachedZSortedEles(),g=[],y=f.cy.zoom(),m=f.cy.hasCompoundNodes(),b=(n?24:8)/y,x=(n?8:2)/y,w=(n?8:2)/y,E=1/0,_=v.length-1;_>=0;_--){var P=v[_];P.isNode()?s(P)||c(P):l(P)||c(P)||c(P,"source")||c(P,"target")}return g},l.getAllInBox=function(e,t,r,n){var a=this.getCachedZSortedEles(),o=a.nodes,s=a.edges,l=[],u=Math.min(e,r),c=Math.max(e,r),d=Math.min(t,n),h=Math.max(t,n);e=u,r=c,t=d,n=h;for(var p=i.makeBoundingBox({x1:e,y1:t,x2:r,y2:n}),f=0;fv;v++)e(p,d[o*h+v],d[o*h+v+1],a.bezierProjPcts[v],a.bezierProjPcts[v+1]);e(p,d[o*h+h-1],p.p2,a.bezierProjPcts[h-1],1)}return u.cache=t},c=function(r){var a,o="source"===r;if(s[r]){var c=e.pstyle(r+"-text-offset").pfValue,d=function(e,t){var r=t.x-e.x,n=t.y-e.y;return Math.atan(n/r)},h=function(e,t,r,n){var a=i.bound(0,n-.001,1),o=i.bound(0,n+.001,1),s=i.qbezierPtAt(e,t,r,a),l=i.qbezierPtAt(e,t,r,o);return d(s,l)};switch(n.edgeType){case"self":case"compound":case"bezier":case"multibezier":for(var p,f=u(),v=0,g=0,y=0;y=c||w){p={cp:m,segment:x};break}}if(p)break}var m=p.cp,x=p.segment,E=(c-v)/x.length,_=x.t1-x.t0,P=o?x.t0+_*E:x.t1-_*E;P=i.bound(0,P,1),t=i.qbezierPtAt(m.p0,m.p1,m.p2,P),a=h(m.p0,m.p1,m.p2,P,t);break;case"straight":case"segments":case"haystack":for(var S,k,T,D,C=0,M=n.allpts.length,y=0;M>y+3&&(o?(T={x:n.allpts[y],y:n.allpts[y+1]},D={x:n.allpts[y+2],y:n.allpts[y+3]}):(T={x:n.allpts[M-2-y],y:n.allpts[M-1-y]},D={x:n.allpts[M-4-y],y:n.allpts[M-3-y]}),S=i.dist(T,D),k=C,C+=S,!(C>=c));y+=2);var N=c-k,P=N/S;P=i.bound(0,P,1),t=i.lineAt(T,D,P),a=d(T,D)}l("labelX",r,t.x),l("labelY",r,t.y),l("labelAutoAngle",r,a)}};c("source"),c("target"),this.applyLabelDimensions(e)}},l.applyLabelDimensions=function(e){this.applyPrefixedLabelDimensions(e),e.isEdge()&&(this.applyPrefixedLabelDimensions(e,"source"),this.applyPrefixedLabelDimensions(e,"target"))},l.applyPrefixedLabelDimensions=function(e,t){var r=e._private,n=this.getLabelText(e,t),i=this.calculateLabelDimensions(e,n);o.setPrefixedProperty(r.rstyle,"labelWidth",t,i.width),o.setPrefixedProperty(r.rscratch,"labelWidth",t,i.width),o.setPrefixedProperty(r.rstyle,"labelHeight",t,i.height),o.setPrefixedProperty(r.rscratch,"labelHeight",t,i.height)},l.getLabelText=function(e,t){var r=e._private,n=t?t+"-":"",i=e.pstyle(n+"label").strValue,a=e.pstyle("text-transform").value,s=function(e,n){return n?(o.setPrefixedProperty(r.rscratch,e,t,n),n):o.getPrefixedProperty(r.rscratch,e,t)};if("none"==a||("uppercase"==a?i=i.toUpperCase():"lowercase"==a&&(i=i.toLowerCase())),"wrap"===e.pstyle("text-wrap").value){var l=s("labelKey");if(l&&s("labelWrapKey")===l)return s("labelWrapCachedText");for(var u=i.split("\n"),c=e.pstyle("text-max-width").pfValue,d=[],h=0;hc){for(var g=p.split(/\s+/),y="",m=0;m=E?y+=b+" ":(d.push(y),y=b+" ")}y.match(/^\s+$/)||d.push(y)}else d.push(p)}s("labelWrapCachedLines",d),i=s("labelWrapCachedText",d.join("\n")),s("labelWrapKey",l)}return i},l.calculateLabelDimensions=function(e,t,r){var n=this,i=e._private.labelStyleKey+"$@$"+t;r&&(i+="$@$"+r);var a=n.labelDimCache||(n.labelDimCache={});if(a[i])return a[i];var o=1,s=e.pstyle("font-style").strValue,l=o*e.pstyle("font-size").pfValue+"px",u=e.pstyle("font-family").strValue,c=e.pstyle("font-weight").strValue,d=this.labelCalcDiv;d||(d=this.labelCalcDiv=document.createElement("div"),document.body.appendChild(d));var h=d.style;return h.fontFamily=u,h.fontStyle=s,h.fontSize=l,h.fontWeight=c,h.position="absolute",h.left="-9999px",h.top="-9999px",h.zIndex="-1",h.visibility="hidden",h.pointerEvents="none",h.padding="0",h.lineHeight="1","wrap"===e.pstyle("text-wrap").value?h.whiteSpace="pre":h.whiteSpace="normal",d.textContent=t,a[i]={width:Math.ceil(d.clientWidth/o),height:Math.ceil(d.clientHeight/o)},a[i]},l.recalculateEdgeProjections=function(e){this.findEdgeControlPoints(e)},l.findEdgeControlPoints=function(e){if(e&&0!==e.length){for(var t,r=this,n=r.cy,o=n.hasCompoundNodes(),s={},l=[],u=[],c=0;cy?y+"$-$"+g:g+"$-$"+y,v&&(t="unbundled$-$"+p.id),null==s[t]&&(s[t]=[],l.push(t)),s[t].push(d),v&&(s[t].hasUnbundled=!0)}else u.push(d)}for(var m,b,x,w,E,_,P,S,k,T,D,C,M,N,B=0;Bb.id()){var I=m;m=b,b=I}if(x=m._private,w=b._private,E=x.position,_=w.position,P=m.outerWidth(),S=m.outerHeight(),k=b.outerWidth(),T=b.outerHeight(),D=r.nodeShapes[this.getNodeShape(m)],C=r.nodeShapes[this.getNodeShape(b)],N=!1,z.length>1&&m!==b||z.hasUnbundled){var L=D.intersectLine(E.x,E.y,P,S,_.x,_.y,0),O=C.intersectLine(_.x,_.y,k,T,E.x,E.y,0),A={x1:L[0],x2:O[0],y1:L[1],y2:O[1]},R={x1:E.x,x2:_.x,y1:E.y,y2:_.y},q=O[1]-L[1],V=O[0]-L[0],F=Math.sqrt(V*V+q*q),j={x:V,y:q},X={x:j.x/F,y:j.y/F};M={x:-X.y,y:X.x},C.checkPoint(L[0],L[1],0,k,T,_.x,_.y)&&D.checkPoint(O[0],O[1],0,P,S,E.x,E.y)&&(M={},N=!0)}for(var d,Y,W,c=0;cze;ze++){var Ie=Me[ze],Le=Ne[ze],Oe=1-Ie,Ae=Ie,Re="node-position"===xe?R:A,qe={x:Re.x1*Oe+Re.x2*Ae,y:Re.y1*Oe+Re.y2*Ae};W.segpts.push(qe.x+M.x*Le,qe.y+M.y*Le)}}else if(z.length%2!==1||c!==Math.floor(z.length/2)||v){var Ve=v;W.edgeType=Ve?"multibezier":"bezier",W.ctrlpts=[];for(var Fe=0;K>Fe;Fe++){var je,Xe=(.5-z.length/2+c)*J,Ye=i.signum(Xe);Ve&&(ee=G?G.pfValue[Fe]:J,te=Q.value[Fe]),je=v?ee:void 0!==ee?Ye*ee:void 0;var We=void 0!==je?je:Xe,Oe=1-te,Ae=te,Re="node-position"===xe?R:A,qe={x:Re.x1*Oe+Re.x2*Ae,y:Re.y1*Oe+Re.y2*Ae};W.ctrlpts.push(qe.x+M.x*We,qe.y+M.y*We)}}else W.edgeType="straight";this.findEndpoints(d);var $e=!a.number(W.startX)||!a.number(W.startY),He=!a.number(W.arrowStartX)||!a.number(W.arrowStartY),Ue=!a.number(W.endX)||!a.number(W.endY),Ze=!a.number(W.arrowEndX)||!a.number(W.arrowEndY),Ge=3,Qe=this.getArrowWidth(d.pstyle("width").pfValue)*this.arrowShapeWidth,Ke=Ge*Qe;if("bezier"===W.edgeType){var Je=i.dist({x:W.ctrlpts[0],y:W.ctrlpts[1]},{x:W.startX,y:W.startY}),et=Ke>Je,tt=i.dist({x:W.ctrlpts[0],y:W.ctrlpts[1]},{x:W.endX,y:W.endY}),rt=Ke>tt,nt=!1;if($e||He||et){nt=!0;var it={x:W.ctrlpts[0]-E.x,y:W.ctrlpts[1]-E.y},at=Math.sqrt(it.x*it.x+it.y*it.y),ot={x:it.x/at,y:it.y/at},st=Math.max(P,S),lt={x:W.ctrlpts[0]+2*ot.x*st,y:W.ctrlpts[1]+2*ot.y*st},ut=D.intersectLine(E.x,E.y,P,S,lt.x,lt.y,0);et?(W.ctrlpts[0]=W.ctrlpts[0]+ot.x*(Ke-Je),W.ctrlpts[1]=W.ctrlpts[1]+ot.y*(Ke-Je)):(W.ctrlpts[0]=ut[0]+ot.x*Ke,W.ctrlpts[1]=ut[1]+ot.y*Ke)}if(Ue||Ze||rt){nt=!0;var it={x:W.ctrlpts[0]-_.x,y:W.ctrlpts[1]-_.y},at=Math.sqrt(it.x*it.x+it.y*it.y),ot={x:it.x/at,y:it.y/at},st=Math.max(P,S),lt={x:W.ctrlpts[0]+2*ot.x*st,y:W.ctrlpts[1]+2*ot.y*st},ct=C.intersectLine(_.x,_.y,k,T,lt.x,lt.y,0);rt?(W.ctrlpts[0]=W.ctrlpts[0]+ot.x*(Ke-tt),W.ctrlpts[1]=W.ctrlpts[1]+ot.y*(Ke-tt)):(W.ctrlpts[0]=ct[0]+ot.x*Ke,W.ctrlpts[1]=ct[1]+ot.y*Ke)}nt&&this.findEndpoints(d)}if("multibezier"===W.edgeType||"bezier"===W.edgeType||"self"===W.edgeType||"compound"===W.edgeType){W.allpts=[],W.allpts.push(W.startX,W.startY);for(var Fe=0;Fe+1c[0]&&i.clientXc[1]&&i.clientY=e.desktopTapThreshold2}var I=r(i);P&&(e.hoverData.tapholdCancelled=!0);var L=function(){var t=e.hoverData.dragDelta=e.hoverData.dragDelta||[];0===t.length?(t.push(T[0]),t.push(T[1])):(t[0]+=T[0],t[1]+=T[1])};if(l=!0,t(_,["mousemove","vmousemove","tapdrag"],i,{cyPosition:{x:b[0],y:b[1]}}),3===e.hoverData.which){if(P){var O=new o(i,{type:"cxtdrag",cyPosition:{x:b[0],y:b[1]}});k?k.trigger(O):v.trigger(O),e.hoverData.cxtDragged=!0,e.hoverData.cxtOver&&_===e.hoverData.cxtOver||(e.hoverData.cxtOver&&e.hoverData.cxtOver.trigger(new o(i,{type:"cxtdragout",cyPosition:{x:b[0],y:b[1]}})),e.hoverData.cxtOver=_,_&&_.trigger(new o(i,{type:"cxtdragover",cyPosition:{x:b[0],y:b[1]}})))}}else if(e.hoverData.dragging){if(l=!0,v.panningEnabled()&&v.userPanningEnabled()){var A;if(e.hoverData.justStartedPan){var R=e.hoverData.mdownPos;A={x:(b[0]-R[0])*g,y:(b[1]-R[1])*g},e.hoverData.justStartedPan=!1}else A={x:T[0]*g,y:T[1]*g};v.panBy(A),e.hoverData.dragged=!0}b=e.projectIntoViewport(i.clientX,i.clientY)}else if(1!=E[4]||null!=k&&!k.isEdge()){if(k&&k.isEdge()&&k.active()&&k.unactivate(),k&&k.grabbed()||_==S||(S&&t(S,["mouseout","tapdragout"],i,{cyPosition:{x:b[0],y:b[1]}}),_&&t(_,["mouseover","tapdragover"],i,{cyPosition:{x:b[0],y:b[1]}}),e.hoverData.last=_),k&&e.nodeIsDraggable(k))if(P){var q=!e.dragData.didDrag;q&&e.redrawHint("eles",!0),e.dragData.didDrag=!0;var V=[];e.hoverData.draggingEles||y(v.collection(D),{inDragLayer:!0});for(var F=0;F0&&e.redrawHint("eles",!0),e.dragData.possibleDragElements=c=[]),t(u,["mouseup","tapend","vmouseup"],n,{cyPosition:{x:s[0],y:s[1]}}),e.dragData.didDrag||e.hoverData.dragged||e.hoverData.selecting||t(d,["click","tap","vclick"],n,{cyPosition:{x:s[0],y:s[1]}}),u!=d||e.dragData.didDrag||e.hoverData.selecting||null!=u&&u._private.selectable&&(e.hoverData.dragging||("additive"===a.selectionType()||h?u.selected()?u.unselect():u.select():h||(a.$(":selected").unmerge(u).unselect(),u.select())),e.redrawHint("eles",!0)),e.hoverData.selecting){var v=a.collection(e.getAllInBox(l[0],l[1],l[2],l[3]));e.redrawHint("select",!0),v.length>0&&e.redrawHint("eles",!0),a.trigger("boxend");var g=function(e){return e.selectable()&&!e.selected()};"additive"===a.selectionType()?v.trigger("box").stdFilter(g).select().trigger("boxselect"):(h||a.$(":selected").unmerge(v).unselect(),v.trigger("box").stdFilter(g).select().trigger("boxselect")),e.redraw()}if(e.hoverData.dragging&&(e.hoverData.dragging=!1,e.redrawHint("select",!0),e.redrawHint("eles",!0),e.redraw()),!l[4]){e.redrawHint("drag",!0),e.redrawHint("eles",!0);var y=d&&d.grabbed();b(c),y&&d.trigger("free")}}l[4]=0,e.hoverData.down=null,e.hoverData.cxtStarted=!1,e.hoverData.draggingEles=!1,e.hoverData.selecting=!1,e.dragData.didDrag=!1,e.hoverData.dragged=!1,e.hoverData.dragDelta=[],e.hoverData.mdownPos=null,e.hoverData.mdownGPos=null}},!1);var k=function(t){if(!e.scrollingPage){var r=e.cy,n=e.projectIntoViewport(t.clientX,t.clientY),i=[n[0]*r.zoom()+r.pan().x,n[1]*r.zoom()+r.pan().y];if(e.hoverData.draggingEles||e.hoverData.dragging||e.hoverData.cxtStarted||S())return void t.preventDefault();if(r.panningEnabled()&&r.userPanningEnabled()&&r.zoomingEnabled()&&r.userZoomingEnabled()){t.preventDefault(),e.data.wheelZooming=!0,clearTimeout(e.data.wheelTimeout),e.data.wheelTimeout=setTimeout(function(){e.data.wheelZooming=!1,e.redrawHint("eles",!0),e.redraw()},150);var a;a=null!=t.deltaY?t.deltaY/-250:null!=t.wheelDeltaY?t.wheelDeltaY/1e3:t.wheelDelta/1e3,a*=e.wheelSensitivity;var o=1===t.deltaMode;o&&(a*=33),r.zoom({level:r.zoom()*Math.pow(10,a),renderedPosition:{x:i[0],y:i[1]}})}}};e.registerBinding(e.container,"wheel",k,!0),e.registerBinding(window,"scroll",function(t){e.scrollingPage=!0,clearTimeout(e.scrollingPageTimeout),e.scrollingPageTimeout=setTimeout(function(){e.scrollingPage=!1},250)},!0),e.registerBinding(e.container,"mouseout",function(t){var r=e.projectIntoViewport(t.clientX,t.clientY);e.cy.trigger(new o(t,{type:"mouseout",cyPosition:{x:r[0],y:r[1]}}))},!1),e.registerBinding(e.container,"mouseover",function(t){var r=e.projectIntoViewport(t.clientX,t.clientY);e.cy.trigger(new o(t,{type:"mouseover",cyPosition:{x:r[0],y:r[1]}}))},!1);var T,D,C,M,N,B,z,I,L,O,A,R,q,V,F=function(e,t,r,n){return Math.sqrt((r-e)*(r-e)+(n-t)*(n-t))},j=function(e,t,r,n){return(r-e)*(r-e)+(n-t)*(n-t)};e.registerBinding(e.container,"touchstart",V=function(r){e.touchData.capture=!0,e.data.bgActivePosistion=void 0;var n=e.cy,i=e.touchData.now,a=e.touchData.earlier;if(r.touches[0]){var s=e.projectIntoViewport(r.touches[0].clientX,r.touches[0].clientY);i[0]=s[0],i[1]=s[1]}if(r.touches[1]){var s=e.projectIntoViewport(r.touches[1].clientX,r.touches[1].clientY);i[2]=s[0],i[3]=s[1]}if(r.touches[2]){var s=e.projectIntoViewport(r.touches[2].clientX,r.touches[2].clientY);i[4]=s[0],i[5]=s[1]}if(r.touches[1]){b(e.dragData.touchDragEles);var l=e.findContainerClientCoords();L=l[0],O=l[1],A=l[2],R=l[3],T=r.touches[0].clientX-L,D=r.touches[0].clientY-O,C=r.touches[1].clientX-L,M=r.touches[1].clientY-O,q=T>=0&&A>=T&&C>=0&&A>=C&&D>=0&&R>=D&&M>=0&&R>=M;var u=n.pan(),c=n.zoom();N=F(T,D,C,M),B=j(T,D,C,M),z=[(T+C)/2,(D+M)/2],I=[(z[0]-u.x)/c,(z[1]-u.y)/c];var d=200,h=d*d;if(h>B&&!r.touches[2]){var f=e.findNearestElement(i[0],i[1],!0,!0),v=e.findNearestElement(i[2],i[3],!0,!0);return f&&f.isNode()?(f.activate().trigger(new o(r,{type:"cxttapstart",cyPosition:{x:i[0],y:i[1]}})),e.touchData.start=f):v&&v.isNode()?(v.activate().trigger(new o(r,{type:"cxttapstart",cyPosition:{x:i[0],y:i[1]}})),e.touchData.start=v):(n.trigger(new o(r,{type:"cxttapstart",cyPosition:{x:i[0],y:i[1]}})),e.touchData.start=null),e.touchData.start&&(e.touchData.start._private.grabbed=!1),e.touchData.cxt=!0,e.touchData.cxtDragged=!1,e.data.bgActivePosistion=void 0,void e.redraw()}}if(r.touches[2]);else if(r.touches[1]);else if(r.touches[0]){var g=e.findNearestElements(i[0],i[1],!0,!0),x=g[0];if(null!=x&&(x.activate(),e.touchData.start=x,e.touchData.starts=g,e.nodeIsGrabbable(x))){var w=e.dragData.touchDragEles=[];if(e.redrawHint("eles",!0),e.redrawHint("drag",!0),x.selected()){var E=n.$(function(){return this.selected()&&e.nodeIsGrabbable(this)});y(E,{addToList:w})}else m(x,{addToList:w});p(x),x.trigger(new o(r,{type:"grab",cyPosition:{x:i[0],y:i[1]}}))}t(x,["touchstart","tapstart","vmousedown"],r,{cyPosition:{x:i[0],y:i[1]}}),null==x&&(e.data.bgActivePosistion={x:s[0],y:s[1]},e.redrawHint("select",!0),e.redraw()),e.touchData.startPosition=[];for(var _=0;_=e.touchTapThreshold2}if(l&&e.touchData.cxt){r.preventDefault();var S=r.touches[0].clientX-L,k=r.touches[0].clientY-O,z=r.touches[1].clientX-L,A=r.touches[1].clientY-O,R=j(S,k,z,A),V=R/B,X=150,Y=X*X,W=1.5,$=W*W;if(V>=$||R>=Y){e.touchData.cxt=!1,e.touchData.start&&(e.touchData.start.unactivate(),e.touchData.start=null),e.data.bgActivePosistion=void 0,e.redrawHint("select",!0);var H=new o(r,{type:"cxttapend",cyPosition:{x:c[0],y:c[1]}});e.touchData.start?e.touchData.start.trigger(H):u.trigger(H)}}if(l&&e.touchData.cxt){var H=new o(r,{type:"cxtdrag",cyPosition:{x:c[0],y:c[1]}});e.data.bgActivePosistion=void 0,e.redrawHint("select",!0),e.touchData.start?e.touchData.start.trigger(H):u.trigger(H),e.touchData.start&&(e.touchData.start._private.grabbed=!1),e.touchData.cxtDragged=!0;var U=e.findNearestElement(c[0],c[1],!0,!0);e.touchData.cxtOver&&U===e.touchData.cxtOver||(e.touchData.cxtOver&&e.touchData.cxtOver.trigger(new o(r,{type:"cxtdragout",cyPosition:{x:c[0],y:c[1]}})),e.touchData.cxtOver=U,U&&U.trigger(new o(r,{type:"cxtdragover",cyPosition:{x:c[0],y:c[1]}})))}else if(l&&r.touches[2]&&u.boxSelectionEnabled())r.preventDefault(),e.data.bgActivePosistion=void 0,this.lastThreeTouch=+new Date,e.touchData.selecting||u.trigger("boxstart"),e.touchData.selecting=!0,e.redrawHint("select",!0),i&&0!==i.length&&void 0!==i[0]?(i[2]=(c[0]+c[2]+c[4])/3,i[3]=(c[1]+c[3]+c[5])/3):(i[0]=(c[0]+c[2]+c[4])/3,i[1]=(c[1]+c[3]+c[5])/3,i[2]=(c[0]+c[2]+c[4])/3+1,i[3]=(c[1]+c[3]+c[5])/3+1),i[4]=1,e.touchData.selecting=!0,e.redraw();else if(l&&r.touches[1]&&u.zoomingEnabled()&&u.panningEnabled()&&u.userZoomingEnabled()&&u.userPanningEnabled()){r.preventDefault(),e.data.bgActivePosistion=void 0,e.redrawHint("select",!0);var Z=e.dragData.touchDragEles;if(Z){e.redrawHint("drag",!0);for(var G=0;G=e*e+t*t}}},i.generateRoundRectangle=function(){return this.nodeShapes.roundrectangle={renderer:this,name:"roundrectangle",points:n.generateUnitNgonPointsFitToSquare(4,0),draw:function(e,t,r,n,i){this.renderer.nodeShapeImpl(this.name,e,t,r,n,i)},intersectLine:function(e,t,r,i,a,o,s){return n.roundRectangleIntersectLine(a,o,e,t,r,i,s)},checkPoint:function(e,t,r,i,a,o,s){var l=n.getRoundRectangleRadius(i,a);if(n.pointInsidePolygon(e,t,this.points,o,s,i,a-2*l,[0,-1],r))return!0;if(n.pointInsidePolygon(e,t,this.points,o,s,i-2*l,a,[0,-1],r))return!0;var u=function(e,t,r,n,i,a,o){return e-=r,t-=n,e/=i/2+o,t/=a/2+o,1>=e*e+t*t};return u(e,t,o-i/2+l,s-a/2+l,2*l,2*l,r)?!0:u(e,t,o+i/2-l,s-a/2+l,2*l,2*l,r)?!0:u(e,t,o+i/2-l,s+a/2-l,2*l,2*l,r)?!0:!!u(e,t,o-i/2+l,s+a/2-l,2*l,2*l,r)}}},i.registerNodeShapes=function(){var e=this.nodeShapes={},t=this;this.generateEllipse(),this.generatePolygon("triangle",n.generateUnitNgonPointsFitToSquare(3,0)),this.generatePolygon("rectangle",n.generateUnitNgonPointsFitToSquare(4,0)),e.square=e.rectangle,this.generateRoundRectangle(),this.generatePolygon("diamond",[0,1,1,0,0,-1,-1,0]),this.generatePolygon("pentagon",n.generateUnitNgonPointsFitToSquare(5,0)),this.generatePolygon("hexagon",n.generateUnitNgonPointsFitToSquare(6,0)),this.generatePolygon("heptagon",n.generateUnitNgonPointsFitToSquare(7,0)),this.generatePolygon("octagon",n.generateUnitNgonPointsFitToSquare(8,0));var r=new Array(20),i=n.generateUnitNgonPoints(5,0),a=n.generateUnitNgonPoints(5,Math.PI/5),o=.5*(3-Math.sqrt(5));o*=1.57;for(var s=0;ss;s++)r[4*s]=i[2*s],r[4*s+1]=i[2*s+1],r[4*s+2]=a[2*s],r[4*s+3]=a[2*s+1];r=n.fitPolygonToSquare(r),this.generatePolygon("star",r),this.generatePolygon("vee",[-1,-1,0,-.333,1,-1,0,1]),this.generatePolygon("rhomboid",[-1,-1,.333,-1,1,1,-.333,1]),e.makePolygon=function(e){var r,n=e.join("$"),i="polygon-"+n;return(r=this[i])?r:t.generatePolygon(i,e)}},t.exports=i},{"../../../math":85}],63:[function(e,t,r){"use strict";var n=e("../../../util"),i={};i.timeToRender=function(){return this.redrawTotalTime/this.redrawCount},i.redraw=function(e){e=e||n.staticEmptyObject();var t=this;void 0===t.averageRedrawTime&&(t.averageRedrawTime=0),void 0===t.lastRedrawTime&&(t.lastRedrawTime=0),void 0===t.lastDrawTime&&(t.lastDrawTime=0),t.requestedFrame=!0,t.renderOptions=e},i.beforeRender=function(e,t){if(!this.destroyed){t=t||0;var r=this.beforeRenderCallbacks;r.push({fn:e,priority:t}),r.sort(function(e,t){return t.priority-e.priority})}};var a=function(e,t,r){for(var n=e.beforeRenderCallbacks,i=0;io)},o.drawElementText=function(e,t,r){var n=this;if(void 0===r){if(!n.eleTextBiggerThanMin(t))return}else if(!r)return;if(t.isNode()){var i=t.pstyle("label");if(!i||!i.value)return;var a=t.pstyle("text-halign").strValue;t.pstyle("text-valign").strValue;switch(a){case"left":e.textAlign="right";break;case"right":e.textAlign="left";break;default:e.textAlign="center"}e.textBaseline="bottom"}else{var i=t.pstyle("label"),o=t.pstyle("source-label"),s=t.pstyle("target-label");if(!(i&&i.value||o&&o.value||s&&s.value))return;e.textAlign="center",e.textBaseline="bottom"}n.drawText(e,t),t.isEdge()&&(n.drawText(e,t,"source"),n.drawText(e,t,"target"))},o.drawNodeText=o.drawEdgeText=o.drawElementText,o.getFontCache=function(e){var t;this.fontCaches=this.fontCaches||[];for(var r=0;r0||k>0&&S>0){var T=l;switch(m){case"left":T-=h;break;case"center":T-=h/2;break;case"right":}var D=u-p;if(P>0){var C=e.fillStyle,M=t.pstyle("text-background-color").value;e.fillStyle="rgba("+M[0]+","+M[1]+","+M[2]+","+P*s+")";var N=t.pstyle("text-background-shape").strValue;"roundrectangle"==N?n(e,T,D,h,p,2):e.fillRect(T,D,h,p),e.fillStyle=C}if(k>0&&S>0){var B=e.strokeStyle,z=e.lineWidth,I=t.pstyle("text-border-color").value,L=t.pstyle("text-border-style").value;if(e.strokeStyle="rgba("+I[0]+","+I[1]+","+I[2]+","+S*s+")",e.lineWidth=k,e.setLineDash)switch(L){case"dotted":e.setLineDash([1,1]);break;case"dashed":e.setLineDash([4,2]);break;case"double":e.lineWidth=k/4,e.setLineDash([]);break;case"solid":e.setLineDash([])}if(e.strokeRect(T,D,h,p),"double"===L){var O=k/2;e.strokeRect(T+O,D+O,h-2*O,p-2*O)}e.setLineDash&&e.setLineDash([]),e.lineWidth=z,e.strokeStyle=B}}var A=2*t.pstyle("text-outline-width").pfValue;if(A>0&&(e.lineWidth=A),"wrap"===t.pstyle("text-wrap").value){var R=o.labelWrapCachedLines,q=p/R.length; +switch(b){case"top":u-=(R.length-1)*q;break;case"center":case"bottom":u-=(R.length-1)*q}for(var V=0;V0&&e.strokeText(R[V],l,u),e.fillText(R[V],l,u),u+=q}else A>0&&e.strokeText(c,l,u),e.fillText(c,l,u);0!==x&&(e.rotate(-x),e.translate(-E,-_)),this.shadowStyle(e,"transparent",0)}}},t.exports=o},{"../../../math":85,"../../../util":100}],69:[function(e,t,r){"use strict";var n=e("../../../is"),i={};i.drawNode=function(e,t,r,i){var a,o,s=this,l=t._private.rscratch,u=t._private,c=c||u.position;if(n.number(c.x)&&n.number(c.y)&&t.visible()){var d,h=t.effectiveOpacity(),p=this.usePaths(),f=!1,v=t.pstyle("padding").pfValue;a=t.width()+2*v,o=t.height()+2*v,e.lineWidth=t.pstyle("border-width").pfValue;var g;r&&(g=r,e.translate(-g.x1,-g.y1));var y,m=t.pstyle("background-image"),b=m.value[2]||m.value[1];if(void 0!==b){var x=t.pstyle("background-image-crossorigin");y=this.getCachedImage(b,x,function(){t.trigger("background"),s.redrawHint("eles",!0),s.redrawHint("drag",!0),s.drawingImage=!0,s.redraw()});var w=u.backgrounding;u.backgrounding=!y.complete,w!==u.backgrounding&&t.updateStyle(!1)}var E=t.pstyle("background-color").value,_=t.pstyle("border-color").value,P=t.pstyle("border-style").value;this.fillStyle(e,E[0],E[1],E[2],t.pstyle("background-opacity").value*h),this.strokeStyle(e,_[0],_[1],_[2],t.pstyle("border-opacity").value*h);var S=t.pstyle("shadow-blur").pfValue,k=t.pstyle("shadow-opacity").value,T=t.pstyle("shadow-color").value,D=t.pstyle("shadow-offset-x").pfValue,C=t.pstyle("shadow-offset-y").pfValue;if(this.shadowStyle(e,T,k,S,D,C),e.lineJoin="miter",e.setLineDash)switch(P){case"dotted":e.setLineDash([1,1]);break;case"dashed":e.setLineDash([4,2]);break;case"solid":case"double":e.setLineDash([])}var M=t.pstyle("shape").strValue,N=t.pstyle("shape-polygon-points").pfValue;if(p){var B=M+"$"+a+"$"+o+("polygon"===M?"$"+N.join("$"):"");e.translate(c.x,c.y),l.pathCacheKey===B?(d=l.pathCache,f=!0):(d=new Path2D,l.pathCacheKey=B,l.pathCache=d)}if(!f){var z=c;p&&(z={x:0,y:0}),s.nodeShapes[this.getNodeShape(t)].draw(d||e,z.x,z.y,a,o)}p?e.fill(d):e.fill(),this.shadowStyle(e,"transparent",0),void 0!==b&&y.complete&&this.drawInscribedImage(e,y,t);var I=t.pstyle("background-blacken").value,L=t.pstyle("border-width").pfValue;if(this.hasPie(t)&&(this.drawPie(e,t,h),0===I&&0===L||p||s.nodeShapes[this.getNodeShape(t)].draw(e,c.x,c.y,a,o)),I>0?(this.fillStyle(e,0,0,0,I),p?e.fill(d):e.fill()):0>I&&(this.fillStyle(e,255,255,255,-I),p?e.fill(d):e.fill()),L>0&&(p?e.stroke(d):e.stroke(),"double"===P)){e.lineWidth=t.pstyle("border-width").pfValue/3;var O=e.globalCompositeOperation;e.globalCompositeOperation="destination-out",p?e.stroke(d):e.stroke(),e.globalCompositeOperation=O}p&&e.translate(-c.x,-c.y),e.setLineDash&&e.setLineDash([]),s.drawElementText(e,t,i);var A=t.pstyle("overlay-padding").pfValue,R=t.pstyle("overlay-opacity").value,q=t.pstyle("overlay-color").value;R>0&&(this.fillStyle(e,q[0],q[1],q[2],R),s.nodeShapes.roundrectangle.draw(e,t._private.position.x,t._private.position.y,a+2*A,o+2*A),e.fill()),r&&e.translate(g.x1,g.y1)}},i.hasPie=function(e){return e=e[0],e._private.hasPie},i.drawPie=function(e,t,r,n){t=t[0];var i=t._private,a=t.cy().style(),o=t.pstyle("pie-size"),s=t.width(),l=t.height(),n=n||i.position,u=n.x,c=n.y,d=Math.min(s,l)/2,h=0,p=this.usePaths();p&&(u=0,c=0),"%"===o.units?d=d*o.value/100:void 0!==o.pfValue&&(d=o.pfValue/2);for(var f=1;f<=a.pieBackgroundN;f++){var v=t.pstyle("pie-"+f+"-background-size").value,g=t.pstyle("pie-"+f+"-background-color").value,y=t.pstyle("pie-"+f+"-background-opacity").value*r,m=v/100;m+h>1&&(m=1-h);var b=1.5*Math.PI+2*Math.PI*h,x=2*Math.PI*m,w=b+x;0===v||h>=1||h+m>1||(e.beginPath(),e.moveTo(u,c),e.arc(u,c,d,b,w),e.closePath(),this.fillStyle(e,g[0],g[1],g[2],y),e.fill(),h+=m)}},t.exports=i},{"../../../is":83}],70:[function(e,t,r){"use strict";var n={},i=e("../../../util"),a=100;n.getPixelRatio=function(){var e=this.data.contexts[0];if(null!=this.forcedPixelRatio)return this.forcedPixelRatio;var t=e.backingStorePixelRatio||e.webkitBackingStorePixelRatio||e.mozBackingStorePixelRatio||e.msBackingStorePixelRatio||e.oBackingStorePixelRatio||e.backingStorePixelRatio||1;return(window.devicePixelRatio||1)/t},n.paintCache=function(e){for(var t,r=this.paintCaches=this.paintCaches||[],n=!0,i=0;i0?(e.shadowBlur=n*o,e.shadowColor="rgba("+t[0]+","+t[1]+","+t[2]+","+r+")",e.shadowOffsetX=i*o,e.shadowOffsetY=a*o):(e.shadowBlur=0,e.shadowColor="transparent",e.shadowOffsetX=0,e.shadowOffsetY=0)},n.matchCanvasSize=function(e){var t=this,r=t.data,n=e.clientWidth,i=e.clientHeight,a=t.getPixelRatio(),o=t.motionBlurPxRatio;e!==t.data.bufferCanvases[t.MOTIONBLUR_BUFFER_NODE]&&e!==t.data.bufferCanvases[t.MOTIONBLUR_BUFFER_DRAG]||(a=o);var s,l=n*a,u=i*a;if(l!==t.canvasWidth||u!==t.canvasHeight){t.fontCaches=null;var c=r.canvasContainer;c.style.width=n+"px",c.style.height=i+"px";for(var d=0;d=a&&(s=r.bufferCanvases[t.TEXTURE_BUFFER],t.textureMult=2,s.width=l*t.textureMult,s.height=u*t.textureMult),t.canvasWidth=l,t.canvasHeight=u}},n.renderTo=function(e,t,r,n){this.render({forcedContext:e,forcedZoom:t,forcedPan:r,drawAllLayers:!0,forcedPxRatio:n})},n.render=function(e){function t(e,t,r,n,i){var a=e.globalCompositeOperation;e.globalCompositeOperation="destination-out",c.fillStyle(e,255,255,255,c.motionBlurTransparency),e.fillRect(t,r,n,i),e.globalCompositeOperation=a}function r(e,r){var i,a,s,d;c.clearingMotionBlur||e!==p.bufferContexts[c.MOTIONBLUR_BUFFER_NODE]&&e!==p.bufferContexts[c.MOTIONBLUR_BUFFER_DRAG]?(i=k,a=P,s=c.canvasWidth,d=c.canvasHeight):(i={x:S.x*y,y:S.y*y},a=_*y,s=c.canvasWidth*y,d=c.canvasHeight*y),e.setTransform(1,0,0,1,0,0),"motionBlur"===r?t(e,0,0,s,d):n||void 0!==r&&!r||e.clearRect(0,0,s,d),o||(e.translate(i.x,i.y),e.scale(a,a)),u&&e.translate(u.x,u.y),l&&e.scale(l,l)}e=e||i.staticEmptyObject();var n=e.forcedContext,o=e.drawAllLayers,s=e.drawOnlyNodeLayer,l=e.forcedZoom,u=e.forcedPan,c=this,d=void 0===e.forcedPxRatio?this.getPixelRatio():e.forcedPxRatio,h=c.cy,p=c.data,f=p.canvasNeedsRedraw,v=c.textureOnViewport&&!n&&(c.pinching||c.hoverData.dragging||c.swipePanning||c.data.wheelZooming),g=void 0!==e.motionBlur?e.motionBlur:c.motionBlur,y=c.motionBlurPxRatio,m=h.hasCompoundNodes(),b=c.hoverData.draggingEles,x=!(!c.hoverData.selecting&&!c.touchData.selecting);g=g&&!n&&c.motionBlurEnabled&&!x;var w=g;n||(c.prevPxRatio!==d&&(c.invalidateContainerClientCoordsCache(),c.matchCanvasSize(c.container),c.redrawHint("eles",!0),c.redrawHint("drag",!0)),c.prevPxRatio=d),!n&&c.motionBlurTimeout&&clearTimeout(c.motionBlurTimeout),g&&(null==c.mbFrames&&(c.mbFrames=0),c.drawingImage||c.mbFrames++,c.mbFrames<3&&(w=!1),c.mbFrames>c.minMbLowQualFrames&&(c.motionBlurPxRatio=c.mbPxRBlurry)),c.clearingMotionBlur&&(c.motionBlurPxRatio=1),c.textureDrawLastFrame&&!v&&(f[c.NODE]=!0,f[c.SELECT_BOX]=!0);var E=h.style()._private.coreStyle,_=h.zoom(),P=void 0!==l?l:_,S=h.pan(),k={x:S.x,y:S.y},T={zoom:_,pan:{x:S.x,y:S.y}},D=c.prevViewport,C=void 0===D||T.zoom!==D.zoom||T.pan.x!==D.pan.x||T.pan.y!==D.pan.y;C||b&&!m||(c.motionBlurPxRatio=1),u&&(k=u),P*=d,k.x*=d,k.y*=d;var M=c.getCachedZSortedEles();if(v||(c.textureDrawLastFrame=!1),v){c.textureDrawLastFrame=!0;var N;if(!c.textureCache){c.textureCache={},N=c.textureCache.bb=h.mutableElements().boundingBox(),c.textureCache.texture=c.data.bufferCanvases[c.TEXTURE_BUFFER];var B=c.data.bufferContexts[c.TEXTURE_BUFFER];B.setTransform(1,0,0,1,0,0),B.clearRect(0,0,c.canvasWidth*c.textureMult,c.canvasHeight*c.textureMult),c.render({forcedContext:B,drawOnlyNodeLayer:!0,forcedPxRatio:d*c.textureMult});var T=c.textureCache.viewport={zoom:h.zoom(),pan:h.pan(),width:c.canvasWidth,height:c.canvasHeight};T.mpan={x:(0-T.pan.x)/T.zoom,y:(0-T.pan.y)/T.zoom}}f[c.DRAG]=!1,f[c.NODE]=!1;var z=p.contexts[c.NODE],I=c.textureCache.texture,T=c.textureCache.viewport;N=c.textureCache.bb,z.setTransform(1,0,0,1,0,0),g?t(z,0,0,T.width,T.height):z.clearRect(0,0,T.width,T.height);var L=E["outside-texture-bg-color"].value,O=E["outside-texture-bg-opacity"].value;c.fillStyle(z,L[0],L[1],L[2],O),z.fillRect(0,0,T.width,T.height);var _=h.zoom();r(z,!1),z.clearRect(T.mpan.x,T.mpan.y,T.width/T.zoom/d,T.height/T.zoom/d),z.drawImage(I,T.mpan.x,T.mpan.y,T.width/T.zoom/d,T.height/T.zoom/d)}else c.textureOnViewport&&!n&&(c.textureCache=null);var A=h.extent(),R=c.pinching||c.hoverData.dragging||c.swipePanning||c.data.wheelZooming||c.hoverData.draggingEles,q=c.hideEdgesOnViewport&&R,V=[];if(V[c.NODE]=!f[c.NODE]&&g&&!c.clearedForMotionBlur[c.NODE]||c.clearingMotionBlur,V[c.NODE]&&(c.clearedForMotionBlur[c.NODE]=!0),V[c.DRAG]=!f[c.DRAG]&&g&&!c.clearedForMotionBlur[c.DRAG]||c.clearingMotionBlur,V[c.DRAG]&&(c.clearedForMotionBlur[c.DRAG]=!0),f[c.NODE]||o||s||V[c.NODE]){var F=g&&!V[c.NODE]&&1!==y,z=n||(F?c.data.bufferContexts[c.MOTIONBLUR_BUFFER_NODE]:p.contexts[c.NODE]),j=g&&!F?"motionBlur":void 0;r(z,j),q?c.drawCachedNodes(z,M.nondrag,d,A):c.drawLayeredElements(z,M.nondrag,d,A),o||g||(f[c.NODE]=!1)}if(!s&&(f[c.DRAG]||o||V[c.DRAG])){var F=g&&!V[c.DRAG]&&1!==y,z=n||(F?c.data.bufferContexts[c.MOTIONBLUR_BUFFER_DRAG]:p.contexts[c.DRAG]);r(z,g&&!F?"motionBlur":void 0),q?c.drawCachedNodes(z,M.drag,d,A):c.drawCachedElements(z,M.drag,d,A),o||g||(f[c.DRAG]=!1)}if(c.showFps||!s&&f[c.SELECT_BOX]&&!o){var z=n||p.contexts[c.SELECT_BOX];if(r(z),1==c.selection[4]&&(c.hoverData.selecting||c.touchData.selecting)){var _=c.cy.zoom(),X=E["selection-box-border-width"].value/_;z.lineWidth=X,z.fillStyle="rgba("+E["selection-box-color"].value[0]+","+E["selection-box-color"].value[1]+","+E["selection-box-color"].value[2]+","+E["selection-box-opacity"].value+")",z.fillRect(c.selection[0],c.selection[1],c.selection[2]-c.selection[0],c.selection[3]-c.selection[1]),X>0&&(z.strokeStyle="rgba("+E["selection-box-border-color"].value[0]+","+E["selection-box-border-color"].value[1]+","+E["selection-box-border-color"].value[2]+","+E["selection-box-opacity"].value+")",z.strokeRect(c.selection[0],c.selection[1],c.selection[2]-c.selection[0],c.selection[3]-c.selection[1]))}if(p.bgActivePosistion&&!c.hoverData.selecting){var _=c.cy.zoom(),Y=p.bgActivePosistion;z.fillStyle="rgba("+E["active-bg-color"].value[0]+","+E["active-bg-color"].value[1]+","+E["active-bg-color"].value[2]+","+E["active-bg-opacity"].value+")",z.beginPath(),z.arc(Y.x,Y.y,E["active-bg-size"].pfValue/_,0,2*Math.PI),z.fill()}var W=c.lastRedrawTime;if(c.showFps&&W){W=Math.round(W);var $=Math.round(1e3/W);z.setTransform(1,0,0,1,0,0),z.fillStyle="rgba(255, 0, 0, 0.75)",z.strokeStyle="rgba(255, 0, 0, 0.75)",z.lineWidth=1,z.fillText("1 frame = "+W+" ms = "+$+" fps",0,20);var H=60;z.strokeRect(0,30,250,20),z.fillRect(0,30,250*Math.min($/H,1),20)}o||(f[c.SELECT_BOX]=!1)}if(g&&1!==y){var U=p.contexts[c.NODE],Z=c.data.bufferCanvases[c.MOTIONBLUR_BUFFER_NODE],G=p.contexts[c.DRAG],Q=c.data.bufferCanvases[c.MOTIONBLUR_BUFFER_DRAG],K=function(e,r,n){e.setTransform(1,0,0,1,0,0),n||!w?e.clearRect(0,0,c.canvasWidth,c.canvasHeight):t(e,0,0,c.canvasWidth,c.canvasHeight);var i=y;e.drawImage(r,0,0,c.canvasWidth*i,c.canvasHeight*i,0,0,c.canvasWidth,c.canvasHeight)};(f[c.NODE]||V[c.NODE])&&(K(U,Z,V[c.NODE]),f[c.NODE]=!1),(f[c.DRAG]||V[c.DRAG])&&(K(G,Q,V[c.DRAG]),f[c.DRAG]=!1)}c.prevViewport=T,c.clearingMotionBlur&&(c.clearingMotionBlur=!1,c.motionBlurCleared=!0,c.motionBlur=!0),g&&(c.motionBlurTimeout=setTimeout(function(){c.motionBlurTimeout=null,c.clearedForMotionBlur[c.NODE]=!1,c.clearedForMotionBlur[c.DRAG]=!1,c.motionBlur=!1,c.clearingMotionBlur=!v,c.mbFrames=0,f[c.NODE]=!0,f[c.DRAG]=!0,c.redraw()},a)),c.drawingImage=!1,n||c.initrender||(c.initrender=!0,h.trigger("initrender")),n||h.trigger("render")},t.exports=n},{"../../../util":100}],71:[function(e,t,r){"use strict";var n=e("../../../math"),i={};i.drawPolygonPath=function(e,t,r,n,i,a){var o=n/2,s=i/2;e.beginPath&&e.beginPath(),e.moveTo(t+o*a[0],r+s*a[1]);for(var l=1;li)i=u;else if(y>=d||i>c)return null;var m=Math.pow(2,i),w=t.h*m,E=t.w*m,_=g.imgCaches=g.imgCaches||{},P=_[i];if(P)return P;var S;if(S=s>=w?s:l>=w?l:Math.ceil(w/l)*l,w>v||E>f||!b&&e.isEdge()||!x&&e.isParent())return null;var k=o.getTextureQueue(S),D=k[k.length-2],C=function(){return o.recycleTexture(S,E)||o.addTexture(S,E)};D||(D=k[k.length-1]),D||(D=C()),D.width-D.usedWidth=O;O++){var A=_[O];if(A){M=A;break}}var R=M&&M.level===i+1?M:null,q=function(){D.context.drawImage(R.texture.canvas,R.x,0,R.width,R.height,D.usedWidth,0,E,w)};if(B(R))q();else if(B(M)){if(!I)return o.queueElement(e,t,M.level-1),M;for(var O=M.level;O>i;O--)R=o.getElement(e,t,r,O,T.downscale);q()}else{var V;if(!z&&!I&&!L)for(var O=i-1;O>=u;O--){var A=_[O];if(A){V=A;break}}if(B(V))return o.queueElement(e,t,i),V;D.context.translate(D.usedWidth,0),D.context.scale(m,m),p.drawElement(D.context,e,t,N),D.context.scale(1/m,1/m),D.context.translate(-D.usedWidth,0)}return P=_[i]={ele:e,x:D.usedWidth,texture:D,level:i,scale:m,width:E,height:w,scaledLabelShown:N},D.usedWidth+=Math.ceil(E+h),D.eleCaches.push(P),o.checkTextureFullness(D),P},C.invalidateElement=function(e){var t=this,r=e._private.rscratch.imgCaches;if(r)for(var n=u;c>=n;n++){var a=r[n];if(a){var o=a.texture;o.invalidatedWidth+=a.width,r[n]=null,i.removeFromArray(o.eleCaches,a),t.checkTextureUtility(o)}}},C.checkTextureUtility=function(e){e.invalidatedWidth>=g*e.width&&this.retireTexture(e)},C.checkTextureFullness=function(e){var t=this,r=t.getTextureQueue(e.height);e.usedWidth/e.width>y&&e.fullnessChecks>=m?i.removeFromArray(r,e):e.fullnessChecks++},C.retireTexture=function(e){var t=this,r=e.height,n=t.getTextureQueue(r);i.removeFromArray(n,e),e.retired=!0;for(var a=e.eleCaches,o=0;o=t)return s.retired=!1,s.usedWidth=0,s.invalidatedWidth=0,s.fullnessChecks=0,i.clearArray(s.eleCaches),s.context.clearRect(0,0,s.width,s.height),i.removeFromArray(a,s),n.push(s),s}},C.queueElement=function(e,t,r){var i=this,a=i.getElementQueue(),o=i.getElementIdToQueue(),s=e.id(),l=o[s];if(l)l.level=Math.max(l.level,r),l.reqs++,a.updateItem(l);else{var u={ele:e,bb:t,position:n.copyPosition(e.position()),level:r,reqs:1};e.isEdge()&&(u.positions={source:n.copyPosition(e.source().position()),target:n.copyPosition(e.target().position())}),a.push(u),o[s]=u}},C.dequeue=function(e,t){for(var r=this,i=r.getElementQueue(),a=r.getElementIdToQueue(),o=[],s=0;k>s&&i.size()>0;s++){var l=i.pop();a[l.ele.id()]=null,o.push(l);var u,c=l.ele;u=(!c.isEdge()||n.arePositionsSame(c.source().position(),l.positions.source)&&n.arePositionsSame(c.target().position(),l.positions.target))&&n.arePositionsSame(c.position(),l.position)?l.bb:c.boundingBox(),r.getElement(l.ele,u,e,l.level,T.dequeue)}return o},C.onDequeue=function(e){this.onDequeues.push(e)},C.offDequeue=function(e){i.removeFromArray(this.onDequeues,e)},C.setupDequeueing=o.setupDequeueing({deqRedrawThreshold:S,deqCost:w,deqAvgCost:E,deqNoDrawCost:_,deqFastCost:P,deq:function(e,t,r){return e.dequeue(t,r)},onDeqd:function(e,t){for(var r=0;r0&&o>0){p.clearRect(0,0,a,o),e.bg&&(p.fillStyle=e.bg,p.rect(0,0,a,o),p.fill()),p.globalCompositeOperation="source-over";var f=this.getCachedZSortedEles();if(e.full)p.translate(-i.x1*u,-i.y1*u),p.scale(u,u),this.drawElements(p,f);else{var v=t.pan(),g={x:v.x*u,y:v.y*u};u*=t.zoom(),p.translate(g.x,g.y),p.scale(u,u),this.drawElements(p,f)}}return h},i.png=function(e){return this.bufferCanvasImage(e).toDataURL("image/png")},i.jpg=function(e){return this.bufferCanvasImage(e).toDataURL("image/jpeg")},t.exports=i},{"../../../is":83}],74:[function(e,t,r){"use strict";function n(e){var t=this;t.data={canvases:new Array(u.CANVAS_LAYERS),contexts:new Array(u.CANVAS_LAYERS),canvasNeedsRedraw:new Array(u.CANVAS_LAYERS),bufferCanvases:new Array(u.BUFFER_COUNT),bufferContexts:new Array(u.CANVAS_LAYERS)},t.data.canvasContainer=document.createElement("div");var r=t.data.canvasContainer.style;t.data.canvasContainer.setAttribute("style","-webkit-tap-highlight-color: rgba(0,0,0,0);"),r.position="relative",r.zIndex="0",r.overflow="hidden";var n=e.cy.container();n.appendChild(t.data.canvasContainer),n.setAttribute("style",(n.getAttribute("style")||"")+"-webkit-tap-highlight-color: rgba(0,0,0,0);");for(var i=0;i0&&t.data.lyrTxrCache.invalidateElements(r)})}var i=e("../../../util"),a=e("../../../is"),o=e("./ele-texture-cache"),s=e("./layered-texture-cache"),l=n,u=n.prototype;u.CANVAS_LAYERS=3,u.SELECT_BOX=0,u.DRAG=1,u.NODE=2,u.BUFFER_COUNT=3,u.TEXTURE_BUFFER=0,u.MOTIONBLUR_BUFFER_NODE=1,u.MOTIONBLUR_BUFFER_DRAG=2,u.redrawHint=function(e,t){var r=this;switch(e){case"eles":r.data.canvasNeedsRedraw[u.NODE]=t;break;case"drag":r.data.canvasNeedsRedraw[u.DRAG]=t;break;case"select":r.data.canvasNeedsRedraw[u.SELECT_BOX]=t}};var c="undefined"!=typeof Path2D;u.path2dEnabled=function(e){return void 0===e?this.pathsEnabled:void(this.pathsEnabled=!!e)},u.usePaths=function(){return c&&this.pathsEnabled},[e("./arrow-shapes"),e("./drawing-elements"),e("./drawing-edges"),e("./drawing-images"),e("./drawing-label-text"),e("./drawing-nodes"),e("./drawing-redraw"),e("./drawing-shapes"),e("./export-image"),e("./node-shapes")].forEach(function(e){i.extend(u,e)}),t.exports=l},{"../../../is":83,"../../../util":100,"./arrow-shapes":64,"./drawing-edges":65,"./drawing-elements":66,"./drawing-images":67,"./drawing-label-text":68,"./drawing-nodes":69,"./drawing-redraw":70,"./drawing-shapes":71,"./ele-texture-cache":72,"./export-image":73,"./layered-texture-cache":75,"./node-shapes":76}],75:[function(e,t,r){"use strict";function n(e,t){null!=e.imageSmoothingEnabled?e.imageSmoothingEnabled=t:(e.webkitImageSmoothingEnabled=t,e.mozImageSmoothingEnabled=t,e.msImageSmoothingEnabled=t)}var i=e("../../../util"),a=e("../../../math"),o=e("../../../heap"),s=e("../../../is"),l=e("./texture-cache-defs"),u=1,c=-4,d=2,h=3.99,p=50,f=50,v=!0,g=.15,y=.1,m=.9,b=.9,x=1,w=250,E=16e6,_=!0,P=!0,S=!0,k=function(e,t){var r=this,n=r.renderer=e;r.layersByLevel={},r.firstGet=!0,r.lastInvalidationTime=i.performanceNow()-2*w,r.skipping=!1,n.beforeRender(function(e,t){t-r.lastInvalidationTime<=w?r.skipping=!0:r.skipping=!1});var a=function(e,t){return t.reqs-e.reqs};r.layersQueue=new o(a),r.eleTxrCache=t,r.setupEleCacheInvalidation(),r.setupDequeueing()},T=k.prototype,D=0,C=Math.pow(2,53)-1;T.makeLayer=function(e,t){var r=Math.pow(2,t),n=Math.ceil(e.w*r),i=Math.ceil(e.h*r),a=document.createElement("canvas");a.width=n,a.height=i;var o={id:D=++D%C,bb:e,level:t,width:n,height:i,canvas:a,context:a.getContext("2d"),eles:[],elesQueue:[],reqs:0},s=o.context,l=-o.bb.x1,u=-o.bb.y1;return s.scale(r,r),s.translate(l,u),o},T.getLayers=function(e,t,r){var n=this,o=n.renderer,s=o.cy,l=s.zoom(),p=n.firstGet;if(n.firstGet=!1,null==r)if(r=Math.ceil(a.log2(l*t)),c>r)r=c;else if(l>=h||r>d)return null;n.validateLayersElesOrdering(r,e);var f,v,g=n.layersByLevel,y=Math.pow(2,r),m=g[r]=g[r]||[],b=n.levelIsComplete(r,e),x=function(){var t=function(t){return n.validateLayersElesOrdering(t,e),n.levelIsComplete(t,e)?(v=g[t],!0):void 0},a=function(e){if(!v)for(var n=r+e;n>=c&&d>=n&&!t(n);n+=e);};a(1),a(-1);for(var o=m.length-1;o>=0;o--){var s=m[o];s.invalid&&i.removeFromArray(m,s)}};if(b)return m;x();var w=function(){if(!f){f=a.makeBoundingBox();for(var t=0;tE)return null;var a=n.makeLayer(f,r);if(null!=t){var o=m.indexOf(t)+1;m.splice(o,0,a)}else(void 0===e.insert||e.insert)&&m.unshift(a);return a};if(n.skipping&&!p)return null;for(var S=null,k=e.length/u,T=_&&!p,D=0;D=k||!a.boundingBoxInBoundingBox(S.bb,C.boundingBox()))&&(S=P({insert:!0,after:S}),!S))return null;v||T?n.queueLayer(S,C):n.drawEleInLayer(S,C,r,t),S.eles.push(C),N[r]=S}}return v?v:T?null:m},T.getEleLevelForLayerLevel=function(e,t){return e},T.drawEleInLayer=function(e,t,r,i){var a=this,o=this.renderer,s=e.context,l=t.boundingBox();if(0!==l.w&&0!==l.h){var u=a.eleTxrCache,c=P?u.reasons.highQuality:void 0;r=a.getEleLevelForLayerLevel(r,i);var d=S?u.getElement(t,l,null,r,c):null;d?(v&&n(s,!1),s.drawImage(d.texture.canvas,d.x,0,d.width,d.height,l.x1,l.y1,l.w,l.h),v&&n(s,!0)):o.drawElement(s,t)}},T.levelIsComplete=function(e,t){var r=this,n=r.layersByLevel[e];if(!n||0===n.length)return!1;for(var i=0,a=0;a0)return!1;if(o.invalid)return!1;i+=o.eles.length}return i===t.length},T.validateLayersElesOrdering=function(e,t){var r=this.layersByLevel[e];if(r)for(var n=0;na)this.invalidateLayer(i);else for(var s=a,o=0;o=h;h++){var p=u[h];p&&(a&&r.getEleLevelForLayerLevel(p.level)!==a.level||t(p,o,a))}},T.haveLayers=function(){for(var e=this,t=!1,r=c;d>=r;r++){var n=e.layersByLevel[r];if(n&&n.length>0){t=!0;break}}return t},T.invalidateElements=function(e){var t=this;t.lastInvalidationTime=i.performanceNow(),0!==e.length&&t.haveLayers()&&t.updateElementsInLayers(e,function(e,r,n){t.invalidateLayer(e)})},T.invalidateLayer=function(e){if(this.lastInvalidationTime=i.performanceNow(),!e.invalid){var t=e.level,r=e.eles,n=this.layersByLevel[t];i.removeFromArray(n,e),e.elesQueue=[],e.invalid=!0,e.replacement&&(e.replacement.invalid=!0);for(var a=0;ai&&0!==r.size();){var a=r.peek();if(a.replacement)r.pop();else if(a.replaces&&a!==a.replaces.replacement)r.pop();else if(a.invalid)r.pop();else{var o=a.elesQueue.shift();o&&(t.drawEleInLayer(a,o,a.level,e),i++),0===n.length&&n.push(!0),0===a.elesQueue.length&&(r.pop(),a.reqs=0,a.replaces&&t.applyLayerReplacement(a),t.requestRedraw())}}return n},T.applyLayerReplacement=function(e){var t=this,r=t.layersByLevel[e.level],n=e.replaces,i=r.indexOf(n);if(!(0>i||n.invalid)){r[i]=e;for(var a=0;ac){var y=i-(o?u:0);if(g>=e.deqFastCost*y)break}else if(o){if(v>=e.deqCost*c||v>=e.deqAvgCost*u)break}else if(g>=e.deqNoDrawCost*i)break;var m=e.deq(t,p,h);if(!(m.length>0))break;for(var b=0;b0&&(e.onDeqd(t,d),!o&&e.shouldRedraw(t,d,p,h)&&a())},s=e.priority||n.noop;r.beforeRender(o,s(t))}}}}},{"../../../util":100}],78:[function(e,t,r){"use strict";t.exports=[{name:"null",impl:e("./null")},{name:"base",impl:e("./base")},{name:"canvas",impl:e("./canvas")}]},{"./base":60,"./canvas":74,"./null":79}],79:[function(e,t,r){"use strict";function n(e){this.options=e,this.notifications=0}var i=function(){};n.prototype={recalculateRenderedStyle:i,notify:function(){this.notifications++},init:i},t.exports=n},{}],80:[function(e,t,r){/*! Weaver licensed under MIT (https://tldrlegal.com/license/mit-license), copyright Max Franz */ +"use strict";var n=e("./is"),i=e("./util"),a=e("./thread"),o=e("./promise"),s=e("./define"),l=function(t){if(!(this instanceof l))return new l(t);this._private={pass:[]};var r=4;if(n.number(t),"undefined"!=typeof navigator&&null!=navigator.hardwareConcurrency)t=navigator.hardwareConcurrency;else try{t=e("os").cpus().length}catch(i){t=r}for(var o=0;t>o;o++)this[o]=new a;this.length=t},u=l.prototype;i.extend(u,{instanceString:function(){return"fabric"},require:function(e,t){for(var r=0;re?-1:e>t?1:0},t.require(e,"_$_$_cmp"),t.spread(function(e){var t=e.sort(_$_$_cmp);resolve(t)}).then(function(t){for(var i=function(n,i,a){i=Math.min(i,r),a=Math.min(a,r);for(var o=n,s=i,l=[],u=o;a>u;u++){var c=t[n],d=t[i];s>n&&(i>=a||e(c,d)<=0)?(l.push(c),n++):(l.push(d),i++)}for(var u=0;ua;a*=2)for(var o=0;r>o;o+=2*a)i(o,o+a,o+2*a);return t})}});var c=function(e){return e=e||{},function(t,r){var n=this._private.pass.shift();return this.random().pass(n)[e.threadFn](t,r)}};i.extend(u,{randomMap:c({threadFn:"map"}),reduce:c({threadFn:"reduce"}),reduceRight:c({threadFn:"reduceRight"})});var d=u;d.promise=d.run,d.terminate=d.halt=d.stop,d.include=d.require,i.extend(u,{on:s.on(),one:s.on({unbindSelfOnTrigger:!0}),off:s.off(),trigger:s.trigger()}),s.eventAliasesOn(u),t.exports=l},{"./define":44,"./is":83,"./promise":86,"./thread":98,"./util":100,os:void 0}],81:[function(e,t,r){/*! +Ported by Xueqiao Xu ; + +PSF LICENSE AGREEMENT FOR PYTHON 2.7.2 + +1. This LICENSE AGREEMENT is between the Python Software Foundation (“PSF”), and the Individual or Organization (“Licensee”) accessing and otherwise using Python 2.7.2 software in source or binary form and its associated documentation. +2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python 2.7.2 alone or in any derivative version, provided, however, that PSF’s License Agreement and PSF’s notice of copyright, i.e., “Copyright © 2001-2012 Python Software Foundation; All Rights Reserved” are retained in Python 2.7.2 alone or in any derivative version prepared by Licensee. +3. In the event Licensee prepares a derivative work that is based on or incorporates Python 2.7.2 or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python 2.7.2. +4. PSF is making Python 2.7.2 available to Licensee on an “AS IS” basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 2.7.2 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 2.7.2 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.7.2, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. +6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. +7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. +8. By copying, installing or otherwise using Python 2.7.2, Licensee agrees to be bound by the terms and conditions of this License Agreement. +*/ +"use strict";var n,i,a,o,s,l,u,c,d,h,p,f,v,g,y;a=Math.floor,h=Math.min,i=function(e,t){return t>e?-1:e>t?1:0},d=function(e,t,r,n,o){var s;if(null==r&&(r=0),null==o&&(o=i),0>r)throw new Error("lo must be non-negative");for(null==n&&(n=e.length);n>r;)s=a((r+n)/2),o(t,e[s])<0?n=s:r=s+1;return[].splice.apply(e,[r,r-r].concat(t)),t},l=function(e,t,r){return null==r&&(r=i),e.push(t),g(e,0,e.length-1,r)},s=function(e,t){var r,n;return null==t&&(t=i),r=e.pop(),e.length?(n=e[0],e[0]=r,y(e,0,t)):n=r,n},c=function(e,t,r){var n;return null==r&&(r=i),n=e[0],e[0]=t,y(e,0,r),n},u=function(e,t,r){var n;return null==r&&(r=i),e.length&&r(e[0],t)<0&&(n=[e[0],t],t=n[0],e[0]=n[1],y(e,0,r)),t},o=function(e,t){var r,n,o,s,l,u;for(null==t&&(t=i),s=function(){u=[];for(var t=0,r=a(e.length/2);r>=0?r>t:t>r;r>=0?t++:t--)u.push(t);return u}.apply(this).reverse(),l=[],n=0,o=s.length;o>n;n++)r=s[n],l.push(y(e,r,t));return l},v=function(e,t,r){var n;return null==r&&(r=i),n=e.indexOf(t),-1!==n?(g(e,0,n,r),y(e,n,r)):void 0},p=function(e,t,r){var n,a,s,l,c;if(null==r&&(r=i),a=e.slice(0,t),!a.length)return a;for(o(a,r),c=e.slice(t),s=0,l=c.length;l>s;s++)n=c[s],u(a,n,r);return a.sort(r).reverse()},f=function(e,t,r){var n,a,l,u,c,p,f,v,g,y;if(null==r&&(r=i),10*t<=e.length){if(u=e.slice(0,t).sort(r),!u.length)return u;for(l=u[u.length-1],v=e.slice(t),c=0,f=v.length;f>c;c++)n=v[c],r(n,l)<0&&(d(u,n,0,null,r),u.pop(),l=u[u.length-1]);return u}for(o(e,r),y=[],a=p=0,g=h(t,e.length);g>=0?g>p:p>g;a=g>=0?++p:--p)y.push(s(e,r));return y},g=function(e,t,r,n){var a,o,s;for(null==n&&(n=i),a=e[r];r>t&&(s=r-1>>1,o=e[s],n(a,o)<0);)e[r]=o,r=s;return e[r]=a},y=function(e,t,r){var n,a,o,s,l;for(null==r&&(r=i),a=e.length,l=t,o=e[t],n=2*t+1;a>n;)s=n+1,a>s&&!(r(e[n],e[s])<0)&&(n=s),e[t]=e[n],t=n,n=2*t+1;return e[t]=o,g(e,l,t,r)},n=function(){function e(e){this.cmp=null!=e?e:i,this.nodes=[]}return e.push=l,e.pop=s,e.replace=c,e.pushpop=u,e.heapify=o,e.updateItem=v,e.nlargest=p,e.nsmallest=f,e.prototype.push=function(e){return l(this.nodes,e,this.cmp)},e.prototype.pop=function(){return s(this.nodes,this.cmp)},e.prototype.peek=function(){return this.nodes[0]},e.prototype.contains=function(e){return-1!==this.nodes.indexOf(e)},e.prototype.replace=function(e){return c(this.nodes,e,this.cmp)},e.prototype.pushpop=function(e){return u(this.nodes,e,this.cmp)},e.prototype.heapify=function(){return o(this.nodes,this.cmp)},e.prototype.updateItem=function(e){return v(this.nodes,e,this.cmp)},e.prototype.clear=function(){return this.nodes=[]},e.prototype.empty=function(){return 0===this.nodes.length},e.prototype.size=function(){return this.nodes.length},e.prototype.clone=function(){var t;return t=new e,t.nodes=this.nodes.slice(0),t},e.prototype.toArray=function(){return this.nodes.slice(0)},e.prototype.insert=e.prototype.push,e.prototype.top=e.prototype.peek,e.prototype.front=e.prototype.peek,e.prototype.has=e.prototype.contains,e.prototype.copy=e.prototype.clone,e}(),t.exports=n},{}],82:[function(e,t,r){"use strict";e("./-preamble");var n=e("./window"),i=e("./is"),a=e("./core"),o=e("./extension"),s=e("./jquery-plugin"),l=e("./stylesheet"),u=e("./thread"),c=e("./fabric"),d=function(e){return void 0===e&&(e={}),i.plainObject(e)?new a(e):i.string(e)?o.apply(o,arguments):void 0};d.version=e("./version.json"),n&&n.jQuery&&s(n.jQuery,d),d.registerJquery=function(e){s(e,d)},d.stylesheet=d.Stylesheet=l,d.thread=d.Thread=u,d.fabric=d.Fabric=c,t.exports=d},{"./-preamble":1,"./core":37,"./extension":46,"./fabric":80,"./is":83,"./jquery-plugin":84,"./stylesheet":97,"./thread":98,"./version.json":106,"./window":107}],83:[function(e,t,r){"use strict";var n=e("./window"),i=n?n.navigator:null,a=n?n.document:null,o="string",s=typeof{},l="function",u=typeof HTMLElement,c=function(e){return e&&e.instanceString&&d.fn(e.instanceString)?e.instanceString():null},d={defined:function(e){return null!=e},string:function(e){return null!=e&&typeof e==o},fn:function(e){return null!=e&&typeof e===l},array:function(e){return Array.isArray?Array.isArray(e):null!=e&&e instanceof Array},plainObject:function(e){return null!=e&&typeof e===s&&!d.array(e)&&e.constructor===Object},object:function(e){return null!=e&&typeof e===s},number:function(e){return null!=e&&"number"==typeof e&&!isNaN(e)},integer:function(e){return d.number(e)&&Math.floor(e)===e},bool:function(e){return null!=e&&typeof e==typeof!0},htmlElement:function(e){return"undefined"===u?void 0:null!=e&&e instanceof HTMLElement},elementOrCollection:function(e){return d.element(e)||d.collection(e)},element:function(e){return"collection"===c(e)&&e._private.single},collection:function(e){return"collection"===c(e)&&!e._private.single},core:function(e){return"core"===c(e)},style:function(e){return"style"===c(e)},stylesheet:function(e){return"stylesheet"===c(e)},event:function(e){return"event"===c(e)},thread:function(e){return"thread"===c(e)},fabric:function(e){return"fabric"===c(e)},emptyString:function(e){return void 0===e||null===e?!0:!(""!==e&&!e.match(/^\s+$/))},nonemptyString:function(e){return!(!e||!d.string(e)||""===e||e.match(/^\s+$/))},domElement:function(e){return"undefined"==typeof HTMLElement?!1:e instanceof HTMLElement},boundingBox:function(e){return d.plainObject(e)&&d.number(e.x1)&&d.number(e.x2)&&d.number(e.y1)&&d.number(e.y2)},promise:function(e){return d.object(e)&&d.fn(e.then)},touch:function(){return n&&("ontouchstart"in n||n.DocumentTouch&&a instanceof DocumentTouch)},gecko:function(){return n&&("undefined"!=typeof InstallTrigger||"MozAppearance"in a.documentElement.style)},webkit:function(){return n&&("undefined"!=typeof webkitURL||"WebkitAppearance"in a.documentElement.style)},chromium:function(){return n&&"undefined"!=typeof chrome},khtml:function(){return i&&i.vendor.match(/kde/i)},khtmlEtc:function(){return d.khtml()||d.webkit()||d.chromium()},ms:function(){return i&&i.userAgent.match(/msie|trident|edge/i)},windows:function(){return i&&i.appVersion.match(/Win/i)},mac:function(){return i&&i.appVersion.match(/Mac/i)},linux:function(){return i&&i.appVersion.match(/Linux/i)},unix:function(){return i&&i.appVersion.match(/X11/i)}};t.exports=d},{"./window":107}],84:[function(e,t,r){"use strict";var n=e("./is"),i=function(e){var t=e[0]._cyreg=e[0]._cyreg||{};return t},a=function(e,t){e&&(e.fn.cytoscape||(e.fn.cytoscape=function(r){var a=e(this);if("get"===r)return i(a).cy;if(n.fn(r)){var o=r,s=i(a).cy;if(s&&s.isReady())s.trigger("ready",[],o);else{var l=i(a),u=l.readies=l.readies||[];u.push(o)}}else if(n.plainObject(r))return a.each(function(){var n=e.extend({},r,{container:e(this)[0]});t(n)})},e.cytoscape=t,null==e.fn.cy&&null==e.cy&&(e.fn.cy=e.fn.cytoscape,e.cy=e.cytoscape)))};t.exports=a},{"./is":83}],85:[function(e,t,r){"use strict";var n={};n.arePositionsSame=function(e,t){return e.x===t.x&&e.y===t.y},n.copyPosition=function(e){return{x:e.x,y:e.y}},n.array2point=function(e){return{x:e[0],y:e[1]}},n.deg2rad=function(e){return Math.PI*e/180},n.log2=Math.log2||function(e){return Math.log(e)/Math.log(2)},n.signum=function(e){return e>0?1:0>e?-1:0},n.dist=function(e,t){return Math.sqrt(n.sqdist(e,t))},n.sqdist=function(e,t){var r=t.x-e.x,n=t.y-e.y;return r*r+n*n},n.qbezierAt=function(e,t,r,n){return(1-n)*(1-n)*e+2*(1-n)*n*t+n*n*r},n.qbezierPtAt=function(e,t,r,i){return{x:n.qbezierAt(e.x,t.x,r.x,i),y:n.qbezierAt(e.y,t.y,r.y,i)}},n.lineAt=function(e,t,r,i){var a={x:t.x-e.x,y:t.y-e.y},o=n.dist(e,t),s={x:a.x/o,y:a.y/o};r=null==r?0:r;var i=null!=i?i:r*o;return{x:e.x+s.x*i,y:e.y+s.y*i}},n.lineAtDist=function(e,t,r){return n.lineAt(e,t,void 0,r)},n.triangleAngle=function(e,t,r){var i=n.dist(t,r),a=n.dist(e,r),o=n.dist(e,t);return Math.acos((i*i+a*a-o*o)/(2*i*a))},n.bound=function(e,t,r){return Math.max(e,Math.min(r,t))},n.makeBoundingBox=function(e){if(null==e)return{x1:1/0,y1:1/0,x2:-(1/0),y2:-(1/0),w:0,h:0};if(null!=e.x1&&null!=e.y1){if(null!=e.x2&&null!=e.y2&&e.x2>=e.x1&&e.y2>=e.y1)return{x1:e.x1,y1:e.y1,x2:e.x2,y2:e.y2,w:e.x2-e.x1,h:e.y2-e.y1};if(null!=e.w&&null!=e.h&&e.w>=0&&e.h>=0)return{x1:e.x1,y1:e.y1,x2:e.x1+e.w,y2:e.y1+e.h,w:e.w,h:e.h}}},n.updateBoundingBox=function(e,t){e.x1=Math.min(e.x1,t.x1),e.x2=Math.max(e.x2,t.x2),e.w=e.x2-e.x1,e.y1=Math.min(e.y1,t.y1),e.y2=Math.max(e.y2,t.y2),e.h=e.y2-e.y1},n.expandBoundingBox=function(e,t){return e.x1-=t,e.x2+=t,e.y1-=t,e.y2+=t,e.w=e.x2-e.x1,e.h=e.y2-e.y1,e},n.boundingBoxesIntersect=function(e,t){return e.x1>t.x2?!1:t.x1>e.x2?!1:e.x2t.y2?!1:!(t.y1>e.y2)},n.inBoundingBox=function(e,t,r){return e.x1<=t&&t<=e.x2&&e.y1<=r&&r<=e.y2},n.pointInBoundingBox=function(e,t){return this.inBoundingBox(e,t.x,t.y)},n.boundingBoxInBoundingBox=function(e,t){return n.inBoundingBox(e,t.x1,t.y1)&&n.inBoundingBox(e,t.x2,t.y2)},n.roundRectangleIntersectLine=function(e,t,r,n,i,a,o){var s,l=this.getRoundRectangleRadius(i,a),u=i/2,c=a/2,d=r-u+l-o,h=n-c-o,p=r+u-l+o,f=h;if(s=this.finiteLinesIntersect(e,t,r,n,d,h,p,f,!1),s.length>0)return s;var v=r+u+o,g=n-c+l-o,y=v,m=n+c-l+o;if(s=this.finiteLinesIntersect(e,t,r,n,v,g,y,m,!1),s.length>0)return s;var b=r-u+l-o,x=n+c+o,w=r+u-l+o,E=x;if(s=this.finiteLinesIntersect(e,t,r,n,b,x,w,E,!1),s.length>0)return s;var _=r-u-o,P=n-c+l-o,S=_,k=n+c-l+o;if(s=this.finiteLinesIntersect(e,t,r,n,_,P,S,k,!1),s.length>0)return s;var T,D=r-u+l,C=n-c+l;if(T=this.intersectLineCircle(e,t,r,n,D,C,l+o),T.length>0&&T[0]<=D&&T[1]<=C)return[T[0],T[1]];var M=r+u-l,N=n-c+l;if(T=this.intersectLineCircle(e,t,r,n,M,N,l+o),T.length>0&&T[0]>=M&&T[1]<=N)return[T[0],T[1]];var B=r+u-l,z=n+c-l;if(T=this.intersectLineCircle(e,t,r,n,B,z,l+o),T.length>0&&T[0]>=B&&T[1]>=z)return[T[0],T[1]];var I=r-u+l,L=n+c-l;return T=this.intersectLineCircle(e,t,r,n,I,L,l+o),T.length>0&&T[0]<=I&&T[1]>=L?[T[0],T[1]]:[]},n.inLineVicinity=function(e,t,r,n,i,a,o){var s=o,l=Math.min(r,i),u=Math.max(r,i),c=Math.min(n,a),d=Math.max(n,a);return e>=l-s&&u+s>=e&&t>=c-s&&d+s>=t},n.inBezierVicinity=function(e,t,r,n,i,a,o,s,l){var u={x1:Math.min(r,o,i)-l,x2:Math.max(r,o,i)+l,y1:Math.min(n,s,a)-l,y2:Math.max(n,s,a)+l};return!(eu.x2||tu.y2)},n.solveCubic=function(e,t,r,n,i){t/=e,r/=e,n/=e;var a,o,s,l,u,c,d,h;return o=(3*r-t*t)/9,s=-(27*n)+t*(9*r-2*(t*t)),s/=54,a=o*o*o+s*s,i[1]=0,d=t/3,a>0?(u=s+Math.sqrt(a),u=0>u?-Math.pow(-u,1/3):Math.pow(u,1/3),c=s-Math.sqrt(a),c=0>c?-Math.pow(-c,1/3):Math.pow(c,1/3),i[0]=-d+u+c,d+=(u+c)/2,i[4]=i[2]=-d,d=Math.sqrt(3)*(-c+u)/2,i[3]=d,void(i[5]=-d)):(i[5]=i[3]=0,0===a?(h=0>s?-Math.pow(-s,1/3):Math.pow(s,1/3),i[0]=-d+2*h,void(i[4]=i[2]=-(h+d))):(o=-o,l=o*o*o,l=Math.acos(s/Math.sqrt(l)),h=2*Math.sqrt(o),i[0]=-d+h*Math.cos(l/3),i[2]=-d+h*Math.cos((l+2*Math.PI)/3),void(i[4]=-d+h*Math.cos((l+4*Math.PI)/3))))},n.sqdistToQuadraticBezier=function(e,t,r,n,i,a,o,s){var l=1*r*r-4*r*i+2*r*o+4*i*i-4*i*o+o*o+n*n-4*n*a+2*n*s+4*a*a-4*a*s+s*s,u=9*r*i-3*r*r-3*r*o-6*i*i+3*i*o+9*n*a-3*n*n-3*n*s-6*a*a+3*a*s,c=3*r*r-6*r*i+r*o-r*e+2*i*i+2*i*e-o*e+3*n*n-6*n*a+n*s-n*t+2*a*a+2*a*t-s*t,d=1*r*i-r*r+r*e-i*e+n*a-n*n+n*t-a*t,h=[];this.solveCubic(l,u,c,d,h);for(var p=1e-7,f=[],v=0;6>v;v+=2)Math.abs(h[v+1])=0&&h[v]<=1&&f.push(h[v]);f.push(1),f.push(0);for(var g,y,m,b,x=-1,w=0;w=0?x>b&&(x=b,g=f[w]):(x=b,g=f[w]);return x},n.sqdistToFiniteLine=function(e,t,r,n,i,a){var o=[e-r,t-n],s=[i-r,a-n],l=s[0]*s[0]+s[1]*s[1],u=o[0]*o[0]+o[1]*o[1],c=o[0]*s[0]+o[1]*s[1],d=c*c/l;return 0>c?u:d>l?(e-i)*(e-i)+(t-a)*(t-a):u-d},n.pointInsidePolygonPoints=function(e,t,r){for(var n,i,a,o,s,l=0,u=0,c=0;c=e&&e>=a||e>=n&&a>=e))continue;s=(e-n)/(a-n)*(o-i)+i,s>t&&l++,t>s&&u++}return l%2!==0},n.pointInsidePolygon=function(e,t,r,i,a,o,s,l,u){var c,d=new Array(r.length);null!=l[0]?(c=Math.atan(l[1]/l[0]),l[0]<0?c+=Math.PI/2:c=-c-Math.PI/2):c=l;for(var h=Math.cos(-c),p=Math.sin(-c),f=0;f0){var g=this.expandPolygon(d,-u);v=this.joinLines(g)}else v=d;return n.pointInsidePolygonPoints(e,t,v)},n.joinLines=function(e){for(var t,r,n,i,a,o,s,l,u=new Array(e.length/2),c=0;cu)return[];var c=u/l;return[(r-e)*c+e,(n-t)*c+t]},n.intersectLineCircle=function(e,t,r,n,i,a,o){var s=[r-e,n-t],l=[i,a],u=[e-i,t-a],c=s[0]*s[0]+s[1]*s[1],d=2*(u[0]*s[0]+u[1]*s[1]),l=u[0]*u[0]+u[1]*u[1]-o*o,h=d*d-4*c*l;if(0>h)return[];var p=(-d+Math.sqrt(h))/(2*c),f=(-d-Math.sqrt(h))/(2*c),v=Math.min(p,f),g=Math.max(p,f),y=[];if(v>=0&&1>=v&&y.push(v),g>=0&&1>=g&&y.push(g),0===y.length)return[];var m=y[0]*s[0]+e,b=y[0]*s[1]+t;if(y.length>1){if(y[0]==y[1])return[m,b];var x=y[1]*s[0]+e,w=y[1]*s[1]+t;return[m,b,x,w]}return[m,b]},n.findCircleNearPoint=function(e,t,r,n,i){var a=n-e,o=i-t,s=Math.sqrt(a*a+o*o),l=a/s,u=o/s;return[e+l*r,t+u*r]},n.findMaxSqDistanceToOrigin=function(e){for(var t,r=1e-6,n=0;nr&&(r=t);return r},n.midOfThree=function(e,t,r){return e>=t&&r>=e||e>=r&&t>=e?e:t>=e&&r>=t||t>=r&&e>=t?t:r},n.finiteLinesIntersect=function(e,t,r,n,i,a,o,s,l){var u=e-i,c=r-e,d=o-i,h=t-a,p=n-t,f=s-a,v=d*h-f*u,g=c*h-p*u,y=f*c-d*p;if(0!==y){var m=v/y,b=g/y,x=.001,w=0-x,E=1+x;return m>=w&&E>=m&&b>=w&&E>=b?[e+m*c,t+m*p]:l?[e+m*c,t+m*p]:[]}return 0===v||0===g?this.midOfThree(e,r,o)===o?[o,s]:this.midOfThree(e,r,i)===i?[i,a]:this.midOfThree(i,o,r)===r?[r,n]:[]:[]},n.polygonIntersectLine=function(e,t,r,i,a,o,s,l){for(var u,c=[],d=new Array(r.length),h=0;h0){var f=n.expandPolygon(d,-l);p=n.joinLines(f)}else p=d;for(var v,g,y,m,h=0;ha&&(a=1e-5),[t[0]+a*n[0],t[1]+a*n[1]]},n.generateUnitNgonPointsFitToSquare=function(e,t){var r=n.generateUnitNgonPoints(e,t);return r=n.fitPolygonToSquare(r)},n.fitPolygonToSquare=function(e){for(var t,r,n=e.length/2,i=1/0,a=1/0,o=-(1/0),s=-(1/0),l=0;n>l;l++)t=e[2*l],r=e[2*l+1],i=Math.min(i,t),o=Math.max(o,t),a=Math.min(a,r),s=Math.max(s,r);for(var u=2/(o-i),c=2/(s-a),l=0;n>l;l++)t=e[2*l]=e[2*l]*u,r=e[2*l+1]=e[2*l+1]*c,i=Math.min(i,t),o=Math.max(o,t),a=Math.min(a,r),s=Math.max(s,r);if(-1>a)for(var l=0;n>l;l++)r=e[2*l+1]=e[2*l+1]+(-1-a);return e},n.generateUnitNgonPoints=function(e,t){var r=1/e*2*Math.PI,n=e%2===0?Math.PI/2+r/2:Math.PI/2;n+=t;for(var i,a,o,s=new Array(2*e),l=0;e>l;l++)i=l*r+n,a=s[2*l]=Math.cos(i),o=s[2*l+1]=Math.sin(-i);return s},n.getRoundRectangleRadius=function(e,t){return Math.min(e/4,t/4,8)},t.exports=n},{}],86:[function(e,t,r){/*! +Embeddable Minimum Strictly-Compliant Promises/A+ 1.1.1 Thenable +Copyright (c) 2013-2014 Ralf S. Engelschall (http://engelschall.com) +Licensed under The MIT License (http://opensource.org/licenses/MIT) +*/ +"use strict";var n=0,i=1,a=2,o=function(e){return this instanceof o?(this.id="Thenable/1.0.7",this.state=n,this.fulfillValue=void 0,this.rejectReason=void 0,this.onFulfilled=[],this.onRejected=[],this.proxy={then:this.then.bind(this)},void("function"==typeof e&&e.call(this,this.fulfill.bind(this),this.reject.bind(this)))):new o(e)};o.prototype={fulfill:function(e){return s(this,i,"fulfillValue",e)},reject:function(e){return s(this,a,"rejectReason",e)},then:function(e,t){var r=this,n=new o;return r.onFulfilled.push(c(e,n,"fulfill")),r.onRejected.push(c(t,n,"reject")),l(r),n.proxy}};var s=function(e,t,r,i){return e.state===n&&(e.state=t,e[r]=i,l(e)),e},l=function(e){e.state===i?u(e,"onFulfilled",e.fulfillValue):e.state===a&&u(e,"onRejected",e.rejectReason)},u=function(e,t,r){if(0!==e[t].length){var n=e[t];e[t]=[];var i=function(){for(var e=0;e\\?\\@\\[\\]\\^\\`\\{\\|\\}\\~]",comparatorOp:"=|\\!=|>|>=|<|<=|\\$=|\\^=|\\*=",boolOp:"\\?|\\!|\\^",string:'"(?:\\\\"|[^"])+"|'+"'(?:\\\\'|[^'])+'",number:i.regex.number,meta:"degree|indegree|outdegree",separator:"\\s*,\\s*",descendant:"\\s+",child:"\\s+>\\s+",subject:"\\$"};l.variable="(?:[\\w-]|(?:\\\\"+l.metaChar+"))+",l.value=l.string+"|"+l.number,l.className=l.variable,l.id=l.variable;for(var u=function(e){return e.replace(new RegExp("\\\\("+l.metaChar+")","g"),function(e,t,r,n){return t})},c=l.comparatorOp.split("|"),d=0;d=0||"="!==h&&(l.comparatorOp+="|\\!"+h)}var p=[{name:"group",query:!0,regex:"(node|edge|\\*)",populate:function(e){this.group="*"===e?e:e+"s"}},{name:"state",query:!0,regex:"(:selected|:unselected|:locked|:unlocked|:visible|:hidden|:transparent|:grabbed|:free|:removed|:inside|:grabbable|:ungrabbable|:animated|:unanimated|:selectable|:unselectable|:orphan|:nonorphan|:parent|:child|:loop|:simple|:active|:inactive|:touch|:backgrounding|:nonbackgrounding)",populate:function(e){this.colonSelectors.push(e)}},{name:"id",query:!0,regex:"\\#("+l.id+")",populate:function(e){this.ids.push(u(e))}},{name:"className",query:!0,regex:"\\.("+l.className+")",populate:function(e){this.classes.push(u(e))}},{name:"dataExists",query:!0,regex:"\\[\\s*("+l.variable+")\\s*\\]",populate:function(e){this.data.push({field:u(e)})}},{name:"dataCompare",query:!0,regex:"\\[\\s*("+l.variable+")\\s*("+l.comparatorOp+")\\s*("+l.value+")\\s*\\]",populate:function(e,t,r){var n=null!=new RegExp("^"+l.string+"$").exec(r);r=n?r.substring(1,r.length-1):parseFloat(r),this.data.push({field:u(e),operator:t,value:r})}},{name:"dataBool",query:!0,regex:"\\[\\s*("+l.boolOp+")\\s*("+l.variable+")\\s*\\]",populate:function(e,t){this.data.push({field:u(t),operator:e})}},{name:"metaCompare",query:!0,regex:"\\[\\[\\s*("+l.meta+")\\s*("+l.comparatorOp+")\\s*("+l.number+")\\s*\\]\\]",populate:function(e,t,r){this.meta.push({field:u(e),operator:t,value:parseFloat(r)})}},{name:"nextQuery",separator:!0,regex:l.separator,populate:function(){t[++d]=r(),s=null}},{name:"child",separator:!0,regex:l.child,populate:function(){var e=r();e.parent=this,e.subject=s,t[d]=e}},{name:"descendant",separator:!0,regex:l.descendant,populate:function(){var e=r();e.ancestor=this,e.subject=s,t[d]=e}},{name:"subject",modifier:!0,regex:l.subject,populate:function(){return null!=s&&this.subject!=this?(i.error("Redefinition of subject in selector `"+e+"`"),!1):(s=this,void(this.subject=this))}}];t._private.selectorText=e;var f=e,d=0,v=function(e){for(var t,r,i,a=0;a=0&&(d=d.toLowerCase(),h=h.toLowerCase(),s=s.replace("@",""),p=!0);var f=!1;s.indexOf("!")>=0&&(s=s.replace("!",""),f=!0),p&&(l=h.toLowerCase(),c=d.toLowerCase());var v=!1;switch(s){case"*=":a=d.indexOf(h)>=0;break;case"$=":a=d.indexOf(h,d.length-h.length)>=0;break;case"^=":a=0===d.indexOf(h);break;case"=":a=c===l;break;case">":v=!0,a=c>l;break;case">=":v=!0,a=c>=l;break;case"<":v=!0,a=l>c;break;case"<=":v=!0,a=l>=c;break;default:a=!1}!f||null==c&&v||(a=!a)}else if(null!=s)switch(s){case"?":a=t.fieldTruthy(u);break;case"!":a=!t.fieldTruthy(u);break;case"^":a=t.fieldUndefined(u)}else a=!t.fieldUndefined(u);if(!a){r=!1;break}}return r},v=f({name:"data",fieldValue:function(e){return r.data[e]},fieldUndefined:function(e){return void 0===r.data[e]},fieldTruthy:function(e){return!!r.data[e]}});if(!v)return!1;var g=f({name:"meta",fieldValue:function(e){return t[e]()},fieldUndefined:function(e){return null==t[e]()},fieldTruthy:function(e){return!!t[e]()}});if(!g)return!1;if(null!=e.collection){var y=e.collection.hasElementWithId(t.id());if(!y)return!1}if(null!=e.filter&&0===t.collection().filter(e.filter).size())return!1;var m=function(e,t){if(null!=e){var r=!1;if(!i.hasCompoundNodes())return!1;t=t();for(var n=0;n "+n),null!=e.ancestor&&(n=a(e.ancestor)+" "+n),null!=e.child&&(n+=" > "+a(e.child)),null!=e.descendant&&(n+=" "+a(e.descendant)),n},o=0;o1&&o0;if(h||p){var f;h&&p?f=u.properties:h?f=u.properties:p&&(f=u.mappedProperties);for(var v=0;v0){n=!0;break}}t.hasPie=n;var o=e.pstyle("text-transform").strValue,s=e.pstyle("label").strValue,l=e.pstyle("source-label").strValue,u=e.pstyle("target-label").strValue,c=e.pstyle("font-style").strValue,a=e.pstyle("font-size").pfValue+"px",d=e.pstyle("font-family").strValue,h=e.pstyle("font-weight").strValue,p=e.pstyle("text-valign").strValue,f=e.pstyle("text-valign").strValue,v=e.pstyle("text-outline-width").pfValue,g=e.pstyle("text-wrap").strValue,y=e.pstyle("text-max-width").pfValue,m=c+"$"+a+"$"+d+"$"+h+"$"+o+"$"+p+"$"+f+"$"+v+"$"+g+"$"+y;t.labelStyleKey=m,t.sourceLabelKey=m+"$"+l,t.targetLabelKey=m+"$"+u,t.labelKey=m+"$"+s,t.fontKey=c+"$"+h+"$"+a+"$"+d,t.styleKey=Date.now()}},a.applyParsedProperty=function(e,t){var r,a,o=this,s=t,l=e._private.style,u=o.types,c=o.properties[s.name].type,d=s.bypass,h=l[s.name],p=h&&h.bypass,f=e._private;if("curve-style"===t.name&&"haystack"===t.value&&e.isEdge()&&(e.isLoop()||e.source().isParent()||e.target().isParent())&&(s=t=this.parse(t.name,"bezier",d)),s["delete"])return l[s.name]=void 0,!0;if(s.deleteBypassed)return h?h.bypass?(h.bypassed=void 0,!0):!1:!0;if(s.deleteBypass)return h?h.bypass?(l[s.name]=h.bypassed,!0):!1:!0;var v=function(){n.error("Do not assign mappings to elements without corresponding data (e.g. ele `"+e.id()+"` for property `"+s.name+"` with data field `"+s.field+"`); try a `["+s.field+"]` selector to limit scope to elements with `"+s.field+"` defined")};switch(s.mapped){case u.mapData:case u.mapLayoutData:case u.mapScratch:var r,g=s.mapped===u.mapLayoutData,y=s.mapped===u.mapScratch,m=s.field.split(".");r=y||g?f.scratch:f.data;for(var b=0;bw?w=0:w>1&&(w=1),c.color){var E=s.valueMin[0],_=s.valueMax[0],P=s.valueMin[1],S=s.valueMax[1],k=s.valueMin[2],T=s.valueMax[2],D=null==s.valueMin[3]?1:s.valueMin[3],C=null==s.valueMax[3]?1:s.valueMax[3],M=[Math.round(E+(_-E)*w),Math.round(P+(S-P)*w),Math.round(k+(T-k)*w),Math.round(D+(C-D)*w)];a={bypass:s.bypass,name:s.name,value:M,strValue:"rgb("+M[0]+", "+M[1]+", "+M[2]+")"}}else{if(!c.number)return!1;var N=s.valueMin+(s.valueMax-s.valueMin)*w;a=this.parse(s.name,N,s.bypass,!0)}a||(a=this.parse(s.name,h.strValue,s.bypass,!0)),a||v(),a.mapping=s,s=a;break;case u.data:case u.layoutData:case u.scratch:var r,g=s.mapped===u.layoutData,y=s.mapped===u.scratch,m=s.field.split(".");if(r=y||g?f.scratch:f.data)for(var b=0;b0&&s>0){for(var u={},c=!1,d=0;d0&&e.delay(l),e.animate({css:u},{duration:s,easing:e.pstyle("transition-timing-function").value,queue:!1,complete:function(){r||n.removeBypasses(e,o),a.transitioning=!1}})}else a.transitioning&&(e.stop(),this.removeBypasses(e,o),a.transitioning=!1)},t.exports=a},{"../is":83,"../util":100}],89:[function(e,t,r){"use strict";var n=e("../is"),i=e("../util"),a={};a.applyBypass=function(e,t,r,a){var o=this,s=[],l=!0;if("*"===t||"**"===t){if(void 0!==r)for(var u=0;uh.max)return null;var B={name:e,value:t,strValue:""+t+(D?D:""),units:D,bypass:r};return h.unitless||"px"!==D&&"em"!==D?B.pfValue=t:B.pfValue="px"!==D&&D?this.getEmSizeInPixels()*t:t,"ms"!==D&&"s"!==D||(B.pfValue="ms"===D?t:1e3*t),"deg"!==D&&"rad"!==D||(B.pfValue="rad"===D?t:a.deg2rad(t)),B}if(h.propList){var z=[],I=""+t;if("none"===I);else{for(var L=I.split(","),O=0;O node").css({shape:"rectangle",padding:10,"background-color":"#eee","border-color":"#ccc","border-width":1}).selector("edge").css({width:3,"curve-style":"haystack"}).selector(":selected").css({"background-color":"#0169D9","line-color":"#0169D9","source-arrow-color":"#0169D9","target-arrow-color":"#0169D9","mid-source-arrow-color":"#0169D9","mid-target-arrow-color":"#0169D9"}).selector("node:parent:selected").css({"background-color":"#CCE1F9","border-color":"#aec8e5"}).selector(":active").css({"overlay-color":"black","overlay-padding":10,"overlay-opacity":.25}).selector("core").css({"selection-box-color":"#ddd","selection-box-opacity":.65,"selection-box-border-color":"#aaa","selection-box-border-width":1,"active-bg-color":"black","active-bg-opacity":.15,"active-bg-size":30,"outside-texture-bg-color":"#000","outside-texture-bg-opacity":.125}),this.defaultLength=this.length},t.exports=i},{"../util":100}],96:[function(e,t,r){"use strict";var n=e("../util"),i=e("../selector"),a={};a.applyFromString=function(e){function t(){c=c.length>a.length?c.substr(a.length):""}function r(){o=o.length>s.length?o.substr(s.length):""}var a,o,s,l=this,u=this,c=""+e;for(c=c.replace(/[\/][*](\s|.)+?[*][\/]/g,"");;){var d=c.match(/^\s*$/);if(d)break;var h=c.match(/^\s*((?:.|\s)+?)\s*\{((?:.|\s)+?)\}/);if(!h){n.error("Halting stylesheet parsing: String stylesheet contains more to parse but no selector and block found in: "+c);break}a=h[0];var p=h[1];if("core"!==p){var f=new i(p);if(f._private.invalid){n.error("Skipping parsing of block: Invalid selector found in string stylesheet: "+p),t();continue}}var v=h[2],g=!1;o=v;for(var y=[];;){var d=o.match(/^\s*$/);if(d)break;var m=o.match(/^\s*(.+?)\s*:\s*(.+?)\s*;/);if(!m){n.error("Skipping parsing of block: Invalid formatting of style property and value definitions found in:"+v),g=!0;break}s=m[0];var b=m[1],x=m[2],w=l.properties[b];if(w){var E=u.parse(b,x);E?(y.push({name:b,val:x}),r()):(n.error("Skipping property: Invalid property definition in: "+s),r())}else n.error("Skipping property: Invalid property name in: "+s),r()}if(g){t();break}u.selector(p);for(var _=0;_1?", "+JSON.stringify(r):"")+" );"," "," resolve = origResolve;"," resolve( res.length > 0 ? res : ret );","}"].join("\n"))}};util.extend(thdfn,{reduce:defineFnal({name:"reduce"}),reduceRight:defineFnal({name:"reduceRight"}),map:defineFnal({name:"map"})});var fn=thdfn;fn.promise=fn.run,fn.terminate=fn.halt=fn.stop,fn.include=fn.require,util.extend(thdfn,{on:define.on(),one:define.on({unbindSelfOnTrigger:!0}),off:define.off(),trigger:define.trigger()}),define.eventAliasesOn(thdfn),module.exports=Thread},{"./define":44,"./event":45,"./is":83,"./promise":86,"./util":100,"./window":107,child_process:void 0,path:void 0}],99:[function(e,t,r){"use strict";var n=e("../is");t.exports={hex2tuple:function(e){if((4===e.length||7===e.length)&&"#"===e[0]){var t,r,n,i=4===e.length,a=16;return i?(t=parseInt(e[1]+e[1],a),r=parseInt(e[2]+e[2],a),n=parseInt(e[3]+e[3],a)):(t=parseInt(e[1]+e[2],a),r=parseInt(e[3]+e[4],a),n=parseInt(e[5]+e[6],a)),[t,r,n]}},hsl2tuple:function(e){function t(e,t,r){return 0>r&&(r+=1),r>1&&(r-=1),1/6>r?e+6*(t-e)*r:.5>r?t:2/3>r?e+(t-e)*(2/3-r)*6:e}var r,n,i,a,o,s,l,u,c=new RegExp("^"+this.regex.hsla+"$").exec(e);if(c){if(n=parseInt(c[1]),0>n?n=(360- -1*n%360)%360:n>360&&(n%=360),n/=360,i=parseFloat(c[2]),0>i||i>100)return;if(i/=100,a=parseFloat(c[3]),0>a||a>100)return;if(a/=100,o=c[4],void 0!==o&&(o=parseFloat(o),0>o||o>1))return;if(0===i)s=l=u=Math.round(255*a);else{var d=.5>a?a*(1+i):a+i-a*i,h=2*a-d;s=Math.round(255*t(h,d,n+1/3)),l=Math.round(255*t(h,d,n)),u=Math.round(255*t(h,d,n-1/3))}r=[s,l,u,o]}return r},rgb2tuple:function(e){var t,r=new RegExp("^"+this.regex.rgba+"$").exec(e);if(r){t=[];for(var n=[],i=1;3>=i;i++){var a=r[i];if("%"===a[a.length-1]&&(n[i]=!0),a=parseFloat(a),n[i]&&(a=a/100*255),0>a||a>255)return;t.push(Math.floor(a))}var o=n[1]||n[2]||n[3],s=n[1]&&n[2]&&n[3];if(o&&!s)return;var l=r[4];if(void 0!==l){if(l=parseFloat(l),0>l||l>1)return;t.push(l)}}return t},colorname2tuple:function(e){return this.colors[e.toLowerCase()]},color2tuple:function(e){return(n.array(e)?e:null)||this.colorname2tuple(e)||this.hex2tuple(e)||this.rgb2tuple(e)||this.hsl2tuple(e)},colors:{transparent:[0,0,0,0],aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],grey:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}}},{"../is":83}],100:[function(e,t,r){"use strict";var n=e("../is"),i=e("../math"),a={trueify:function(){return!0},falsify:function(){return!1},zeroify:function(){return 0},noop:function(){},error:function(e){console.error?(console.error.apply(console,arguments),console.trace&&console.trace()):(console.log.apply(console,arguments),console.trace&&console.trace())},clone:function(e){return this.extend({},e)},copy:function(e){return null==e?e:n.array(e)?e.slice():n.plainObject(e)?this.clone(e):e},uuid:function(e,t){for(t=e="";e++<36;t+=51*e&52?(15^e?8^Math.random()*(20^e?16:4):4).toString(16):"-");return t}};a.makeBoundingBox=i.makeBoundingBox.bind(i),a._staticEmptyObject={},a.staticEmptyObject=function(){return a._staticEmptyObject},a.extend=null!=Object.assign?Object.assign:function(e){for(var t=arguments,r=1;r=0&&(e[n]!==t||(e.splice(n,1),r));n--);},a.clearArray=function(e){e.splice(0,e.length)},a.getPrefixedProperty=function(e,t,r){return r&&(t=this.prependCamel(r,t)),e[t]},a.setPrefixedProperty=function(e,t,r,n){r&&(t=this.prependCamel(r,t)),e[t]=n},[e("./colors"),e("./maps"),{memoize:e("./memoize")},e("./regex"),e("./strings"),e("./timing")].forEach(function(e){a.extend(a,e)}),t.exports=a},{"../is":83,"../math":85,"./colors":99,"./maps":101,"./memoize":102,"./regex":103,"./strings":104,"./timing":105}],101:[function(e,t,r){"use strict";var n=e("../is");t.exports={mapEmpty:function(e){var t=!0;return null!=e?0===Object.keys(e).length:t},pushMap:function(e){var t=this.getMap(e);null==t?this.setMap(this.extend({},e,{value:[e.value]})):t.push(e.value)},setMap:function(e){for(var t,r=e.map,i=e.keys,a=i.length,o=0;a>o;o++){var t=i[o];n.plainObject(t)&&this.error("Tried to set map with object key"),oa;a++){var o=r[a];if(n.plainObject(o)&&this.error("Tried to get map with object key"),t=t[o],null==t)return t}return t},deleteMap:function(e){for(var t=e.map,r=e.keys,i=r.length,a=e.keepChildren,o=0;i>o;o++){var s=r[o];n.plainObject(s)&&this.error("Tried to delete map with object key");var l=o===e.keys.length-1;if(l)if(a)for(var u=Object.keys(t),c=0;c=r){a&&clearTimeout(a);var i=c;a=u=c=void 0,i&&(h=d.now(),o=e.apply(l,n),u||a||(n=l=null))}else u=setTimeout(g,r)},y=function(){u&&clearTimeout(u),a=u=c=void 0,(f||p!==t)&&(h=d.now(),o=e.apply(l,n),u||a||(n=l=null))};return function(){if(n=arguments,s=d.now(),l=this,c=f&&(u||!v),p===!1)var r=v&&!u;else{a||v||(h=s);var i=p-(s-h),m=0>=i;m?(a&&(a=clearTimeout(a)),h=s,o=e.apply(l,n)):a||(a=setTimeout(y,i))}return m&&u?u=clearTimeout(u):u||t===p||(u=setTimeout(g,t)),r&&(m=!0,o=e.apply(l,n)),!m||u||a||(n=l=null),o}}},t.exports=o},{"../is":83,"../window":107}],106:[function(e,t,r){t.exports="2.7.13"},{}],107:[function(e,t,r){t.exports="undefined"==typeof window?null:window},{}]},{},[82])(82)}); \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/botFrameworkInspector/dagre.min.js b/Plugins/Vorlon/plugins/botFrameworkInspector/dagre.min.js new file mode 100644 index 00000000..b7a9bbc4 --- /dev/null +++ b/Plugins/Vorlon/plugins/botFrameworkInspector/dagre.min.js @@ -0,0 +1,6 @@ +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.dagre=e()}}(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o0;--i){entry=buckets[i].dequeue();if(entry){results=results.concat(removeNode(g,buckets,zeroIdx,entry,true));break}}}}return results}function removeNode(g,buckets,zeroIdx,entry,collectPredecessors){var results=collectPredecessors?[]:undefined;_.each(g.inEdges(entry.v),function(edge){var weight=g.edge(edge),uEntry=g.node(edge.v);if(collectPredecessors){results.push({v:edge.v,w:edge.w})}uEntry.out-=weight;assignBucket(buckets,zeroIdx,uEntry)});_.each(g.outEdges(entry.v),function(edge){var weight=g.edge(edge),w=edge.w,wEntry=g.node(w);wEntry["in"]-=weight;assignBucket(buckets,zeroIdx,wEntry)});g.removeNode(entry.v);return results}function buildState(g,weightFn){var fasGraph=new Graph,maxIn=0,maxOut=0;_.each(g.nodes(),function(v){fasGraph.setNode(v,{v:v,"in":0,out:0})});_.each(g.edges(),function(e){var prevWeight=fasGraph.edge(e.v,e.w)||0,weight=weightFn(e),edgeWeight=prevWeight+weight;fasGraph.setEdge(e.v,e.w,edgeWeight);maxOut=Math.max(maxOut,fasGraph.node(e.v).out+=weight);maxIn=Math.max(maxIn,fasGraph.node(e.w)["in"]+=weight)});var buckets=_.range(maxOut+maxIn+3).map(function(){return new List});var zeroIdx=maxIn+1;_.each(fasGraph.nodes(),function(v){assignBucket(buckets,zeroIdx,fasGraph.node(v))});return{graph:fasGraph,buckets:buckets,zeroIdx:zeroIdx}}function assignBucket(buckets,zeroIdx,entry){if(!entry.out){buckets[0].enqueue(entry)}else if(!entry["in"]){buckets[buckets.length-1].enqueue(entry)}else{buckets[entry.out-entry["in"]+zeroIdx].enqueue(entry)}}},{"./data/list":5,"./graphlib":7,"./lodash":10}],9:[function(require,module,exports){"use strict";var _=require("./lodash"),acyclic=require("./acyclic"),normalize=require("./normalize"),rank=require("./rank"),normalizeRanks=require("./util").normalizeRanks,parentDummyChains=require("./parent-dummy-chains"),removeEmptyRanks=require("./util").removeEmptyRanks,nestingGraph=require("./nesting-graph"),addBorderSegments=require("./add-border-segments"),coordinateSystem=require("./coordinate-system"),order=require("./order"),position=require("./position"),util=require("./util"),Graph=require("./graphlib").Graph;module.exports=layout;function layout(g,opts){var time=opts&&opts.debugTiming?util.time:util.notime;time("layout",function(){var layoutGraph=time(" buildLayoutGraph",function(){return buildLayoutGraph(g)});time(" runLayout",function(){runLayout(layoutGraph,time)});time(" updateInputGraph",function(){updateInputGraph(g,layoutGraph)})})}function runLayout(g,time){time(" makeSpaceForEdgeLabels",function(){makeSpaceForEdgeLabels(g)});time(" removeSelfEdges",function(){removeSelfEdges(g)});time(" acyclic",function(){acyclic.run(g)});time(" nestingGraph.run",function(){nestingGraph.run(g)});time(" rank",function(){rank(util.asNonCompoundGraph(g))});time(" injectEdgeLabelProxies",function(){injectEdgeLabelProxies(g)});time(" removeEmptyRanks",function(){removeEmptyRanks(g)});time(" nestingGraph.cleanup",function(){nestingGraph.cleanup(g)});time(" normalizeRanks",function(){normalizeRanks(g)});time(" assignRankMinMax",function(){assignRankMinMax(g)});time(" removeEdgeLabelProxies",function(){removeEdgeLabelProxies(g)});time(" normalize.run",function(){normalize.run(g)});time(" parentDummyChains",function(){parentDummyChains(g)});time(" addBorderSegments",function(){addBorderSegments(g)});time(" order",function(){order(g)});time(" insertSelfEdges",function(){insertSelfEdges(g)});time(" adjustCoordinateSystem",function(){coordinateSystem.adjust(g)});time(" position",function(){position(g)});time(" positionSelfEdges",function(){positionSelfEdges(g)});time(" removeBorderNodes",function(){removeBorderNodes(g)});time(" normalize.undo",function(){normalize.undo(g)});time(" fixupEdgeLabelCoords",function(){fixupEdgeLabelCoords(g)});time(" undoCoordinateSystem",function(){coordinateSystem.undo(g)});time(" translateGraph",function(){translateGraph(g)});time(" assignNodeIntersects",function(){assignNodeIntersects(g)});time(" reversePoints",function(){reversePointsForReversedEdges(g)});time(" acyclic.undo",function(){acyclic.undo(g)})}function updateInputGraph(inputGraph,layoutGraph){_.each(inputGraph.nodes(),function(v){var inputLabel=inputGraph.node(v),layoutLabel=layoutGraph.node(v);if(inputLabel){inputLabel.x=layoutLabel.x;inputLabel.y=layoutLabel.y;if(layoutGraph.children(v).length){inputLabel.width=layoutLabel.width;inputLabel.height=layoutLabel.height}}});_.each(inputGraph.edges(),function(e){var inputLabel=inputGraph.edge(e),layoutLabel=layoutGraph.edge(e);inputLabel.points=layoutLabel.points;if(_.has(layoutLabel,"x")){inputLabel.x=layoutLabel.x;inputLabel.y=layoutLabel.y}});inputGraph.graph().width=layoutGraph.graph().width;inputGraph.graph().height=layoutGraph.graph().height}var graphNumAttrs=["nodesep","edgesep","ranksep","marginx","marginy"],graphDefaults={ranksep:50,edgesep:20,nodesep:50,rankdir:"tb"},graphAttrs=["acyclicer","ranker","rankdir","align"],nodeNumAttrs=["width","height"],nodeDefaults={width:0,height:0},edgeNumAttrs=["minlen","weight","width","height","labeloffset"],edgeDefaults={minlen:1,weight:1,width:0,height:0,labeloffset:10,labelpos:"r"},edgeAttrs=["labelpos"];function buildLayoutGraph(inputGraph){var g=new Graph({multigraph:true,compound:true}),graph=canonicalize(inputGraph.graph());g.setGraph(_.merge({},graphDefaults,selectNumberAttrs(graph,graphNumAttrs),_.pick(graph,graphAttrs)));_.each(inputGraph.nodes(),function(v){var node=canonicalize(inputGraph.node(v));g.setNode(v,_.defaults(selectNumberAttrs(node,nodeNumAttrs),nodeDefaults));g.setParent(v,inputGraph.parent(v))});_.each(inputGraph.edges(),function(e){var edge=canonicalize(inputGraph.edge(e));g.setEdge(e,_.merge({},edgeDefaults,selectNumberAttrs(edge,edgeNumAttrs),_.pick(edge,edgeAttrs)))});return g}function makeSpaceForEdgeLabels(g){var graph=g.graph();graph.ranksep/=2;_.each(g.edges(),function(e){var edge=g.edge(e);edge.minlen*=2;if(edge.labelpos.toLowerCase()!=="c"){if(graph.rankdir==="TB"||graph.rankdir==="BT"){edge.width+=edge.labeloffset}else{edge.height+=edge.labeloffset}}})}function injectEdgeLabelProxies(g){_.each(g.edges(),function(e){var edge=g.edge(e);if(edge.width&&edge.height){var v=g.node(e.v),w=g.node(e.w),label={rank:(w.rank-v.rank)/2+v.rank,e:e};util.addDummyNode(g,"edge-proxy",label,"_ep")}})}function assignRankMinMax(g){var maxRank=0;_.each(g.nodes(),function(v){var node=g.node(v);if(node.borderTop){node.minRank=g.node(node.borderTop).rank;node.maxRank=g.node(node.borderBottom).rank;maxRank=_.max(maxRank,node.maxRank)}});g.graph().maxRank=maxRank}function removeEdgeLabelProxies(g){_.each(g.nodes(),function(v){var node=g.node(v);if(node.dummy==="edge-proxy"){g.edge(node.e).labelRank=node.rank;g.removeNode(v)}})}function translateGraph(g){var minX=Number.POSITIVE_INFINITY,maxX=0,minY=Number.POSITIVE_INFINITY,maxY=0,graphLabel=g.graph(),marginX=graphLabel.marginx||0,marginY=graphLabel.marginy||0;function getExtremes(attrs){var x=attrs.x,y=attrs.y,w=attrs.width,h=attrs.height;minX=Math.min(minX,x-w/2);maxX=Math.max(maxX,x+w/2);minY=Math.min(minY,y-h/2);maxY=Math.max(maxY,y+h/2)}_.each(g.nodes(),function(v){getExtremes(g.node(v))});_.each(g.edges(),function(e){var edge=g.edge(e);if(_.has(edge,"x")){getExtremes(edge)}});minX-=marginX;minY-=marginY;_.each(g.nodes(),function(v){var node=g.node(v);node.x-=minX;node.y-=minY});_.each(g.edges(),function(e){var edge=g.edge(e);_.each(edge.points,function(p){p.x-=minX;p.y-=minY});if(_.has(edge,"x")){edge.x-=minX}if(_.has(edge,"y")){edge.y-=minY}});graphLabel.width=maxX-minX+marginX;graphLabel.height=maxY-minY+marginY}function assignNodeIntersects(g){_.each(g.edges(),function(e){var edge=g.edge(e),nodeV=g.node(e.v),nodeW=g.node(e.w),p1,p2;if(!edge.points){edge.points=[];p1=nodeW;p2=nodeV}else{p1=edge.points[0];p2=edge.points[edge.points.length-1]}edge.points.unshift(util.intersectRect(nodeV,p1));edge.points.push(util.intersectRect(nodeW,p2))})}function fixupEdgeLabelCoords(g){_.each(g.edges(),function(e){var edge=g.edge(e);if(_.has(edge,"x")){if(edge.labelpos==="l"||edge.labelpos==="r"){edge.width-=edge.labeloffset}switch(edge.labelpos){case"l":edge.x-=edge.width/2+edge.labeloffset;break;case"r":edge.x+=edge.width/2+edge.labeloffset;break}}})}function reversePointsForReversedEdges(g){_.each(g.edges(),function(e){var edge=g.edge(e);if(edge.reversed){edge.points.reverse()}})}function removeBorderNodes(g){_.each(g.nodes(),function(v){if(g.children(v).length){var node=g.node(v),t=g.node(node.borderTop),b=g.node(node.borderBottom),l=g.node(_.last(node.borderLeft)),r=g.node(_.last(node.borderRight));node.width=Math.abs(r.x-l.x);node.height=Math.abs(b.y-t.y);node.x=l.x+node.width/2;node.y=t.y+node.height/2}});_.each(g.nodes(),function(v){if(g.node(v).dummy==="border"){g.removeNode(v)}})}function removeSelfEdges(g){_.each(g.edges(),function(e){if(e.v===e.w){var node=g.node(e.v);if(!node.selfEdges){node.selfEdges=[]}node.selfEdges.push({e:e,label:g.edge(e)});g.removeEdge(e)}})}function insertSelfEdges(g){var layers=util.buildLayerMatrix(g);_.each(layers,function(layer){var orderShift=0;_.each(layer,function(v,i){var node=g.node(v);node.order=i+orderShift;_.each(node.selfEdges,function(selfEdge){util.addDummyNode(g,"selfedge",{width:selfEdge.label.width,height:selfEdge.label.height,rank:node.rank,order:i+ ++orderShift,e:selfEdge.e,label:selfEdge.label},"_se")});delete node.selfEdges})})}function positionSelfEdges(g){_.each(g.nodes(),function(v){var node=g.node(v);if(node.dummy==="selfedge"){var selfNode=g.node(node.e.v),x=selfNode.x+selfNode.width/2,y=selfNode.y,dx=node.x-x,dy=selfNode.height/2;g.setEdge(node.e,node.label);g.removeNode(v);node.label.points=[{x:x+2*dx/3,y:y-dy},{x:x+5*dx/6,y:y-dy},{x:x+dx,y:y},{x:x+5*dx/6,y:y+dy},{x:x+2*dx/3,y:y+dy}];node.label.x=node.x;node.label.y=node.y}})}function selectNumberAttrs(obj,attrs){return _.mapValues(_.pick(obj,attrs),Number)}function canonicalize(attrs){var newAttrs={};_.each(attrs,function(v,k){newAttrs[k.toLowerCase()]=v});return newAttrs}},{"./acyclic":2,"./add-border-segments":3,"./coordinate-system":4,"./graphlib":7,"./lodash":10,"./nesting-graph":11,"./normalize":12,"./order":17,"./parent-dummy-chains":22,"./position":24,"./rank":26,"./util":29}],10:[function(require,module,exports){var lodash;if(typeof require==="function"){try{lodash=require("lodash")}catch(e){}}if(!lodash){lodash=window._}module.exports=lodash},{lodash:51}],11:[function(require,module,exports){var _=require("./lodash"),util=require("./util");module.exports={run:run,cleanup:cleanup};function run(g){var root=util.addDummyNode(g,"root",{},"_root"),depths=treeDepths(g),height=_.max(depths)-1,nodeSep=2*height+1;g.graph().nestingRoot=root;_.each(g.edges(),function(e){g.edge(e).minlen*=nodeSep});var weight=sumWeights(g)+1;_.each(g.children(),function(child){dfs(g,root,nodeSep,weight,height,depths,child)});g.graph().nodeRankFactor=nodeSep}function dfs(g,root,nodeSep,weight,height,depths,v){var children=g.children(v);if(!children.length){if(v!==root){g.setEdge(root,v,{weight:0,minlen:nodeSep})}return}var top=util.addBorderNode(g,"_bt"),bottom=util.addBorderNode(g,"_bb"),label=g.node(v);g.setParent(top,v);label.borderTop=top;g.setParent(bottom,v);label.borderBottom=bottom;_.each(children,function(child){dfs(g,root,nodeSep,weight,height,depths,child);var childNode=g.node(child),childTop=childNode.borderTop?childNode.borderTop:child,childBottom=childNode.borderBottom?childNode.borderBottom:child,thisWeight=childNode.borderTop?weight:2*weight,minlen=childTop!==childBottom?1:height-depths[v]+1;g.setEdge(top,childTop,{weight:thisWeight,minlen:minlen,nestingEdge:true});g.setEdge(childBottom,bottom,{weight:thisWeight,minlen:minlen,nestingEdge:true})});if(!g.parent(v)){g.setEdge(root,top,{weight:0,minlen:height+depths[v]})}}function treeDepths(g){var depths={};function dfs(v,depth){var children=g.children(v);if(children&&children.length){_.each(children,function(child){dfs(child,depth+1)})}depths[v]=depth}_.each(g.children(),function(v){dfs(v,1)});return depths}function sumWeights(g){return _.reduce(g.edges(),function(acc,e){return acc+g.edge(e).weight},0)}function cleanup(g){var graphLabel=g.graph();g.removeNode(graphLabel.nestingRoot);delete graphLabel.nestingRoot;_.each(g.edges(),function(e){var edge=g.edge(e);if(edge.nestingEdge){g.removeEdge(e)}})}},{"./lodash":10,"./util":29}],12:[function(require,module,exports){"use strict";var _=require("./lodash"),util=require("./util");module.exports={run:run,undo:undo};function run(g){g.graph().dummyChains=[];_.each(g.edges(),function(edge){normalizeEdge(g,edge)})}function normalizeEdge(g,e){var v=e.v,vRank=g.node(v).rank,w=e.w,wRank=g.node(w).rank,name=e.name,edgeLabel=g.edge(e),labelRank=edgeLabel.labelRank;if(wRank===vRank+1)return;g.removeEdge(e);var dummy,attrs,i;for(i=0,++vRank;vRank0){if(index%2){weightSum+=tree[index+1]}index=index-1>>1;tree[index]+=entry.weight}cc+=entry.weight*weightSum}));return cc}},{"../lodash":10}],17:[function(require,module,exports){"use strict";var _=require("../lodash"),initOrder=require("./init-order"),crossCount=require("./cross-count"),sortSubgraph=require("./sort-subgraph"),buildLayerGraph=require("./build-layer-graph"),addSubgraphConstraints=require("./add-subgraph-constraints"),Graph=require("../graphlib").Graph,util=require("../util");module.exports=order;function order(g){var maxRank=util.maxRank(g),downLayerGraphs=buildLayerGraphs(g,_.range(1,maxRank+1),"inEdges"),upLayerGraphs=buildLayerGraphs(g,_.range(maxRank-1,-1,-1),"outEdges");var layering=initOrder(g);assignOrder(g,layering);var bestCC=Number.POSITIVE_INFINITY,best;for(var i=0,lastBest=0;lastBest<4;++i,++lastBest){sweepLayerGraphs(i%2?downLayerGraphs:upLayerGraphs,i%4>=2);layering=util.buildLayerMatrix(g);var cc=crossCount(g,layering);if(cc=vEntry.barycenter){mergeEntries(vEntry,uEntry)}}}function handleOut(vEntry){return function(wEntry){wEntry["in"].push(vEntry);if(--wEntry.indegree===0){sourceSet.push(wEntry)}}}while(sourceSet.length){var entry=sourceSet.pop();entries.push(entry);_.each(entry["in"].reverse(),handleIn(entry));_.each(entry.out,handleOut(entry))}return _.chain(entries).filter(function(entry){return!entry.merged}).map(function(entry){return _.pick(entry,["vs","i","barycenter","weight"])}).value()}function mergeEntries(target,source){var sum=0,weight=0;if(target.weight){sum+=target.barycenter*target.weight;weight+=target.weight}if(source.weight){sum+=source.barycenter*source.weight;weight+=source.weight}target.vs=source.vs.concat(target.vs);target.barycenter=sum/weight;target.weight=weight;target.i=Math.min(source.i,target.i);source.merged=true}},{"../lodash":10}],20:[function(require,module,exports){var _=require("../lodash"),barycenter=require("./barycenter"),resolveConflicts=require("./resolve-conflicts"),sort=require("./sort");module.exports=sortSubgraph;function sortSubgraph(g,v,cg,biasRight){var movable=g.children(v),node=g.node(v),bl=node?node.borderLeft:undefined,br=node?node.borderRight:undefined,subgraphs={};if(bl){movable=_.filter(movable,function(w){return w!==bl&&w!==br})}var barycenters=barycenter(g,movable);_.each(barycenters,function(entry){if(g.children(entry.v).length){var subgraphResult=sortSubgraph(g,entry.v,cg,biasRight);subgraphs[entry.v]=subgraphResult;if(_.has(subgraphResult,"barycenter")){mergeBarycenters(entry,subgraphResult)}}});var entries=resolveConflicts(barycenters,cg);expandSubgraphs(entries,subgraphs);var result=sort(entries,biasRight);if(bl){result.vs=_.flatten([bl,result.vs,br],true);if(g.predecessors(bl).length){var blPred=g.node(g.predecessors(bl)[0]),brPred=g.node(g.predecessors(br)[0]);if(!_.has(result,"barycenter")){result.barycenter=0;result.weight=0}result.barycenter=(result.barycenter*result.weight+blPred.order+brPred.order)/(result.weight+2);result.weight+=2}}return result}function expandSubgraphs(entries,subgraphs){_.each(entries,function(entry){entry.vs=_.flatten(entry.vs.map(function(v){if(subgraphs[v]){return subgraphs[v].vs}return v}),true)})}function mergeBarycenters(target,other){if(!_.isUndefined(target.barycenter)){target.barycenter=(target.barycenter*target.weight+other.barycenter*other.weight)/(target.weight+other.weight);target.weight+=other.weight}else{target.barycenter=other.barycenter;target.weight=other.weight}}},{"../lodash":10,"./barycenter":14,"./resolve-conflicts":19,"./sort":21}],21:[function(require,module,exports){var _=require("../lodash"),util=require("../util");module.exports=sort;function sort(entries,biasRight){var parts=util.partition(entries,function(entry){return _.has(entry,"barycenter")});var sortable=parts.lhs,unsortable=_.sortBy(parts.rhs,function(entry){return-entry.i}),vs=[],sum=0,weight=0,vsIndex=0;sortable.sort(compareWithBias(!!biasRight));vsIndex=consumeUnsortable(vs,unsortable,vsIndex);_.each(sortable,function(entry){vsIndex+=entry.vs.length;vs.push(entry.vs);sum+=entry.barycenter*entry.weight;weight+=entry.weight;vsIndex=consumeUnsortable(vs,unsortable,vsIndex)});var result={vs:_.flatten(vs,true)};if(weight){result.barycenter=sum/weight;result.weight=weight}return result}function consumeUnsortable(vs,unsortable,index){var last;while(unsortable.length&&(last=_.last(unsortable)).i<=index){unsortable.pop();vs.push(last.vs);index++}return index}function compareWithBias(bias){return function(entryV,entryW){if(entryV.barycenterentryW.barycenter){return 1}return!bias?entryV.i-entryW.i:entryW.i-entryV.i}}},{"../lodash":10,"../util":29}],22:[function(require,module,exports){var _=require("./lodash");module.exports=parentDummyChains;function parentDummyChains(g){var postorderNums=postorder(g);_.each(g.graph().dummyChains,function(v){var node=g.node(v),edgeObj=node.edgeObj,pathData=findPath(g,postorderNums,edgeObj.v,edgeObj.w),path=pathData.path,lca=pathData.lca,pathIdx=0,pathV=path[pathIdx],ascending=true;while(v!==edgeObj.w){node=g.node(v);if(ascending){while((pathV=path[pathIdx])!==lca&&g.node(pathV).maxRanklow||lim>postorderNums[parent].lim));lca=parent;parent=w;while((parent=g.parent(parent))!==lca){wPath.push(parent)}return{path:vPath.concat(wPath.reverse()),lca:lca}}function postorder(g){var result={},lim=0;function dfs(v){var low=lim;_.each(g.children(v),dfs);result[v]={low:low,lim:lim++}}_.each(g.children(),dfs);return result}},{"./lodash":10}],23:[function(require,module,exports){"use strict";var _=require("../lodash"),Graph=require("../graphlib").Graph,util=require("../util");module.exports={positionX:positionX,findType1Conflicts:findType1Conflicts,findType2Conflicts:findType2Conflicts,addConflict:addConflict,hasConflict:hasConflict,verticalAlignment:verticalAlignment,horizontalCompaction:horizontalCompaction,alignCoordinates:alignCoordinates,findSmallestWidthAlignment:findSmallestWidthAlignment,balance:balance};function findType1Conflicts(g,layering){var conflicts={};function visitLayer(prevLayer,layer){var k0=0,scanPos=0,prevLayerLength=prevLayer.length,lastNode=_.last(layer);_.each(layer,function(v,i){var w=findOtherInnerSegmentNode(g,v),k1=w?g.node(w).order:prevLayerLength;if(w||v===lastNode){_.each(layer.slice(scanPos,i+1),function(scanNode){_.each(g.predecessors(scanNode),function(u){var uLabel=g.node(u),uPos=uLabel.order; +if((uPosnextNorthBorder)){addConflict(conflicts,u,v)}})}})}function visitLayer(north,south){var prevNorthPos=-1,nextNorthPos,southPos=0;_.each(south,function(v,southLookahead){if(g.node(v).dummy==="border"){var predecessors=g.predecessors(v);if(predecessors.length){nextNorthPos=g.node(predecessors[0]).order;scan(south,southPos,southLookahead,prevNorthPos,nextNorthPos);southPos=southLookahead;prevNorthPos=nextNorthPos}}scan(south,southPos,south.length,nextNorthPos,north.length)});return south}_.reduce(layering,visitLayer);return conflicts}function findOtherInnerSegmentNode(g,v){if(g.node(v).dummy){return _.find(g.predecessors(v),function(u){return g.node(u).dummy})}}function addConflict(conflicts,v,w){if(v>w){var tmp=v;v=w;w=tmp}var conflictsV=conflicts[v];if(!conflictsV){conflicts[v]=conflictsV={}}conflictsV[w]=true}function hasConflict(conflicts,v,w){if(v>w){var tmp=v;v=w;w=tmp}return _.has(conflicts[v],w)}function verticalAlignment(g,layering,conflicts,neighborFn){var root={},align={},pos={};_.each(layering,function(layer){_.each(layer,function(v,order){root[v]=v;align[v]=v;pos[v]=order})});_.each(layering,function(layer){var prevIdx=-1;_.each(layer,function(v){var ws=neighborFn(v);if(ws.length){ws=_.sortBy(ws,function(w){return pos[w]});var mp=(ws.length-1)/2;for(var i=Math.floor(mp),il=Math.ceil(mp);i<=il;++i){var w=ws[i];if(align[v]===v&&prevIdxwLabel.lim){tailLabel=wLabel;flip=true}var candidates=_.filter(g.edges(),function(edge){return flip===isDescendant(t,t.node(edge.v),tailLabel)&&flip!==isDescendant(t,t.node(edge.w),tailLabel)});return _.min(candidates,function(edge){return slack(g,edge)})}function exchangeEdges(t,g,e,f){var v=e.v,w=e.w;t.removeEdge(v,w);t.setEdge(f.v,f.w,{});initLowLimValues(t);initCutValues(t,g);updateRanks(t,g)}function updateRanks(t,g){var root=_.find(t.nodes(),function(v){return!g.node(v).parent}),vs=preorder(t,root);vs=vs.slice(1);_.each(vs,function(v){var parent=t.node(v).parent,edge=g.edge(v,parent),flipped=false;if(!edge){edge=g.edge(parent,v);flipped=true}g.node(v).rank=g.node(parent).rank+(flipped?edge.minlen:-edge.minlen)})}function isTreeEdge(tree,u,v){return tree.hasEdge(u,v)}function isDescendant(tree,vLabel,rootLabel){return rootLabel.low<=vLabel.lim&&vLabel.lim<=rootLabel.lim}},{"../graphlib":7,"../lodash":10,"../util":29,"./feasible-tree":25,"./util":28}],28:[function(require,module,exports){"use strict";var _=require("../lodash");module.exports={longestPath:longestPath,slack:slack};function longestPath(g){var visited={};function dfs(v){var label=g.node(v);if(_.has(visited,v)){return label.rank}visited[v]=true;var rank=_.min(_.map(g.outEdges(v),function(e){return dfs(e.w)-g.edge(e).minlen}));if(rank===Number.POSITIVE_INFINITY){rank=0}return label.rank=rank}_.each(g.sources(),dfs)}function slack(g,e){return g.node(e.w).rank-g.node(e.v).rank-g.edge(e).minlen}},{"../lodash":10}],29:[function(require,module,exports){"use strict";var _=require("./lodash"),Graph=require("./graphlib").Graph;module.exports={addDummyNode:addDummyNode,simplify:simplify,asNonCompoundGraph:asNonCompoundGraph,successorWeights:successorWeights,predecessorWeights:predecessorWeights,intersectRect:intersectRect,buildLayerMatrix:buildLayerMatrix,normalizeRanks:normalizeRanks,removeEmptyRanks:removeEmptyRanks,addBorderNode:addBorderNode,maxRank:maxRank,partition:partition,time:time,notime:notime};function addDummyNode(g,type,attrs,name){var v;do{v=_.uniqueId(name)}while(g.hasNode(v));attrs.dummy=type;g.setNode(v,attrs);return v}function simplify(g){var simplified=(new Graph).setGraph(g.graph());_.each(g.nodes(),function(v){simplified.setNode(v,g.node(v))});_.each(g.edges(),function(e){var simpleLabel=simplified.edge(e.v,e.w)||{weight:0,minlen:1},label=g.edge(e);simplified.setEdge(e.v,e.w,{weight:simpleLabel.weight+label.weight,minlen:Math.max(simpleLabel.minlen,label.minlen)})});return simplified}function asNonCompoundGraph(g){var simplified=new Graph({multigraph:g.isMultigraph()}).setGraph(g.graph());_.each(g.nodes(),function(v){if(!g.children(v).length){simplified.setNode(v,g.node(v))}});_.each(g.edges(),function(e){simplified.setEdge(e,g.edge(e))});return simplified}function successorWeights(g){var weightMap=_.map(g.nodes(),function(v){var sucs={};_.each(g.outEdges(v),function(e){sucs[e.w]=(sucs[e.w]||0)+g.edge(e).weight});return sucs});return _.zipObject(g.nodes(),weightMap)}function predecessorWeights(g){var weightMap=_.map(g.nodes(),function(v){var preds={};_.each(g.inEdges(v),function(e){preds[e.v]=(preds[e.v]||0)+g.edge(e).weight});return preds});return _.zipObject(g.nodes(),weightMap)}function intersectRect(rect,point){var x=rect.x;var y=rect.y;var dx=point.x-x;var dy=point.y-y;var w=rect.width/2;var h=rect.height/2;if(!dx&&!dy){throw new Error("Not possible to find intersection inside of the rectangle")}var sx,sy;if(Math.abs(dy)*w>Math.abs(dx)*h){if(dy<0){h=-h}sx=h*dx/dy;sy=h}else{if(dx<0){w=-w}sx=w;sy=w*dy/dx}return{x:x+sx,y:y+sy}}function buildLayerMatrix(g){var layering=_.map(_.range(maxRank(g)+1),function(){return[]});_.each(g.nodes(),function(v){var node=g.node(v),rank=node.rank;if(!_.isUndefined(rank)){layering[rank][node.order]=v}});return layering}function normalizeRanks(g){var min=_.min(_.map(g.nodes(),function(v){return g.node(v).rank}));_.each(g.nodes(),function(v){var node=g.node(v);if(_.has(node,"rank")){node.rank-=min}})}function removeEmptyRanks(g){var offset=_.min(_.map(g.nodes(),function(v){return g.node(v).rank}));var layers=[];_.each(g.nodes(),function(v){var rank=g.node(v).rank-offset;if(!layers[rank]){layers[rank]=[]}layers[rank].push(v)});var delta=0,nodeRankFactor=g.graph().nodeRankFactor;_.each(layers,function(vs,i){if(_.isUndefined(vs)&&i%nodeRankFactor!==0){--delta}else if(delta){_.each(vs,function(v){g.node(v).rank+=delta})}})}function addBorderNode(g,prefix,rank,order){var node={width:0,height:0};if(arguments.length>=4){node.rank=rank;node.order=order}return addDummyNode(g,"border",node,prefix)}function maxRank(g){return _.max(_.map(g.nodes(),function(v){var rank=g.node(v).rank;if(!_.isUndefined(rank)){return rank}}))}function partition(collection,fn){var result={lhs:[],rhs:[]};_.each(collection,function(value){if(fn(value)){result.lhs.push(value)}else{result.rhs.push(value)}});return result}function time(name,fn){var start=_.now();try{return fn()}finally{console.log(name+" time: "+(_.now()-start)+"ms")}}function notime(name,fn){return fn()}},{"./graphlib":7,"./lodash":10}],30:[function(require,module,exports){module.exports="0.7.4"},{}],31:[function(require,module,exports){var lib=require("./lib");module.exports={Graph:lib.Graph,json:require("./lib/json"),alg:require("./lib/alg"),version:lib.version}},{"./lib":47,"./lib/alg":38,"./lib/json":48}],32:[function(require,module,exports){var _=require("../lodash");module.exports=components;function components(g){var visited={},cmpts=[],cmpt;function dfs(v){if(_.has(visited,v))return;visited[v]=true;cmpt.push(v);_.each(g.successors(v),dfs);_.each(g.predecessors(v),dfs)}_.each(g.nodes(),function(v){cmpt=[];dfs(v);if(cmpt.length){cmpts.push(cmpt)}});return cmpts}},{"../lodash":49}],33:[function(require,module,exports){var _=require("../lodash");module.exports=dfs;function dfs(g,vs,order){if(!_.isArray(vs)){vs=[vs]}var acc=[],visited={};_.each(vs,function(v){if(!g.hasNode(v)){throw new Error("Graph does not have node: "+v)}doDfs(g,v,order==="post",visited,acc)});return acc}function doDfs(g,v,postorder,visited,acc){if(!_.has(visited,v)){visited[v]=true;if(!postorder){acc.push(v)}_.each(g.neighbors(v),function(w){doDfs(g,w,postorder,visited,acc)});if(postorder){acc.push(v)}}}},{"../lodash":49}],34:[function(require,module,exports){var dijkstra=require("./dijkstra"),_=require("../lodash");module.exports=dijkstraAll;function dijkstraAll(g,weightFunc,edgeFunc){return _.transform(g.nodes(),function(acc,v){acc[v]=dijkstra(g,v,weightFunc,edgeFunc)},{})}},{"../lodash":49,"./dijkstra":35}],35:[function(require,module,exports){var _=require("../lodash"),PriorityQueue=require("../data/priority-queue");module.exports=dijkstra;var DEFAULT_WEIGHT_FUNC=_.constant(1);function dijkstra(g,source,weightFn,edgeFn){return runDijkstra(g,String(source),weightFn||DEFAULT_WEIGHT_FUNC,edgeFn||function(v){return g.outEdges(v)})}function runDijkstra(g,source,weightFn,edgeFn){var results={},pq=new PriorityQueue,v,vEntry;var updateNeighbors=function(edge){var w=edge.v!==v?edge.v:edge.w,wEntry=results[w],weight=weightFn(edge),distance=vEntry.distance+weight;if(weight<0){throw new Error("dijkstra does not allow negative edge weights. "+"Bad edge: "+edge+" Weight: "+weight)}if(distance0){v=pq.removeMin();vEntry=results[v];if(vEntry.distance===Number.POSITIVE_INFINITY){break}edgeFn(v).forEach(updateNeighbors)}return results}},{"../data/priority-queue":45,"../lodash":49}],36:[function(require,module,exports){var _=require("../lodash"),tarjan=require("./tarjan");module.exports=findCycles;function findCycles(g){return _.filter(tarjan(g),function(cmpt){return cmpt.length>1||cmpt.length===1&&g.hasEdge(cmpt[0],cmpt[0])})}},{"../lodash":49,"./tarjan":43}],37:[function(require,module,exports){var _=require("../lodash");module.exports=floydWarshall;var DEFAULT_WEIGHT_FUNC=_.constant(1);function floydWarshall(g,weightFn,edgeFn){return runFloydWarshall(g,weightFn||DEFAULT_WEIGHT_FUNC,edgeFn||function(v){return g.outEdges(v)})}function runFloydWarshall(g,weightFn,edgeFn){var results={},nodes=g.nodes();nodes.forEach(function(v){results[v]={};results[v][v]={distance:0};nodes.forEach(function(w){if(v!==w){results[v][w]={distance:Number.POSITIVE_INFINITY}}});edgeFn(v).forEach(function(edge){var w=edge.v===v?edge.w:edge.v,d=weightFn(edge);results[v][w]={distance:d,predecessor:v}})});nodes.forEach(function(k){var rowK=results[k];nodes.forEach(function(i){var rowI=results[i];nodes.forEach(function(j){var ik=rowI[k];var kj=rowK[j];var ij=rowI[j];var altDistance=ik.distance+kj.distance;if(altDistance0){v=pq.removeMin();if(_.has(parents,v)){result.setEdge(v,parents[v])}else if(init){throw new Error("Input graph is not connected: "+g)}else{init=true}g.nodeEdges(v).forEach(updateNeighbors)}return result}},{"../data/priority-queue":45,"../graph":46,"../lodash":49}],43:[function(require,module,exports){var _=require("../lodash");module.exports=tarjan;function tarjan(g){var index=0,stack=[],visited={},results=[];function dfs(v){var entry=visited[v]={onStack:true,lowlink:index,index:index++};stack.push(v);g.successors(v).forEach(function(w){if(!_.has(visited,w)){dfs(w);entry.lowlink=Math.min(entry.lowlink,visited[w].lowlink)}else if(visited[w].onStack){entry.lowlink=Math.min(entry.lowlink,visited[w].index)}});if(entry.lowlink===entry.index){var cmpt=[],w;do{w=stack.pop();visited[w].onStack=false;cmpt.push(w)}while(v!==w);results.push(cmpt)}}g.nodes().forEach(function(v){if(!_.has(visited,v)){dfs(v)}});return results}},{"../lodash":49}],44:[function(require,module,exports){var _=require("../lodash");module.exports=topsort;topsort.CycleException=CycleException;function topsort(g){var visited={},stack={},results=[];function visit(node){if(_.has(stack,node)){throw new CycleException}if(!_.has(visited,node)){stack[node]=true;visited[node]=true;_.each(g.predecessors(node),visit);delete stack[node];results.push(node)}}_.each(g.sinks(),visit);if(_.size(visited)!==g.nodeCount()){throw new CycleException}return results}function CycleException(){}},{"../lodash":49}],45:[function(require,module,exports){var _=require("../lodash");module.exports=PriorityQueue;function PriorityQueue(){this._arr=[];this._keyIndices={}}PriorityQueue.prototype.size=function(){return this._arr.length};PriorityQueue.prototype.keys=function(){return this._arr.map(function(x){return x.key})};PriorityQueue.prototype.has=function(key){return _.has(this._keyIndices,key)};PriorityQueue.prototype.priority=function(key){var index=this._keyIndices[key];if(index!==undefined){return this._arr[index].priority}};PriorityQueue.prototype.min=function(){if(this.size()===0){throw new Error("Queue underflow")}return this._arr[0].key};PriorityQueue.prototype.add=function(key,priority){var keyIndices=this._keyIndices;key=String(key);if(!_.has(keyIndices,key)){var arr=this._arr;var index=arr.length;keyIndices[key]=index;arr.push({key:key,priority:priority});this._decrease(index);return true}return false};PriorityQueue.prototype.removeMin=function(){this._swap(0,this._arr.length-1);var min=this._arr.pop();delete this._keyIndices[min.key];this._heapify(0);return min.key};PriorityQueue.prototype.decrease=function(key,priority){var index=this._keyIndices[key];if(priority>this._arr[index].priority){throw new Error("New priority is greater than current priority. "+"Key: "+key+" Old: "+this._arr[index].priority+" New: "+priority)}this._arr[index].priority=priority;this._decrease(index)};PriorityQueue.prototype._heapify=function(i){var arr=this._arr;var l=2*i,r=l+1,largest=i;if(l>1;if(arr[parent].priority1){this.setNode(v,value)}else{this.setNode(v)}},this);return this};Graph.prototype.setNode=function(v,value){if(_.has(this._nodes,v)){if(arguments.length>1){this._nodes[v]=value}return this}this._nodes[v]=arguments.length>1?value:this._defaultNodeLabelFn(v);if(this._isCompound){this._parent[v]=GRAPH_NODE;this._children[v]={};this._children[GRAPH_NODE][v]=true}this._in[v]={};this._preds[v]={};this._out[v]={};this._sucs[v]={};++this._nodeCount;return this};Graph.prototype.node=function(v){return this._nodes[v]};Graph.prototype.hasNode=function(v){return _.has(this._nodes,v)};Graph.prototype.removeNode=function(v){var self=this;if(_.has(this._nodes,v)){var removeEdge=function(e){self.removeEdge(self._edgeObjs[e])};delete this._nodes[v];if(this._isCompound){this._removeFromParentsChildList(v);delete this._parent[v];_.each(this.children(v),function(child){this.setParent(child)},this);delete this._children[v]}_.each(_.keys(this._in[v]),removeEdge);delete this._in[v];delete this._preds[v];_.each(_.keys(this._out[v]),removeEdge);delete this._out[v];delete this._sucs[v];--this._nodeCount}return this};Graph.prototype.setParent=function(v,parent){if(!this._isCompound){throw new Error("Cannot set parent in a non-compound graph")}if(_.isUndefined(parent)){parent=GRAPH_NODE}else{parent+="";for(var ancestor=parent;!_.isUndefined(ancestor);ancestor=this.parent(ancestor)){if(ancestor===v){throw new Error("Setting "+parent+" as parent of "+v+" would create create a cycle")}}this.setNode(parent)}this.setNode(v);this._removeFromParentsChildList(v);this._parent[v]=parent;this._children[parent][v]=true;return this};Graph.prototype._removeFromParentsChildList=function(v){delete this._children[this._parent[v]][v]};Graph.prototype.parent=function(v){if(this._isCompound){var parent=this._parent[v];if(parent!==GRAPH_NODE){return parent}}};Graph.prototype.children=function(v){if(_.isUndefined(v)){v=GRAPH_NODE}if(this._isCompound){var children=this._children[v];if(children){return _.keys(children)}}else if(v===GRAPH_NODE){return this.nodes()}else if(this.hasNode(v)){return[]}};Graph.prototype.predecessors=function(v){var predsV=this._preds[v];if(predsV){return _.keys(predsV)}};Graph.prototype.successors=function(v){var sucsV=this._sucs[v];if(sucsV){return _.keys(sucsV)}};Graph.prototype.neighbors=function(v){var preds=this.predecessors(v);if(preds){return _.union(preds,this.successors(v))}};Graph.prototype.setDefaultEdgeLabel=function(newDefault){if(!_.isFunction(newDefault)){newDefault=_.constant(newDefault)}this._defaultEdgeLabelFn=newDefault;return this};Graph.prototype.edgeCount=function(){return this._edgeCount};Graph.prototype.edges=function(){return _.values(this._edgeObjs)};Graph.prototype.setPath=function(vs,value){var self=this,args=arguments;_.reduce(vs,function(v,w){if(args.length>1){self.setEdge(v,w,value)}else{self.setEdge(v,w)}return w});return this};Graph.prototype.setEdge=function(){var v,w,name,value,valueSpecified=false;if(_.isPlainObject(arguments[0])){v=arguments[0].v;w=arguments[0].w;name=arguments[0].name;if(arguments.length===2){value=arguments[1];valueSpecified=true}}else{v=arguments[0];w=arguments[1];name=arguments[3];if(arguments.length>2){value=arguments[2];valueSpecified=true}}v=""+v;w=""+w;if(!_.isUndefined(name)){name=""+name}var e=edgeArgsToId(this._isDirected,v,w,name);if(_.has(this._edgeLabels,e)){if(valueSpecified){this._edgeLabels[e]=value}return this}if(!_.isUndefined(name)&&!this._isMultigraph){throw new Error("Cannot set a named edge when isMultigraph = false")}this.setNode(v);this.setNode(w);this._edgeLabels[e]=valueSpecified?value:this._defaultEdgeLabelFn(v,w,name);var edgeObj=edgeArgsToObj(this._isDirected,v,w,name);v=edgeObj.v;w=edgeObj.w;Object.freeze(edgeObj);this._edgeObjs[e]=edgeObj;incrementOrInitEntry(this._preds[w],v);incrementOrInitEntry(this._sucs[v],w);this._in[w][e]=edgeObj;this._out[v][e]=edgeObj;this._edgeCount++;return this};Graph.prototype.edge=function(v,w,name){var e=arguments.length===1?edgeObjToId(this._isDirected,arguments[0]):edgeArgsToId(this._isDirected,v,w,name);return this._edgeLabels[e]};Graph.prototype.hasEdge=function(v,w,name){var e=arguments.length===1?edgeObjToId(this._isDirected,arguments[0]):edgeArgsToId(this._isDirected,v,w,name);return _.has(this._edgeLabels,e)};Graph.prototype.removeEdge=function(v,w,name){var e=arguments.length===1?edgeObjToId(this._isDirected,arguments[0]):edgeArgsToId(this._isDirected,v,w,name),edge=this._edgeObjs[e];if(edge){v=edge.v;w=edge.w;delete this._edgeLabels[e];delete this._edgeObjs[e];decrementOrRemoveEntry(this._preds[w],v);decrementOrRemoveEntry(this._sucs[v],w);delete this._in[w][e];delete this._out[v][e];this._edgeCount--}return this};Graph.prototype.inEdges=function(v,u){var inV=this._in[v];if(inV){var edges=_.values(inV);if(!u){return edges}return _.filter(edges,function(edge){return edge.v===u})}};Graph.prototype.outEdges=function(v,w){var outV=this._out[v];if(outV){var edges=_.values(outV);if(!w){return edges}return _.filter(edges,function(edge){return edge.w===w})}};Graph.prototype.nodeEdges=function(v,w){var inEdges=this.inEdges(v,w);if(inEdges){return inEdges.concat(this.outEdges(v,w))}};function incrementOrInitEntry(map,k){if(_.has(map,k)){map[k]++}else{map[k]=1}}function decrementOrRemoveEntry(map,k){if(!--map[k]){delete map[k]}}function edgeArgsToId(isDirected,v,w,name){if(!isDirected&&v>w){var tmp=v;v=w;w=tmp}return v+EDGE_KEY_DELIM+w+EDGE_KEY_DELIM+(_.isUndefined(name)?DEFAULT_EDGE_NAME:name)}function edgeArgsToObj(isDirected,v,w,name){if(!isDirected&&v>w){var tmp=v;v=w;w=tmp}var edgeObj={v:v,w:w};if(name){edgeObj.name=name}return edgeObj}function edgeObjToId(isDirected,edgeObj){return edgeArgsToId(isDirected,edgeObj.v,edgeObj.w,edgeObj.name)}},{"./lodash":49}],47:[function(require,module,exports){module.exports={Graph:require("./graph"),version:require("./version")}},{"./graph":46,"./version":50}],48:[function(require,module,exports){var _=require("./lodash"),Graph=require("./graph");module.exports={write:write,read:read};function write(g){var json={options:{directed:g.isDirected(),multigraph:g.isMultigraph(),compound:g.isCompound()},nodes:writeNodes(g),edges:writeEdges(g)}; +if(!_.isUndefined(g.graph())){json.value=_.clone(g.graph())}return json}function writeNodes(g){return _.map(g.nodes(),function(v){var nodeValue=g.node(v),parent=g.parent(v),node={v:v};if(!_.isUndefined(nodeValue)){node.value=nodeValue}if(!_.isUndefined(parent)){node.parent=parent}return node})}function writeEdges(g){return _.map(g.edges(),function(e){var edgeValue=g.edge(e),edge={v:e.v,w:e.w};if(!_.isUndefined(e.name)){edge.name=e.name}if(!_.isUndefined(edgeValue)){edge.value=edgeValue}return edge})}function read(json){var g=new Graph(json.options).setGraph(json.value);_.each(json.nodes,function(entry){g.setNode(entry.v,entry.value);if(entry.parent){g.setParent(entry.v,entry.parent)}});_.each(json.edges,function(entry){g.setEdge({v:entry.v,w:entry.w,name:entry.name},entry.value)});return g}},{"./graph":46,"./lodash":49}],49:[function(require,module,exports){module.exports=require(10)},{"/Users/cpettitt/projects/dagre/lib/lodash.js":10,lodash:51}],50:[function(require,module,exports){module.exports="1.0.5"},{}],51:[function(require,module,exports){(function(global){(function(){var undefined;var VERSION="3.10.0";var BIND_FLAG=1,BIND_KEY_FLAG=2,CURRY_BOUND_FLAG=4,CURRY_FLAG=8,CURRY_RIGHT_FLAG=16,PARTIAL_FLAG=32,PARTIAL_RIGHT_FLAG=64,ARY_FLAG=128,REARG_FLAG=256;var DEFAULT_TRUNC_LENGTH=30,DEFAULT_TRUNC_OMISSION="...";var HOT_COUNT=150,HOT_SPAN=16;var LARGE_ARRAY_SIZE=200;var LAZY_FILTER_FLAG=1,LAZY_MAP_FLAG=2;var FUNC_ERROR_TEXT="Expected a function";var PLACEHOLDER="__lodash_placeholder__";var argsTag="[object Arguments]",arrayTag="[object Array]",boolTag="[object Boolean]",dateTag="[object Date]",errorTag="[object Error]",funcTag="[object Function]",mapTag="[object Map]",numberTag="[object Number]",objectTag="[object Object]",regexpTag="[object RegExp]",setTag="[object Set]",stringTag="[object String]",weakMapTag="[object WeakMap]";var arrayBufferTag="[object ArrayBuffer]",float32Tag="[object Float32Array]",float64Tag="[object Float64Array]",int8Tag="[object Int8Array]",int16Tag="[object Int16Array]",int32Tag="[object Int32Array]",uint8Tag="[object Uint8Array]",uint8ClampedTag="[object Uint8ClampedArray]",uint16Tag="[object Uint16Array]",uint32Tag="[object Uint32Array]";var reEmptyStringLeading=/\b__p \+= '';/g,reEmptyStringMiddle=/\b(__p \+=) '' \+/g,reEmptyStringTrailing=/(__e\(.*?\)|\b__t\)) \+\n'';/g;var reEscapedHtml=/&(?:amp|lt|gt|quot|#39|#96);/g,reUnescapedHtml=/[&<>"'`]/g,reHasEscapedHtml=RegExp(reEscapedHtml.source),reHasUnescapedHtml=RegExp(reUnescapedHtml.source);var reEscape=/<%-([\s\S]+?)%>/g,reEvaluate=/<%([\s\S]+?)%>/g,reInterpolate=/<%=([\s\S]+?)%>/g;var reIsDeepProp=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/,reIsPlainProp=/^\w*$/,rePropName=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g;var reRegExpChars=/^[:!,]|[\\^$.*+?()[\]{}|\/]|(^[0-9a-fA-Fnrtuvx])|([\n\r\u2028\u2029])/g,reHasRegExpChars=RegExp(reRegExpChars.source);var reComboMark=/[\u0300-\u036f\ufe20-\ufe23]/g;var reEscapeChar=/\\(\\)?/g;var reEsTemplate=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;var reFlags=/\w*$/;var reHasHexPrefix=/^0[xX]/;var reIsHostCtor=/^\[object .+?Constructor\]$/;var reIsUint=/^\d+$/;var reLatin1=/[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g;var reNoMatch=/($^)/;var reUnescapedString=/['\n\r\u2028\u2029\\]/g;var reWords=function(){var upper="[A-Z\\xc0-\\xd6\\xd8-\\xde]",lower="[a-z\\xdf-\\xf6\\xf8-\\xff]+";return RegExp(upper+"+(?="+upper+lower+")|"+upper+"?"+lower+"|"+upper+"+|[0-9]+","g")}();var contextProps=["Array","ArrayBuffer","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Math","Number","Object","RegExp","Set","String","_","clearTimeout","isFinite","parseFloat","parseInt","setTimeout","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap"];var templateCounter=-1;var typedArrayTags={};typedArrayTags[float32Tag]=typedArrayTags[float64Tag]=typedArrayTags[int8Tag]=typedArrayTags[int16Tag]=typedArrayTags[int32Tag]=typedArrayTags[uint8Tag]=typedArrayTags[uint8ClampedTag]=typedArrayTags[uint16Tag]=typedArrayTags[uint32Tag]=true;typedArrayTags[argsTag]=typedArrayTags[arrayTag]=typedArrayTags[arrayBufferTag]=typedArrayTags[boolTag]=typedArrayTags[dateTag]=typedArrayTags[errorTag]=typedArrayTags[funcTag]=typedArrayTags[mapTag]=typedArrayTags[numberTag]=typedArrayTags[objectTag]=typedArrayTags[regexpTag]=typedArrayTags[setTag]=typedArrayTags[stringTag]=typedArrayTags[weakMapTag]=false;var cloneableTags={};cloneableTags[argsTag]=cloneableTags[arrayTag]=cloneableTags[arrayBufferTag]=cloneableTags[boolTag]=cloneableTags[dateTag]=cloneableTags[float32Tag]=cloneableTags[float64Tag]=cloneableTags[int8Tag]=cloneableTags[int16Tag]=cloneableTags[int32Tag]=cloneableTags[numberTag]=cloneableTags[objectTag]=cloneableTags[regexpTag]=cloneableTags[stringTag]=cloneableTags[uint8Tag]=cloneableTags[uint8ClampedTag]=cloneableTags[uint16Tag]=cloneableTags[uint32Tag]=true;cloneableTags[errorTag]=cloneableTags[funcTag]=cloneableTags[mapTag]=cloneableTags[setTag]=cloneableTags[weakMapTag]=false;var deburredLetters={"À":"A","Á":"A","Â":"A","Ã":"A","Ä":"A","Å":"A","à":"a","á":"a","â":"a","ã":"a","ä":"a","å":"a","Ç":"C","ç":"c","Ð":"D","ð":"d","È":"E","É":"E","Ê":"E","Ë":"E","è":"e","é":"e","ê":"e","ë":"e","Ì":"I","Í":"I","Î":"I","Ï":"I","ì":"i","í":"i","î":"i","ï":"i","Ñ":"N","ñ":"n","Ò":"O","Ó":"O","Ô":"O","Õ":"O","Ö":"O","Ø":"O","ò":"o","ó":"o","ô":"o","õ":"o","ö":"o","ø":"o","Ù":"U","Ú":"U","Û":"U","Ü":"U","ù":"u","ú":"u","û":"u","ü":"u","Ý":"Y","ý":"y","ÿ":"y","Æ":"Ae","æ":"ae","Þ":"Th","þ":"th","ß":"ss"};var htmlEscapes={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"};var htmlUnescapes={"&":"&","<":"<",">":">",""":'"',"'":"'","`":"`"};var objectTypes={"function":true,object:true};var regexpEscapes={0:"x30",1:"x31",2:"x32",3:"x33",4:"x34",5:"x35",6:"x36",7:"x37",8:"x38",9:"x39",A:"x41",B:"x42",C:"x43",D:"x44",E:"x45",F:"x46",a:"x61",b:"x62",c:"x63",d:"x64",e:"x65",f:"x66",n:"x6e",r:"x72",t:"x74",u:"x75",v:"x76",x:"x78"};var stringEscapes={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"};var freeExports=objectTypes[typeof exports]&&exports&&!exports.nodeType&&exports;var freeModule=objectTypes[typeof module]&&module&&!module.nodeType&&module;var freeGlobal=freeExports&&freeModule&&typeof global=="object"&&global&&global.Object&&global;var freeSelf=objectTypes[typeof self]&&self&&self.Object&&self;var freeWindow=objectTypes[typeof window]&&window&&window.Object&&window;var moduleExports=freeModule&&freeModule.exports===freeExports&&freeExports;var root=freeGlobal||freeWindow!==(this&&this.window)&&freeWindow||freeSelf||this;function baseCompareAscending(value,other){if(value!==other){var valIsNull=value===null,valIsUndef=value===undefined,valIsReflexive=value===value;var othIsNull=other===null,othIsUndef=other===undefined,othIsReflexive=other===other;if(value>other&&!othIsNull||!valIsReflexive||valIsNull&&!othIsUndef&&othIsReflexive||valIsUndef&&othIsReflexive){return 1}if(value-1){}return index}function charsRightIndex(string,chars){var index=string.length;while(index--&&chars.indexOf(string.charAt(index))>-1){}return index}function compareAscending(object,other){return baseCompareAscending(object.criteria,other.criteria)||object.index-other.index}function compareMultiple(object,other,orders){var index=-1,objCriteria=object.criteria,othCriteria=other.criteria,length=objCriteria.length,ordersLength=orders.length;while(++index=ordersLength){return result}var order=orders[index];return result*(order==="asc"||order===true?1:-1)}}return object.index-other.index}function deburrLetter(letter){return deburredLetters[letter]}function escapeHtmlChar(chr){return htmlEscapes[chr]}function escapeRegExpChar(chr,leadingChar,whitespaceChar){if(leadingChar){chr=regexpEscapes[chr]}else if(whitespaceChar){chr=stringEscapes[chr]}return"\\"+chr}function escapeStringChar(chr){return"\\"+stringEscapes[chr]}function indexOfNaN(array,fromIndex,fromRight){var length=array.length,index=fromIndex+(fromRight?0:-1);while(fromRight?index--:++index=9&&charCode<=13)||charCode==32||charCode==160||charCode==5760||charCode==6158||charCode>=8192&&(charCode<=8202||charCode==8232||charCode==8233||charCode==8239||charCode==8287||charCode==12288||charCode==65279)}function replaceHolders(array,placeholder){var index=-1,length=array.length,resIndex=-1,result=[];while(++index>>1;var MAX_SAFE_INTEGER=9007199254740991;var metaMap=WeakMap&&new WeakMap;var realNames={};function lodash(value){if(isObjectLike(value)&&!isArray(value)&&!(value instanceof LazyWrapper)){if(value instanceof LodashWrapper){return value}if(hasOwnProperty.call(value,"__chain__")&&hasOwnProperty.call(value,"__wrapped__")){return wrapperClone(value)}}return new LodashWrapper(value)}function baseLodash(){}function LodashWrapper(value,chainAll,actions){this.__wrapped__=value;this.__actions__=actions||[];this.__chain__=!!chainAll}var support=lodash.support={};lodash.templateSettings={escape:reEscape,evaluate:reEvaluate,interpolate:reInterpolate,variable:"",imports:{_:lodash}};function LazyWrapper(value){this.__wrapped__=value;this.__actions__=[];this.__dir__=1;this.__filtered__=false;this.__iteratees__=[];this.__takeCount__=POSITIVE_INFINITY;this.__views__=[]}function lazyClone(){var result=new LazyWrapper(this.__wrapped__);result.__actions__=arrayCopy(this.__actions__);result.__dir__=this.__dir__;result.__filtered__=this.__filtered__;result.__iteratees__=arrayCopy(this.__iteratees__);result.__takeCount__=this.__takeCount__;result.__views__=arrayCopy(this.__views__);return result}function lazyReverse(){if(this.__filtered__){var result=new LazyWrapper(this);result.__dir__=-1;result.__filtered__=true}else{result=this.clone();result.__dir__*=-1}return result}function lazyValue(){var array=this.__wrapped__.value(),dir=this.__dir__,isArr=isArray(array),isRight=dir<0,arrLength=isArr?array.length:0,view=getView(0,arrLength,this.__views__),start=view.start,end=view.end,length=end-start,index=isRight?end:start-1,iteratees=this.__iteratees__,iterLength=iteratees.length,resIndex=0,takeCount=nativeMin(length,this.__takeCount__);if(!isArr||arrLength=LARGE_ARRAY_SIZE?createCache(values):null,valuesLength=values.length;if(cache){indexOf=cacheIndexOf;isCommon=false;values=cache}outer:while(++indexlength?0:length+start}end=end===undefined||end>length?length:+end||0;if(end<0){end+=length}length=start>end?0:end>>>0;start>>>=0;while(startlength?0:length+start}end=end===undefined||end>length?length:+end||0;if(end<0){end+=length}length=start>end?0:end-start>>>0;start>>>=0;var result=Array(length);while(++index=LARGE_ARRAY_SIZE,seen=isLarge?createCache():null,result=[];if(seen){indexOf=cacheIndexOf;isCommon=false}else{isLarge=false;seen=iteratee?[]:result}outer:while(++index>>1,computed=array[mid];if((retHighest?computed<=value:computed2?sources[length-2]:undefined,guard=length>2?sources[2]:undefined,thisArg=length>1?sources[length-1]:undefined;if(typeof customizer=="function"){customizer=bindCallback(customizer,thisArg,5);length-=2}else{customizer=typeof thisArg=="function"?thisArg:undefined;length-=customizer?1:0}if(guard&&isIterateeCall(sources[0],sources[1],guard)){customizer=length<3?undefined:customizer;length=1}while(++index-1?collection[index]:undefined}return baseFind(collection,predicate,eachFunc)}}function createFindIndex(fromRight){return function(array,predicate,thisArg){if(!(array&&array.length)){return-1}predicate=getCallback(predicate,thisArg,3);return baseFindIndex(array,predicate,fromRight)}}function createFindKey(objectFunc){return function(object,predicate,thisArg){predicate=getCallback(predicate,thisArg,3);return baseFind(object,predicate,objectFunc,true)}}function createFlow(fromRight){return function(){var wrapper,length=arguments.length,index=fromRight?length:-1,leftIndex=0,funcs=Array(length);while(fromRight?index--:++index=LARGE_ARRAY_SIZE){return wrapper.plant(value).value()}var index=0,result=length?funcs[index].apply(this,args):value;while(++index=length||!nativeIsFinite(length)){return""}var padLength=length-strLength;chars=chars==null?" ":chars+"";return repeat(chars,nativeCeil(padLength/chars.length)).slice(0,padLength)}function createPartialWrapper(func,bitmask,thisArg,partials){var isBind=bitmask&BIND_FLAG,Ctor=createCtorWrapper(func);function wrapper(){var argsIndex=-1,argsLength=arguments.length,leftIndex=-1,leftLength=partials.length,args=Array(leftLength+argsLength);while(++leftIndexarrLength)){return false}while(++index-1&&value%1==0&&value-1&&value%1==0&&value<=MAX_SAFE_INTEGER}function isStrictComparable(value){return value===value&&!isObject(value)}function mergeData(data,source){var bitmask=data[1],srcBitmask=source[1],newBitmask=bitmask|srcBitmask,isCommon=newBitmask0){if(++count>=HOT_COUNT){return key}}else{count=0}return baseSetData(key,value)}}();function shimKeys(object){var props=keysIn(object),propsLength=props.length,length=propsLength&&object.length;var allowIndexes=!!length&&isLength(length)&&(isArray(object)||isArguments(object));var index=-1,result=[];while(++index=120?createCache(othIndex&&value):null}var array=arrays[0],index=-1,length=array?array.length:0,seen=caches[0];outer:while(++index-1){splice.call(array,fromIndex,1)}}return array}var pullAt=restParam(function(array,indexes){indexes=baseFlatten(indexes);var result=baseAt(array,indexes);basePullAt(array,indexes.sort(baseCompareAscending));return result});function remove(array,predicate,thisArg){var result=[];if(!(array&&array.length)){return result}var index=-1,indexes=[],length=array.length;predicate=getCallback(predicate,thisArg,3);while(++index2?arrays[length-2]:undefined,thisArg=length>1?arrays[length-1]:undefined;if(length>2&&typeof iteratee=="function"){length-=2}else{iteratee=length>1&&typeof thisArg=="function"?(--length,thisArg):undefined;thisArg=undefined}arrays.length=length;return unzipWith(arrays,iteratee,thisArg)});function chain(value){var result=lodash(value);result.__chain__=true;return result}function tap(value,interceptor,thisArg){interceptor.call(thisArg,value);return value}function thru(value,interceptor,thisArg){return interceptor.call(thisArg,value)}function wrapperChain(){return chain(this)}function wrapperCommit(){return new LodashWrapper(this.value(),this.__chain__)}var wrapperConcat=restParam(function(values){values=baseFlatten(values);return this.thru(function(array){return arrayConcat(isArray(array)?array:[toObject(array)],values)})});function wrapperPlant(value){var result,parent=this;while(parent instanceof baseLodash){var clone=wrapperClone(parent);if(result){previous.__wrapped__=clone}else{result=clone}var previous=clone;parent=parent.__wrapped__}previous.__wrapped__=value;return result}function wrapperReverse(){var value=this.__wrapped__;var interceptor=function(value){return wrapped&&wrapped.__dir__<0?value:value.reverse()};if(value instanceof LazyWrapper){var wrapped=value;if(this.__actions__.length){wrapped=new LazyWrapper(this)}wrapped=wrapped.reverse();wrapped.__actions__.push({func:thru,args:[interceptor],thisArg:undefined});return new LodashWrapper(wrapped,this.__chain__)}return this.thru(interceptor)}function wrapperToString(){return this.value()+""}function wrapperValue(){return baseWrapperValue(this.__wrapped__,this.__actions__)}var at=restParam(function(collection,props){return baseAt(collection,baseFlatten(props))});var countBy=createAggregator(function(result,value,key){hasOwnProperty.call(result,key)?++result[key]:result[key]=1});function every(collection,predicate,thisArg){var func=isArray(collection)?arrayEvery:baseEvery;if(thisArg&&isIterateeCall(collection,predicate,thisArg)){predicate=undefined}if(typeof predicate!="function"||thisArg!==undefined){predicate=getCallback(predicate,thisArg,3) +}return func(collection,predicate)}function filter(collection,predicate,thisArg){var func=isArray(collection)?arrayFilter:baseFilter;predicate=getCallback(predicate,thisArg,3);return func(collection,predicate)}var find=createFind(baseEach);var findLast=createFind(baseEachRight,true);function findWhere(collection,source){return find(collection,baseMatches(source))}var forEach=createForEach(arrayEach,baseEach);var forEachRight=createForEach(arrayEachRight,baseEachRight);var groupBy=createAggregator(function(result,value,key){if(hasOwnProperty.call(result,key)){result[key].push(value)}else{result[key]=[value]}});function includes(collection,target,fromIndex,guard){var length=collection?getLength(collection):0;if(!isLength(length)){collection=values(collection);length=collection.length}if(typeof fromIndex!="number"||guard&&isIterateeCall(target,fromIndex,guard)){fromIndex=0}else{fromIndex=fromIndex<0?nativeMax(length+fromIndex,0):fromIndex||0}return typeof collection=="string"||!isArray(collection)&&isString(collection)?fromIndex<=length&&collection.indexOf(target,fromIndex)>-1:!!length&&getIndexOf(collection,target,fromIndex)>-1}var indexBy=createAggregator(function(result,value,key){result[key]=value});var invoke=restParam(function(collection,path,args){var index=-1,isFunc=typeof path=="function",isProp=isKey(path),result=isArrayLike(collection)?Array(collection.length):[];baseEach(collection,function(value){var func=isFunc?path:isProp&&value!=null?value[path]:undefined;result[++index]=func?func.apply(value,args):invokePath(value,path,args)});return result});function map(collection,iteratee,thisArg){var func=isArray(collection)?arrayMap:baseMap;iteratee=getCallback(iteratee,thisArg,3);return func(collection,iteratee)}var partition=createAggregator(function(result,value,key){result[key?0:1].push(value)},function(){return[[],[]]});function pluck(collection,path){return map(collection,property(path))}var reduce=createReduce(arrayReduce,baseEach);var reduceRight=createReduce(arrayReduceRight,baseEachRight);function reject(collection,predicate,thisArg){var func=isArray(collection)?arrayFilter:baseFilter;predicate=getCallback(predicate,thisArg,3);return func(collection,function(value,index,collection){return!predicate(value,index,collection)})}function sample(collection,n,guard){if(guard?isIterateeCall(collection,n,guard):n==null){collection=toIterable(collection);var length=collection.length;return length>0?collection[baseRandom(0,length-1)]:undefined}var index=-1,result=toArray(collection),length=result.length,lastIndex=length-1;n=nativeMin(n<0?0:+n||0,length);while(++index0){result=func.apply(this,arguments)}if(n<=1){func=undefined}return result}}var bind=restParam(function(func,thisArg,partials){var bitmask=BIND_FLAG;if(partials.length){var holders=replaceHolders(partials,bind.placeholder);bitmask|=PARTIAL_FLAG}return createWrapper(func,bitmask,thisArg,partials,holders)});var bindAll=restParam(function(object,methodNames){methodNames=methodNames.length?baseFlatten(methodNames):functions(object);var index=-1,length=methodNames.length;while(++indexwait){complete(trailingCall,maxTimeoutId)}else{timeoutId=setTimeout(delayed,remaining)}}function maxDelayed(){complete(trailing,timeoutId)}function debounced(){args=arguments;stamp=now();thisArg=this;trailingCall=trailing&&(timeoutId||!leading);if(maxWait===false){var leadingCall=leading&&!timeoutId}else{if(!maxTimeoutId&&!leading){lastCalled=stamp}var remaining=maxWait-(stamp-lastCalled),isCalled=remaining<=0||remaining>maxWait;if(isCalled){if(maxTimeoutId){maxTimeoutId=clearTimeout(maxTimeoutId)}lastCalled=stamp;result=func.apply(thisArg,args)}else if(!maxTimeoutId){maxTimeoutId=setTimeout(maxDelayed,remaining)}}if(isCalled&&timeoutId){timeoutId=clearTimeout(timeoutId)}else if(!timeoutId&&wait!==maxWait){timeoutId=setTimeout(delayed,wait)}if(leadingCall){isCalled=true;result=func.apply(thisArg,args)}if(isCalled&&!timeoutId&&!maxTimeoutId){args=thisArg=undefined}return result}debounced.cancel=cancel;return debounced}var defer=restParam(function(func,args){return baseDelay(func,1,args)});var delay=restParam(function(func,wait,args){return baseDelay(func,wait,args)});var flow=createFlow();var flowRight=createFlow(true);function memoize(func,resolver){if(typeof func!="function"||resolver&&typeof resolver!="function"){throw new TypeError(FUNC_ERROR_TEXT)}var memoized=function(){var args=arguments,key=resolver?resolver.apply(this,args):args[0],cache=memoized.cache;if(cache.has(key)){return cache.get(key)}var result=func.apply(this,args);memoized.cache=cache.set(key,result);return result};memoized.cache=new memoize.Cache;return memoized}var modArgs=restParam(function(func,transforms){transforms=baseFlatten(transforms);if(typeof func!="function"||!arrayEvery(transforms,baseIsFunction)){throw new TypeError(FUNC_ERROR_TEXT)}var length=transforms.length;return restParam(function(args){var index=nativeMin(args.length,length);while(index--){args[index]=transforms[index](args[index])}return func.apply(this,args)})});function negate(predicate){if(typeof predicate!="function"){throw new TypeError(FUNC_ERROR_TEXT)}return function(){return!predicate.apply(this,arguments)}}function once(func){return before(2,func)}var partial=createPartial(PARTIAL_FLAG);var partialRight=createPartial(PARTIAL_RIGHT_FLAG);var rearg=restParam(function(func,indexes){return createWrapper(func,REARG_FLAG,undefined,undefined,undefined,baseFlatten(indexes))});function restParam(func,start){if(typeof func!="function"){throw new TypeError(FUNC_ERROR_TEXT)}start=nativeMax(start===undefined?func.length-1:+start||0,0);return function(){var args=arguments,index=-1,length=nativeMax(args.length-start,0),rest=Array(length);while(++indexother}function gte(value,other){return value>=other}function isArguments(value){return isObjectLike(value)&&isArrayLike(value)&&hasOwnProperty.call(value,"callee")&&!propertyIsEnumerable.call(value,"callee")}var isArray=nativeIsArray||function(value){return isObjectLike(value)&&isLength(value.length)&&objToString.call(value)==arrayTag};function isBoolean(value){return value===true||value===false||isObjectLike(value)&&objToString.call(value)==boolTag}function isDate(value){return isObjectLike(value)&&objToString.call(value)==dateTag}function isElement(value){return!!value&&value.nodeType===1&&isObjectLike(value)&&!isPlainObject(value)}function isEmpty(value){if(value==null){return true}if(isArrayLike(value)&&(isArray(value)||isString(value)||isArguments(value)||isObjectLike(value)&&isFunction(value.splice))){return!value.length}return!keys(value).length}function isEqual(value,other,customizer,thisArg){customizer=typeof customizer=="function"?bindCallback(customizer,thisArg,3):undefined;var result=customizer?customizer(value,other):undefined;return result===undefined?baseIsEqual(value,other,customizer):!!result}function isError(value){return isObjectLike(value)&&typeof value.message=="string"&&objToString.call(value)==errorTag}function isFinite(value){return typeof value=="number"&&nativeIsFinite(value)}function isFunction(value){return isObject(value)&&objToString.call(value)==funcTag}function isObject(value){var type=typeof value;return!!value&&(type=="object"||type=="function")}function isMatch(object,source,customizer,thisArg){customizer=typeof customizer=="function"?bindCallback(customizer,thisArg,3):undefined;return baseIsMatch(object,getMatchData(source),customizer)}function isNaN(value){return isNumber(value)&&value!=+value}function isNative(value){if(value==null){return false}if(isFunction(value)){return reIsNative.test(fnToString.call(value))}return isObjectLike(value)&&reIsHostCtor.test(value)}function isNull(value){return value===null}function isNumber(value){return typeof value=="number"||isObjectLike(value)&&objToString.call(value)==numberTag}function isPlainObject(value){var Ctor;if(!(isObjectLike(value)&&objToString.call(value)==objectTag&&!isArguments(value))||!hasOwnProperty.call(value,"constructor")&&(Ctor=value.constructor,typeof Ctor=="function"&&!(Ctor instanceof Ctor))){return false}var result;baseForIn(value,function(subValue,key){result=key});return result===undefined||hasOwnProperty.call(value,result)}function isRegExp(value){return isObject(value)&&objToString.call(value)==regexpTag}function isString(value){return typeof value=="string"||isObjectLike(value)&&objToString.call(value)==stringTag}function isTypedArray(value){return isObjectLike(value)&&isLength(value.length)&&!!typedArrayTags[objToString.call(value)]}function isUndefined(value){return value===undefined}function lt(value,other){return value0;while(++index=nativeMin(start,end)&&value=0&&string.indexOf(target,position)==position}function escape(string){string=baseToString(string);return string&&reHasUnescapedHtml.test(string)?string.replace(reUnescapedHtml,escapeHtmlChar):string}function escapeRegExp(string){string=baseToString(string);return string&&reHasRegExpChars.test(string)?string.replace(reRegExpChars,escapeRegExpChar):string||"(?:)"}var kebabCase=createCompounder(function(result,word,index){return result+(index?"-":"")+word.toLowerCase()});function pad(string,length,chars){string=baseToString(string);length=+length;var strLength=string.length;if(strLength>=length||!nativeIsFinite(length)){return string}var mid=(length-strLength)/2,leftLength=nativeFloor(mid),rightLength=nativeCeil(mid);chars=createPadding("",rightLength,chars);return chars.slice(0,leftLength)+string+chars}var padLeft=createPadDir();var padRight=createPadDir(true);function parseInt(string,radix,guard){if(guard?isIterateeCall(string,radix,guard):radix==null){radix=0}else if(radix){radix=+radix}string=trim(string);return nativeParseInt(string,radix||(reHasHexPrefix.test(string)?16:10))}function repeat(string,n){var result="";string=baseToString(string);n=+n;if(n<1||!string||!nativeIsFinite(n)){return result}do{if(n%2){result+=string}n=nativeFloor(n/2);string+=string}while(n);return result}var snakeCase=createCompounder(function(result,word,index){return result+(index?"_":"")+word.toLowerCase()});var startCase=createCompounder(function(result,word,index){return result+(index?" ":"")+(word.charAt(0).toUpperCase()+word.slice(1))});function startsWith(string,target,position){string=baseToString(string);position=position==null?0:nativeMin(position<0?0:+position||0,string.length);return string.lastIndexOf(target,position)==position}function template(string,options,otherOptions){var settings=lodash.templateSettings;if(otherOptions&&isIterateeCall(string,options,otherOptions)){options=otherOptions=undefined}string=baseToString(string);options=assignWith(baseAssign({},otherOptions||options),settings,assignOwnDefaults);var imports=assignWith(baseAssign({},options.imports),settings.imports,assignOwnDefaults),importsKeys=keys(imports),importsValues=baseValues(imports,importsKeys);var isEscaping,isEvaluating,index=0,interpolate=options.interpolate||reNoMatch,source="__p += '";var reDelimiters=RegExp((options.escape||reNoMatch).source+"|"+interpolate.source+"|"+(interpolate===reInterpolate?reEsTemplate:reNoMatch).source+"|"+(options.evaluate||reNoMatch).source+"|$","g");var sourceURL="//# sourceURL="+("sourceURL"in options?options.sourceURL:"lodash.templateSources["+ ++templateCounter+"]")+"\n";string.replace(reDelimiters,function(match,escapeValue,interpolateValue,esTemplateValue,evaluateValue,offset){interpolateValue||(interpolateValue=esTemplateValue);source+=string.slice(index,offset).replace(reUnescapedString,escapeStringChar);if(escapeValue){isEscaping=true;source+="' +\n__e("+escapeValue+") +\n'"}if(evaluateValue){isEvaluating=true;source+="';\n"+evaluateValue+";\n__p += '"}if(interpolateValue){source+="' +\n((__t = ("+interpolateValue+")) == null ? '' : __t) +\n'"}index=offset+match.length;return match});source+="';\n";var variable=options.variable;if(!variable){source="with (obj) {\n"+source+"\n}\n"}source=(isEvaluating?source.replace(reEmptyStringLeading,""):source).replace(reEmptyStringMiddle,"$1").replace(reEmptyStringTrailing,"$1;");source="function("+(variable||"obj")+") {\n"+(variable?"":"obj || (obj = {});\n")+"var __t, __p = ''"+(isEscaping?", __e = _.escape":"")+(isEvaluating?", __j = Array.prototype.join;\n"+"function print() { __p += __j.call(arguments, '') }\n":";\n")+source+"return __p\n}";var result=attempt(function(){return Function(importsKeys,sourceURL+"return "+source).apply(undefined,importsValues)});result.source=source;if(isError(result)){throw result}return result}function trim(string,chars,guard){var value=string;string=baseToString(string);if(!string){return string}if(guard?isIterateeCall(value,chars,guard):chars==null){return string.slice(trimmedLeftIndex(string),trimmedRightIndex(string)+1)}chars=chars+"";return string.slice(charsLeftIndex(string,chars),charsRightIndex(string,chars)+1)}function trimLeft(string,chars,guard){var value=string;string=baseToString(string);if(!string){return string}if(guard?isIterateeCall(value,chars,guard):chars==null){return string.slice(trimmedLeftIndex(string))}return string.slice(charsLeftIndex(string,chars+""))}function trimRight(string,chars,guard){var value=string;string=baseToString(string);if(!string){return string}if(guard?isIterateeCall(value,chars,guard):chars==null){return string.slice(0,trimmedRightIndex(string)+1)}return string.slice(0,charsRightIndex(string,chars+"")+1)}function trunc(string,options,guard){if(guard&&isIterateeCall(string,options,guard)){options=undefined}var length=DEFAULT_TRUNC_LENGTH,omission=DEFAULT_TRUNC_OMISSION;if(options!=null){if(isObject(options)){var separator="separator"in options?options.separator:separator;length="length"in options?+options.length||0:length;omission="omission"in options?baseToString(options.omission):omission}else{length=+options||0}}string=baseToString(string);if(length>=string.length){return string}var end=length-omission.length;if(end<1){return omission}var result=string.slice(0,end);if(separator==null){return result+omission}if(isRegExp(separator)){if(string.slice(end).search(separator)){var match,newEnd,substring=string.slice(0,end);if(!separator.global){separator=RegExp(separator.source,(reFlags.exec(separator)||"")+"g")}separator.lastIndex=0;while(match=separator.exec(substring)){newEnd=match.index}result=result.slice(0,newEnd==null?end:newEnd)}}else if(string.indexOf(separator,end)!=end){var index=result.lastIndexOf(separator);if(index>-1){result=result.slice(0,index)}}return result+omission}function unescape(string){string=baseToString(string);return string&&reHasEscapedHtml.test(string)?string.replace(reEscapedHtml,unescapeHtmlChar):string}function words(string,pattern,guard){if(guard&&isIterateeCall(string,pattern,guard)){pattern=undefined}string=baseToString(string);return string.match(pattern||reWords)||[]}var attempt=restParam(function(func,args){try{return func.apply(undefined,args)}catch(e){return isError(e)?e:new Error(e)}});function callback(func,thisArg,guard){if(guard&&isIterateeCall(func,thisArg,guard)){thisArg=undefined}return isObjectLike(func)?matches(func):baseCallback(func,thisArg)}function constant(value){return function(){return value}}function identity(value){return value}function matches(source){return baseMatches(baseClone(source,true))}function matchesProperty(path,srcValue){return baseMatchesProperty(path,baseClone(srcValue,true))}var method=restParam(function(path,args){return function(object){return invokePath(object,path,args)}});var methodOf=restParam(function(object,args){return function(path){return invokePath(object,path,args)}});function mixin(object,source,options){if(options==null){var isObj=isObject(source),props=isObj?keys(source):undefined,methodNames=props&&props.length?baseFunctions(source,props):undefined;if(!(methodNames?methodNames.length:isObj)){methodNames=false;options=source;source=object;object=this}}if(!methodNames){methodNames=baseFunctions(source,keys(source))}var chain=true,index=-1,isFunc=isFunction(object),length=methodNames.length;if(options===false){chain=false}else if(isObject(options)&&"chain"in options){chain=options.chain}while(++index0||end<0)){return new LazyWrapper(result)}if(start<0){result=result.takeRight(-start)}else if(start){result=result.drop(start)}if(end!==undefined){end=+end||0;result=end<0?result.dropRight(-end):result.take(end-start)}return result};LazyWrapper.prototype.takeRightWhile=function(predicate,thisArg){return this.reverse().takeWhile(predicate,thisArg).reverse()};LazyWrapper.prototype.toArray=function(){return this.take(POSITIVE_INFINITY)};baseForOwn(LazyWrapper.prototype,function(func,methodName){var checkIteratee=/^(?:filter|map|reject)|While$/.test(methodName),retUnwrapped=/^(?:first|last)$/.test(methodName),lodashFunc=lodash[retUnwrapped?"take"+(methodName=="last"?"Right":""):methodName];if(!lodashFunc){return}lodash.prototype[methodName]=function(){var args=retUnwrapped?[1]:arguments,chainAll=this.__chain__,value=this.__wrapped__,isHybrid=!!this.__actions__.length,isLazy=value instanceof LazyWrapper,iteratee=args[0],useLazy=isLazy||isArray(value);if(useLazy&&checkIteratee&&typeof iteratee=="function"&&iteratee.length!=1){isLazy=useLazy=false}var interceptor=function(value){return retUnwrapped&&chainAll?lodashFunc(value,1)[0]:lodashFunc.apply(undefined,arrayPush([value],args))};var action={func:thru,args:[interceptor],thisArg:undefined},onlyLazy=isLazy&&!isHybrid;if(retUnwrapped&&!chainAll){if(onlyLazy){value=value.clone();value.__actions__.push(action);return func.call(value)}return lodashFunc.call(undefined,this.value())[0]}if(!retUnwrapped&&useLazy){value=onlyLazy?value:new LazyWrapper(this);var result=func.apply(value,args);result.__actions__.push(action);return new LodashWrapper(result,chainAll)}return this.thru(interceptor)}});arrayEach(["join","pop","push","replace","shift","sort","splice","split","unshift"],function(methodName){var func=(/^(?:replace|split)$/.test(methodName)?stringProto:arrayProto)[methodName],chainName=/^(?:push|sort|unshift)$/.test(methodName)?"tap":"thru",retUnwrapped=/^(?:join|pop|replace|shift)$/.test(methodName);lodash.prototype[methodName]=function(){var args=arguments;if(retUnwrapped&&!this.__chain__){return func.apply(this.value(),args)}return this[chainName](function(value){return func.apply(value,args)})}});baseForOwn(LazyWrapper.prototype,function(func,methodName){var lodashFunc=lodash[methodName];if(lodashFunc){var key=lodashFunc.name,names=realNames[key]||(realNames[key]=[]);names.push({name:methodName,func:lodashFunc})}});realNames[createHybridWrapper(undefined,BIND_KEY_FLAG).name]=[{name:"wrapper",func:undefined}];LazyWrapper.prototype.clone=lazyClone;LazyWrapper.prototype.reverse=lazyReverse;LazyWrapper.prototype.value=lazyValue;lodash.prototype.chain=wrapperChain;lodash.prototype.commit=wrapperCommit;lodash.prototype.concat=wrapperConcat;lodash.prototype.plant=wrapperPlant;lodash.prototype.reverse=wrapperReverse;lodash.prototype.toString=wrapperToString;lodash.prototype.run=lodash.prototype.toJSON=lodash.prototype.valueOf=lodash.prototype.value=wrapperValue;lodash.prototype.collect=lodash.prototype.map;lodash.prototype.head=lodash.prototype.first;lodash.prototype.select=lodash.prototype.filter;lodash.prototype.tail=lodash.prototype.rest;return lodash}var _=runInContext();if(typeof define=="function"&&typeof define.amd=="object"&&define.amd){root._=_;define(function(){return _})}else if(freeExports&&freeModule){if(moduleExports){(freeModule.exports=_)._=_}else{freeExports._=_}}else{root._=_}}).call(this)}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{}]},{},[1])(1)}); \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/botFrameworkInspector/icon.png b/Plugins/Vorlon/plugins/botFrameworkInspector/icon.png new file mode 100644 index 00000000..4e791426 Binary files /dev/null and b/Plugins/Vorlon/plugins/botFrameworkInspector/icon.png differ diff --git a/Plugins/Vorlon/plugins/botFrameworkInspector/vorlon.botFrameworkInspector.client.ts b/Plugins/Vorlon/plugins/botFrameworkInspector/vorlon.botFrameworkInspector.client.ts new file mode 100644 index 00000000..2ee8079e --- /dev/null +++ b/Plugins/Vorlon/plugins/botFrameworkInspector/vorlon.botFrameworkInspector.client.ts @@ -0,0 +1,160 @@ +/// + +module VORLON { + + export class BotFrameworkInspectorClient extends ClientPlugin { + private path; + private requireHook; + private _hooked:boolean; + private _botInfo:BotInfo; + + constructor() { + super("botFrameworkInspector"); + + //In case the plugin is activated but not running on node client + if(Tools.IsWindowAvailable) + return; + + this.path = require("path"); + this.requireHook = require("require-hook"); + //this.debug = true; + this._ready = true; + this._hooked = false; + this._botInfo = new BotInfo(); + } + + public startClientSide(): void { + //In case the plugin is activated but not running on node client + if(Tools.IsWindowAvailable) + return; + + this.requireHook.attach(this.path.resolve()); + this.hookBotFrameworkFunctions(); + } + + private clone(obj:any):any{ + return (JSON.parse(JSON.stringify(obj || {}))); + } + + private addDialogStack(session:any, eventType:EventType, impactedDialogId:string = undefined){ + var botCallStack = new BotDialogSessionInfo(); + botCallStack.sessionState = this.clone(session.sessionState); + botCallStack.conversationData = this.clone(session.conversationData); + botCallStack.dialogData = this.clone(session.dialogData); + botCallStack.privateConversationData = this.clone(session.privateConversationData); + botCallStack.userData = this.clone(session.userData); + botCallStack.eventType = eventType; + botCallStack.impactedDialogId = impactedDialogId; + + var found = false; + this._botInfo.userEntries.forEach((entry) =>{ + if(entry.message.address.id === session.message.address.id){ + entry.dialogStacks.push(botCallStack); + found = true; + } + }); + + if(!found){ + var newEntry = new UserEntry(); + newEntry.message = session.message; + newEntry.dialogStacks.push(botCallStack); + this._botInfo.userEntries.push(newEntry); + } + + this.refresh(); + }; + + public hookBotFrameworkFunctions(){ + var that = this; + + + this.requireHook.setEvent(function(result, e){ + if (!that._hooked && e.require === "botbuilder") { + //Hooking onSave on Session class + var previousSendBatchFunction = result.Session.prototype.sendBatch; + + result.Session.prototype.sendBatch = function(callback:any) { + var returned = previousSendBatchFunction.apply(this, arguments); + var previousOnSaveFunction = this.options.onSave; + + var thatSession = this; + this.options.onSave(function(err:any){ + var returned = previousOnSaveFunction.apply(this, arguments); + that.addDialogStack(thatSession, EventType.FinalState); + return returned; + }); + + return returned; + } + + //Hooking beginDialog on Session class + var previousBeginDialogFunction = result.Session.prototype.beginDialog; + + result.Session.prototype.beginDialog = function(id: string, args?: any) { + that.addDialogStack(this, EventType.BeginDialog, id); + return previousBeginDialogFunction.apply(this, arguments); + } + + //Hooking endDialog on Session class + var previousEndDialogFunction = result.Session.prototype.endDialog; + + result.Session.prototype.endDialog = function(message?: any, ...args: any[]) { + that.addDialogStack(this, EventType.EndDialog); + return previousEndDialogFunction.apply(this, arguments); + } + + //Hooking endDialog on Session class + var previousEndDialogWithResultFunction = result.Session.prototype.endDialogWithResult; + + result.Session.prototype.endDialogWithResult = function(message?: any) { + that.addDialogStack(this, EventType.EndDialogWithResult); + return previousEndDialogWithResultFunction.apply(this, arguments); + } + + //Hooking endDialog on Session class + var previousEndConversationFunction = result.Session.prototype.endConversation; + + result.Session.prototype.endConversation = function(message?: any, ...args: any[]) { + that.addDialogStack(this, EventType.EndConversation); + return previousEndConversationFunction.apply(this, arguments); + } + + //Hooking Dialog on Library class + var previousDialogFunction = result.Library.prototype.dialog; + + result.Library.prototype.dialog = function(id: string, dialog?: any | any[] | any) { + if(dialog){ + + var dialogData = new DialogData(); + dialogData.id = id; + if(dialog instanceof Array){ + dialogData.dialog = dialog.map((d) => { return d.toString(); }); + } else { + dialogData.dialog = [dialog.toString()]; + } + dialogData.library = this.name; + that._botInfo.dialogDataList.push(dialogData); + + that.refresh(); + } + return previousDialogFunction.apply(this, arguments); + } + + that._hooked = true; + } + return result; + }); + } + + public getID(): string { + return "BOTFRAMEWORKINSPECTOR"; + } + + public refresh(): void { + this.sendToDashboard(this._botInfo); + } + } + + //Register the plugin with vorlon core + Core.RegisterClientPlugin(new BotFrameworkInspectorClient()); +} \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/botFrameworkInspector/vorlon.botFrameworkInspector.dashboard.ts b/Plugins/Vorlon/plugins/botFrameworkInspector/vorlon.botFrameworkInspector.dashboard.ts new file mode 100644 index 00000000..2332b20a --- /dev/null +++ b/Plugins/Vorlon/plugins/botFrameworkInspector/vorlon.botFrameworkInspector.dashboard.ts @@ -0,0 +1,295 @@ +/// + +declare var cytoscape: any; + +module VORLON { + export class BotFrameworkInspectorDashboard extends DashboardPlugin { + constructor() { + super("botFrameworkInspector", "control.html", "control.css"); + this._ready = false; + this._id = "BOTFRAMEWORKINSPECTOR"; + } + + private _lastReceivedBotInfo: BotInfo; + private _dialogsContainer: HTMLDivElement; + private _dialogStacksContainer: HTMLDivElement; + private _divPluginBot: HTMLDivElement; + private _datacheckbox: HTMLInputElement; + + public startDashboardSide(div: HTMLDivElement = null): void { + this._insertHtmlContentAsync(div, (filledDiv) => { + this._dialogsContainer = document.getElementById("dialogs"); + this._dialogStacksContainer = document.getElementById("dialogstacks"); + + var firstInitialization = () => { + if (!this._ready && this._divPluginBot.style.display === "none") { + window.setTimeout(firstInitialization, 500); + } + else { + this._ready = true; + this.display(); + //this._drawGraphNodes(); + } + }; + + this._loadScript("/vorlon/plugins/botFrameworkInspector/cytoscape.min.js", () => { + this._loadScript("/vorlon/plugins/botFrameworkInspector/dagre.min.js", () => { + this._loadScript("/vorlon/plugins/botFrameworkInspector/cytoscape-dagre.js", () => { + this._divPluginBot = document.getElementsByClassName("plugin-botframeworkinspector")[0]; + window.setTimeout(firstInitialization, 500); + }); + }); + }); + + this._datacheckbox = document.getElementById("showdatacheckbox") as HTMLInputElement; + this._datacheckbox.addEventListener("click", (elem) => { + var from = 'data'; + var to = 'data-hidden'; + if (this._datacheckbox.checked) { + from = 'data-hidden'; + to = 'data'; + } + + var els = document.getElementsByClassName(from); + while (els.length) { + els[0].className = to; + } + }); + }); + } + + private _drawGraphNodes(nodesList: any[], edgesList: any[]) { + var cy = cytoscape({ + container: document.getElementById('right'), + boxSelectionEnabled: false, + autounselectify: true, + wheelSensitivity: 0.2, + layout: { + name: 'dagre' + }, + style: [ + { + selector: 'node', + style: { + 'content': 'data(value)', + 'text-opacity': 0.5, + 'text-valign': 'center', + 'text-halign': 'right', + 'background-color': '#11479e' + } + }, + { + selector: 'edge', + style: { + 'width': 2, + 'target-arrow-shape': 'triangle', + 'line-color': '#9dbaea', + 'target-arrow-color': '#9dbaea', + 'curve-style': 'bezier' + } + }, + { + selector: 'node.currentState', + style: { + 'background-color': '#86B342' + } + } + ], + elements: { + nodes: nodesList, + edges: edgesList + }, + }); + cy.on('tap', 'node', function(event){ + var evtTarget = event.cyTarget; + console.log(evtTarget.id()); + }); + } + + private _loadScript(url, callback) { + var script = document.createElement("script"); + script.type = "text/javascript"; + + script.onload = function () { + callback(); + }; + + script.src = url; + document.getElementsByTagName("head")[0].appendChild(script); + } + + public onRealtimeMessageReceivedFromClientSide(receivedObject: any): void { + this._lastReceivedBotInfo = receivedObject; + this.display(); + //this._drawGraphNodes(); + } + + public display() { + var nodesList = []; + var edgesList = []; + var currentTreeBranch = []; + + if (this._lastReceivedBotInfo) { + + this._dialogsContainer.innerHTML = null; + this._lastReceivedBotInfo.dialogDataList.forEach((dataList) => { + var dialog = document.createElement("div"); + dialog.classList.add("dialog"); + + var dialogid = document.createElement("p"); + dialogid.innerText = `${dataList.library} > ${dataList.id}`; + dialogid.classList.add("dialog-id"); + dialog.appendChild(dialogid); + + var dialogDetail = document.createElement("div"); + dialogDetail.classList.add("dialog-detail"); + dialog.appendChild(dialogDetail); + + var waterfallStepsLabel = document.createElement("p"); + waterfallStepsLabel.innerText = "Steps "; + dialogDetail.appendChild(waterfallStepsLabel); + + var waterfallSteps = document.createElement("div"); + waterfallSteps.classList.add("waterfall-steps"); + dialogDetail.appendChild(waterfallSteps); + + if (dataList.dialog && dataList.dialog.length) { + if(dataList.dialog.length > 0){ + for(var i = 0; i < dataList.dialog.length; i++){ + var waterfallStep = document.createElement("div"); + waterfallStep.classList.add("waterfall-step"); + waterfallStep.innerText = (i + 1).toString(); + waterfallStep.title = dataList.dialog[i]; + waterfallSteps.appendChild(waterfallStep); + } + } + else { + waterfallStepsLabel.innerText += " none."; + } + } + else { + waterfallStepsLabel.innerText += " none."; + } + + this._dialogsContainer.appendChild(dialog); + }); + + if(this._lastReceivedBotInfo.userEntries && this._lastReceivedBotInfo.userEntries.length){ + this._dialogStacksContainer.innerHTML = null; + this._lastReceivedBotInfo.userEntries.forEach((botUserEntry) => { + var userEntry = document.createElement("div"); + userEntry.classList.add("user-entry"); + + var entry = document.createElement("p"); + entry.classList.add("entry"); + entry.innerHTML = "User entry: " + botUserEntry.message.text; + userEntry.appendChild(entry); + + var stacks = document.createElement("div"); + stacks.classList.add("stacks"); + userEntry.appendChild(stacks); + + //loop on each stack: one for now + botUserEntry.dialogStacks.forEach((dialogSessionInfo) => { + var stack = document.createElement("div"); + stack.classList.add("stack"); + stacks.appendChild(stack); + + var dialogsInStack = document.createElement("div"); + dialogsInStack.classList.add("dialogs-in-stack"); + stack.appendChild(dialogsInStack); + + if(dialogSessionInfo.sessionState.callstack && dialogSessionInfo.sessionState.callstack.length > 0){ + var lineSeparator:HTMLDivElement; + + //loop on each dialog in the stack + dialogSessionInfo.sessionState.callstack.forEach((dialog) => { + var dialogInStack = document.createElement("div"); + dialogInStack.classList.add("dialog-in-stack"); + dialogInStack.innerText = dialog.id; + if(dialog.state["BotBuilder.Data.WaterfallStep"] != undefined) + dialogInStack.innerText += "(" + (dialog.state["BotBuilder.Data.WaterfallStep"] + 1) + ")"; + dialogsInStack.appendChild(dialogInStack); + + lineSeparator = document.createElement("div"); + lineSeparator.classList.add("line"); + dialogsInStack.appendChild(lineSeparator); + }); + } + else { + dialogsInStack.innerText = "(No dialog in stack)"; + lineSeparator = document.createElement("div"); + lineSeparator.classList.add("line"); + dialogsInStack.appendChild(lineSeparator); + } + + var eventType = document.createElement("p"); + eventType.innerText = `[${EventType[dialogSessionInfo.eventType]}`; + + if(dialogSessionInfo.impactedDialogId) { + eventType.innerText += `(${dialogSessionInfo.impactedDialogId})]`; + if (dialogSessionInfo.eventType === EventType.BeginDialog) { + //If begin dialog is called from an empty start + if(!dialogSessionInfo.sessionState.callstack || dialogSessionInfo.sessionState.callstack.length == 0){ + //Make sure we start from scratch + currentTreeBranch = []; + } + + var newNode = {data: { id: nodesList.length, value: dialogSessionInfo.impactedDialogId}}; + nodesList.push(newNode); + currentTreeBranch.push(newNode); + if (currentTreeBranch.length > 1) { + var currentIndex = currentTreeBranch.length - 1; + var newEdge = { data: { source: currentTreeBranch[currentIndex-1].data.id, target: currentTreeBranch[currentIndex].data.id } }; + edgesList.push(newEdge); + } + } + } + else { + eventType.innerText += "]"; + } + if (dialogSessionInfo.eventType === EventType.EndDialog || dialogSessionInfo.eventType === EventType.EndDialogWithResult) { + if (currentTreeBranch.length > 1) { + var currentIndex = currentTreeBranch.length - 1; + var newEdge = { data: { source: currentTreeBranch[currentIndex].data.id, target: currentTreeBranch[currentIndex-1].data.id } }; + edgesList.push(newEdge); + currentTreeBranch.pop(); + } + } + + if (dialogSessionInfo.eventType === EventType.EndConversation) { + if (currentTreeBranch.length > 1) { + currentTreeBranch = []; + } + } + + dialogsInStack.appendChild(eventType); + + var userData = document.createElement("div"); + userData.classList.add(this._datacheckbox.checked ? "data" : "data-hidden"); + userData.innerHTML = "

ConversationData: " + JSON.stringify(dialogSessionInfo.conversationData) + "

"; + userData.innerHTML += "

DialogData: " + JSON.stringify(dialogSessionInfo.dialogData) + "

"; + userData.innerHTML += "

PrivateConversationData: " + JSON.stringify(dialogSessionInfo.privateConversationData) + "

"; + userData.innerHTML += "

UserData: " + JSON.stringify(dialogSessionInfo.userData) + "

"; + stack.appendChild(userData); + }); + this._dialogStacksContainer.appendChild(userEntry); + }); + nodesList[nodesList.length - 1].classes = 'currentState'; + this._drawGraphNodes(nodesList, edgesList); + } + } + } + } + + //Register the plugin with vorlon core + Core.RegisterDashboardPlugin(new BotFrameworkInspectorDashboard()); +} + +interface Window { + flowchart: FlowChart; +} + +interface FlowChart { + parse(code: string): any; +}; \ No newline at end of file diff --git a/Plugins/Vorlon/plugins/botFrameworkInspector/vorlon.botFrameworkInspector.interfaces.ts b/Plugins/Vorlon/plugins/botFrameworkInspector/vorlon.botFrameworkInspector.interfaces.ts new file mode 100644 index 00000000..8b4d4df9 --- /dev/null +++ b/Plugins/Vorlon/plugins/botFrameworkInspector/vorlon.botFrameworkInspector.interfaces.ts @@ -0,0 +1,47 @@ +module VORLON { + // export class PerformanceItem { + // public name: string; + // } + + export enum EventType { + BeginDialog, FinalState, EndDialog, EndDialogWithResult, EndConversation + } + + export class BotInfo { + public dialogDataList: DialogData[]; + public userEntries:UserEntry[]; + + /** + * + */ + constructor() { + this.dialogDataList = []; + this.userEntries = []; + } + } + + export class DialogData { + public id:string; + public dialog:any|any[]; + public library:string; + } + + export class UserEntry { + public dialogStacks:BotDialogSessionInfo[]; + public message: any; + + constructor() { + this.dialogStacks = []; + } + } + + export class BotDialogSessionInfo { + public eventType: EventType; + public sessionState: any; + public dialogData: any; + public userData: any; + public conversationData: any; + public privateConversationData: any; + public impactedDialogId: any; + } +} \ No newline at end of file diff --git a/Plugins/Vorlon/typings/botbuilder/botbuilder.d.ts b/Plugins/Vorlon/typings/botbuilder/botbuilder.d.ts new file mode 100644 index 00000000..792b96d7 --- /dev/null +++ b/Plugins/Vorlon/typings/botbuilder/botbuilder.d.ts @@ -0,0 +1,2405 @@ +//============================================================================= +// +// INTERFACES +// +//============================================================================= + +/** + * An event received from or being sent to a source. + */ +export interface IEvent { + /** Defines type of event. Should be 'message' for an IMessage. */ + type: string; + + /** SDK thats processing the event. Will always be 'botbuilder'. */ + agent: string; + + /** The original source of the event (i.e. 'facebook', 'skype', 'slack', etc.) */ + source: string; + + /** The original event in the sources native schema. For outgoing messages can be used to pass source specific event data like custom attachments. */ + sourceEvent: any; + + /** Address routing information for the event. Save this field to external storage somewhere to later compose a proactive message to the user. */ + address: IAddress; + + /** + * For incoming messages this is the user that sent the message. By default this is a copy of [address.user](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iaddress.html#user) but you can configure your bot with a + * [lookupUser](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iuniversalbotsettings.html#lookupuser) function that lets map the incoming user to an internal user id. + */ + user: IIdentity; +} + +/** The Properties of a conversation have changed. */ +export interface IConversationUpdate extends IEvent { + /** Array of members added to the conversation. */ + membersAdded?: IIdentity[]; + + /** Array of members removed from the conversation. */ + membersRemoved?: IIdentity[]; + + /** The conversations new topic name. */ + topicName?: string; + + /** If true then history was disclosed. */ + historyDisclosed?: boolean; +} + +/** A user has updated their contact list. */ +export interface IContactRelationUpdate extends IEvent { + /** The action taken. Valid values are "add" or "remove". */ + action: string; +} + +/** + * A chat message sent between a User and a Bot. Messages from the bot to the user come in two flavors: + * + * * __reactive messages__ are messages sent from the Bot to the User as a reply to an incoming message from the user. + * * __proactive messages__ are messages sent from the Bot to the User in response to some external event like an alarm triggering. + * + * In the reactive case the you should copy the [address](#address) field from the incoming message to the outgoing message (if you use the [Message]( /en-us/node/builder/chat-reference/classes/_botbuilder_d_.message.html) builder class and initialize it with the + * [session](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session.html) this will happen automatically) and then set the [text](#text) or [attachments](#attachments). For proactive messages you’ll need save the [address](#address) from the incoming message to + * an external storage somewhere. You can then later pass this in to [UniversalBot.beginDialog()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.universalbot.html#begindialog) or copy it to an outgoing message passed to + * [UniversalBot.send()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.universalbot.html#send). + * + * Composing a message to the user using the incoming address object will by default send a reply to the user in the context of the current conversation. Some channels allow for the starting of new conversations with the user. To start a new proactive conversation with the user simply delete + * the [conversation](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iaddress.html#conversation) field from the address object before composing the outgoing message. + */ +export interface IMessage extends IEvent { + /** Timestamp of message given by chat service for incoming messages. */ + timestamp: string; + + /** Text to be displayed by as fall-back and as short description of the message content in e.g. list of recent conversations. */ + summary: string; + + /** Message text. */ + text: string; + + /** Identified language of the message text if known. */ + textLocale: string; + + /** For incoming messages contains attachments like images sent from the user. For outgoing messages contains objects like cards or images to send to the user. */ + attachments: IAttachment[]; + + /** Structured objects passed to the bot or user. */ + entities: any[]; + + /** Format of text fields. The default value is 'markdown'. */ + textFormat: string; + + /** Hint for how clients should layout multiple attachments. The default value is 'list'. */ + attachmentLayout: string; +} + +/** Implemented by classes that can be converted into a message. */ +export interface IIsMessage { + /** Returns the JSON object for the message. */ + toMessage(): IMessage; +} + +/** Represents a user, bot, or conversation. */ +export interface IIdentity { + /** Channel specific ID for this identity. */ + id: string; + + /** Friendly name for this identity. */ + name?: string; + + /** If true the identity is a group. Typically only found on conversation identities. */ + isGroup?: boolean; +} + +/** + * Address routing information for a [message](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.imessage.html#address). + * Addresses are bidirectional meaning they can be used to address both incoming and outgoing messages. They're also connector specific meaning that + * [connectors](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iconnector.html) are free to add their own fields. + */ +export interface IAddress { + /** Unique identifier for channel. */ + channelId: string; + + /** User that sent or should receive the message. */ + user: IIdentity; + + /** Bot that either received or is sending the message. */ + bot: IIdentity; + + /** + * Represents the current conversation and tracks where replies should be routed to. + * Can be deleted to start a new conversation with a [user](#user) on channels that support new conversations. + */ + conversation?: IIdentity; +} + +/** Chat connector specific address. */ +export interface IChatConnectorAddress extends IAddress { + /** Incoming Message ID. */ + id?: string; + + /** Specifies the URL to post messages back. */ + serviceUrl?: string; +} + +/** + * Many messaging channels provide the ability to attach richer objects. Bot Builder lets you express these attachments in a cross channel way and [connectors](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iconnector.html) will do their best to render the + * attachments using the channels native constructs. If you desire more control over the channels rendering of a message you can use [IEvent.sourceEvent](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ievent.html#sourceevent) to provide attachments using + * the channels native schema. The types of attachments that can be sent varies by channel but these are the basic types: + * + * * __Media and Files:__ Basic files can be sent by setting [contentType](#contenttype) to the MIME type of the file and then passing a link to the file in [contentUrl](#contenturl). + * * __Cards and Keyboards:__ A rich set of visual cards and custom keyboards can by setting [contentType](#contenttype) to the cards type and then passing the JSON for the card in [content](#content). If you use one of the rich card builder classes like + * [HeroCard](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.herocard.html) the attachment will automatically filled in for you. + */ +export interface IAttachment { + /** MIME type string which describes type of attachment. */ + contentType: string; + + /** (Optional) object structure of attachment. */ + content?: any; + + /** (Optional) reference to location of attachment content. */ + contentUrl?: string; +} + +/** Implemented by classes that can be converted into an attachment. */ +export interface IIsAttachment { + /** Returns the JSON object for the attachment. */ + toAttachment(): IAttachment; +} + +/** Displays a signin card and button to the user. Some channels may choose to render this as a text prompt and link to click. */ +export interface ISigninCard { + /** Title of the Card. */ + title: string; + + /** Sign in action. */ + buttons: ICardAction[]; +} + +/** + * Displays a card to the user using either a smaller thumbnail layout or larger hero layout (the attachments [contentType](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iattachment.html#contenttype) determines which). + * All of the cards fields are optional so this card can be used to specify things like a keyboard on certain channels. Some channels may choose to render a lower fidelity version of the card or use an alternate representation. + */ +export interface IThumbnailCard { + /** Title of the Card. */ + title?: string; + + /** Subtitle appears just below Title field, differs from Title in font styling only. */ + subtitle?: string; + + /** Text field appears just below subtitle, differs from Subtitle in font styling only. */ + text?: string; + + /** Messaging supports all media formats: audio, video, images and thumbnails as well to optimize content download. */ + images?: ICardImage[]; + + /** This action will be activated when user taps on the card. Not all channels support tap actions and some channels may choose to render the tap action as the titles link. */ + tap?: ICardAction; + + /** Set of actions applicable to the current card. Not all channels support buttons or cards with buttons. Some channels may choose to render the buttons using a custom keyboard. */ + buttons?: ICardAction[]; +} + +/** Displays a rich receipt to a user for something they've either bought or are planning to buy. */ +export interface IReceiptCard { + /** Title of the Card. */ + title: string; + + /** Array of receipt items. */ + items: IReceiptItem[]; + + /** Array of additional facts to display to user (shipping charges and such.) Not all facts will be displayed on all channels. */ + facts: IFact[]; + + /** This action will be activated when user taps on the card. Not all channels support tap actions. */ + tap: ICardAction; + + /** Total amount of money paid (or should be paid.) */ + total: string; + + /** Total amount of TAX paid (or should be paid.) */ + tax: string; + + /** Total amount of VAT paid (or should be paid.) */ + vat: string; + + /** Set of actions applicable to the current card. Not all channels support buttons and the number of allowed buttons varies by channel. */ + buttons: ICardAction[]; +} + +/** An individual item within a [receipt](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ireceiptcard.html). */ +export interface IReceiptItem { + /** Title of the item. */ + title: string; + + /** Subtitle appears just below Title field, differs from Title in font styling only. On some channels may be combined with the [title](#title) or [text](#text). */ + subtitle: string; + + /** Text field appears just below subtitle, differs from Subtitle in font styling only. */ + text: string; + + /** Image to display on the card. Some channels may either send the image as a seperate message or simply include a link to the image. */ + image: ICardImage; + + /** Amount with currency. */ + price: string; + + /** Number of items of given kind. */ + quantity: string; + + /** This action will be activated when user taps on the Item bubble. Not all channels support tap actions. */ + tap: ICardAction; +} + +/** Implemented by classes that can be converted into a receipt item. */ +export interface IIsReceiptItem { + /** Returns the JSON object for the receipt item. */ + toItem(): IReceiptItem; +} + +/** The action that should be performed when a card, button, or image is tapped. */ +export interface ICardAction { + /** Defines the type of action implemented by this button. Not all action types are supported by all channels. */ + type: string; + + /** Text description for button actions. */ + title?: string; + + /** Parameter for Action. Content of this property depends on Action type. */ + value: string; + + /** (Optional) Picture to display for button actions. Not all channels support button images. */ + image?: string; +} + +/** Implemented by classes that can be converted into a card action. */ +export interface IIsCardAction { + /** Returns the JSON object for the card attachment. */ + toAction(): ICardAction; +} + +/** An image on a card. */ +export interface ICardImage { + /** Thumbnail image for major content property. */ + url: string; + + /** Image description intended for screen readers. Not all channels will support alt text. */ + alt: string; + + /** Action assigned to specific Attachment. E.g. navigate to specific URL or play/open media content. Not all channels will support tap actions. */ + tap: ICardAction; +} + +/** Implemented by classes that can be converted into a card image. */ +export interface IIsCardImage { + /** Returns the JSON object for the card image. */ + toImage(): ICardImage; +} + +/** A fact displayed on a card like a [receipt](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ireceiptcard.html). */ +export interface IFact { + /** Display name of the fact. */ + key: string; + + /** Display value of the fact. */ + value: string; +} + +/** Implemented by classes that can be converted into a fact. */ +export interface IIsFact { + /** Returns the JSON object for the fact. */ + toFact(): IFact; +} + +/** Settings used to initialize an ILocalizer implementation. */ +interface IDefaultLocalizerSettings { + /** The path to the parent of the bot's locale directory */ + botLocalePath?: string; + + /** The default locale of the bot */ + defaultLocale?: string; +} + +/** Plugin for localizing messages sent to the user by a bot. */ +export interface ILocalizer { + /** + * Loads the localied table for the supplied locale, and call's the supplied callback once the load is complete. + * @param locale The locale to load. + * @param callback callback that is called once the supplied locale has been loaded, or an error if the load fails. + */ + load(locale: string, callback: (err: Error) => void): void; + + /** + * Loads a localized string for the specified language. + * @param locale Desired locale of the string to return. + * @param msgid String to use as a key in the localized string table. Typically this will just be the english version of the string. + * @param namespace (Optional) namespace for the msgid keys. + */ + trygettext(locale: string, msgid: string, namespace?: string): string; + + /** + * Loads a localized string for the specified language. + * @param locale Desired locale of the string to return. + * @param msgid String to use as a key in the localized string table. Typically this will just be the english version of the string. + * @param namespace (Optional) namespace for the msgid keys. + */ + gettext(locale: string, msgid: string, namespace?: string): string; + + /** + * Loads the plural form of a localized string for the specified language. + * @param locale Desired locale of the string to return. + * @param msgid Singular form of the string to use as a key in the localized string table. + * @param msgid_plural Plural form of the string to use as a key in the localized string table. + * @param count Count to use when determining whether the singular or plural form of the string should be used. + * @param namespace (Optional) namespace for the msgid and msgid_plural keys. + */ + ngettext(locale: string, msgid: string, msgid_plural: string, count: number, namespace?: string): string; +} + +/** Persisted session state used to track a conversations dialog stack. */ +export interface ISessionState { + /** Dialog stack for the current session. */ + callstack: IDialogState[]; + + /** Timestamp of when the session was last accessed. */ + lastAccess: number; + + /** Version number of the current callstack. */ + version: number; +} + +/** An entry on the sessions dialog stack. */ +export interface IDialogState { + /** ID of the dialog. */ + id: string; + + /** Persisted state for the dialog. */ + state: any; +} + +/** + * Results returned by a child dialog to its parent via a call to session.endDialog(). + */ +export interface IDialogResult { + /** The reason why the current dialog is being resumed. Defaults to {ResumeReason.completed} */ + resumed?: ResumeReason; + + /** ID of the child dialog thats ending. */ + childId?: string; + + /** If an error occured the child dialog can return the error to the parent. */ + error?: Error; + + /** The users response. */ + response?: T; +} + +/** Context of the received message passed to the Dialog.recognize() method. */ +export interface IRecognizeContext { + /** Message that was received. */ + message: IMessage; + + /** The users preferred locale for the message. */ + locale: string; + + /** If true the Dialog is the active dialog on the callstack. */ + activeDialog: boolean; + + /** Data persisted for the current dialog. */ + dialogData: any; +} + +/** Results from a call to a recognize() function. The implementation is free to add any additional properties to the result. */ +export interface IRecognizeResult { + /** Confidence that the users utterance was understood on a scale from 0.0 - 1.0. */ + score: number; +} + +/** Options passed when binding a dialog action handler. */ +export interface IDialogActionOptions { + /** (Optional) regular expression that should be matched against the users utterance to trigger an action. If this is ommitted the action can only be invoked from a button. */ + matches?: RegExp; + + /** Minimum score needed to trigger the action using the value of [expression](#expression). The default value is 0.1. */ + intentThreshold?: number; + + /** (Optional) arguments to pass to the dialog spawned when the action is triggered. */ + dialogArgs?: any; +} + +/** Results for a recognized dialog action. */ +export interface IRecognizeActionResult extends IRecognizeResult { + /** Named dialog action that was matched. */ + action?: string; + + /** A regular expression that was matched. */ + expression?: RegExp; + + /** The results of the [expression](#expression) that was matched. matched[0] will be the text that was matched and matched[1...n] is the result of capture groups. */ + matched?: string[]; + + /** Optional data passed as part of the action binding. */ + data?: string; + + /** ID of the dialog the action is bound to. */ + dialogId?: string; + + /** Index on the dialog stack of the dialog the action is bound to. */ + dialogIndex?: number; +} + +/** Options passed to built-in prompts. */ +export interface IPromptOptions { + /** + * (Optional) retry prompt to send if the users response isn't understood. Default is to just + * reprompt with the configured [defaultRetryPrompt](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ipromptsoptions.html#defaultretryprompt) + * plus the original prompt. + * + * Note that if the original prompt is an _IMessage_ the retry prompt will be sent as a seperate + * message followed by the original message. If the retryPrompt is also an _IMessage_ it will + * instead be sent in place of the original message. + * * _{string}_ - Initial message to send the user. + * * _{string[]}_ - Array of possible messages to send user. One will be chosen at random. + * * _{IMessage}_ - Initial message to send the user. Message can contain attachments. + * * _{IIsMessage}_ - Instance of the [Message](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.message.html) builder class. + */ + retryPrompt?: string|string[]|IMessage|IIsMessage; + + /** (Optional) maximum number of times to reprompt the user. By default the user will be reprompted indefinitely. */ + maxRetries?: number; + + /** (Optional) reference date when recognizing times. Date expressed in ticks using Date.getTime(). */ + refDate?: number; + + /** (Optional) type of list to render for PromptType.choice. Default value is ListStyle.auto. */ + listStyle?: ListStyle; + + /** (Optional) flag used to control the reprompting of a user after a dialog started by an action ends. The default value is true. */ + promptAfterAction?: boolean; + + /** (Optional) namespace to use when localizing a passed in prompt. */ + localizationNamespace?: string; +} + +/** Arguments passed to the built-in prompts beginDialog() call. */ +export interface IPromptArgs extends IPromptOptions { + /** Type of prompt invoked. */ + promptType: PromptType; + + /** + * Initial message to send to user. + * * _{string}_ - Initial message to send the user. + * * _{string[]}_ - Array of possible messages to send user. One will be chosen at random. + * * _{IMessage}_ - Initial message to send the user. Message can contain attachments. + * * _{IIsMessage}_ - Instance of the [Message](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.message.html) builder class. + */ + prompt: string|string[]|IMessage|IIsMessage; + + /** Enum values for a choice prompt. */ + enumsValues?: string[]; +} + +/** Dialog result returned by a system prompt. */ +export interface IPromptResult extends IDialogResult { + /** Type of prompt completing. */ + promptType?: PromptType; +} + +/** Result returned from an IPromptRecognizer. */ +export interface IPromptRecognizerResult extends IPromptResult { + /** Returned from a prompt recognizer to indicate that a parent dialog handled (or captured) the utterance. */ + handled?: boolean; +} + +/** Strongly typed Text Prompt Result. */ +export interface IPromptTextResult extends IPromptResult { } + +/** Strongly typed Number Prompt Result. */ +export interface IPromptNumberResult extends IPromptResult { } + +/** Strongly typed Confirm Prompt Result. */ +export interface IPromptConfirmResult extends IPromptResult { } + +/** Strongly typed Choice Prompt Result. */ +export interface IPromptChoiceResult extends IPromptResult { } + +/** Strongly typed Time Prompt Result. */ +export interface IPromptTimeResult extends IPromptResult { } + +/** Strongly typed Attachment Prompt Result. */ +export interface IPromptAttachmentResult extends IPromptResult { } + +/** Plugin for recognizing prompt responses received by a user. */ +export interface IPromptRecognizer { + /** + * Attempts to match a users reponse to a given prompt. + * @param args Arguments passed to the recognizer including that language, text, and prompt choices. + * @param callback Function to invoke with the result of the recognition attempt. + * @param callback.result Returns the result of the recognition attempt. + */ + recognize(args: IPromptRecognizerArgs, callback: (result: IPromptRecognizerResult) => void): void; +} + +/** Arguments passed to the IPromptRecognizer.recognize() method.*/ +export interface IPromptRecognizerArgs { + /** Type of prompt being responded to. */ + promptType: PromptType; + + /** Text of the users response to the prompt. */ + text: string; + + /** Language of the text if known. */ + language?: string; + + /** For choice prompts the list of possible choices. */ + enumValues?: string[]; + + /** (Optional) reference date when recognizing times. */ + refDate?: number; +} + +/** Global configuration options for the Prompts dialog. */ +export interface IPromptsOptions { + /** Replaces the default recognizer (SimplePromptRecognizer) used to recognize prompt replies. */ + recognizer?: IPromptRecognizer +} + +/** A recognized intent. */ +export interface IIntent { + /** Intent that was recognized. */ + intent: string; + + /** Confidence on a scale from 0.0 - 1.0 that the proper intent was recognized. */ + score: number; +} + +/** A recognized entity. */ +export interface IEntity { + /** Type of entity that was recognized. */ + type: string; + + /** Value of the recognized entity. */ + entity: string; + + /** Start position of entity within text utterance. */ + startIndex?: number; + + /** End position of entity within text utterance. */ + endIndex?: number; + + /** Confidence on a scale from 0.0 - 1.0 that the proper entity was recognized. */ + score?: number; +} + +/** Options used to configure an [IntentDialog](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.intentdialog.html). */ +export interface IIntentDialogOptions { + /** Minimum score needed to trigger the recognition of an intent. The default value is 0.1. */ + intentThreshold?: number; + + /** Controls the dialogs processing of incomming user utterances. The default is RecognizeMode.onBeginIfRoot. The default prior to v3.2 was RecognizeMode.onBegin. */ + recognizeMode?: RecognizeMode; + + /** The order in which the configured [recognizers](#recognizers) should be evaluated. The default order is parallel. */ + recognizeOrder?: RecognizeOrder; + + /** (Optional) list of intent recognizers to run the users utterance through. */ + recognizers?: IIntentRecognizer[]; + + /** Maximum number of recognizers to evaluate at one time when [recognizerOrder](#recognizerorder) is parallel. */ + processLimit?: number; +} + +/** export interface implemented by intent recognizers like the LuisRecognizer class. */ +export interface IIntentRecognizer { + /** Attempts to match a users text utterance to an intent. */ + recognize(context: IRecognizeContext, callback: (err: Error, result: IIntentRecognizerResult) => void): void; +} + +/** Results returned by an intent recognizer. */ +export interface IIntentRecognizerResult extends IRecognizeResult { + /** Top intent that was matched. */ + intent: string; + + /** A regular expression that was matched. */ + expression?: RegExp; + + /** The results of the [expression](#expression) that was matched. matched[0] will be the text that was matched and matched[1...n] is the result of capture groups. */ + matched?: string[]; + + /** Full list of intents that were matched. */ + intents?: IIntent[]; + + /** List of entities recognized. */ + entities?: IEntity[]; +} + +/** Options passed to the constructor of a session. */ +export interface ISessionOptions { + /** Function to invoke when the sessions state is saved. */ + onSave: (done: (err: Error) => void) => void; + + /** Function to invoke when a batch of messages are sent. */ + onSend: (messages: IMessage[], done: (err: Error) => void) => void; + + /** The bots root library of dialogs. */ + library: Library; + + /** The localizer to use for the session. */ + localizer: ILocalizer; + + /** Array of session middleware to execute prior to each request. */ + middleware: ISessionMiddleware[]; + + /** Unique ID of the dialog to use when starting a new conversation with a user. */ + dialogId: string; + + /** (Optional) arguments to pass to the conversations initial dialog. */ + dialogArgs?: any; + + /** (Optional) time to allow between each message sent as a batch. The default value is 250ms. */ + autoBatchDelay?: number; + + /** Default error message to send users when a dialog error occurs. */ + dialogErrorMessage?: string|string[]|IMessage|IIsMessage; + + /** Global actions registered for the bot. */ + actions?: ActionSet; +} + +/** result returnd from a call to EntityRecognizer.findBestMatch() or EntityRecognizer.findAllMatches(). */ +export interface IFindMatchResult { + /** Index of the matched value. */ + index: number; + + /** Value that was matched. */ + entity: string; + + /** Confidence score on a scale from 0.0 - 1.0 that an value matched the users utterance. */ + score: number; +} + +/** Context object passed to IBotStorage calls. */ +export interface IBotStorageContext { + /** (Optional) ID of the user being persisted. If missing __userData__ won't be persisted. */ + userId?: string; + + /** (Optional) ID of the conversation being persisted. If missing __conversationData__ and __privateConversationData__ won't be persisted. */ + conversationId?: string; + + /** (Optional) Address of the message received by the bot. */ + address?: IAddress; + + /** If true IBotStorage should persist __userData__. */ + persistUserData: boolean; + + /** If true IBotStorage should persist __conversationData__. */ + persistConversationData: boolean; +} + +/** Data values persisted to IBotStorage. */ +export interface IBotStorageData { + /** The bots data about a user. This data is global across all of the users conversations. */ + userData?: any; + + /** The bots shared data for a conversation. This data is visible to every user within the conversation. */ + conversationData?: any; + + /** + * The bots private data for a conversation. This data is only visible to the given user within the conversation. + * The session stores its session state using privateConversationData so it should always be persisted. + */ + privateConversationData?: any; +} + +/** Replacable storage system used by UniversalBot. */ +export interface IBotStorage { + /** Reads in data from storage. */ + getData(context: IBotStorageContext, callback: (err: Error, data: IBotStorageData) => void): void; + + /** Writes out data to storage. */ + saveData(context: IBotStorageContext, data: IBotStorageData, callback?: (err: Error) => void): void; +} + +/** Options used to initialize a ChatConnector instance. */ +export interface IChatConnectorSettings { + /** The bots App ID assigned in the Bot Framework portal. */ + appId?: string; + + /** The bots App Password assigned in the Bot Framework Portal. */ + appPassword?: string; + + /** If true the bots userData, privateConversationData, and conversationData will be gzipped prior to writing to storage. */ + gzipData?: boolean; +} + +/** Options used to initialize a UniversalBot instance. */ +export interface IUniversalBotSettings { + /** (Optional) dialog to launch when a user initiates a new conversation with a bot. Default value is '/'. */ + defaultDialogId?: string; + + /** (Optional) arguments to pass to the initial dialog for a conversation. */ + defaultDialogArgs?: any; + + /** (Optional) settings used to configure the frameworks built in default localizer. */ + localizerSettings?: IDefaultLocalizerSettings; + + /** (Optional) function used to map the user ID for an incoming message to another user ID. This can be used to implement user account linking. */ + lookupUser?: (address: IAddress, done: (err: Error, user: IIdentity) => void) => void; + + /** (Optional) maximum number of async options to conduct in parallel. */ + processLimit?: number; + + /** (Optional) time to allow between each message sent as a batch. The default value is 150ms. */ + autoBatchDelay?: number; + + /** (Optional) storage system to use for storing user & conversation data. */ + storage?: IBotStorage; + + /** (optional) if true userData will be persisted. The default value is true. */ + persistUserData?: boolean; + + /** (Optional) if true shared conversationData will be persisted. The default value is false. */ + persistConversationData?: boolean; + + /** (Optional) message to send the user should an unexpected error occur during a conversation. A default message is provided. */ + dialogErrorMessage?: string|string[]|IMessage|IIsMessage; +} + +/** Implemented by connector plugins for the UniversalBot. */ +export interface IConnector { + /** Called by the UniversalBot at registration time to register a handler for receiving incoming events from a channel. */ + onEvent(handler: (events: IEvent[], callback?: (err: Error) => void) => void): void; + + /** Called by the UniversalBot to deliver outgoing messages to a user. */ + send(messages: IMessage[], callback: (err: Error) => void): void; + + /** Called when a UniversalBot wants to start a new proactive conversation with a user. The connector should return a properly formated __address__ object with a populated __conversation__ field. */ + startConversation(address: IAddress, callback: (err: Error, address?: IAddress) => void): void; +} + +/** Function signature for a piece of middleware that hooks the 'receive' or 'send' events. */ +export interface IEventMiddleware { + (event: IEvent, next: Function): void; +} + +/** Function signature for a piece of middleware that hooks the 'botbuilder' event. */ +export interface ISessionMiddleware { + (session: Session, next: Function): void; +} + +/** + * Map of middleware hooks that can be registered in a call to __UniversalBot.use()__. + */ +export interface IMiddlewareMap { + /** Called in series when an incoming event is received. */ + receive?: IEventMiddleware|IEventMiddleware[]; + + /** Called in series before an outgoing event is sent. */ + send?: IEventMiddleware|IEventMiddleware[]; + + /** Called in series once an incoming message has been bound to a session. Executed after [receive](#receive) middleware. */ + botbuilder?: ISessionMiddleware|ISessionMiddleware[]; +} + +/** + * Signature for functions passed as steps to [DialogAction.waterfall()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.dialogaction.html#waterfall). + * + * Waterfalls let you prompt a user for information using a sequence of questions. Each step of the + * waterfall can either execute one of the built-in [Prompts](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.prompts.html), + * start a new dialog by calling [session.beginDialog()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session.html#begindialog), + * advance to the next step of the waterfall manually using `skip()`, or terminate the waterfall. + * + * When either a dialog or built-in prompt is called from a waterfall step, the results from that + * dialog or prompt will be passed via the `results` parameter to the next step of the waterfall. + * Users can say things like "nevermind" to cancel the built-in prompts so you should guard against + * that by at least checking for [results.response](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogresult.html#response) + * before proceeding. A more detailed explination of why the waterfall is being continued can be + * determined by looking at the [code](/en-us/node/builder/chat-reference/enums/_botbuilder_d_.resumereason.html) + * returned for [results.resumed](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogresult.html#resumed). + * + * You can manually advance to the next step of the waterfall using the `skip()` function passed + * in. Calling `skip({ response: "some text" })` with an [IDialogResult](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogresult.html) + * lets you more accurately mimic the results from a built-in prompt and can simplify your overall + * waterfall logic. + * + * You can terminate a waterfall early by either falling through every step of the waterfall using + * calls to `skip()` or simply not starting another prompt or dialog. + * + * __note:__ Waterfalls have a hidden last step which will automatically end the current dialog if + * if you call a prompt or dialog from the last step. This is useful where you have a deep stack of + * dialogs and want a call to [session.endDialog()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session.html#enddialog) + * from the last child on the stack to end the entire stack. The close of the last child will trigger + * all of its parents to move to this hidden step which will cascade the close all the way up the stack. + * This is typically a desired behaviour but if you want to avoid it or stop it somewhere in the + * middle you'll need to add a step to the end of your waterfall that either does nothing or calls + * something liek [session.send()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session.html#send) + * which isn't going to advance the waterfall forward. + * @example + *

+ * var bot = new builder.BotConnectorBot();
+ * bot.add('/', [
+ *     function (session) {
+ *         builder.Prompts.text(session, "Hi! What's your name?");
+ *     },
+ *     function (session, results) {
+ *         if (results && results.response) {
+ *             // User answered question.
+ *             session.send("Hello %s.", results.response);
+ *         } else {
+ *             // User said nevermind.
+ *             session.send("OK. Goodbye.");
+ *         }
+ *     }
+ * ]);
+ * 
+ */ +export interface IDialogWaterfallStep { + /** + * @param session Session object for the current conversation. + * @param result + * * __result:__ _{any}_ - For the first step of the waterfall this will be `null` or the value of any arguments passed to the handler. + * * __result:__ _{IDialogResult}_ - For subsequent waterfall steps this will be the result of the prompt or dialog called in the previous step. + * @param skip Fuction used to manually skip to the next step of the waterfall. + * @param skip.results (Optional) results to pass to the next waterfall step. This lets you more accurately mimic the results returned from a prompt or dialog. + */ + (session: Session, result?: any | IDialogResult, skip?: (results?: IDialogResult) => void): any; +} + +/** A per/local mapping of LUIS service url's to use for a LuisRecognizer. */ +export interface ILuisModelMap { + [local: string]: string; +} + +/** A per/source mapping of custom event data to send. */ +export interface ISourceEventMap { + [source: string]: any; +} + +/** Options passed to Middleware.dialogVersion(). */ +export interface IDialogVersionOptions { + /** Current major.minor version for the bots dialogs. Major version increments result in existing conversations between the bot and user being restarted. */ + version: number; + + /** Optional message to send the user when their conversation is ended due to a version number change. A default message is provided. */ + message?: string|string[]|IMessage|IIsMessage; + + /** Optional regular expression to listen for to manually detect a request to reset the users session state. */ + resetCommand?: RegExp; +} + +/** Options passed to Middleware.firstRun(). */ +export interface IFirstRunOptions { + /** Current major.minor version for the bots first run experience. Major version increments result in redirecting users to [dialogId](#dialogid) and minor increments redirect users to [upgradeDialogId](#upgradedialogid). */ + version: number; + + /** Dialog to redirect users to when the major [version](#version) changes. */ + dialogId: string; + + /** (Optional) args to pass to [dialogId](#dialogid). */ + dialogArgs?: any; + + /** (Optional) dialog to redirect users to when the minor [version](#version) changes. Useful for minor Terms of Use changes. */ + upgradeDialogId?: string; + + /** (Optional) args to pass to [upgradeDialogId](#upgradedialogid). */ + upgradeDialogArgs?: string; +} + +//============================================================================= +// +// ENUMS +// +//============================================================================= + +/** Reason codes for why a dialog was resumed. */ +export enum ResumeReason { + /** The user completed the child dialog and a result was returned. */ + completed, + + /** The user did not complete the child dialog for some reason. They may have exceeded maxRetries or canceled. */ + notCompleted, + + /** The dialog was canceled in response to some user initiated action. */ + canceled, + + /** The user requested to return to the previous step in a dialog flow. */ + back, + + /** The user requested to skip the current step of a dialog flow. */ + forward +} + +/** Order in which an [IntentDialogs](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.intentdialog.html) recognizers should be evaluated. */ +export enum RecognizeOrder { + /** All recognizers will be evaluated in parallel. */ + parallel, + + /** Recognizers will be evaluated in series. Any recognizer that returns a score of 1.0 will prevent the evaluation of the remaining recognizers. */ + series +} + +/** Controls an [IntentDialogs](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.intentdialog.html) processing of the users text utterances. */ +export enum RecognizeMode { + /** Process text utterances whenever the dialog is first loaded through a call to session.beginDialog() and anytime a reply from the user is received. This was the default behaviour prior to version 3.2. */ + onBegin, + + /** Processes text utterances anytime a reply is received but only when the dialog is first loaded if it's the root dialog. This is the default behaviour as of 3.2. */ + onBeginIfRoot, + + /** Only process text utterances when a reply is received. */ + onReply +} + +/** + * Type of prompt invoked. + */ +export enum PromptType { + /** The user is prompted for a string of text. */ + text, + + /** The user is prompted to enter a number. */ + number, + + /** The user is prompted to confirm an action with a yes/no response. */ + confirm, + + /** The user is prompted to select from a list of choices. */ + choice, + + /** The user is prompted to enter a time. */ + time, + + /** The user is prompted to upload an attachment. */ + attachment +} + +/** Type of list to render for PromptType.choice prompt. */ +export enum ListStyle { + /** No list is rendered. This is used when the list is included as part of the prompt. */ + none, + + /** Choices are rendered as an inline list of the form "1. red, 2. green, or 3. blue". */ + inline, + + /** Choices are rendered as a numbered list. */ + list, + + /** Choices are rendered as buttons for channels that support buttons. For other channels they will be rendered as text. */ + button, + + /** The style is selected automatically based on the channel and number of options. */ + auto +} + +/** Identifies the type of text being sent in a message. */ +export var TextFormat: { + /** Text fields should be treated as plain text. */ + plain: string; + + /** Text fields may contain markdown formatting information. */ + markdown: string; + + /** Text fields may contain xml formatting information. */ + xml: string; +}; + +/** Identities how the client should render attachments for a message. */ +export var AttachmentLayout: { + /** Attachments should be rendred as a list. */ + list: string; + + /** Attachments should be rendered as a carousel. */ + carousel: string; +}; + + +//============================================================================= +// +// CLASSES +// +//============================================================================= + +/** + * Manages the bots conversation with a user. + */ +export class Session { + /** + * Registers an event listener. + * @param event Name of the event. Event types: + * - __error:__ An error occured. Passes a JavaScript `Error` object. + * @param listener Function to invoke. + * @param listener.data The data for the event. Consult the list above for specific types of data you can expect to receive. + */ + on(event: string, listener: (data: any) => void): void; + + /** + * Creates an instance of the session. + * @param options Sessions configuration options. + */ + constructor(options: ISessionOptions); + + /** + * Dispatches a message for processing. The session will call any installed middleware before + * the message to the active dialog for processing. + * @param sessionState The current session state. If _null_ a new conversation will be started beginning with the configured [dialogId](#dialogid). + * @param message The message to dispatch. + */ + dispatch(sessionState: ISessionState, message: IMessage): Session; + + /** The bots root library of dialogs. */ + library: Library; + + /** Sessions current state information. */ + sessionState: ISessionState; + + /** The message received from the user. For bot originated messages this may only contain the "to" & "from" fields. */ + message: IMessage; + + /** Data for the user that's persisted across all conversations with the bot. */ + userData: any; + + /** Shared conversation data that's visible to all members of the conversation. */ + conversationData: any; + + /** Private conversation data that's only visible to the user. */ + privateConversationData: any; + + /** Data that's only visible to the current dialog. */ + dialogData: any; + + /** The localizer (if available) to use. */ + localizer:ILocalizer ; + + /** + * Signals that an error occured. The bot will signal the error via an on('error', err) event. + * @param err Error that occured. + */ + error(err: Error): Session; + + /** + * Returns the preferred locale when no parameters are supplied, otherwise sets the preferred locale. + * @param locale (Optional) the locale to use for localizing messages. + * @param callback (Optional) function called when the localization table has been loaded for the supplied locale. + */ + preferredLocale(locale?: string, callback?: (err: Error) => void): string; + + /** + * Loads a localized string for the messages language. If arguments are passed the localized string + * will be treated as a template and formatted using [sprintf-js](https://github.com/alexei/sprintf.js) (see their docs for details.) + * @param msgid String to use as a key in the localized string table. Typically this will just be the english version of the string. + * @param args (Optional) arguments used to format the final output string. + */ + gettext(msgid: string, ...args: any[]): string; + + /** + * Loads the plural form of a localized string for the messages language. The output string will be formatted to + * include the count by replacing %d in the string with the count. + * @param msgid Singular form of the string to use as a key in the localized string table. Use %d to specify where the count should go. + * @param msgid_plural Plural form of the string to use as a key in the localized string table. Use %d to specify where the count should go. + * @param count Count to use when determining whether the singular or plural form of the string should be used. + */ + ngettext(msgid: string, msgid_plural: string, count: number): string; + + /** Triggers saving of changes made to [dialogData](#dialogdata), [userData](#userdata), [conversationdata](#conversationdata), or [privateConversationData'(#privateconversationdata). */ + save(): Session; + + /** + * Sends a message to the user. + * @param message + * * __message:__ _{string}_ - Text of the message to send. The message will be localized using the sessions configured localizer. If arguments are passed in the message will be formatted using [sprintf-js](https://github.com/alexei/sprintf.js). + * * __message:__ _{string[]}_ - The sent message will be chosen at random from the array. + * * __message:__ _{IMessage|IIsMessage}_ - Message to send. + * @param args (Optional) arguments used to format the final output text when __message__ is a _{string|string[]}_. + */ + send(message: string|string[]|IMessage|IIsMessage, ...args: any[]): Session; + + /** + * Sends the user an indication that the bot is typing. For long running operations this should be called every few seconds. + */ + sendTyping(): Session; + + /** + * Returns true if a message has been sent for this session. + */ + messageSent(): boolean; + + /** + * Passes control of the conversation to a new dialog. The current dialog will be suspended + * until the child dialog completes. Once the child ends the current dialog will receive a + * call to [dialogResumed()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.dialog.html#dialogresumed) + * where it can inspect any results returned from the child. + * @param id Unique ID of the dialog to start. + * @param args (Optional) arguments to pass to the dialogs [begin()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.dialog.html#begin) method. + */ + beginDialog(id: string, args?: T): Session; + + /** + * Ends the current dialog and starts a new one its place. The parent dialog will not be + * resumed until the new dialog completes. + * @param id Unique ID of the dialog to start. + * @param args (Optional) arguments to pass to the dialogs [begin()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.dialog.html#begin) method. + */ + replaceDialog(id: string, args?: T): Session; + + /** + * Ends the current conversation and optionally sends a message to the user. + * @param message (Optional) + * * __message:__ _{string}_ - Text of the message to send. The message will be localized using the sessions configured localizer. If arguments are passed in the message will be formatted using [sprintf-js](https://github.com/alexei/sprintf.js). + * * __message:__ _{string[]}_ - The sent message will be chosen at random from the array. + * * __message:__ _{IMessage|IIsMessage}_ - Message to send. + * @param args (Optional) arguments used to format the final output text when __message__ is a _{string|string[]}_. + */ + endConversation(message?: string|string[]|IMessage|IIsMessage, ...args: any[]): Session; + + /** + * Ends the current dialog and optionally sends a message to the user. The parent will be resumed with an [IDialogResult.resumed](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogresult.html#resumed) + * reason of [completed](/en-us/node/builder/chat-reference/enums/_botbuilder_d_.resumereason.html#completed). + * @param message (Optional) + * * __message:__ _{string}_ - Text of the message to send. The message will be localized using the sessions configured localizer. If arguments are passed in the message will be formatted using [sprintf-js](https://github.com/alexei/sprintf.js). + * * __message:__ _{string[]}_ - The sent message will be chosen at random from the array. + * * __message:__ _{IMessage|IIsMessage}_ - Message to send. + * @param args (Optional) arguments used to format the final output text when __message__ is a _{string|string[]}_. + */ + endDialog(message?: string|string[]|IMessage|IIsMessage, ...args: any[]): Session; + + /** + * Ends the current dialog and optionally returns a result to the dialogs parent. + */ + endDialogWithResult(result?: IDialogResult): Session; + + /** + * Cancels an existing dialog and optionally starts a new one it its place. Unlike [endDialog()](#enddialog) + * and [replaceDialog()](#replacedialog) which affect the current dialog, this method lets you end a + * parent dialog anywhere on the stack. The parent of the canceled dialog will be continued as if the + * dialog had called endDialog(). A special [ResumeReason.canceled](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.resumereason#canceled) + * will be returned to indicate that the dialog was canceled. + * @param dialogId + * * __dialogId:__ _{string}_ - ID of the dialog to end. If multiple occurences of the dialog exist on the dialog stack, the last occurance will be canceled. + * * __dialogId:__ _{number}_ - Index of the dialog on the stack to cancel. This is the preferred way to cancel a dialog from an action handler as it ensures that the correct instance is canceled. + * @param replaceWithId (Optional) specifies an ID to start in the canceled dialogs place. This prevents the dialogs parent from being resumed. + * @param replaceWithArgs (Optional) arguments to pass to the new dialog. + */ + cancelDialog(dialogId: string|number, replaceWithId?: string, replaceWithArgs?: any): Session; + + /** + * Clears the sessions callstack and restarts the conversation with the configured dialogId. + * @param dialogId (Optional) ID of the dialog to start. + * @param dialogArgs (Optional) arguments to pass to the dialogs [begin()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.dialog.html#begin) method. + */ + reset(dialogId?: string, dialogArgs?: any): Session; + + /** Returns true if the session has been reset. */ + isReset(): boolean; + + /** + * Immediately ends the current batch and delivers any queued up messages. + * @param callback (Optional) function called when the batch was either successfully delievered or failed for some reason. + */ + sendBatch(callback?: (err: Error) => void): void; +} + +/** + * Message builder class that simplifies building complex messages with attachments. + */ +export class Message implements IIsMessage { + + /** + * Creates a new Message builder. + * @param session (Optional) will be used to populate the messages address and localize any text. + */ + constructor(session?: Session); + + /** Language of the message. */ + textLocale(locale: string): Message; + + /** Format of text fields. */ + textFormat(style: string): Message; + + /** Sets the message text. */ + text(text: string|string[], ...args: any[]): Message; + + /** Conditionally set this message text given a specified count. */ + ntext(msg: string|string[], msg_plural: string|string[], count: number): Message; + + /** Composes a complex and randomized reply to the user. */ + compose(prompts: string[][], ...args: any[]): Message; + + /** Text to be displayed by as fall-back and as short description of the message content in e.g. list of recent conversations. */ + summary(text: string|string[], ...args: any[]): Message; + + /** Hint for how clients should layout multiple attachments. The default value is 'list'. */ + attachmentLayout(style: string): Message; + + /** Cards or images to send to the user. */ + attachments(list: IAttachment[]|IIsAttachment[]): Message; + + /** + * Adds an attachment to the message. See [IAttachment](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iattachment.html) for examples. + * @param attachment The attachment to add. + */ + addAttachment(attachment: IAttachment|IIsAttachment): Message; + + /** Structured objects passed to the bot or user. */ + entities(list: Object[]): Message; + + /** Adds an entity to the message. */ + addEntity(obj: Object): Message; + + /** Address routing information for the message. Save this field to external storage somewhere to later compose a proactive message to the user. */ + address(adr: IAddress): Message; + + /** Timestamp of the message. If called will default the timestamp to (now). */ + timestamp(time?: string): Message; + + /** Message in original/native format of the channel for incoming messages. */ + originalEvent(event: any): Message; + + /** For outgoing messages can be used to pass source specific event data like custom attachments. */ + sourceEvent(map: ISourceEventMap): Message; + + /** Returns the JSON for the message. */ + toMessage(): IMessage; + + /** __DEPRECATED__ use [local()](#local) instead. */ + setLanguage(language: string): Message; + + /** __DEPRECATED__ use [text()](#text) instead. */ + setText(session: Session, prompt: string|string[], ...args: any[]): Message; + + /** __DEPRECATED__ use [ntext()](#ntext) instead. */ + setNText(session: Session, msg: string, msg_plural: string, count: number): Message; + + /** __DEPRECATED__ use [compose()](#compose) instead. */ + composePrompt(session: Session, prompts: string[][], ...args: any[]): Message; + + /** __DEPRECATED__ use [sourceEvent()](#sourceevent) instead. */ + setChannelData(data: any): Message; + + /** + * Selects a prompt at random. + * @param prompts Array of prompts to choose from. When prompts is type _string_ the prompt will simply be returned unmodified. + */ + static randomPrompt(prompts: string|string[]): string; + + /** + * Combines an array of prompts into a single localized prompt and then optionally fills the + * prompts template slots with the passed in arguments. + * @param session Session object used to localize the individual prompt parts. + * @param prompts Array of prompt lists. Each entry in the array is another array of prompts + * which will be chosen at random. The combined output text will be space delimited. + * @param args (Optional) array of arguments used to format the output text when the prompt is a template. + */ + static composePrompt(session: Session, prompts: string[][], args?: any[]): string; +} + +/** Builder class to simplify adding actions to a card. */ +export class CardAction implements IIsCardAction { + + /** + * Creates a new CardAction. + * @param session (Optional) will be used to localize any text. + */ + constructor(session?: Session); + + /** Type of card action. */ + type(t: string): CardAction; + + /** Title of the action. For buttons this will be the label of the button. For tap actions this may be used for accesibility purposes or shown on hover. */ + title(text: string|string[], ...args: any[]): CardAction; + + /** The actions value. */ + value(v: string): CardAction; + + /** For buttons an image to include next to the buttons label. Not supported by all channels. */ + image(url: string): CardAction; + + /** Returns the JSON for the action. */ + toAction(): ICardAction; + + /** + * Places a call to a phone number. The should include country code in +44/+1 format for Skype calls. + * @param session (Optional) Current session object for the conversation. If specified will be used to localize titles. + */ + static call(session: Session, number: string, title?: string|string[]): CardAction; + + /** + * Opens the specified URL. + * @param session (Optional) Current session object for the conversation. If specified will be used to localize titles. + */ + static openUrl(session: Session, url: string, title?: string|string[]): CardAction; + + /** + * Sends a message to the bot for processing in a way that's visible to all members of the conversation. For some channels this may get mapped to a [postBack](#postback). + * @param session (Optional) Current session object for the conversation. If specified will be used to localize titles. + */ + static imBack(session: Session, msg: string, title?: string|string[]): CardAction; + + /** + * Sends a message to the bot for processing in a way that's hidden from all members of the conversation. For some channels this may get mapped to a [imBack](#imback). + * @param session (Optional) Current session object for the conversation. If specified will be used to localize titles. + */ + static postBack(session: Session, msg: string, title?: string|string[]): CardAction; + + /** + * Plays the specified audio file to the user. Not currently supported for Skype. + * @param session (Optional) Current session object for the conversation. If specified will be used to localize titles. + */ + static playAudio(session: Session, url: string, title?: string|string[]): CardAction; + + /** + * Plays the specified video to the user. Not currently supported for Skype. + * @param session (Optional) Current session object for the conversation. If specified will be used to localize titles. + */ + static playVideo(session: Session, url: string, title?: string|string[]): CardAction; + + /** + * Opens the specified image in a native image viewer. For Skype only valid as a tap action on a CardImage. + * @param session (Optional) Current session object for the conversation. If specified will be used to localize titles. + */ + static showImage(session: Session, url: string, title?: string|string[]): CardAction; + + /** + * Downloads the specified file to the users device. Not currently supported for Skype. + * @param session (Optional) Current session object for the conversation. If specified will be used to localize titles. + */ + static downloadFile(session: Session, url: string, title?: string|string[]): CardAction; + + /** + * Binds a button or tap action to a named action registered for a dialog or globally off the bot. + * + * Can be used anywhere a [postBack](#postback) is valid. You may also statically bind a button + * to an action for something like Facebooks [Persistent Menus](https://developers.facebook.com/docs/messenger-platform/thread-settings/persistent-menu). + * The payload for the button should be `action?` for actions without data or + * `action?=` for actions with data. + * @param session (Optional) Current session object for the conversation. If specified will be used to localize titles. + * @param action Name of the action to invoke when tapped. + * @param data (Optional) data to pass to the action when invoked. The [IRecognizeActionResult.data](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.irecognizeactionresult#data) + * property can be used to access this data. If using [beginDialogAction()](dlg./en-us/node/builder/chat-reference/classes/_botbuilder_d_.dialog#begindialogaction) this value will be passed + * as part of the dialogs initial arguments. + * @param title (Optional) title to assign when binding the action to a button. + */ + static dialogAction(session: Session, action: string, data?: string, title?: string|string[]): CardAction; +} + +/** Builder class to simplify adding images to a card. */ +export class CardImage implements IIsCardImage { + + /** + * Creates a new CardImage. + * @param session (Optional) will be used to localize any text. + */ + constructor(session?: Session); + + /** URL of the image to display. */ + url(u: string): CardImage; + + /** Alternate text of the image to use for accessibility pourposes. */ + alt(text: string|string[], ...args: any[]): CardImage; + + /** Action to take when the image is tapped. */ + tap(action: ICardAction|IIsCardAction): CardImage; + + /** Returns the JSON for the image. */ + toImage(): ICardImage; + + /** Creates a new CardImage for a given url. */ + static create(session: Session, url: string): CardImage; +} + +/** Card builder class that simplifies building thumbnail cards. */ +export class ThumbnailCard implements IIsAttachment { + + /** + * Creates a new ThumbnailCard. + * @param session (Optional) will be used to localize any text. + */ + constructor(session?: Session); + + /** Title of the Card. */ + title(text: string|string[], ...args: any[]): ThumbnailCard; + + /** Subtitle appears just below Title field, differs from Title in font styling only. */ + subtitle(text: string|string[], ...args: any[]): ThumbnailCard; + + /** Text field appears just below subtitle, differs from Subtitle in font styling only. */ + text(text: string|string[], ...args: any[]): ThumbnailCard; + + /** Messaging supports all media formats: audio, video, images and thumbnails as well to optimize content download. */ + images(list: ICardImage[]|IIsCardImage[]): ThumbnailCard; + + /** Set of actions applicable to the current card. Not all channels support buttons or cards with buttons. Some channels may choose to render the buttons using a custom keyboard. */ + buttons(list: ICardAction[]|IIsCardAction[]): ThumbnailCard; + + /** This action will be activated when user taps on the card. Not all channels support tap actions and some channels may choose to render the tap action as the titles link. */ + tap(action: ICardAction|IIsCardAction): ThumbnailCard; + + /** Returns the JSON for the card, */ + toAttachment(): IAttachment; +} + +/** Card builder class that simplifies building hero cards. Hero cards contain the same information as a thumbnail card, just with a larger more pronounced layout for the cards images. */ +export class HeroCard extends ThumbnailCard { + + /** + * Creates a new HeroCard. + * @param session (Optional) will be used to localize any text. + */ + constructor(session?: Session); +} + +/** Card builder class that simplifies building signin cards. */ +export class SigninCard implements IIsAttachment { + + /** + * Creates a new SigninCard. + * @param session (Optional) will be used to localize any text. + */ + constructor(session?: Session); + + /** Title of the Card. */ + text(prompts: string|string[], ...args: any[]): SigninCard; + + /** Signin button label and link. */ + button(title: string|string[], url: string): SigninCard; + + /** Returns the JSON for the card, */ + toAttachment(): IAttachment; +} + +/** Card builder class that simplifies building receipt cards. */ +export class ReceiptCard implements IIsAttachment { + + /** + * Creates a new ReceiptCard. + * @param session (Optional) will be used to localize any text. + */ + constructor(session?: Session); + + /** Title of the Card. */ + title(text: string|string[], ...args: any[]): ReceiptCard; + + /** Array of receipt items. */ + items(list: IReceiptItem[]|IIsReceiptItem[]): ReceiptCard; + + /** Array of additional facts to display to user (shipping charges and such.) Not all facts will be displayed on all channels. */ + facts(list: IFact[]|IIsFact[]): ReceiptCard; + + /** This action will be activated when user taps on the card. Not all channels support tap actions. */ + tap(action: ICardAction|IIsCardAction): ReceiptCard; + + /** Total amount of money paid (or should be paid.) */ + total(v: string): ReceiptCard; + + /** Total amount of TAX paid (or should be paid.) */ + tax(v: string): ReceiptCard; + + /** Total amount of VAT paid (or should be paid.) */ + vat(v: string): ReceiptCard; + + /** Set of actions applicable to the current card. Not all channels support buttons and the number of allowed buttons varies by channel. */ + buttons(list: ICardAction[]|IIsCardAction[]): ReceiptCard; + + /** Returns the JSON for the card. */ + toAttachment(): IAttachment; +} + +/** Builder class to simplify adding items to a receipt card. */ +export class ReceiptItem implements IIsReceiptItem { + + /** + * Creates a new ReceiptItem. + * @param session (Optional) will be used to localize any text. + */ + constructor(session?: Session); + + /** Title of the item. */ + title(text: string|string[], ...args: any[]): ReceiptItem; + + /** Subtitle appears just below Title field, differs from Title in font styling only. On some channels may be combined with the [title](#title) or [text](#text). */ + subtitle(text: string|string[], ...args: any[]): ReceiptItem; + + /** Text field appears just below subtitle, differs from Subtitle in font styling only. */ + text(text: string|string[], ...args: any[]): ReceiptItem; + + /** Image to display on the card. Some channels may either send the image as a seperate message or simply include a link to the image. */ + image(img: ICardImage|IIsCardImage): ReceiptItem; + + /** Amount with currency. */ + price(v: string): ReceiptItem; + + /** Number of items of given kind. */ + quantity(v: string): ReceiptItem; + + /** This action will be activated when user taps on the Item bubble. Not all channels support tap actions. */ + tap(action: ICardAction|IIsCardAction): ReceiptItem; + + /** Returns the JSON for the item. */ + toItem(): IReceiptItem; + + /** Creates a new ReceiptItem. */ + static create(session: Session, price: string, title?: string|string[]): ReceiptItem; +} + +/** Builder class to simplify creating a list of facts for a card like a receipt. */ +export class Fact implements IIsFact { + + /** + * Creates a new Fact. + * @param session (Optional) will be used to localize any text. + */ + constructor(session?: Session); + + /** Display name of the fact. */ + key(text: string|string[], ...args: any[]): Fact; + + /** Display value of the fact. */ + value(v: string): Fact; + + /** Returns the JSON for the fact. */ + toFact(): IFact; + + /** Creates a new Fact. */ + static create(session: Session, value: string, key?: string|string[]): Fact; +} + + +/** + * Implement support for named actions which can be bound to a dialog to handle global utterances from the user like "help" or + * "cancel". Actions get pushed onto and off of the dialog stack as part of dialogs so these listeners can + * come into and out of scope as the conversation progresses. You can also bind named to actions to buttons + * which let your bot respond to button clicks on cards that have maybe scrolled off the screen. + */ +export class ActionSet { + /** + * Called to recognize any actions triggered by the users utterance. + * @param message The message received from the user. + * @param callback Function to invoke with the results of the recognition. The top scoring action, if any, will be returned. + */ + recognizeAction(message: IMessage, callback: (err: Error, result: IRecognizeActionResult) => void): void; + + /** + * Invokes an action that had the highest confidence score for the utterance. + * @param session Session object for the current conversation. + * @param recognizeResult Results returned from call to [recognizeAction()](#recognizeaction). + */ + invokeAction(session: Session, recognizeResult: IRecognizeActionResult): void; +} + +/** + * Base class for all dialogs. Dialogs are the core component of the BotBuilder + * framework. Bots use Dialogs to manage arbitrarily complex conversations with + * a user. + */ +export abstract class Dialog extends ActionSet { + /** + * Called when a new dialog session is being started. + * @param session Session object for the current conversation. + * @param args (Optional) arguments passed to the dialog by its parent. + */ + begin(session: Session, args?: T): void; + + /** + * Called when a new reply message has been received from a user. + * + * Derived classes should implement this to process the message received from the user. + * @param session Session object for the current conversation. + * @param recognizeResult Results returned from a prior call to the dialogs [recognize()](#recognize) method. + */ + abstract replyReceived(session: Session, recognizeResult: IRecognizeResult): void; + + /** + * A child dialog has ended and the current one is being resumed. + * @param session Session object for the current conversation. + * @param result Result returned by the child dialog. + */ + dialogResumed(session: Session, result: IDialogResult): void; + + /** + * Parses the users utterance and assigns a score from 0.0 - 1.0 indicating how confident the + * dialog is that it understood the users utterance. This method is always called for the active + * dialog on the stack. A score of 1.0 will indicate a perfect match and terminate any further + * recognition. + * + * When the score is less than 1.0, every dialog on the stack will have its + * [recognizeAction()](#recognizeaction) method called as well to see if there are any named + * actions bound to the dialog that better matches the users utterance. Global actions registered + * at the bot level will also be evaluated. If the dialog has a score higher then any bound actions, + * the dialogs [replyReceived()](#replyreceived) method will be called with the result object + * returned from the recognize() call. This lets the dialog pass additional data collected during + * the recognize phase to the replyReceived() method for handling. + * + * Should there be an action with a higher score then the dialog the action will be invoked instead + * of the dialogs replyReceived() method. The dialog will stay on the stack and may be resumed + * at some point should the action invoke a new dialog so dialogs should prepare for unexpected calls + * to [dialogResumed()](#dialogresumed). + * @param context The context of the request. + * @param callback Function to invoke with the recognition results. + */ + recognize(context: IRecognizeContext, callback: (err: Error, result: IRecognizeResult) => void): void; + + /** + * Binds an action to the dialog that will cancel the dialog anytime its triggered. When canceled, the + * dialogs parent will be resumed with a [resumed](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogresult#resumed) code indicating that it was [canceled](/en-us/node/builder/chat-reference/enums/_botbuilder_d_.resumereason#canceled). + * @param name Unique name to assign the action. + * @param msg (Optional) message to send the user prior to canceling the dialog. + * @param options (Optional) options used to configure the action. If [matches](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogactionoptions#matches) is specified the action will listen + * for the user to say a word or phrase that triggers the action, otherwise the action needs to be bound to a button using [CardAction.dialogAction()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.cardaction#dialogaction) + * to trigger the action. + */ + cancelAction(name: string, msg?: string|string[]|IMessage|IIsMessage, options?: IDialogActionOptions): Dialog; + + /** + * Binds an action to the dialog that will cause the dialog to be reloaded anytime its triggered. This is + * useful to implement logic that handle user utterances like "start over". + * @param name Unique name to assign the action. + * @param msg (Optional) message to send the user prior to reloading the dialog. + * @param options (Optional) options used to configure the action. If [matches](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogactionoptions#matches) is specified the action will listen + * for the user to say a word or phrase that triggers the action, otherwise the action needs to be bound to a button using [CardAction.dialogAction()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.cardaction#dialogaction) + * to trigger the action. You can also use [dialogArgs](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogactionoptions#dialogargs) to pass additional params to the dialog when reloaded. + */ + reloadAction(name: string, msg?: string|string[]|IMessage|IIsMessage, options?: IDialogActionOptions): Dialog; + + /** + * Binds an action to the dialog that will start another dialog anytime its triggered. The new + * dialog will be pushed onto the stack so it does not automatically end the current task. The + * current task will be continued once the new dialog ends. The built-in prompts will automatically + * re-prompt the user once this happens but that behaviour can be disabled by setting the [promptAfterAction](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ipromptoptions#promptafteraction) + * flag when calling a built-in prompt. + * @param name Unique name to assign the action. + * @param id ID of the dialog to start. + * @param options (Optional) options used to configure the action. If [matches](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogactionoptions#matches) is specified the action will listen + * for the user to say a word or phrase that triggers the action, otherwise the action needs to be bound to a button using [CardAction.dialogAction()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.cardaction#dialogaction) + * to trigger the action. You can also use [dialogArgs](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogactionoptions#dialogargs) to pass additional params to the dialog being started. + */ + beginDialogAction(name: string, id: string, options?: IDialogActionOptions): Dialog; + + /** + * Binds an action that will end the conversation with the user when triggered. + * @param name Unique name to assign the action. + * @param msg (Optional) message to send the user prior to ending the conversation. + * @param options (Optional) options used to configure the action. If [matches](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogactionoptions#matches) is specified the action will listen + * for the user to say a word or phrase that triggers the action, otherwise the action needs to be bound to a button using [CardAction.dialogAction()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.cardaction#dialogaction) + * to trigger the action. + */ + endConversationAction(name: string, msg?: string|string[]|IMessage|IIsMessage, options?: IDialogActionOptions): Dialog; +} + +/** + * Dialog actions offer static shortcuts to implementing common actions. They also implement support for + * named actions which can be bound to a dialog to handle global utterances from the user like "help" or + * "cancel". Actions get pushed onto and off of the dialog stack as part of dialogs so these listeners can + * come into and out of scope as the conversation progresses. You can also bind named to actions to buttons + * which let your bot respond to button clicks on cards that have maybe scrolled off the screen. + */ +export class DialogAction { + /** + * Returns a closure that will send a simple text message to the user. + * @param msg Text of the message to send. The message will be localized using the sessions configured [localizer](#localizer). If arguments are passed in the message will be formatted using [sprintf-js](https://github.com/alexei/sprintf.js) (see the docs for details.) + * @param args (Optional) arguments used to format the final output string. + */ + static send(msg: string, ...args: any[]): IDialogWaterfallStep; + + /** + * Returns a closure that will passes control of the conversation to a new dialog. + * @param id Unique ID of the dialog to start. + * @param args (Optional) arguments to pass to the dialogs begin() method. + */ + static beginDialog(id: string, args?: T): IDialogWaterfallStep; + + /** + * Returns a closure that will end the current dialog. + * @param result (Optional) results to pass to the parent dialog. + */ + static endDialog(result?: any): IDialogWaterfallStep; + + /** + * Returns a closure that wraps a built-in prompt with validation logic. The closure should be used + * to define a new dialog for the prompt using bot.add('/myPrompt', builder.DialogAction.) + * @param promptType Type of built-in prompt to validate. + * @param validator Function used to validate the response. Should return true if the response is valid. + * @param validator.response The users [IDialogResult.response](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogresult.html#response) returned by the built-in prompt. + * @example + *

+     * var bot = new builder.BotConnectorBot();
+     * bot.add('/', [
+     *     function (session) {
+     *         session.beginDialog('/meaningOfLife', { prompt: "What's the meaning of life?" });
+     *     },
+     *     function (session, results) {
+     *         if (results.response) {
+     *             session.send("That's correct! The meaning of life is 42.");
+     *         } else {
+     *             session.send("Sorry you couldn't figure it out. Everyone knows that the meaning of life is 42.");
+     *         }
+     *     }
+     * ]);
+     * bot.add('/meaningOfLife', builder.DialogAction.validatedPrompt(builder.PromptType.text, function (response) {
+     *     return response === '42';
+     * }));
+     * 
+ */ + static validatedPrompt(promptType: PromptType, validator: (response: any) => boolean): Dialog; +} + + +/** + * A library of related dialogs used for routing purposes. Libraries can be chained together to enable + * the development of complex bots. The [UniversalBot](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.universalbot.html) + * class is itself a Library that forms the root of this chain. + * + * Libraries of reusable parts can be developed by creating a new Library instance and adding dialogs + * just as you would to a bot. Your library should have a unique name that corresponds to either your + * libraries website or NPM module name. Bots can then reuse your library by simply adding your parts + * Library instance to their bot using [UniversalBot.library()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.universalbot.html#library). + * If your library itself depends on other libraries you should add them to your library as a dependency + * using [Library.library()](#library). You can easily manage multiple versions of your library by + * adding a version number to your library name. + * + * To invoke dialogs within your library bots will need to call [session.beginDialog()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session.html#begindialog) + * with a fully qualified dialog id in the form of ':'. You'll typically hide + * this from the devloper by exposing a function from their module that starts the dialog for them. + * So calling something like `myLib.someDialog(session, { arg: '' });` would end up calling + * `session.beginDialog('myLib:someDialog', args);` under the covers. + * + * Its worth noting that dialogs are always invoked within the current dialog so once your within + * a dialog from your library you don't need to prefix every beginDialog() call your with your + * libraries name. Its only when crossing from one library context to another that you need to + * include the library name prefix. + */ +export class Library { + /** + * The libraries unique namespace. This is used to issolate the libraries dialogs and localized + * prompts. + */ + name: string; + + /** + * Creates a new instance of the library. + * @param name Unique namespace for the library. + */ + constructor(name: string); + + /** + * Gets or sets the path to the libraries "/locale/" folder containing its localized prompts. + * The prompts for the library should be stored in a "/locale//.json" file + * under this path where "" representes the 2-3 digit language tage for the locale and + * "" is a filename matching the libraries namespace. + * @param path (Optional) path to the libraries "/locale/" folder. If specified this will update the libraries path. + */ + localePath(path?: string): string; + + /** + * Registers or returns a dialog from the library. + * @param id Unique ID of the dialog being regsitered or retrieved. + * @param dialog (Optional) dialog or waterfall to register. + * * __dialog:__ _{Dialog}_ - Dialog to add. + * * __dialog:__ _{IDialogWaterfallStep[]}_ - Waterfall of steps to execute. See [IDialogWaterfallStep](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogwaterfallstep.html) for details. + * * __dialog:__ _{IDialogWaterfallStep}_ - Single step waterfall. Calling a built-in prompt or starting a new dialog will result in the current dialog ending upon completion of the child prompt/dialog. + */ + dialog(id: string, dialog?: Dialog|IDialogWaterfallStep[]|IDialogWaterfallStep): Dialog; + + /** + * Registers or returns a library dependency. + * @param lib + * * __lib:__ _{Library}_ - Library to register as a dependency. + * * __lib:__ _{string}_ - Unique name of the library to lookup. All dependencies will be searched as well. + */ + library(lib: Library|string): Library; + + /** + * Searches the library and all of its dependencies for a specific dialog. Returns the dialog + * if found, otherwise null. + * @param libName Name of the library containing the dialog. + * @param dialogId Unique ID of the dialog within the library. + */ + findDialog(libName: string, dialogId: string): Dialog; + + /** + * Enumerates all of the libraries child libraries. The caller should take appropriate steps to + * avoid circular references when enumerating the hierarchy. Maintaining a map of visited + * libraries should be enough. + * @param callback Iterator function to call with each child. + * @param callback.library The current child. + */ + forEachLibrary(callback: (library: Library) => void): void; +} + +/** + * Built in built-in prompts that can be called from any dialog. + */ +export class Prompts extends Dialog { + /** + * Processes messages received from the user. Called by the dialog system. + * @param session Session object for the current conversation. + * @param (Optional) recognition results returned from a prior call to the dialogs [recognize()](#recognize) method. + */ + replyReceived(session: Session, recognizeResult?: IRecognizeResult): void; + + /** + * Updates global options for the Prompts dialog. + * @param options Options to set. + */ + static configure(options: IPromptsOptions): void; + + /** + * Captures from the user a raw string of text. + * @param session Session object for the current conversation. + * @param prompt + * * __prompt:__ _{string}_ - Initial message to send the user. + * * __prompt:__ _{string[]}_ - Array of possible messages to send user. One will be chosen at random. + * * __prompt:__ _{IMessage|IIsMessage}_ - Initial message to send the user. Message can contain attachments. + * @param options (Optional) parameters to control the behaviour of the prompt. + */ + static text(session: Session, prompt: string|string[]|IMessage|IIsMessage, options?: IPromptOptions): void; + + /** + * Prompts the user to enter a number. + * @param session Session object for the current conversation. + * @param prompt + * * __prompt:__ _{string}_ - Initial message to send the user. + * * __prompt:__ _{string[]}_ - Array of possible messages to send user. One will be chosen at random. + * * __prompt:__ _{IMessage|IIsMessage}_ - Initial message to send the user. Message can contain attachments. + * @param options (Optional) parameters to control the behaviour of the prompt. + */ + static number(session: Session, prompt: string|string[]|IMessage|IIsMessage, options?: IPromptOptions): void; + + /** + * Prompts the user to confirm an action with a yes/no response. + * @param session Session object for the current conversation. + * @param prompt + * * __prompt:__ _{string}_ - Initial message to send the user. + * * __prompt:__ _{string[]}_ - Array of possible messages to send user. One will be chosen at random. + * * __prompt:__ _{IMessage|IIsMessage}_ - Initial message to send the user. Message can contain attachments. + * @param options (Optional) parameters to control the behaviour of the prompt. + */ + static confirm(session: Session, prompt: string|string[]|IMessage|IIsMessage, options?: IPromptOptions): void; + + /** + * Prompts the user to choose from a list of options. + * @param session Session object for the current conversation. + * @param prompt + * * __prompt:__ _{string}_ - Initial message to send the user. + * * __prompt:__ _{string[]}_ - Array of possible messages to send user. One will be chosen at random. + * * __prompt:__ _{IMessage|IIsMessage}_ - Initial message to send the user. Message can contain attachments. Any [listStyle](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ipromptoptions.html#liststyle) options will be ignored. + * @param choices + * * __choices:__ _{string}_ - List of choices as a pipe ('|') delimted string. + * * __choices:__ _{Object}_ - List of choices expressed as an Object map. The objects field names will be used to build the list of values. + * * __choices:__ _{string[]}_ - List of choices as an array of strings. + * @param options (Optional) parameters to control the behaviour of the prompt. + */ + static choice(session: Session, prompt: string|string[]|IMessage|IIsMessage, choices: string|Object|string[], options?: IPromptOptions): void; + + /** + * Prompts the user to enter a time. + * @param session Session object for the current conversation. + * @param prompt + * * __prompt:__ _{string}_ - Initial message to send the user. + * * __prompt:__ _{string[]}_ - Array of possible messages to send user. One will be chosen at random. + * * __prompt:__ _{IMessage|IIsMessage}_ - Initial message to send the user. Message can contain attachments. + * @param options (Optional) parameters to control the behaviour of the prompt. + */ + static time(session: Session, prompt: string|string[]|IMessage|IIsMessage, options?: IPromptOptions): void; + + /** + * Prompts the user to upload a file attachment. + * @param session Session object for the current conversation. + * @param prompt + * * __prompt:__ _{string}_ - Initial message to send the user. + * * __prompt:__ _{string[]}_ - Array of possible messages to send user. One will be chosen at random. + * * __prompt:__ _{IMessage|IIsMessage}_ - Initial message to send the user. Message can contain attachments. + * @param options (Optional) parameters to control the behaviour of the prompt. + */ + static attachment(session: Session, prompt: string|string[]|IMessage|IIsMessage, options?: IPromptOptions): void; +} + +/** + * Implements a simple pattern based recognizer for parsing the built-in prompts. Derived classes can + * inherit from SimplePromptRecognizer and override the recognize() method to change the recognition + * of one or more prompt types. + */ +export class SimplePromptRecognizer implements IPromptRecognizer { + /** + * Attempts to match a users reponse to a given prompt. + * @param args Arguments passed to the recognizer including that language, text, and prompt choices. + * @param callback Function to invoke with the result of the recognition attempt. + */ + recognize(args: IPromptRecognizerArgs, callback: (result: IPromptResult) => void): void; +} + +/** Identifies a users intent and optionally extracts entities from a users utterance. */ +export class IntentDialog extends Dialog { + /** + * Constructs a new instance of an IntentDialog. + * @param options (Optional) options used to initialize the dialog. + */ + constructor(options?: IIntentDialogOptions); + + /** + * Processes messages received from the user. Called by the dialog system. + * @param session Session object for the current conversation. + * @param (Optional) recognition results returned from a prior call to the dialogs [recognize()](#recognize) method. + */ + replyReceived(session: Session, recognizeResult?: IRecognizeResult): void; + + /** + * Called when the dialog is first loaded after a call to [session.beginDialog()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session#begindialog). This gives the bot an opportunity to process arguments passed to the dialog. Handlers should always call the `next()` function to continue execution of the dialogs main logic. + * @param handler Function to invoke when the dialog begins. + * @param handler.session Session object for the current conversation. + * @param handler.args Arguments passed to the dialog in the `session.beginDialog()` call. + * @param handler.next Function to call when handler is finished to continue execution of the dialog. + */ + onBegin(handler: (session: Session, args: any, next: () => void) => void): IntentDialog; + + /** + * Invokes a handler when a given intent is detected in the users utterance. + * + * > __NOTE:__ The full details of the match, including the list of intents & entities detected, will be passed to the [args](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iintentrecognizerresult) of the first waterfall step or dialog that's started. + * @param intent + * * __intent:__ _{RegExp}_ - A regular expression that will be evaluated to detect the users intent. + * * __intent:__ _{string}_ - A named intent returned by an [IIntentRecognizer](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iintentrecognizer) plugin that will be used to match the users intent. + * @param dialogId + * * __dialogId:__ _{string} - The ID of a dialog to begin when the intent is matched. + * * __dialogId:__ _{IDialogWaterfallStep[]}_ - Waterfall of steps to execute when the intent is matched. + * * __dialogId:__ _{IDialogWaterfallStep}_ - Single step waterfall to execute when the intent is matched. Calling a built-in prompt or starting a new dialog will result in the current dialog ending upon completion of the child prompt/dialog. + * @param dialogArgs (Optional) arguments to pass the dialog that started when `dialogId` is a _{string}_. + */ + matches(intent: RegExp|string, dialogId: string|IDialogWaterfallStep[]|IDialogWaterfallStep, dialogArgs?: any): IntentDialog; + + /** + * Invokes a handler when any of the given intents are detected in the users utterance. + * + * > __NOTE:__ The full details of the match, including the list of intents & entities detected, will be passed to the [args](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iintentrecognizerresult) of the first waterfall step or dialog that's started. + * @param intent + * * __intent:__ _{RegExp[]}_ - Array of regular expressions that will be evaluated to detect the users intent. + * * __intent:__ _{string[]}_ - Array of named intents returned by an [IIntentRecognizer](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iintentrecognizer) plugin that will be used to match the users intent. + * @param dialogId + * * __dialogId:__ _{string} - The ID of a dialog to begin when the intent is matched. + * * __dialogId:__ _{IDialogWaterfallStep[]}_ - Waterfall of steps to execute when the intent is matched. + * * __dialogId:__ _{IDialogWaterfallStep}_ - Single step waterfall to execute when the intent is matched. Calling a built-in prompt or starting a new dialog will result in the current dialog ending upon completion of the child prompt/dialog. + * @param dialogArgs (Optional) arguments to pass the dialog that started when `dialogId` is a _{string}_. + */ + matchesAny(intent: RegExp[]|string[], dialogId: string|IDialogWaterfallStep[]|IDialogWaterfallStep, dialogArgs?: any): IntentDialog; + + /** + * The default handler to invoke when there are no handlers that match the users intent. + * + * > __NOTE:__ The full details of the recognition attempt, including the list of intents & entities detected, will be passed to the [args](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iintentrecognizerresult) of the first waterfall step or dialog that's started. + * @param dialogId + * * __dialogId:__ _{string} - The ID of a dialog to begin. + * * __dialogId:__ _{IDialogWaterfallStep[]}_ - Waterfall of steps to execute. + * * __dialogId:__ _{IDialogWaterfallStep}_ - Single step waterfall to execute. Calling a built-in prompt or starting a new dialog will result in the current dialog ending upon completion of the child prompt/dialog. + * @param dialogArgs (Optional) arguments to pass the dialog that started when `dialogId` is a _{string}_. + */ + onDefault(dialogId: string|IDialogWaterfallStep[]|IDialogWaterfallStep, dialogArgs?: any): IntentDialog; + + /** + * Adds a new recognizer plugin to the intent dialog. + * @param plugin The recognizer to add. + */ + recognizer(plugin: IIntentRecognizer): IntentDialog; +} + +/** + * Routes incoming messages to a LUIS app hosted on http://luis.ai for intent recognition. + * Once a messages intent has been recognized it will rerouted to a registered intent handler, along + * with any entities, for further processing. + */ +export class LuisRecognizer implements IIntentRecognizer { + /** + * Constructs a new instance of a LUIS recognizer. + * @param models Either an individual LUIS model used for all utterances or a map of per/local models conditionally used depending on the local of the utterance. + */ + constructor(models: string|ILuisModelMap); + + /** Called by the IntentDialog to perform the actual recognition. */ + public recognize(context: IRecognizeContext, callback: (err: Error, result: IIntentRecognizerResult) => void): void; + + /** + * Calls LUIS to recognizing intents & entities in a users utterance. + * @param utterance The text to pass to LUIS for recognition. + * @param serviceUri URI for LUIS App hosted on http://luis.ai. + * @param callback Callback to invoke with the results of the intent recognition step. + * @param callback.err Error that occured during the recognition step. + * @param callback.intents List of intents that were recognized. + * @param callback.entities List of entities that were recognized. + */ + static recognize(utterance: string, modelUrl: string, callback: (err: Error, intents?: IIntent[], entities?: IEntity[]) => void): void; +} + +/** + * Utility class used to parse & resolve common entities like datetimes received from LUIS. + */ +export class EntityRecognizer { + /** + * Searches for the first occurance of a specific entity type within a set. + * @param entities Set of entities to search over. + * @param type Type of entity to find. + */ + static findEntity(entities: IEntity[], type: string): IEntity; + + /** + * Finds all occurrences of a specific entity type within a set. + * @param entities Set of entities to search over. + * @param type Type of entity to find. + */ + static findAllEntities(entities: IEntity[], type: string): IEntity[]; + + /** + * Parses a date from either a users text utterance or a set of entities. + * @param value + * * __value:__ _{string}_ - Text utterance to parse. The utterance is parsed using the [Chrono](http://wanasit.github.io/pages/chrono/) library. + * * __value:__ _{IEntity[]}_ - Set of entities to resolve. + * @returns A valid Date object if the user spoke a time otherwise _null_. + */ + static parseTime(value: string | IEntity[]): Date; + + /** + * Calculates a Date from a set of datetime entities. + * @param entities List of entities to extract date from. + * @returns The successfully calculated Date or _null_ if a date couldn't be determined. + */ + static resolveTime(entities: IEntity[]): Date; + + /** + * Recognizes a time from a users utterance. The utterance is parsed using the [Chrono](http://wanasit.github.io/pages/chrono/) library. + * @param utterance Text utterance to parse. + * @param refDate (Optional) reference date used to calculate the final date. + * @returns An entity containing the resolved date if successful or _null_ if a date couldn't be determined. + */ + static recognizeTime(utterance: string, refDate?: Date): IEntity; + + /** + * Parses a number from either a users text utterance or a set of entities. + * @param value + * * __value:__ _{string}_ - Text utterance to parse. + * * __value:__ _{IEntity[]}_ - Set of entities to resolve. + * @returns A valid number otherwise _Number.NaN_. + */ + static parseNumber(value: string | IEntity[]): number; + + /** + * Parses a boolean from a users utterance. + * @param value Text utterance to parse. + * @returns A valid boolean otherwise _undefined_. + */ + static parseBoolean(value: string): boolean; + + /** + * Finds the best match for a users utterance given a list of choices. + * @param choices + * * __choices:__ _{string}_ - Pipe ('|') delimited list of values to compare against the users utterance. + * * __choices:__ _{Object}_ - Object used to generate the list of choices. The objects field names will be used to build the list of choices. + * * __choices:__ _{string[]}_ - Array of strings to compare against the users utterance. + * @param utterance Text utterance to parse. + * @param threshold (Optional) minimum score needed for a match to be considered. The default value is 0.6. + */ + static findBestMatch(choices: string | Object | string[], utterance: string, threshold?: number): IFindMatchResult; + + /** + * Finds all possible matches for a users utterance given a list of choices. + * @param choices + * * __choices:__ _{string}_ - Pipe ('|') delimited list of values to compare against the users utterance. + * * __choices:__ _{Object}_ - Object used to generate the list of choices. The objects field names will be used to build the list of choices. + * * __choices:__ _{string[]}_ - Array of strings to compare against the users utterance. + * @param utterance Text utterance to parse. + * @param threshold (Optional) minimum score needed for a match to be considered. The default value is 0.6. + */ + static findAllMatches(choices: string | Object | string[], utterance: string, threshold?: number): IFindMatchResult[]; + + /** + * Converts a set of choices into an expanded array. + * @param choices + * * __choices:__ _{string}_ - Pipe ('|') delimited list of values. + * * __choices:__ _{Object}_ - Object used to generate the list of choices. The objects field names will be used to build the list of choices. + * * __choices:__ _{string[]}_ - Array of strings. This will just be echoed back as the output. + */ + static expandChoices(choices: string | Object | string[]): string[]; +} + +/** + * Allows for the creation of custom dialogs that are based on a simple closure. This is useful for + * cases where you want a dynamic conversation flow or you have a situation that just doesn’t map + * very well to using a waterfall. The things to keep in mind: + * * Your dialogs closure is can get called in two different contexts that you potentially need to + * test for. It will get called as expected when the user send your dialog a message but if you + * call another prompt or dialog from your closure it will get called a second time with the + * results from the prompt/dialog. You can typically test for this second case by checking for the + * existant of an `args.resumed` property. It's important to avoid getting yourself into an + * infinite loop which can be easy to do. + * * Unlike a waterfall your dialog will not automatically end. It will remain the active dialog + * until you call [session.endDialog()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session.html#enddialog). + */ +export class SimpleDialog extends Dialog { + /** + * Creates a new custom dialog based on a simple closure. + * @param handler The function closure for your dialog. + * @param handler.session Session object for the current conversation. + * @param handler.args + * * __args:__ _{any}_ - For the first call to the handler this will be either `null` or the value of any arguments passed to [Session.beginDialog()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session.html#begindialog). + * * __args:__ _{IDialogResult}_ - If the handler takes an action that results in a new dialog being started those results will be returned via subsequent calls to the handler. + */ + constructor(handler: (session: Session, args?: any | IDialogResult) => void); + + /** + * Processes messages received from the user. Called by the dialog system. + * @param session Session object for the current conversation. + */ + replyReceived(session: Session): void; +} + +/** Default in memory storage implementation for storing user & session state data. */ +export class MemoryBotStorage implements IBotStorage { + /** Returns data from memmory for the given context. */ + getData(context: IBotStorageContext, callback: (err: Error, data: IBotStorageData) => void): void; + + /** Saves data to memory for the given context. */ + saveData(context: IBotStorageContext, data: IBotStorageData, callback?: (err: Error) => void): void; + + /** Deletes in-memory data for the given context. */ + deleteData(context: IBotStorageContext): void; +} + +/** Manages your bots conversations with users across multiple channels. */ +export class UniversalBot { + + /** + * Creates a new instance of the UniversalBot. + * @param connector (Optional) the default connector to use for requests. If there's not a more specific connector registered for a channel then this connector will be used./** + * @param settings (Optional) settings to configure the bot with. + */ + constructor(connector?: IConnector, settings?: IUniversalBotSettings); + + /** + * Registers an event listener. The bot will emit its own events as it process incoming and outgoing messages. It will also forward activity related events emitted from the connector, giving you one place to listen for all activity from your bot. The flow of events from the bot is as follows: + * + * #### Message Received + * When the bot receives a new message it will emit the following events in order: + * + * > lookupUser -> receive -> incoming -> getStorageData -> routing + * + * Any [receive middleware](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.imiddlewaremap#receive) that's been installed will be executed between the 'receive' and 'incoming' events. After the 'routing' event is emmited any + * [botbuilder middleware](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.imiddlewaremap#botbuilder) will be executed prior to dispatching the message to the bots active dialog. + * + * #### Connector Activity Received + * Connectors can emit activity events to signal things like a user is typing or that they friended a bot. These activities get routed through middleware like messages but they are not routed through the bots dialog system. They are only ever emitted as events. + * + * The flow of connector events is: + * + * > lookupUser -> receive -> (activity) + * + * #### Message sent + * Bots can send multiple messages so the session will batch up all outgoing message and then save the bots current state before delivering the sent messages. You'll see a single 'saveStorageData' event emitted and then for every outgoing message in the batch you'll see the following + * sequence of events: + * + * > send -> outgoing + * + * Any [send middleware](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.imiddlewaremap#send) that's been installed will be executed between the 'send' and 'outgoing' events. + * + * @param event Name of the event. Bot and connector specific event types: + * #### Bot Events + * - __error:__ An error occured. Passed a JavaScript `Error` object. + * - __lookupUser:__ The user is for an address is about to be looked up. Passed an [IAddress](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iaddress.html) object. + * - __receive:__ An incoming message has been received. Passed an [IEvent](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ievent.html) object. + * - __incoming:__ An incoming message has been received and processed by middleware. Passed an [IMessage](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.imessage.html) object. + * - __routing:__ An incoming message has been bound to a session and is about to be routed through any session middleware and then dispatched to the active dialog for processing. Passed a [Session](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session.html) object. + * - __send:__ An outgoing message is about to be sent to middleware for processing. Passed an [IMessage](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.imessage.html) object. + * - __getStorageData:__ The sessions persisted state data is being loaded from storage. Passed an [IBotStorageContext](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ibotstoragecontext.html) object. + * - __saveStorageData:__ The sessions persisted state data is being written to storage. Passed an [IBotStorageContext](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ibotstoragecontext.html) object. + * + * #### ChatConnector Events + * - __conversationUpdate:__ Your bot was added to a conversation or other conversation metadata changed. Passed an [IConversationUpdate](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iconversationupdate.html) object. + * - __contactRelationUpdate:__ The bot was added to or removed from a user's contact list. Passed an [IContactRelationUpdate](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.icontactrelationupdate.html) object. + * - __typing:__ The user or bot on the other end of the conversation is typing. Passed an [IEvent](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ievent.html) object. + * + * @param listener Function to invoke. + * @param listener.data The data for the event. Consult the list above for specific types of data you can expect to receive. + */ + on(event: string, listener: (data: any) => void): void; + + /** + * Sets a setting on the bot. + * @param name Name of the property to set. Valid names are properties on [IUniversalBotSettings](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iuniversalbotsettings.html). + * @param value The value to assign to the setting. + */ + set(name: string, value: any): UniversalBot; + + /** + * Returns the current value of a setting. + * @param name Name of the property to return. Valid names are properties on [IUniversalBotSettings](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iuniversalbotsettings.html). + */ + get(name: string): any; + + /** + * Registers or returns a connector for a specific channel. + * @param channelId Unique ID of the channel. Use a channelId of '*' to reference the default connector. + * @param connector (Optional) connector to register. If ommited the connector for __channelId__ will be returned. + */ + connector(channelId: string, connector?: IConnector): IConnector; + + /** + * Registers or returns a dialog for the bot. + * @param id Unique ID of the dialog being regsitered or retrieved. + * @param dialog (Optional) dialog or waterfall to register. + * * __dialog:__ _{Dialog}_ - Dialog to add. + * * __dialog:__ _{IDialogWaterfallStep[]}_ - Waterfall of steps to execute. See [IDialogWaterfallStep](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogwaterfallstep.html) for details. + * * __dialog:__ _{IDialogWaterfallStep}_ - Single step waterfall. Calling a built-in prompt or starting a new dialog will result in the current dialog ending upon completion of the child prompt/dialog. + */ + dialog(id: string, dialog?: Dialog|IDialogWaterfallStep[]|IDialogWaterfallStep): Dialog; + + /** + * Registers or returns a library dependency. + * @param lib + * * __lib:__ _{Library}_ - Library to register as a dependency. + * * __lib:__ _{string}_ - Unique name of the library to lookup. All dependencies will be searched as well. + */ + library(lib: Library|string): Library; + + /** + * Installs middleware for the bot. Middleware lets you intercept incoming and outgoing events/messages. + * @param args One or more sets of middleware hooks to install. + */ + use(...args: IMiddlewareMap[]): UniversalBot; + + /** + * Called when a new event is received. This can be called manually to mimic the bot receiving a message from the user. + * @param events Event or (array of events) received. + * @param done (Optional) function to invoke once the operation is completed. + */ + receive(events: IEvent|IEvent[], done?: (err: Error) => void): void; + + /** + * Proactively starts a new dialog with the user. Any current conversation between the bot and user will be replaced with a new dialog stack. + * @param address Address of the user to start a new conversation with. This should be saved during a previous conversation with the user. Any existing conversation or dialog will be immediately terminated. + * @param dialogId ID of the dialog to begin. + * @param dialogArgs (Optional) arguments to pass to dialog. + * @param done (Optional) function to invoke once the operation is completed. + */ + beginDialog(address: IAddress, dialogId: string, dialogArgs?: any, done?: (err: Error) => void): void; + + /** + * Sends a message to the user without disrupting the current conversations dialog stack. + * @param messages The message (or array of messages) to send the user. + * @param done (Optional) function to invoke once the operation is completed. + */ + send(messages: IIsMessage|IMessage|IMessage[], done?: (err: Error) => void): void; + + /** + * Returns information about when the last turn between the user and a bot occured. This can be called + * before [beginDialog](#begindialog) to determine if the user is currently in a conversation with the + * bot. + * @param address Address of the user to lookup. This should be saved during a previous conversation with the user. + * @param callback Function to invoke with the results of the query. + */ + isInConversation(address: IAddress, callback: (err: Error, lastAccess: Date) => void): void; + + /** + * Registers a global action that will start another dialog anytime its triggered. The new + * dialog will be pushed onto the stack so it does not automatically end any current task. The + * current task will be continued once the new dialog ends. The built-in prompts will automatically + * re-prompt the user once this happens but that behaviour can be disabled by setting the [promptAfterAction](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.ipromptoptions#promptafteraction) + * flag when calling a built-in prompt. + * @param name Unique name to assign the action. + * @param id ID of the dialog to start. + * @param options (Optional) options used to configure the action. If [matches](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogactionoptions#matches) is specified the action will listen + * for the user to say a word or phrase that triggers the action, otherwise the action needs to be bound to a button using [CardAction.dialogAction()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.cardaction#dialogaction) + * to trigger the action. You can also use [dialogArgs](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogactionoptions#dialogargs) to pass additional params to the dialog being started. + */ + beginDialogAction(name: string, id: string, options?: IDialogActionOptions): Dialog; + + /** + * Registers a global action that will end the conversation with the user when triggered. + * @param name Unique name to assign the action. + * @param msg (Optional) message to send the user prior to ending the conversation. + * @param options (Optional) options used to configure the action. If [matches](/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.idialogactionoptions#matches) is specified the action will listen + * for the user to say a word or phrase that triggers the action, otherwise the action needs to be bound to a button using [CardAction.dialogAction()](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.cardaction#dialogaction) + * to trigger the action. + */ + endConversationAction(name: string, msg?: string|string[]|IMessage|IIsMessage, options?: IDialogActionOptions): Dialog; +} + +/** Connects a UniversalBot to multiple channels via the Bot Framework. */ +export class ChatConnector implements IConnector, IBotStorage { + + /** + * Creates a new instnace of the ChatConnector. + * @param settings (Optional) config params that let you specify the bots App ID & Password you were assigned in the Bot Frameworks developer portal. + */ + constructor(settings?: IChatConnectorSettings); + + /** Registers an Express or Restify style hook to listen for new messages. */ + listen(): (req: any, res: any) => void; + + /** Called by the UniversalBot at registration time to register a handler for receiving incoming events from a channel. */ + onEvent(handler: (events: IEvent[], callback?: (err: Error) => void) => void): void; + + /** Called by the UniversalBot to deliver outgoing messages to a user. */ + send(messages: IMessage[], done: (err: Error) => void): void; + + /** Called when a UniversalBot wants to start a new proactive conversation with a user. The connector should return a properly formated __address__ object with a populated __conversation__ field. */ + startConversation(address: IAddress, done: (err: Error, address?: IAddress) => void): void; + + /** Reads in data from the Bot Frameworks state service. */ + getData(context: IBotStorageContext, callback: (err: Error, data: IBotStorageData) => void): void; + + /** Writes out data to the Bot Frameworks state service. */ + saveData(context: IBotStorageContext, data: IBotStorageData, callback?: (err: Error) => void): void; +} + +/** Connects a UniversalBot to the command line via a console window. */ +export class ConsoleConnector implements IConnector { + /** Starts the connector listening to stdIn. */ + listen(): ConsoleConnector; + + /** Sends a message through the connector. */ + processMessage(line: string): ConsoleConnector; + + /** Called by the UniversalBot at registration time to register a handler for receiving incoming events from a channel. */ + onEvent(handler: (events: IEvent[], callback?: (err: Error) => void) => void): void; + + /** Called by the UniversalBot to deliver outgoing messages to a user. */ + send(messages: IMessage[], callback: (err: Error, conversationId?: string) => void): void; + + /** Called when a UniversalBot wants to start a new proactive conversation with a user. The connector should return a properly formated __address__ object with a populated __conversation__ field. */ + startConversation(address: IAddress, callback: (err: Error, address?: IAddress) => void): void; +} + +export class Middleware { + /** + * Installs a piece of middleware that manages the versioning of a bots dialogs. + * @param options Settings to configure the bahviour of the installed middleware. + */ + static dialogVersion(options: IDialogVersionOptions): IMiddlewareMap; + + /** + * Adds a first run experience to a bot. The middleware uses Session.userData to store the latest version of the first run dialog the user has been through. Incrementing the version number can force users to run back through either the full or a partial first run dialog. + * @param options Settings to configure the bahviour of the installed middleware. + */ + static firstRun(options: IFirstRunOptions): IMiddlewareMap; + + /** + * Installs a piece of middleware that will always send an initial typing indication to the user. + * This is useful because it lets you send the typing indication before any LUIS models are called. + * The typing indicator will only stay valid for a few seconds so if you're performing any long running + * operations you may want to send an additional typing indicator using [session.sendTyping](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session#sendtyping). + */ + static sendTyping(): IMiddlewareMap; +} + +/** __DEPRECATED__ use an [IntentDialog](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.intentdialog) with a [LuisRecognizer](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.luisrecognizer) instead. */ +export class LuisDialog extends Dialog { + replyReceived(session: Session, recognizeResult?: IRecognizeResult): void; +} + +/** __DEPRECATED__ use an [IntentDialog](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.intentdialog) instead. */ +export class CommandDialog extends Dialog { + replyReceived(session: Session, recognizeResult?: IRecognizeResult): void; +} + +/** __DEPRECATED__ use [UniversalBot](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.universalbot) and a [ChatConnector](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.chatconnector) instead. */ +export class BotConnectorBot extends Dialog { + replyReceived(session: Session, recognizeResult?: IRecognizeResult): void; +} + +/** __DEPRECATED__ use [UniversalBot](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.universalbot) and a [ConsoleConnector](/en-us/node/builder/chat-reference/classes/_botbuilder_d_.consoleconnector) instead. */ +export class TextBot extends Dialog { + replyReceived(session: Session, recognizeResult?: IRecognizeResult): void; +} \ No newline at end of file diff --git a/Server/Scripts/vorlon.httpproxy.server.ts b/Server/Scripts/vorlon.httpproxy.server.ts index 5de382df..9149ff0e 100644 --- a/Server/Scripts/vorlon.httpproxy.server.ts +++ b/Server/Scripts/vorlon.httpproxy.server.ts @@ -73,6 +73,7 @@ export module VORLON { public startProxyServer() { this._server = express(); + this._server.set('host', this.httpConfig.proxyHost); if(this.httpConfig.proxyEnvPort) this._server.set('port', process.env.PORT); else @@ -88,12 +89,14 @@ export module VORLON { // }); if (this.httpConfig.useSSL) { - https.createServer(this.httpConfig.options, this._server).listen(this._server.get('port'), () => { - this._log.info('Vorlon.js PROXY with SSL listening on port ' + this._server.get('port')); + https.createServer(this.httpConfig.options, this._server).listen( + this._server.get('port'), this._server.get('host'), undefined, () => { + this._log.info('Vorlon.js PROXY with SSL listening at ' + this._server.get('host') + ':' + this._server.get('port')); }); } else { - http.createServer(this._server).listen(this._server.get('port'), () => { - this._log.info('Vorlon.js PROXY listening on port ' + this._server.get('port')); + http.createServer(this._server).listen( + this._server.get('port'), this._server.get('host'), undefined, () => { + this._log.info('Vorlon.js PROXY listening at ' + this._server.get('host') + ':' + this._server.get('port')); }); } diff --git a/Server/Scripts/vorlon.webServer.ts b/Server/Scripts/vorlon.webServer.ts index 2f47f3c9..a42316b5 100644 --- a/Server/Scripts/vorlon.webServer.ts +++ b/Server/Scripts/vorlon.webServer.ts @@ -79,6 +79,7 @@ export module VORLON { var cors = require("cors"); //Sets + app.set('host', this.httpConfig.host); app.set('port', this.httpConfig.port); app.set('views', path.join(__dirname, '../views')); app.set('view engine', 'jade'); @@ -133,12 +134,14 @@ export module VORLON { this.init(); if (this.httpConfig.useSSL) { - this._httpServer = this.httpConfig.httpModule.createServer(this.httpConfig.options, app).listen(app.get('port'), () => { - this._log.info('Vorlon.js SERVER with SSL listening on port ' + app.get('port')); + this._httpServer = this.httpConfig.httpModule.createServer(this.httpConfig.options, app).listen( + app.get('port'), app.get('host'), undefined, () => { + this._log.info('Vorlon.js SERVER with SSL listening at ' + app.get('host') + ':' + app.get('port')); }); } else { - this._httpServer = this.httpConfig.httpModule.createServer(app).listen(app.get('port'), () => { - this._log.info('Vorlon.js SERVER listening on port ' + app.get('port')); + this._httpServer = this.httpConfig.httpModule.createServer(app).listen( + app.get('port'), app.get('host'), undefined, () => { + this._log.info('Vorlon.js SERVER listening at ' + app.get('host') + ':' + app.get('port')); }); } diff --git a/Server/config.json b/Server/config.json index 4bd8a379..ac162a12 100644 --- a/Server/config.json +++ b/Server/config.json @@ -7,9 +7,11 @@ "activateAuth": false, "username": "", "password": "", + "host": "localhost", "port": 1337, "enableWebproxy": true, "baseProxyURL": "", + "proxyHost": "localhost", "proxyPort": 5050, "proxyEnvPort": false, "vorlonServerURL": "", @@ -38,13 +40,6 @@ "enabled": true, "nodeCompliant": true }, - { - "id": "BABYLONINSPECTOR", - "name": "Babylon Inspector", - "panel": "top", - "foldername": "babylonInspector", - "enabled": false - }, { "id": "WEBSTANDARDS", "name": "Best practices", @@ -73,20 +68,6 @@ "foldername": "unitTestRunner", "enabled": true }, - { - "id": "UWP", - "name": "UWP apps", - "panel": "top", - "foldername": "uwp", - "enabled": false - }, - { - "id": "NGINSPECTOR", - "name": "Ng. Inspector", - "panel": "top", - "foldername": "ngInspector", - "enabled": false - }, { "id": "DEVICE", "name": "My Device", @@ -94,13 +75,6 @@ "foldername": "device", "enabled": true }, - { - "id": "OFFICE", - "name": "Office Addin", - "panel": "top", - "foldername": "office", - "enabled": false - }, { "id": "NODEJS", "name": "NodeJS", @@ -110,6 +84,31 @@ "nodeCompliant": true, "nodeOnly": true }, + { + "id": "BOTFRAMEWORKINSPECTOR", + "name": "Bot Framework Inspector", + "panel": "top", + "foldername": "botFrameworkInspector", + "enabled": true, + "nodeCompliant": true, + "nodeOnly": true + }, + { + "id": "EXPRESS", + "name": "Express", + "panel": "top", + "foldername": "express", + "enabled": false, + "nodeCompliant": true, + "nodeOnly": true + }, + { + "id": "BABYLONINSPECTOR", + "name": "Babylon Inspector", + "panel": "top", + "foldername": "babylonInspector", + "enabled": false + }, { "id": "CONSOLE", "name": "Interactive Console", @@ -118,28 +117,40 @@ "enabled": true, "nodeCompliant": true }, + { + "id": "UWP", + "name": "UWP apps", + "panel": "top", + "foldername": "uwp", + "enabled": false + }, + { + "id": "NGINSPECTOR", + "name": "Ng. Inspector", + "panel": "top", + "foldername": "ngInspector", + "enabled": false + }, + { + "id": "OFFICE", + "name": "Office Addin", + "panel": "top", + "foldername": "office", + "enabled": false + }, { "id": "MODERNIZR", "name": "Modernizr", "panel": "bottom", "foldername": "modernizrReport", - "enabled": true - }, - { - "id": "EXPRESS", - "name": "Express", - "panel": "bottom", - "foldername": "express", - "enabled": true, - "nodeCompliant": true, - "nodeOnly": true + "enabled": false }, { "id": "DOMTIMELINE", "name": "Dom timeline", "panel": "top", "foldername": "domtimeline", - "enabled": true, + "enabled": false, "nodeCompliant": false, "nodeOnly": false } diff --git a/Server/config/vorlon.httpconfig.ts b/Server/config/vorlon.httpconfig.ts index bf8b0821..dbdcb6a9 100644 --- a/Server/config/vorlon.httpconfig.ts +++ b/Server/config/vorlon.httpconfig.ts @@ -10,7 +10,9 @@ export module VORLON { public protocol: String; public httpModule; public options; + public host; public port; + public proxyHost; public proxyPort; public enableWebproxy: boolean; public vorlonServerURL: string; @@ -42,11 +44,13 @@ export module VORLON { this.httpModule = http; } } + this.proxyHost = process.env.PROXY_HOST || catalog.proxyHost || 'localhost'; this.proxyEnvPort = catalog.proxyEnvPort; if (catalog.proxyEnvPort) this.proxyPort = process.env.PORT; else this.proxyPort = catalog.proxyPort || 5050; + this.host = process.env.HOST || catalog.host || 'localhost'; this.port = process.env.PORT || catalog.port || 1337; this.proxyPort = catalog.proxyPort || 5050; this.enableWebproxy = catalog.enableWebproxy || false; diff --git a/Server/config/vorlon.servercontext.ts b/Server/config/vorlon.servercontext.ts index 52a8bbc7..dd6a228a 100644 --- a/Server/config/vorlon.servercontext.ts +++ b/Server/config/vorlon.servercontext.ts @@ -23,7 +23,9 @@ export module VORLON { protocol: String; options; httpModule: any; + host: String; port: number; + proxyHost: String; proxyPort: number; enableWebproxy: boolean; vorlonServerURL: string; diff --git a/VorlonNodeWrapper/package.json b/VorlonNodeWrapper/package.json index f7b7f393..7fae16d9 100644 --- a/VorlonNodeWrapper/package.json +++ b/VorlonNodeWrapper/package.json @@ -1,6 +1,6 @@ { "name": "vorlon-node-wrapper", - "version": "0.0.2", + "version": "0.0.3", "description": "Wrapper for getting Vorlon.js plugin file inside a node.js application", "main": "wrapper.js", "author": "Vorlon Team", diff --git a/client samples/botbuilder/index.js b/client samples/botbuilder/index.js new file mode 100644 index 00000000..7f045470 --- /dev/null +++ b/client samples/botbuilder/index.js @@ -0,0 +1,65 @@ +// ========== VORLON Setup +var vorlonWrapper = require("vorlon-node-wrapper"); +vorlonWrapper.start("http://localhost:1337", "default", false); + +// ========== Requiring +var restify = require('restify'); +var builder = require('botbuilder'); + +//========================================================= +// Bot Setup +//========================================================= + +// Setup Restify Server +var server = restify.createServer(); +server.listen(process.env.port || process.env.PORT || 3978, function () { + console.log('%s listening to %s', server.name, server.url); +}); + +// Create chat bot +var connector = new builder.ChatConnector({ + appId: process.env.MICROSOFT_APP_ID, + appPassword: process.env.MICROSOFT_APP_PASSWORD +}); +var bot = new builder.UniversalBot(connector); +server.post('/api/messages', connector.listen()); + +//========================================================= +// Bots Dialogs +//========================================================= + +bot.dialog('/', function (session) { + session.send("Slash, oh oh, savior of the universe!"); + + session.userData.toto = "data1"; + session.privateConversationData.tata = "Yo"; + session.dialogData.titi = "ahahah"; + + session.beginDialog("/hello"); +}); + +bot.dialog('/hello', [function (session) { + session.send("Hello World"); + session.userData.toto = "hey2"; + session.privateConversationData.tata = "Yo2"; + session.dialogData.titi = "ahahah2"; + builder.Prompts.text(session, "What do you want?"); + +}, +function (session, args) { + session.send("Ok!"); + session.userData.toto = "hey3"; + session.privateConversationData.tata = "Yo"; + session.dialogData.titi = "ahahah"; + session.beginDialog('/foo'); + session.beginDialog('/foo'); +}, +]); + +bot.dialog('/foo', function (session) { + session.endDialog("foo world"); +}); + +bot.dialog('/foo2', function (session) { + session.endDialogWithResult("foo world"); +}); \ No newline at end of file diff --git a/client samples/botbuilder/package.json b/client samples/botbuilder/package.json new file mode 100644 index 00000000..9446a436 --- /dev/null +++ b/client samples/botbuilder/package.json @@ -0,0 +1,17 @@ +{ + "name": "sample", + "version": "1.0.0", + "description": "", + "main": "index.js", + "engine": "node <= 4.6.0", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "botbuilder": "^3.4.4", + "restify": "^4.2.0", + "vorlon-node-wrapper": "0.0.3" + } +} diff --git a/client samples/contosoflowers/.vscode/launch.json b/client samples/contosoflowers/.vscode/launch.json new file mode 100644 index 00000000..b7cd9417 --- /dev/null +++ b/client samples/contosoflowers/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch", + "type": "node", + "request": "launch", + "program": "${workspaceRoot}/app.js", + "stopOnEntry": false, + "args": [], + "cwd": "${workspaceRoot}", + "preLaunchTask": null, + "runtimeExecutable": null, + "runtimeArgs": [ + "--nolazy" + ], + "env": { + "NODE_ENV": "development", + "MICROSOFT_APP_ID": "", + "MICROSOFT_APP_PASSWORD": "", + "BING_MAPS_KEY": "ApBn8xoItlENbFx-rr1kzt_JakWdFTH24taCasYxQCgit15NtDeYrztO4chDtrg5" + }, + "externalConsole": false, + "sourceMaps": false, + "outDir": null + } + ] +} diff --git a/client samples/contosoflowers/README.md b/client samples/contosoflowers/README.md new file mode 100644 index 00000000..7d8649ae --- /dev/null +++ b/client samples/contosoflowers/README.md @@ -0,0 +1,573 @@ +# Contoso Flowers Sample Bot + +Your company started looking for a new platform to create new chat bots and port existing bots and is searching which better suits their needs. One of the requirements is that you'll need to support different platforms (Facebook, Slack, Skype and Webchat). +Several chat bots already exists in these platforms using different tools and learned that different platforms support different native features. Moreover, there's a running implementation of the bot in the native platform (e.g. a Facebook bot) which makes you want to make sure using native features is supported. + +You came across the Microsoft Bot Framework which support a great variety of channels (platforms), programming languages (C# and Node) and supports both state-of-the-art standard bot features and mechanisms to also take advantage of native features (via ChannelData). + +[![Deploy to Azure][Deploy Button]][Deploy ContosoFlowers/Node] +[Deploy Button]: https://azuredeploy.net/deploybutton.png +[Deploy ContosoFlowers/Node]: https://azuredeploy.net + +### Prerequisites + +The minimum prerequisites to run this sample are: +* Latest Node.js with NPM. Download it from [here](https://nodejs.org/en/download/). +* The Bot Framework Emulator. To install the Bot Framework Emulator, download it from [here](https://aka.ms/bf-bc-emulator). Please refer to [this documentation article](https://docs.botframework.com/en-us/csharp/builder/sdkreference/gettingstarted.html#emulator) to know more about the Bot Framework Emulator. +* **[Recommended]** Visual Studio Code for IntelliSense and debugging, download it from [here](https://code.visualstudio.com/) for free. + +#### Integration with Express.js +BotBuilder is implemented as a REST API, basically a web endpoint where all your bot messages will be routed to. This is done through the ChatConnector's [listen()](https://docs.botframework.com/en-us/node/builder/chat-reference/classes/_botbuilder_d_.chatconnector.html#listen) function and it is hooked to your existing [express.js](https://expressjs.com/) or [restify.js](http://restify.com/) application, thus leveraging your web application scalability. If the scaling requirements of your bot are different than your web application, you can opt to host it separatly. + +The simplest way to hook your bot with your express.js app is to use express.js routing as follows: +````JavaScript +server.post('/api/messages', connector.listen()); +```` + +In Contoso Flowers, we are wrapping the Connector's `listen()` method in order to capture the web application's url. We'll use this url later to create a link to the ckeckout form. + +See [bot/index.js](bot/index.js#L81-L91) for capturing the url and [app.js](app.js#L23-L25) for registering the hook. + +````JavaScript +// /bot/index.js +var connectorListener = connector.listen(); +function listen() { + return function (req, res) { + // Capture the url for the hosted application + // We'll later need this url to create the checkout link + var url = req.protocol + '://' + req.get('host'); + siteUrl.save(url); + connectorListener(req, res); + }; +} + +module.exports.listen = listen; + +// /app.js +// Then, register the hook from your express.js application: +var bot = require('./bot'); +app.post('/api/messages', bot.listen()); +```` + +#### Welcome Message + +Some platforms provide a way to detect when a new conversation with the bot is created. We can use this to provide a welcome message before the user starts typing. This can be achived using the [`conversationUpdate`](https://docs.botframework.com/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iconversationupdate.html) event. Checkout [bot/index.js](bot/index.js#L70-L79) for details on how the root dialog is triggered. + +````JavaScript +// Send welcome when conversation with bot is started, by initiating the root dialog +bot.on('conversationUpdate', (message) => { + if (message.membersAdded) { + message.membersAdded.forEach((identity) => { + if (identity.id === message.address.bot.id) { + bot.beginDialog(message.address, '/'); + } + }); + } +}); +```` + +![Welcome Message](images/welcomemessage-emulator.png) + +#### Multi-Dialogs Approach + +Dialogs can be composed with other dialogs to maximize reuse, and a dialog context maintains a stack of dialogs active in the conversation. In this sample, the main flow is implemented in the [shop dialog](bot/dialogs/shop.js) and it is composed of several other dialogs that may also break-down into sub-dialogs. + +Each of these dialogs are implemented as a [BotBuilder Library](https://docs.botframework.com/en-us/node/builder/chat-reference/classes/_botbuilder_d_.library.html) (more information below). The important thing is that each library manages a small step in the flow, and the result of each is passed back to the dialog stack using [session.endDialogWithResult()](https://docs.botframework.com/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session.html#enddialogwithresult). + +These are the more important ones related to the shopping experience: + +* [**Shop Dialog**](bot/dialogs/shop.js) + + Handles the main flow of the shopping experience. Calls other dialogs to capture information like the selected product, delivery address, recipient information and triggers the checkout flow. + +* [**Address Dialogs**](bot/dialogs/address.js) + + Asks for address and validates it using Bing Maps GeoCode service. It also contains the dialog for asking and saving the billing addresses. + +* [**Details Dialogs**](bot/dialogs/details.js) + + Asks for recipient name, notes and sender information. + +* [**Product Selection Dialog**](bot/dialogs/product-selection.js) + + Displays categories and their products. Handles pagination of products and validating the product selection. + +* [**Checkout Dialog**](bot/dialogs/checkout.js) + + Displays a summary of the order and provides a link to the web application for payment. Also handles sending the receipt to the user once the purchase is completed. + +* [**Settings Dialog**](bot/dialogs/settings.js) + + Handles viewing and editing the user's saved information, like phone number, email and billing addresses. + +#### Bot Libraries for Creating Reusable Dialogs + +Libraries of reusable parts can be developed by creating a new Library instance and adding dialogs just as you would to a bot. Your library should have a unique name that corresponds to either your libraries website or NPM module name. Bots can then reuse your library by simply adding your parts Library instance to their bot using [UniversalBot.library()](https://docs.botframework.com/en-us/node/builder/chat-reference/classes/_botbuilder_d_.universalbot.html#library). + +To invoke dialogs within the bot, we use [session.beginDialog()](https://docs.botframework.com/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session.html#begindialog) with a fully qualified dialog id in the form of ':'. + +E.g.: To start the shopping's experience root dialog we use `session.beginDialog('shop:/')`. + +````JavaScript +// /bot/dialogs/shop.js +const library = new builder.Library('shop'); +library.dialog('/', [ + function (session) { + // Ask for delivery address using 'address' library + session.beginDialog('address:/', + { + promptMessage: util.format('%s, please enter the delivery address for these flowers. Include apartment # if needed.', session.message.user.name) + }); + }, + function (session, args) { + // Retrieve address, continue to shop + session.dialogData.recipientAddress = args.address; + session.beginDialog('product-selection:/'); + }, + // ... +}); +```` + +Another more common approach for this feature is encapsulating a re-usable dialog. A good example of these are prompt validators. In this sample, common validations are packaged in the [bot/validators](bot/validators.js) library. + +This is how you could package an email validation: + +````JavaScript +const EmailRegex = new RegExp(/[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/); + +const library = new builder.Library('validators'); + +library.dialog('/email', + builder.DialogAction.validatedPrompt(builder.PromptType.text, (response) => + EmailRegex.test(response))); + +module.exports = library; +```` + +And this is how you can call the validator from your existing code: + +````JavaScript +// Waterfall Dialog +[ + function (session) { + session.beginDialog('validators:/email', { + prompt: 'What\'s your email?', + retryPrompt: 'Something is wrong with that email address. Please try again.' + }); + }, + function (session, args, next) { + var email = args.response; + // TODO: Save email address + // ... + } +] +```` + +> It is worth noting that calling other dialogs within your library don't need to be prefixed with the library's id. It is only when crossing from one library context to another that you need to include the library name prefix on your `session.beginDialog()` calls. + +#### Rich Cards + +Many messaging channels provide the ability to attach richer objects. The Bot Framework has the ability to render rich cards as attachments. + +The bot will render a Welcome message upon the first message or conversation start using a [HeroCard](https://docs.botframework.com/en-us/node/builder/chat-reference/classes/_botbuilder_d_.herocard) attachment within the [bot's root dialog](bot/index.js#L21-L35). + +````JavaScript +var welcomeCard = new builder.HeroCard(session) + .title('Welcome to the Contoso Flowers') + .subtitle('These are the flowers you are looking for!') + .images([ + new builder.CardImage(session) + .url('https://placeholdit.imgix.net/~text?txtsize=56&txt=Contoso%20Flowers&w=640&h=330') + .alt('Contoso Flowers') + ]) + .buttons([ + builder.CardAction.imBack(session, MainOptions.Shop, MainOptions.Shop), + builder.CardAction.imBack(session, MainOptions.Support, MainOptions.Support) + ]); + +session.send(new builder.Message(session) + .addAttachment(welcomeCard)); +```` + +| Emulator | Facebook | Skype | +|----------|-------|----------| +|![Rich Cards - Hero Card](images/richcards-herocard-emulator.png)|![Rich Cards - Hero Card](images/richcards-herocard-facebook.png)|![Rich Cards - Hero Card](images/richcards-herocard-skype.png)| + + +Another example of rich card, is the ReceiptCard which renders differently depending on the messaging channel being supported. The receipt card is created in the [checkout's `completed` dialog](bot/dialogs.js#L87-L104) and is sent once the user completed the order payment. + +````JavaScript +// Retrieve order and create ReceiptCard +orderService.retrieveOrder(orderId).then((order) => { + if (!order) { + throw new Error('Order Id not found'); + } + + var messageText = util.format( + '**Your order %s has been processed!**\n\n' + + 'The **%s** will be sent to **%s %s** with the following note:\n\n' + + '**"%s"**\n\n' + + 'Thank you for using Contoso Flowers.\n\n' + + 'Here is your receipt:', + order.id, + order.selection.name, + order.details.recipient.firstName, + order.details.recipient.lastName, + order.details.note); + + var receiptCard = new builder.ReceiptCard(session) + .title(order.paymentDetails.creditcardHolder) + .facts([ + builder.Fact.create(session, order.id, 'Order Number'), + builder.Fact.create(session, offuscateNumber(order.paymentDetails.creditcardNumber), 'Payment Method'), + ]) + .items([ + builder.ReceiptItem.create(session, order.selection.price, order.selection.name) + .image(builder.CardImage.create(session, order.selection.imageUrl)), + ]) + .total(order.selection.price) + .buttons([ + builder.CardAction.openUrl(session, 'https://dev.botframework.com/', 'More Information') + ]); + + var message = new builder.Message(session) + .text(messageText) + .addAttachment(receiptCard) + + session.endDialog(message); +}); +```` + +| Emulator | Facebook | Skype | +|----------|-------|----------| +|![Rich Cards - Receipt Card](images/richcards-receiptcard-emulator.png)|![Rich Cards - Receipt Card](images/richcards-receiptcard-facebook.png)|![Rich Cards - Receipt Card](images/richcards-receiptcard-skype.png)| + +> You can also see a full sample explaining the different types of rich cards in the [Rich Cards Bot Sample](../cards-RichCards). + +#### Carousel of Cards + +You can send multiple rich card attachments in a single message. On most channels they will be sent as a list of rich cards, but some channels (like Skype and Facebook) can render them as a carousel of rich cards. + +Listing categories and product accompanied with a descriptive image is an example of how a Carousel of Cards can be used. + +The [product-selection](bot/dialogs/product-selection.js#L27) dialog uses the [CarouselPagination](bot/dialogs/CarouselPagination.js) helper to create a dialog closure that displays a carousel of very rich cards. + +This helper provides an easy way to display results to the user, handle pagination of results and validates user selection. Example usage: + +````JavaScript +var Products = require('../services/products'); +var CarouselPagination = require('./dialogs/CarouselPagination'); +var DefaultCategory = 'Flower 2'; + +bot.dialog('/', [ + function (session, args, next) { + // Create dialog function + var displayProducts = CarouselPagination.create( + // getPageFunc(pageNumber: number, pageSize: number):Promise + (pageNumber, pageSize) => Products.getProducts(DefaultCategory, pageNumber, pageSize), + // getItemFunc(title: string):Promise + Products.getProduct, + // itemToCardFunc(product: object):object + (product) => ({ + title: product.name, + subtitle: '$ ' + product.price.toFixed(2), + imageUrl: product.imageUrl, + buttonLabel: 'Choose' + }), + // settings + { + showMoreTitle: 'More items?', + showMoreValue: 'Show me', + selectTemplate: 'Select: ', + pageSize: 5, + unknownOption: 'I couldn\'t understand your selection. Please try again.' + }); + + // Invoke dialog function + // It will handle product selection, pagination call or product list display + displayProducts(session, args, next); + + }, + function (session, args) { + // Read selection + var selectedProduct = args.selected; + if (selectedProduct) { + session.send('You selected "%s"', selectedProduct.name); + } + } +]); +```` + +| Emulator | Facebook | Skype | +|----------|-------|----------| +|![Carousel of Cards](images/carousel-cards-emulator.png)|![Carousel of Cards](images/carousel-cards-facebook.png)|![Carousel of Cards](images/carousel-cards-skype.png)| + +> You can also see a full sample bot sending multiple rich card attachments in a single message using the Carousel layout in the [Carousel of Cards Bot Sample](../cards-CarouselCards). + +#### Complex Forms + +Handling a guided conversation like ordering a bouquet of flowers for your loved one can require a lot of effort. In order to simplify building guided conversations, the Bot Framework provides [Waterfall dialogs](https://docs.botframework.com/en-us/node/builder/chat/dialogs/#waterfall) that let you collect input from a user using a sequence of steps. A bot is always in a state of providing a user with information or asking a question and then waiting for input. In the Node version of Bot Builder its waterfalls that drive this back and forth flow. + +Paired with the built-in Prompts you can easily prompt the user with a series of questions: + +````JavaScript +library.dialog('/', [ + function (session) { + builder.Prompts.text(session, 'What\'s the recipient\'s first name?'); + }, + function (session, args) { + session.dialogData.recipientFirstName = args.response; + builder.Prompts.text(session, 'What\'s the recipient\'s last name?'); + }, + function (session, args) { + session.dialogData.recipientLastName = args.response; + session.beginDialog('validators:/phonenumber', { + prompt: 'What\'s the recipient\'s phone number?', + retryPrompt: 'Oops, that doesn\'t look like a valid number. Try again.', + maxRetries: Number.MAX_VALUE + }); + }, + function (session, args) { + session.dialogData.recipientPhoneNumber = args.response; + session.beginDialog('validators:/notes', { + prompt: 'What do you want the note to say? (in 200 characters)', + retryPrompt: 'Oops, the note is max 200 characters. Try again.', + maxRetries: Number.MAX_VALUE + }); + }, + function (session, args) { + session.dialogData.note = args.response; + // ... + } +]); +```` + +| Emulator | Facebook | Skype | +|----------|-------|----------| +|![Complex Form](images/complexforms-emulator.png)|![Complex Form](images/complexforms-facebook.png)|![Complex Form](images/complexforms-skype.png)| + +Bots based on Bot Builder implement something we call *Guided Dialog* meaning that the bot is generally driving (or guiding) the conversation with the user. With waterfalls you drive the conversation by taking an action that moves the waterfall from one step to the next. Calling a built-in prompt like Prompts.text() moves the conversation along because the users response to the prompt is passed to the input of the next waterfall step. + +In the previous code sample we're using [session.dialogData](https://docs.botframework.com/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session#dialogdata) to temporarily hold the details information. We do this because when our bot is distributed across multiple compute nodes, every step of the waterfall could be processed by a different compute node. The `dialogData` field ensures that the dialog's state is properly maintained between each turn of the conversation. You can store anything you want into this field but should limit yourself to JavaScript primitives that can be properly serialized. + +#### State + +Bots built using Bot Builder are designed to be stateless so that they can easily be scaled to run across multiple compute nodes. Because of that you should generally avoid the temptation to save state using a global variable or within a function closure. Doing so will create issues when you want to scale out your bot. Instead leverage the data bags below to persist temporary and permanent state. + +Field | Use Cases +-------- | --------- +userData | Stores information globally for the user across all conversations. +conversationData | Stores information globally for a single conversation. This data is visible to everyone within the conversation so care should be used to what's stored there. It's disabled by default and needs to be enabled using the bots [`persistConversationData`](https://docs.botframework.com/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iuniversalbotsettings.html#persistconversationdata) setting. +privateConversationData | Stores information globally for a single conversation but its private data for the current user. This data spans all dialogs so it's useful for storing temporary state that you want cleaned up when the conversation ends. +dialogData | Persists information for a single dialog instance. This is essential for storing temporary information in between the steps of a waterfall. + +> If you are planning to use `conversationData`, remember to instantiate the bot using the [`persistConversationData`](https://docs.botframework.com/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iuniversalbotsettings.html#persistconversationdata) setting flag. + +In this sample, the `userData` is used to store and retrieve several user settings, and `dialogData` for saving information between steps of a waterfall dialog. + +The [settings dialog](bot/dialogs/settings.js) is used to manage the `userData`. + +````JavaScript +switch (option) { + case SettingChoice.Email: + var promptMessage = 'Type your email or use (B)ack to return to the menu.'; + if (session.userData.sender && session.userData.sender.email) { + promptMessage = 'This is your current email: ' + session.userData.sender.email + '.\n\nType a new email if you need to update, or use (B)ack to return to the menu.'; + } + session.send(promptMessage); + return session.beginDialog('/email'); + + case SettingChoice.Phone: + var promptMessage = 'Type your phone number or use (B)ack to return to the menu.'; + if (session.userData.sender && session.userData.sender.phoneNumber) { + promptMessage = 'This is your current phone number: ' + session.userData.sender.phoneNumber + '.\n\nType a new number if you need to update, or use (B)ack to return to the menu.'; + } + session.send(promptMessage); + return session.beginDialog('/phone'); + // ... +} +```` +| Emulator | Facebook | Skype | +|----------|-------|----------| +|![State SettingsDialog](images/state-settingsdialog-emulator.png)|![State SettingsDialog](images/state-settingsdialog-facebook.png)|![State SettingsDialog](images/state-settingsdialog-skype.png)| + +> You can also see a full sample bot tracking context of a conversation in the [State API Bot Sample](../core-State). + +The [shop dialog](bot/dialogs/shop.js) on the other hand, shows how to use `dialogData` to store information about the order and details, within the dialog instance, and then use it in the last step to trigger the checkout process. + +````JavaScript +library.dialog('/', [ + function (session) { + // Ask for delivery address using 'address' library + session.beginDialog('address:/'); + }, + function (session, args) { + // Retrieve address, continue to shop + session.dialogData.recipientAddress = args.address; + session.beginDialog('product-selection:/'); + }, + function (session, args) { + // Retrieve selection, continue to delivery date + session.dialogData.selection = args.selection; + session.beginDialog('delivery:/date'); + }, + //... + function (session, args) { + // Continue to checkout + var order = { + selection: session.dialogData.selection, + delivery: { + date: session.dialogData.deliveryDate, + address: session.dialogData.recipientAddress + }, + details: session.dialogData.details, + billingAddress: session.dialogData.billingAddress + }; + + console.log('order', order); + session.beginDialog('checkout:/', { order: order }); + } +]); +```` + +![DialogData - Order](images/dialogdata-debug.png) + +#### Globally Available Commands + +Additionally, you'll notice the Settings dialog is globally available, meaning that the user can type `settings` anytime and the settings dialog will be taken on top of the conversation's dialog stack. A piece of [middleware](https://docs.botframework.com/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.imiddlewaremap.html) inspects every incoming message to see if it contains a specified word and gives the opportunity to manipulate the conversation stack, interrupting the normal dialog flow. Checkout the [middleware](bot/index.js#L51-L68) used to do this. + +````JavaScript +// Trigger secondary dialogs when 'settings' or 'support' is called +const settingsRegex = /^settings/i; +const supportRegex = new RegExp('^(' + MainOptions.Support + '|help)', 'i'); +bot.use({ + botbuilder: (session, next) => { + var text = session.message.text; + if (settingsRegex.test(text)) { + // interrupt and trigger 'settings' dialog + return session.beginDialog('settings:/'); + } else if (supportRegex.test(text)) { + // interrupt and trigger 'help' dialog + return session.beginDialog('help:/'); + } + + // continue normal flow + next(); + } +}); +```` + +| Emulator | Facebook | Skype | +|----------|-------|----------| +|![Middleware Settings](images/middleware-settings-emulator.png)|![Middleware Settings](images/middleware-settings-facebook.png)|![Middleware Settings](images/middleware-settings-skype.png)| + +#### External Events - Resuming Conversations + +The exchange of messages between bot and user through a channel (e.g. Facebook Messenger, Skype, Slack) is the primary means of interaction. However, in some scenarios the bot is waiting for an event that occurs in an external component. For example, the passing of time, an external authentication provider (e.g. OAuth scenarios) or an external payment service. In such cases, an [Address](https://docs.botframework.com/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iaddress.html) object has the information necessary to resume the conversation. + +In this sample, the user proceeds to checkout the order by browsing to an url provided by the bot. This url includes an encoded version of the Address object generated in the [checkout's root dialog](bot/dialogs/checkout.js#L25) using the included [serializeAddress()](bot/utils.js#L5-L15) helper function. + +````JavaScript +// Serialize user address +var addressSerialized = botUtils.serializeAddress(session.message.address); + +// Create order (with no payment - pending) +orderService.placePendingOrder(order).then((order) => { + + // Build Checkout url using previously stored Site url + var checkoutUrl = util.format( + '%s/checkout?orderId=%s&address=%s', + siteUrl.retrieve(), + encodeURIComponent(order.id), + encodeURIComponent(addressSerialized)); + + // ... +}); +```` + +Once the user browses to the checkout page and process the payment, the `Address` included in the url is then decoded (using the [deserializeAddress](bot/utils.js#L17) function) and used to resume the conversation with the bot. You can check [express.js Checkout route](checkout.js) calling the [bot.beginDialog()](checkout.js#L64) function. + +> These [helpers methods](bot/utils.js) serialize the address into JSON and then encrypts the string using AES256-CTR to avoid tampering. The inverse process occurs while deserializing the address. + +````JavaScript +/* POST Checkout */ +router.post('/', function (req, res, next) { + // orderId and user address + var orderId = req.body.orderId; + var address = botUtils.deserializeAddress(req.body.address); + + // Payment information + var paymentDetails = { + creditcardNumber: req.body.creditcard, + creditcardHolder: req.body.fullname + }; + + // Complete order + orderService.confirmOrder(orderId, paymentDetails).then((processedOrder) => { + + // Dispatch completion dialog + bot.beginDialog(address, 'checkout:/completed', { orderId: orderId }); + + // Show completion + return res.render('checkout/completed', { + title: 'Contoso Flowers - Order processed', + order: processedOrder + }); + }); +}); +```` + +| Emulator | Facebook | Skype | +|----------|-------|----------| +|![Address Checkout](images/address-checkout-emulator.png)|![Address Checkout](images/address-checkout-facebook.png)|![Address Checkout](images/address-checkout-skype.png)| + +#### More Advanced Features + +While not covered in this sample, it is important to highlight two features that can help tailoring your bot the your specific needs. + +If you want to be able to take advantage of special features or concepts for a channel we provide a way for you to send native metadata to that channel giving you much deeper control over how your bot interacts on a channel. The way you do this is to pass extra properties via the [sourceEvent method](https://docs.botframework.com/en-us/node/builder/chat-reference/classes/_botbuilder_d_.message.html#sourceevent). + +> You can also see a full sample bot sending native metadata to Facebook using ChannelData in the [ChannelData Bot Sample](../core-ChannelData). + +One of the key problems in human-computer interactions is the ability of the computer to understand what a person wants, and to find the pieces of information that are relevant to their intent. In the LUIS application, you will bundle together the intents and entities that are important to your task. + +> You can also see a full sample bot using LuisDialog to integrate with a LUIS.ai application in the [LUIS Bot Sample](../intelligence-LUIS). + +#### Localization + +At time of this writting, the [latest NPM package](https://www.npmjs.com/package/botbuilder) (botbuilder@3.2.3) does not support localization. + +### More Information + +To get more information about how to get started in Bot Builder for Node review the following resources: + +* [Dialogs](https://docs.botframework.com/en-us/node/builder/chat/dialogs/) +* [Dialog Stack](https://docs.botframework.com/en-us/node/builder/chat/session/#dialog-stack) +* [Prompts](https://docs.botframework.com/en-us/node/builder/chat/prompts/) +* [Adding Dialogs and Memory](https://docs.botframework.com/en-us/node/builder/guides/core-concepts/#adding-dialogs-and-memory) +* [Collecting Input](https://docs.botframework.com/en-us/node/builder/guides/core-concepts/#collecting-input) +* [Attachments, Cards and Actions](https://docs.botframework.com/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iattachment.html) +* [Custom Channel Capabilities](https://docs.botframework.com/en-us/csharp/builder/sdkreference/channels.html) +* [LUIS](https://docs.botframework.com/en-us/node/builder/guides/understanding-natural-language/) + + +> **Limitations** +> The functionality provided by the Bot Framework Activity can be used across many channels. Moreover, some special channel features can be unleashed using the [Message.sourceEvent](https://docs.botframework.com/en-us/node/builder/chat-reference/classes/_botbuilder_d_.message.html#sourceevent) method. +> +> The Bot Framework does its best to support the reuse of your Bot in as many channels as you want. However, due to the very nature of some of these channels, some features are not fully portable. +> +> The features used in this sample are fully supported in the following channels: +> - Skype +> - Facebook +> - Slack +> - DirectLine +> - WebChat +> - GroupMe +> +> They are also supported, with some limitations, in the following channel: +> - Email +> +> On the other hand, they are not supported and the sample won't work as expected in the following channels: +> - Telegram +> - SMS +> - Kik diff --git a/client samples/contosoflowers/app.js b/client samples/contosoflowers/app.js new file mode 100644 index 00000000..86e331e0 --- /dev/null +++ b/client samples/contosoflowers/app.js @@ -0,0 +1,60 @@ +var express = require('express'); +var path = require('path'); +var favicon = require('serve-favicon'); + +// Web app +var app = express(); +var bodyParser = require('body-parser') +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: true })); +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'pug'); +app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); +app.use(express.static(path.join(__dirname, 'public'))); + +// Register your web app routes here +app.get('/', (req, res, next) => + res.render('index', { title: 'Contoso Flowers' })); + +// Register Checkout page +var checkout = require('./checkout'); +app.use('/checkout', checkout); + +// Register Bot +var bot = require('./bot'); +app.post('/api/messages', bot.listen()); + +// Catch 404 and forward to error handler +app.use(function (req, res, next) { + var err = new Error('Not Found'); + err.status = 404; + next(err); +}); + +// Error handlers + +// Development error handler, will print stacktrace +if (app.get('env') === 'development') { + app.use(function (err, req, res, next) { + res.status(err.status || 500); + res.render('error', { + message: err.message, + error: err + }); + }); +} + +// Production error handler, no stacktraces leaked to user +app.use(function (err, req, res, next) { + res.status(err.status || 500); + res.render('error', { + message: err.message, + error: {} + }); +}); + +// Start listening +var port = process.env.port || process.env.PORT || 3978; +app.listen(port, function () { + console.log('Web Server listening on port %s', port); +}); \ No newline at end of file diff --git a/client samples/contosoflowers/azuredeploy.json b/client samples/contosoflowers/azuredeploy.json new file mode 100644 index 00000000..fe63b85f --- /dev/null +++ b/client samples/contosoflowers/azuredeploy.json @@ -0,0 +1,137 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "siteName": { + "defaultValue": "BotBuilder-Samples", + "type": "string" + }, + "hostingPlanName": { + "type": "string" + }, + "siteLocation": { + "type": "string" + }, + "sku": { + "type": "string", + "allowedValues": [ + "Free", + "Shared", + "Basic", + "Standard" + ], + "defaultValue": "Free" + }, + "workerSize": { + "type": "string", + "allowedValues": [ + "0", + "1", + "2" + ], + "defaultValue": "0" + }, + "repoUrl": { + "type": "string" + }, + "branch": { + "type": "string" + }, + "Project": { + "type": "string", + "defaultValue": "Node/demo-ContosoFlowers" + }, + "WEBSITE_NODE_DEFAULT_VERSION": { + "type": "string", + "defaultValue": "5.9.1" + }, + "MICROSOFT_APP_ID": { + "type": "string" + }, + "MICROSOFT_APP_PASSWORD": { + "type": "string" + }, + "BING_MAPS_KEY": { + "type": "string", + "defaultValue": "ApBn8xoItlENbFx-rr1kzt_JakWdFTH24taCasYxQCgit15NtDeYrztO4chDtrg5" + } + }, + "resources": [ + { + "apiVersion": "2014-06-01", + "name": "[parameters('hostingPlanName')]", + "type": "Microsoft.Web/serverFarms", + "location": "[parameters('siteLocation')]", + "properties": { + "name": "[parameters('hostingPlanName')]", + "sku": "[parameters('sku')]", + "workerSize": "[parameters('workerSize')]", + "numberOfWorkers": 1 + } + }, + { + "apiVersion": "2014-06-01", + "name": "[parameters('siteName')]", + "type": "Microsoft.Web/Sites", + "location": "[parameters('siteLocation')]", + "dependsOn": [ + "[concat('Microsoft.Web/serverFarms/', parameters('hostingPlanName'))]" + ], + "tags": { + "[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]": "empty" + }, + "properties": { + "name": "[parameters('siteName')]", + "serverFarm": "[parameters('hostingPlanName')]" + }, + "resources": [ + { + "apiVersion": "2014-04-01", + "type": "config", + "name": "web", + "dependsOn": [ + "[concat('Microsoft.Web/Sites/', parameters('siteName'))]" + ], + "properties": { + "appSettings": [ + { + "name": "Project", + "value": "[parameters('Project')]" + }, + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "[parameters('WEBSITE_NODE_DEFAULT_VERSION')]" + }, + { + "name": "MICROSOFT_APP_ID", + "value": "[parameters('MICROSOFT_APP_ID')]" + }, + { + "name": "MICROSOFT_APP_PASSWORD", + "value": "[parameters('MICROSOFT_APP_PASSWORD')]" + }, + { + "name": "BING_MAPS_KEY", + "value": "[parameters('BING_MAPS_KEY')]" + } + ] + } + }, + { + "apiVersion": "2014-04-01", + "name": "web", + "type": "sourcecontrols", + "dependsOn": [ + "[resourceId('Microsoft.Web/Sites', parameters('siteName'))]", + "[concat('Microsoft.Web/Sites/', parameters('siteName'), '/config/web')]" + ], + "properties": { + "RepoUrl": "[parameters('repoUrl')]", + "branch": "[parameters('branch')]", + "IsManualIntegration": true + } + } + ] + } + ] +} \ No newline at end of file diff --git a/client samples/contosoflowers/bot/dialogs/CarouselPagination.js b/client samples/contosoflowers/bot/dialogs/CarouselPagination.js new file mode 100644 index 00000000..22565eab --- /dev/null +++ b/client samples/contosoflowers/bot/dialogs/CarouselPagination.js @@ -0,0 +1,104 @@ +var builder = require('botbuilder'); + +const defaultSettings = { + showMoreTitle: 'There are more items', + showMoreValue: 'Next Page', + selectTemplate: 'Select: ', + pageSize: 10, + unknownOption: 'I couldn\'t understand your selection. Please try again.' +}; + +module.exports = { + create: function (getPageFunc, getItemFunc, itemToCardFunc, settings) { + // parameter validation + settings = Object.assign({}, defaultSettings, settings); + if (typeof (getPageFunc) !== 'function') { + throw new Error('getPageFunc must be a function'); + } + + if (typeof (getItemFunc) !== 'function') { + throw new Error('getItemFunc must be a function'); + } + + if (typeof (itemToCardFunc) !== 'function') { + throw new Error('itemToCardFunc must be a function'); + } + + // map item info into HeroCard + var asCard = function (cardInfo) { + var card = new builder.HeroCard() + .title(cardInfo.title) + .buttons([ + new builder.CardAction() + .type('imBack') + .value(settings.selectTemplate + cardInfo.title) + .title(cardInfo.buttonLabel) + ]); + + if (cardInfo.subtitle) { + card = card.subtitle(cardInfo.subtitle); + } + + if (cardInfo.imageUrl) { + card = card.images([new builder.CardImage().url(cardInfo.imageUrl).alt(cardInfo.title)]) + } + + return card; + }; + + // return dialog handler funciton + return function (session, args, next) { + var pageNumber = session.dialogData.pageNumber || 1; + var input = session.message.text; + if (!!input && input.toLowerCase() === settings.showMoreValue.toLowerCase()) { + // next page + pageNumber++; + } else if (!!input && isSelection(input, settings.selectTemplate)) { + // Validate selection + var selectedName = input.substring(settings.selectTemplate.length); + getItemFunc(selectedName).then((selectedItem) => { + if (!selectedItem) { + return session.send(settings.unknownOption); + } + + // reset page + session.dialogData.pageNumber = null; + + // return selection to dialog stack + return next({ selected: selectedItem }); + }); + + return; + } + + // retrieve from service and send items + getPageFunc(pageNumber, settings.pageSize).then((pageResult) => { + // save current page number + session.dialogData.pageNumber = pageNumber; + + // items carousel + var cards = pageResult.items + .map(itemToCardFunc) + .map(asCard); + var message = new builder.Message(session) + .attachmentLayout(builder.AttachmentLayout.carousel) + .attachments(cards); + session.send(message); + + // more items link + if (pageResult.totalCount > pageNumber * settings.pageSize) { + var moreCard = new builder.HeroCard(session) + .title(settings.showMoreTitle) + .buttons([ + builder.CardAction.imBack(session, settings.showMoreValue, settings.showMoreValue) + ]); + session.send(new builder.Message(session).addAttachment(moreCard)); + } + }); + } + } +}; + +function isSelection(input, selectTemplate) { + return input.toLowerCase().indexOf(selectTemplate.toLowerCase()) === 0; +} \ No newline at end of file diff --git a/client samples/contosoflowers/bot/dialogs/SimpleWaterfallDialog.js b/client samples/contosoflowers/bot/dialogs/SimpleWaterfallDialog.js new file mode 100644 index 00000000..bd1b3e8c --- /dev/null +++ b/client samples/contosoflowers/bot/dialogs/SimpleWaterfallDialog.js @@ -0,0 +1,33 @@ +var util = require('util'); +var builder = require('botbuilder'); + +function SimpleWaterfallDialog(dialogSteps) { + function fn(session, args) { + if (session.dialogData.step === undefined) { + session.dialogData.step = 0; + } + + var next = function (args) { + session.dialogData.step++; + var dialogStep = dialogSteps[session.dialogData.step]; + if (!dialogStep) { + // no more steps + if (args) { + session.endDialogWithResult(args); + } else { + session.endDialog(); + } + } else { + dialogStep(session, args, next); + } + } + + // run step + dialogSteps[session.dialogData.step](session, args, next); + } + SimpleWaterfallDialog.super_.call(this, fn); +} + +util.inherits(SimpleWaterfallDialog, builder.SimpleDialog); + +module.exports = SimpleWaterfallDialog; \ No newline at end of file diff --git a/client samples/contosoflowers/bot/dialogs/address.js b/client samples/contosoflowers/bot/dialogs/address.js new file mode 100644 index 00000000..40d69adb --- /dev/null +++ b/client samples/contosoflowers/bot/dialogs/address.js @@ -0,0 +1,186 @@ +var builder = require('botbuilder'); +var locationService = require('../../services/location'); + +const library = new builder.Library('address'); + +// Address constants +const InvalidAddress = 'Sorry, I could not understand that address. Can you try again? (Number, street, city, state, and ZIP)'; +const ConfirmChoice = 'Use this address'; +const EditChoice = 'Edit'; +library.dialog('/', [ + function (session, args) { + // Ask for address + args = args || {}; + var promptMessage = args.promptMessage || 'Address?'; + session.dialogData.promptMessage = promptMessage; + if (args.reprompt) { + // re-routed from invalid result + promptMessage = InvalidAddress; + } + + builder.Prompts.text(session, promptMessage); + }, + function (session, args, next) { + // Validate address + var address = args.response; + locationService.parseAddress(address) + .then((addresses) => { + if (addresses.length === 0) { + // Could not resolve address, retry dialog + session.replaceDialog('/', { reprompt: true, promptMessage: session.dialogData.promptMessage }); + } else if (addresses.length === 1) { + // Valid address, continue + next({ response: addresses[0] }); + } else { + session.beginDialog('/choose', { addresses }); + } + }).catch((err) => { + // Validation error, retry dialog + console.error('Address.Validation.Error!', err); + session.send('There was an error validating your address'); + session.replaceDialog('/', { reprompt: true, promptMessage: session.dialogData.promptMessage }); + }); + }, + function (session, args) { + // Confirm address + var address = args.response; + session.dialogData.address = address; + builder.Prompts.choice(session, address, [ConfirmChoice, EditChoice]); + }, + function (session, args) { + if (args.response.entity === ConfirmChoice) { + // Confirmed, end dialog with address + session.endDialogWithResult({ + address: session.dialogData.address + }); + } else { + // Edit, restart dialog + session.replaceDialog('/', { promptMessage: session.dialogData.promptMessage }); + } + } +]); + +// Select address from list +library.dialog('/choose', + function (session, args) { + args = args || {}; + var addresses = args.addresses; + if (addresses) { + // display options + session.dialogData.addresses = addresses; + var message = new builder.Message(session) + .attachmentLayout(builder.AttachmentLayout.carousel) + .attachments(addresses.map((addr) => + new builder.HeroCard(session) + .title('Did you mean?') + .subtitle(addr) + .buttons([builder.CardAction.imBack(session, addr, 'Use this address')]))); + session.send(message); + } else { + // process selected option + var address = session.message.text; + addresses = session.dialogData.addresses; + if (addresses.indexOf(address) === -1) { + // not a valid selection + session.replaceDialog('/choose', { addresses }); + } else { + // return + session.endDialogWithResult({ response: address }); + } + } + }); + +// Request Billing Address +// Prompt/Save selected address. Uses previous dialog to request and validate address. +const UseSavedInfoChoices = { + Home: 'Home address', + Work: 'Work address', + NotThisTime: 'No, thanks!' +}; +library.dialog('/billing', [ + function (session, args, next) { + var selection = session.message.text; + var saved = session.userData.billingAddresses = session.userData.billingAddresses || {}; + if (hasAddresses(saved)) { + // Saved data found, check for selection + if (selection && saved[selection]) { + // Retrieve selection + var savedAddress = saved[selection]; + session.dialogData.billingAddress = savedAddress; + next(); + } else if (selection === UseSavedInfoChoices.NotThisTime) { + // Ask for data + next(); + } else { + // No selection, prompt which saved address to use + session.send('Please select your billing address'); + + var message = new builder.Message(session) + .attachmentLayout(builder.AttachmentLayout.carousel); + var homeAddress = saved[UseSavedInfoChoices.Home]; + var workAddress = saved[UseSavedInfoChoices.Work]; + if (homeAddress) message.addAttachment(createAddressCard(session, UseSavedInfoChoices.Home, homeAddress)); + if (workAddress) message.addAttachment(createAddressCard(session, UseSavedInfoChoices.Work, workAddress)); + message.addAttachment(createAddressCard(session, UseSavedInfoChoices.NotThisTime, 'Add a new address')); + session.send(message); + } + } else { + // No data + next(); + } + }, + function (session, args, next) { + if (session.dialogData.billingAddress) { + // Address selected in previous step, skip + return next(); + } + + // Ask for address + session.beginDialog('/', + { + promptMessage: 'What\'s your billing address? Include apartment # if needed.' + }); + }, + function (session, args, next) { + if (session.dialogData.billingAddress) { + return next(); + } + + // Retrieve address from previous dialog + session.dialogData.billingAddress = args.address; + + // Ask to save address + var options = [UseSavedInfoChoices.Home, UseSavedInfoChoices.Work, UseSavedInfoChoices.NotThisTime]; + builder.Prompts.choice(session, 'Would you like to save this address?', options); + }, + function (session, args, next) { + var billingAddress = session.dialogData.billingAddress; + + if (args.response && args.response.entity !== UseSavedInfoChoices.NotThisTime) { + // Save address + session.userData.billingAddresses = session.userData.billingAddresses || {}; + session.userData.billingAddresses[args.response.entity] = billingAddress; + } + + // Return address + session.endDialogWithResult({ billingAddress: billingAddress }); + } +]); + + +// Helpers +function hasAddresses(addresses) { + return !!addresses[UseSavedInfoChoices.Home] || !!addresses[UseSavedInfoChoices.Work]; +} + +function createAddressCard(session, buttonTitle, address) { + return new builder.HeroCard(session) + .title(buttonTitle) + .subtitle(address) + .buttons([ + builder.CardAction.imBack(session, buttonTitle, buttonTitle) + ]); +} + +module.exports = library; +module.exports.UseSavedInfoChoices = UseSavedInfoChoices; \ No newline at end of file diff --git a/client samples/contosoflowers/bot/dialogs/checkout.js b/client samples/contosoflowers/bot/dialogs/checkout.js new file mode 100644 index 00000000..01972a93 --- /dev/null +++ b/client samples/contosoflowers/bot/dialogs/checkout.js @@ -0,0 +1,117 @@ +var util = require('util'); +var builder = require('botbuilder'); +var botUtils = require('../utils'); +var siteUrl = require('../site-url'); +var orderService = require('../../services/orders'); + +const library = new builder.Library('checkout'); + +// Checkout flow +const RestartMessage = 'Changed my mind' +const StartOver = 'Start over'; +const KeepGoing = 'Keep going'; +const Help = 'Talk to support'; +library.dialog('/', [ + function (session, args, next) { + args = args || {}; + var order = args.order; + + if (!order) { + // 'Changed my mind' was pressed, continue to next step and prompt for options + return next(); + } + + // Serialize user address + var addressSerialized = botUtils.serializeAddress(session.message.address); + + // Create order (with no payment - pending) + orderService.placePendingOrder(order).then((order) => { + + // Build Checkout url using previously stored Site url + var checkoutUrl = util.format( + '%s/checkout?orderId=%s&address=%s', + siteUrl.retrieve(), + encodeURIComponent(order.id), + encodeURIComponent(addressSerialized)); + + var messageText = util.format('The final price is $%d (including delivery). Pay securely using our payment provider.', order.selection.price); + var card = new builder.HeroCard(session) + .text(messageText) + .buttons([ + builder.CardAction.openUrl(session, checkoutUrl, 'Add credit card'), + builder.CardAction.imBack(session, RestartMessage, RestartMessage) + ]); + + session.send(new builder.Message(session) + .addAttachment(card)); + }); + }, + function (session, args) { + builder.Prompts.choice(session, 'What are you looking to do?', [StartOver, KeepGoing, Help]); + }, + function (session, args) { + switch (args.response.entity) { + case KeepGoing: + return session.reset(); + case StartOver: + return session.reset('/'); + case Help: + return session.beginDialog('help:/'); + } + } +]); + +// Checkout completed (initiated from web application. See /checkout.js in the root folder) +library.dialog('/completed', function (session, args, next) { + args = args || {}; + var orderId = args.orderId; + + // Retrieve order and create ReceiptCard + orderService.retrieveOrder(orderId).then((order) => { + if (!order) { + throw new Error('Order Id not found'); + } + + var messageText = util.format( + '**Your order %s has been processed!**\n\n' + + 'The **%s** will be sent to **%s %s** with the following note:\n\n' + + '**"%s"**\n\n' + + 'Thank you for using Contoso Flowers.\n\n' + + 'Here is your receipt:', + order.id, + order.selection.name, + order.details.recipient.firstName, + order.details.recipient.lastName, + order.details.note); + + var receiptCard = new builder.ReceiptCard(session) + .title(order.paymentDetails.creditcardHolder) + .facts([ + builder.Fact.create(session, order.id, 'Order Number'), + builder.Fact.create(session, offuscateNumber(order.paymentDetails.creditcardNumber), 'Payment Method'), + ]) + .items([ + builder.ReceiptItem.create(session, order.selection.price, order.selection.name) + .image(builder.CardImage.create(session, order.selection.imageUrl)), + ]) + .total(order.selection.price) + .buttons([ + builder.CardAction.openUrl(session, 'https://dev.botframework.com/', 'More Information') + ]); + + var message = new builder.Message(session) + .text(messageText) + .addAttachment(receiptCard) + + session.endDialog(message); + }).catch((err) => { + session.endDialog(util.format('An error has ocurred: %s', err.message)) + }); +}); + +// Helpers +function offuscateNumber(cardNumber) { + return cardNumber.substring(0, 4) + ' ****'; +} + +module.exports = library; diff --git a/client samples/contosoflowers/bot/dialogs/delivery.js b/client samples/contosoflowers/bot/dialogs/delivery.js new file mode 100644 index 00000000..442d0c61 --- /dev/null +++ b/client samples/contosoflowers/bot/dialogs/delivery.js @@ -0,0 +1,26 @@ +var builder = require('botbuilder'); + +const Today = 'Today'; +const Tomorrow = 'Tomorrow'; + +const library = new builder.Library('delivery'); +library.dialog('/date', [ + function (session, args, next) { + builder.Prompts.choice(session, 'When would you like these delivered?', [Today, Tomorrow]); + }, + function (session, args) { + var deliveryDate = args.response.entity == Today ? new Date() : new Date().addDays(1); + session.endDialogWithResult({ + deliveryDate: deliveryDate + }); + } +]); + +// Helpers +Date.prototype.addDays = function (days) { + var date = new Date(this.valueOf()); + date.setDate(date.getDate() + days); + return date; +} + +module.exports = library; \ No newline at end of file diff --git a/client samples/contosoflowers/bot/dialogs/details.js b/client samples/contosoflowers/bot/dialogs/details.js new file mode 100644 index 00000000..f36008ae --- /dev/null +++ b/client samples/contosoflowers/bot/dialogs/details.js @@ -0,0 +1,123 @@ +var util = require('util'); +var builder = require('botbuilder'); + +const library = new builder.Library('details'); + +// Recipient & Sender details +library.dialog('/', [ + function (session) { + builder.Prompts.text(session, 'What\'s the recipient\'s first name?'); + }, + function (session, args) { + session.dialogData.recipientFirstName = args.response; + builder.Prompts.text(session, 'What\'s the recipient\'s last name?'); + }, + function (session, args) { + session.dialogData.recipientLastName = args.response; + session.beginDialog('validators:/phonenumber', { + prompt: 'What\'s the recipient\'s phone number?', + retryPrompt: 'Oops, that doesn\'t look like a valid number. Try again.', + maxRetries: Number.MAX_VALUE + }); + }, + function (session, args) { + session.dialogData.recipientPhoneNumber = args.response; + session.beginDialog('validators:/notes', { + prompt: 'What do you want the note to say? (in 200 characters)', + retryPrompt: 'Oops, the note is max 200 characters. Try again.', + maxRetries: Number.MAX_VALUE + }); + }, + function (session, args) { + session.dialogData.note = args.response; + session.beginDialog('/sender'); + }, + function (session, args) { + session.dialogData.sender = args.sender; + var details = { + recipient: { + firstName: session.dialogData.recipientFirstName, + lastName: session.dialogData.recipientLastName, + phoneNumber: session.dialogData.recipientPhoneNumber + }, + note: session.dialogData.note, + sender: session.dialogData.sender + }; + session.endDialogWithResult({ details: details }); + } +]); + +// Sender details +const UseSavedInfoChoices = { + Yes: 'Yes', + No: 'Edit' +}; + +library.dialog('/sender', [ + function (session, args, next) { + var sender = session.userData.sender; + if (!!sender) { + // sender data previously saved + var promptMessage = util.format('Would you like to use this email %s and this phone number \'%s\' info?', sender.email, sender.phoneNumber); + builder.Prompts.choice(session, promptMessage, [UseSavedInfoChoices.Yes, UseSavedInfoChoices.No]); + } else { + // no data + next(); + } + }, + function (session, args, next) { + if (args.response && args.response.entity === UseSavedInfoChoices.Yes && session.userData.sender) { + // Use previously saved data, store it in dialogData + // Next steps will skip if present + session.dialogData.useSaved = true; + session.dialogData.email = session.userData.sender.email; + session.dialogData.phoneNumber = session.userData.sender.phoneNumber; + } + next(); + }, + function (session, args, next) { + if (session.dialogData.useSaved) { + return next(); + } + session.beginDialog('validators:/email', { + prompt: 'What\'s your email?', + retryPrompt: 'Something is wrong with that email address. Please try again.', + maxRetries: Number.MAX_VALUE + }); + }, + function (session, args, next) { + if (session.dialogData.useSaved) { + return next(); + } + session.dialogData.email = args.response; + session.beginDialog('validators:/phonenumber', { + prompt: 'What\'s your phone number?', + retryPrompt: 'Oops, that doesn\'t look like a valid number. Try again.', + maxRetries: Number.MAX_VALUE + }); + }, + function (session, args, next) { + if (session.dialogData.useSaved) { + return next(); + } + session.dialogData.phoneNumber = args.response; + builder.Prompts.confirm(session, 'Would you like to save your info?'); + }, + function (session, args) { + var sender = { + email: session.dialogData.email, + phoneNumber: session.dialogData.phoneNumber + }; + + // Save data? + var shouldSave = args.response; + if (shouldSave) { + session.userData.sender = sender; + } + + // return sender information + session.endDialogWithResult({ sender: sender }); + } +]); + +module.exports = library; \ No newline at end of file diff --git a/client samples/contosoflowers/bot/dialogs/help.js b/client samples/contosoflowers/bot/dialogs/help.js new file mode 100644 index 00000000..7a4b3240 --- /dev/null +++ b/client samples/contosoflowers/bot/dialogs/help.js @@ -0,0 +1,6 @@ +var builder = require('botbuilder'); + +const library = new builder.Library('help'); +library.dialog('/', builder.DialogAction.endDialog('Support will contact you shortly. Have a nice day :)')); + +module.exports = library; \ No newline at end of file diff --git a/client samples/contosoflowers/bot/dialogs/product-selection.js b/client samples/contosoflowers/bot/dialogs/product-selection.js new file mode 100644 index 00000000..481b8182 --- /dev/null +++ b/client samples/contosoflowers/bot/dialogs/product-selection.js @@ -0,0 +1,72 @@ +var _ = require('lodash'); +var builder = require('botbuilder'); +var products = require('../../services/products'); +var SimpleWaterfallDialog = require('./SimpleWaterfallDialog'); +var CarouselPagination = require('./CarouselPagination'); + +var carouselOptions = { + showMoreTitle: 'Want more options?', + showMoreValue: 'Show me', + selectTemplate: 'Select: ', + pageSize: 5, + unknownOption: 'I couldn\'t understand your selection. Please try again.' +}; + +const library = new builder.Library('product-selection'); + +// These steps are defined as a waterfall dialog, +// but the control is done manually by calling the next func argument. +library.dialog('/', + new SimpleWaterfallDialog([ + // First message + function (session, args, next) { + session.send('Please select a category:'); + next(); + }, + // Show Categories + CarouselPagination.create(products.getCategories, products.getCategory, categoryMapping, carouselOptions), + // Category selected + function (session, args, next) { + var category = args.selected; + session.send('Please select a bouquet for "%s" category:', category.name); + session.dialogData.category = category; + session.message.text = null; // remove message so next step does not take it as input + next(); + }, + // Show Products + function (session, args, next) { + var categoryName = session.dialogData.category.name; + CarouselPagination.create( + (pageNumber, pageSize) => products.getProducts(categoryName, pageNumber, pageSize), + products.getProduct, + productMapping, + carouselOptions + )(session, args, next); + }, + // Product selected + function (session, args, next) { + // this is last step, calling next with args will end in session.endDialogWithResult(args) + next({ selection: args.selected }); + } + ])); + +function categoryMapping(category) { + return { + title: category.name, + imageUrl: category.imageUrl, + buttonLabel: 'View bouquets' + }; +} + +function productMapping(product) { + return { + title: product.name, + subtitle: '$ ' + product.price.toFixed(2), + imageUrl: product.imageUrl, + buttonLabel: 'Select' + }; +} + + + +module.exports = library; \ No newline at end of file diff --git a/client samples/contosoflowers/bot/dialogs/settings.js b/client samples/contosoflowers/bot/dialogs/settings.js new file mode 100644 index 00000000..ad303d7c --- /dev/null +++ b/client samples/contosoflowers/bot/dialogs/settings.js @@ -0,0 +1,151 @@ +var util = require('util'); +var builder = require('botbuilder'); +var validators = require('../validators'); +var addressLibrary = require('./address'); + +const SettingChoice = { + Email: 'Edit your email', + Phone: 'Edit phone number', + Addresses: 'Edit your addresses', + Cancel: 'Go back' +}; + +var library = new builder.Library('settings'); +library.dialog('/', [ + // Display options + function (session) { + builder.Prompts.choice( + session, + 'Want to make changes to your personal info or addresses? You\'re in the right place.', + [SettingChoice.Email, SettingChoice.Phone, SettingChoice.Addresses, SettingChoice.Cancel]); + }, + // Trigger option edit + function (session, args, next) { + args = args || {}; + var response = args.response || {}; + var option = response.entity; + switch (option) { + case SettingChoice.Email: + var promptMessage = 'Type your email or use (B)ack to return to the menu.'; + if (session.userData.sender && session.userData.sender.email) { + promptMessage = 'This is your current email: ' + session.userData.sender.email + '.\n\nType a new email if you need to update, or use (B)ack to return to the menu.'; + } + session.send(promptMessage); + return session.beginDialog('/email'); + + case SettingChoice.Phone: + var promptMessage = 'Type your phone number or use (B)ack to return to the menu.'; + if (session.userData.sender && session.userData.sender.phoneNumber) { + promptMessage = 'This is your current phone number: ' + session.userData.sender.phoneNumber + '.\n\nType a new number if you need to update, or use (B)ack to return to the menu.'; + } + session.send(promptMessage); + return session.beginDialog('/phone'); + + case SettingChoice.Addresses: + return session.beginDialog('/addresses'); + + case SettingChoice.Cancel: + return session.endDialog(); + } + }, + // Setting updated/cancelled + function (session, args) { + args = args || {}; + var text = !!args.updated ? 'Thanks! Your setting was updated!' : 'No setting was updated.'; + session.send(text); + session.replaceDialog('/'); + } +]).reloadAction('restart', null, { matches: /^back|b/i }); // restart menu options when 'B' or 'Back' is received + + + +// Email edit +library.dialog('/email', editOptionDialog( + (input) => validators.EmailRegex.test(input), + 'Something is wrong with that email address. Please try again.', + (session, email) => saveSenderSetting(session, 'email', email))); + +// Phone Number edit +library.dialog('/phone', editOptionDialog( + (input) => validators.PhoneRegex.test(input), + 'Oops, that doesn\'t look like a valid number. Try again.', + (session, phone) => saveSenderSetting(session, 'phoneNumber', phone))); + +// Addresses +const UseSavedInfoChoices = addressLibrary.UseSavedInfoChoices; +library.dialog('/addresses', [ + function(session, args, next) { + + // Check if an option was selected + var selection = session.message.text; + if(selection === UseSavedInfoChoices.Home || selection === UseSavedInfoChoices.Work) { + session.dialogData.selection = selection; + return next(); + } + + // Show saved addresses + session.send('Which address do you wish to update?'); + var saved = session.userData.billingAddresses = session.userData.billingAddresses || {}; + var message = new builder.Message(session) + .attachmentLayout(builder.AttachmentLayout.carousel); + var homeAddress = saved[UseSavedInfoChoices.Home]; + var workAddress = saved[UseSavedInfoChoices.Work]; + message.addAttachment(createAddressCard(session, UseSavedInfoChoices.Home, homeAddress || 'Not set')); + message.addAttachment(createAddressCard(session, UseSavedInfoChoices.Work, workAddress || 'Not set')); + message.addAttachment(new builder.HeroCard(session) + .title('Not this time') + .subtitle('Do not change any addresses') + .buttons([ + builder.CardAction.imBack(session, 'Back', '(B)ack') + ])); + session.send(message); + }, + function (session, args, next) { + // Trigger address request dialog + session.beginDialog('address:/', { promptMessage: util.format('Please specify your new %s.', session.dialogData.selection) }); + }, + function (session, args, next) { + // Save new address + var selection = session.dialogData.selection; + var newAddress = args.address; + session.userData.billingAddresses = session.userData.billingAddresses || {}; + session.userData.billingAddresses[selection] = newAddress; + session.endDialogWithResult({ updated: true }); + } +]); + +function saveSenderSetting(session, name, value) { + session.userData.sender = session.userData.sender || {}; + session.userData.sender[name] = value; +} + +function editOptionDialog(validationFunc, invalidMessage, saveFunc) { + return new builder.SimpleDialog(function (session, args, next) { + // check dialog was just forwarded + if (!session.dialogData.loop) { + session.dialogData.loop = true; + session.sendBatch(); + return; + } + + if(!validationFunc(session.message.text)) { + // invalid + session.send(invalidMessage); + } else { + // save + saveFunc(session, session.message.text); + session.endDialogWithResult({ updated: true }); + } + }); +} + +function createAddressCard(session, buttonTitle, address) { + return new builder.HeroCard(session) + .title(buttonTitle) + .subtitle(address) + .buttons([ + builder.CardAction.imBack(session, buttonTitle, buttonTitle) + ]); +} + +module.exports = library; diff --git a/client samples/contosoflowers/bot/dialogs/shop.js b/client samples/contosoflowers/bot/dialogs/shop.js new file mode 100644 index 00000000..0b857f01 --- /dev/null +++ b/client samples/contosoflowers/bot/dialogs/shop.js @@ -0,0 +1,56 @@ +var util = require('util'); +var builder = require('botbuilder'); + +const library = new builder.Library('shop'); +library.dialog('/', [ + function (session) { + // Ask for delivery address using 'address' library + session.beginDialog('address:/', + { + promptMessage: util.format('%s, please enter the delivery address for these flowers. Include apartment # if needed.', session.message.user.name || "User") + }); + }, + function (session, args) { + // Retrieve address, continue to shop + session.dialogData.recipientAddress = args.address; + session.beginDialog('product-selection:/'); + }, + function (session, args) { + // Retrieve selection, continue to delivery date + session.dialogData.selection = args.selection; + session.beginDialog('delivery:/date'); + }, + function (session, args) { + // Retrieve deliveryDate, continue to details + session.dialogData.deliveryDate = args.deliveryDate; + session.send('Great choice "%s"! Delivery on %s', session.dialogData.selection.name, session.dialogData.deliveryDate.toLocaleDateString()); + session.beginDialog('details:/'); + }, + function (session, args) { + // Retrieve details, continue to billing address + session.dialogData.details = args.details; + session.beginDialog('address:/billing'); + }, + function (session, args, next) { + // Retrieve billing address + session.dialogData.billingAddress = args.billingAddress; + next(); + }, + function (session, args) { + // Continue to checkout + var order = { + selection: session.dialogData.selection, + delivery: { + date: session.dialogData.deliveryDate, + address: session.dialogData.recipientAddress + }, + details: session.dialogData.details, + billingAddress: session.dialogData.billingAddress + }; + + console.log('order', order); + session.beginDialog('checkout:/', { order: order }); + } +]); + +module.exports = library; \ No newline at end of file diff --git a/client samples/contosoflowers/bot/index.js b/client samples/contosoflowers/bot/index.js new file mode 100644 index 00000000..01a3f7a8 --- /dev/null +++ b/client samples/contosoflowers/bot/index.js @@ -0,0 +1,110 @@ +// ========== VORLON Setup +var vorlonWrapper = require("vorlon-node-wrapper"); +vorlonWrapper.start("http://localhost:1337", "default", false); + +var builder = require('botbuilder'); +var siteUrl = require('./site-url'); + +var connector = new builder.ChatConnector({ + appId: process.env.MICROSOFT_APP_ID, + appPassword: process.env.MICROSOFT_APP_PASSWORD +}); +var bot = new builder.UniversalBot(connector, { persistUserData: true }); + +// Welcome Dialog +const MainOptions = { + Shop: 'Order flowers', + Support: 'Talk to support' +}; +bot.dialog('/', (session) => { + if(session.message.text.trim().toUpperCase() === MainOptions.Shop.toUpperCase()) { + // Order Flowers + return session.beginDialog('shop:/'); + } + + var welcomeCard = new builder.HeroCard(session) + .title('Welcome to the Contoso Flowers') + .subtitle('These are the flowers you are looking for!') + .images([ + new builder.CardImage(session) + .url('https://placeholdit.imgix.net/~text?txtsize=56&txt=Contoso%20Flowers&w=640&h=330') + .alt('Contoso Flowers') + ]) + .buttons([ + builder.CardAction.imBack(session, MainOptions.Shop, MainOptions.Shop), + builder.CardAction.imBack(session, MainOptions.Support, MainOptions.Support) + ]); + + session.send(new builder.Message(session) + .addAttachment(welcomeCard)); +}); + +// Sub-Dialogs +bot.library(require('./dialogs/shop')); +bot.library(require('./dialogs/address')); +bot.library(require('./dialogs/product-selection')); +bot.library(require('./dialogs/delivery')); +bot.library(require('./dialogs/details')); +bot.library(require('./dialogs/checkout')); +bot.library(require('./dialogs/settings')); +bot.library(require('./dialogs/help')); + +// Validators +bot.library(require('./validators')); + +// Trigger secondary dialogs when 'settings' or 'support' is called +const settingsRegex = /^settings/i; +const supportRegex = new RegExp('^(' + MainOptions.Support + '|help)', 'i'); +bot.use({ + botbuilder: (session, next) => { + var text = session.message.text; + if (settingsRegex.test(text)) { + // interrupt and trigger 'settings' dialog + return session.beginDialog('settings:/'); + } else if (supportRegex.test(text)) { + // interrupt and trigger 'help' dialog + return session.beginDialog('help:/'); + } + + // continue normal flow + next(); + } +}); + +// Send welcome when conversation with bot is started, by initiating the root dialog +bot.on('conversationUpdate', (message) => { + if (message.membersAdded) { + message.membersAdded.forEach((identity) => { + if (identity.id === message.address.bot.id) { + bot.beginDialog(message.address, '/'); + } + }); + } +}); + +// Connector listener wrapper to capture site url +var connectorListener = connector.listen(); +function listen() { + return function (req, res) { + // Capture the url for the hosted application + // We'll later need this url to create the checkout link + var url = req.protocol + '://' + req.get('host'); + siteUrl.save(url); + connectorListener(req, res); + }; +} + +// Other wrapper functions +function beginDialog(address, dialogId, dialogArgs) { + bot.beginDialog(address, dialogId, dialogArgs) +} + +function sendMessage(message) { + bot.send(message); +} + +module.exports = { + listen: listen, + beginDialog: beginDialog, + sendMessage: sendMessage +}; \ No newline at end of file diff --git a/client samples/contosoflowers/bot/site-url.js b/client samples/contosoflowers/bot/site-url.js new file mode 100644 index 00000000..ecc11987 --- /dev/null +++ b/client samples/contosoflowers/bot/site-url.js @@ -0,0 +1,6 @@ +// This module provides a singleton store for saving the Site's hosted url +var siteUrl = null; +module.exports = { + save: (url) => siteUrl = url, + retrieve: () => siteUrl +}; \ No newline at end of file diff --git a/client samples/contosoflowers/bot/utils.js b/client samples/contosoflowers/bot/utils.js new file mode 100644 index 00000000..32bae2a1 --- /dev/null +++ b/client samples/contosoflowers/bot/utils.js @@ -0,0 +1,30 @@ +var crypto = require('crypto'); +var algo = 'aes-256-ctr'; +var password = 'itsasecret!'; + +function serializeAddress(address) { + return JSON.stringify(address); +} + +function deserializeAddress(json) { + return JSON.parse(json); +} + +function encrypt(input) { + var cipher = crypto.createCipher(algo, password); + var encrypted = cipher.update(input, 'utf8', 'hex'); + encrypted += cipher.final('hex'); + return encrypted; +} + +function descrypt(cryptedInput) { + var decipher = crypto.createDecipher(algo, password); + var decryted = decipher.update(cryptedInput, 'hex', 'utf8'); + decryted += decipher.final('utf8'); + return decryted; +} + +module.exports = { + serializeAddress: (address) => encrypt(serializeAddress(address)), + deserializeAddress: (input) => deserializeAddress(descrypt(input)) +}; \ No newline at end of file diff --git a/client samples/contosoflowers/bot/validators.js b/client samples/contosoflowers/bot/validators.js new file mode 100644 index 00000000..aad158c3 --- /dev/null +++ b/client samples/contosoflowers/bot/validators.js @@ -0,0 +1,22 @@ +var builder = require('botbuilder'); + +const PhoneRegex = new RegExp(/^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/); +const EmailRegex = new RegExp(/[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/); + +const library = new builder.Library('validators'); + +library.dialog('/notes', + builder.DialogAction.validatedPrompt(builder.PromptType.text, (response) => + response && response.length <= 200)); + +library.dialog('/phonenumber', + builder.DialogAction.validatedPrompt(builder.PromptType.text, (response) => + PhoneRegex.test(response))); + +library.dialog('/email', + builder.DialogAction.validatedPrompt(builder.PromptType.text, (response) => + EmailRegex.test(response))); + +module.exports = library; +module.exports.PhoneRegex = PhoneRegex; +module.exports.EmailRegex = EmailRegex; \ No newline at end of file diff --git a/client samples/contosoflowers/checkout.js b/client samples/contosoflowers/checkout.js new file mode 100644 index 00000000..601dd69e --- /dev/null +++ b/client samples/contosoflowers/checkout.js @@ -0,0 +1,77 @@ +var util = require('util'); +var express = require('express'); +var router = express.Router(); + +var orderService = require('./services/orders'); +var bot = require('./bot'); +var botUtils = require('./bot/utils'); + +/* GET Checkout */ +router.get('/', function (req, res, next) { + // orderId and user address + var orderId = req.query.orderId; + var address = botUtils.deserializeAddress(req.query.address); + console.log('user address is', address); + + orderService.retrieveOrder(orderId).then((order) => { + // Check order exists + if (!order) { + throw new Error('Order ID not found'); + } + + // Check order if order is already processed + if (order.payed) { + // Dispatch completion dialog + bot.beginDialog(address, 'checkout:/completed', { orderId: orderId }); + + // Show completion + return res.render('checkout/completed', { + title: 'Contoso Flowers - Order Processed', + order: order + }); + } + + // Payment form + return res.render('checkout/index', { + title: 'Contoso Flowers - Order Checkout', + address: req.query.address, + order: order + }); + + }).catch((err) => { + next(err); + }); + +}); + +/* POST Checkout */ +router.post('/', function (req, res, next) { + // orderId and user address + var orderId = req.body.orderId; + var address = botUtils.deserializeAddress(req.body.address); + console.log('user address is', address); + + // Payment information + var paymentDetails = { + creditcardNumber: req.body.creditcard, + creditcardHolder: req.body.fullname + }; + + // Complete order + orderService.confirmOrder(orderId, paymentDetails).then((processedOrder) => { + + // Dispatch completion dialog + bot.beginDialog(address, 'checkout:/completed', { orderId: orderId }); + + // Show completion + return res.render('checkout/completed', { + title: 'Contoso Flowers - Order processed', + order: processedOrder + }); + + }).catch((err) => { + next(err); + }); +}); + +module.exports = router; diff --git a/client samples/contosoflowers/data/orders.json b/client samples/contosoflowers/data/orders.json new file mode 100644 index 00000000..0637a088 --- /dev/null +++ b/client samples/contosoflowers/data/orders.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/client samples/contosoflowers/images/address-checkout-emulator.png b/client samples/contosoflowers/images/address-checkout-emulator.png new file mode 100644 index 00000000..5f10e1ad Binary files /dev/null and b/client samples/contosoflowers/images/address-checkout-emulator.png differ diff --git a/client samples/contosoflowers/images/address-checkout-facebook.png b/client samples/contosoflowers/images/address-checkout-facebook.png new file mode 100644 index 00000000..cbfad887 Binary files /dev/null and b/client samples/contosoflowers/images/address-checkout-facebook.png differ diff --git a/client samples/contosoflowers/images/address-checkout-skype.png b/client samples/contosoflowers/images/address-checkout-skype.png new file mode 100644 index 00000000..bf9485bc Binary files /dev/null and b/client samples/contosoflowers/images/address-checkout-skype.png differ diff --git a/client samples/contosoflowers/images/carousel-cards-emulator.png b/client samples/contosoflowers/images/carousel-cards-emulator.png new file mode 100644 index 00000000..52639433 Binary files /dev/null and b/client samples/contosoflowers/images/carousel-cards-emulator.png differ diff --git a/client samples/contosoflowers/images/carousel-cards-facebook.png b/client samples/contosoflowers/images/carousel-cards-facebook.png new file mode 100644 index 00000000..4f80554c Binary files /dev/null and b/client samples/contosoflowers/images/carousel-cards-facebook.png differ diff --git a/client samples/contosoflowers/images/carousel-cards-skype.png b/client samples/contosoflowers/images/carousel-cards-skype.png new file mode 100644 index 00000000..1ab6dd96 Binary files /dev/null and b/client samples/contosoflowers/images/carousel-cards-skype.png differ diff --git a/client samples/contosoflowers/images/complexforms-emulator.png b/client samples/contosoflowers/images/complexforms-emulator.png new file mode 100644 index 00000000..5a3f7c35 Binary files /dev/null and b/client samples/contosoflowers/images/complexforms-emulator.png differ diff --git a/client samples/contosoflowers/images/complexforms-facebook.png b/client samples/contosoflowers/images/complexforms-facebook.png new file mode 100644 index 00000000..8c1f2016 Binary files /dev/null and b/client samples/contosoflowers/images/complexforms-facebook.png differ diff --git a/client samples/contosoflowers/images/complexforms-skype.png b/client samples/contosoflowers/images/complexforms-skype.png new file mode 100644 index 00000000..307b8f5e Binary files /dev/null and b/client samples/contosoflowers/images/complexforms-skype.png differ diff --git a/client samples/contosoflowers/images/dialogdata-debug.png b/client samples/contosoflowers/images/dialogdata-debug.png new file mode 100644 index 00000000..d5824cc9 Binary files /dev/null and b/client samples/contosoflowers/images/dialogdata-debug.png differ diff --git a/client samples/contosoflowers/images/middleware-settings-emulator.png b/client samples/contosoflowers/images/middleware-settings-emulator.png new file mode 100644 index 00000000..c1dd090e Binary files /dev/null and b/client samples/contosoflowers/images/middleware-settings-emulator.png differ diff --git a/client samples/contosoflowers/images/middleware-settings-facebook.png b/client samples/contosoflowers/images/middleware-settings-facebook.png new file mode 100644 index 00000000..1dab9507 Binary files /dev/null and b/client samples/contosoflowers/images/middleware-settings-facebook.png differ diff --git a/client samples/contosoflowers/images/middleware-settings-skype.png b/client samples/contosoflowers/images/middleware-settings-skype.png new file mode 100644 index 00000000..f6ce42e2 Binary files /dev/null and b/client samples/contosoflowers/images/middleware-settings-skype.png differ diff --git a/client samples/contosoflowers/images/richcards-herocard-emulator.png b/client samples/contosoflowers/images/richcards-herocard-emulator.png new file mode 100644 index 00000000..d4c243d1 Binary files /dev/null and b/client samples/contosoflowers/images/richcards-herocard-emulator.png differ diff --git a/client samples/contosoflowers/images/richcards-herocard-facebook.png b/client samples/contosoflowers/images/richcards-herocard-facebook.png new file mode 100644 index 00000000..d6e77866 Binary files /dev/null and b/client samples/contosoflowers/images/richcards-herocard-facebook.png differ diff --git a/client samples/contosoflowers/images/richcards-herocard-skype.png b/client samples/contosoflowers/images/richcards-herocard-skype.png new file mode 100644 index 00000000..3d3b0b6d Binary files /dev/null and b/client samples/contosoflowers/images/richcards-herocard-skype.png differ diff --git a/client samples/contosoflowers/images/richcards-receiptcard-emulator.png b/client samples/contosoflowers/images/richcards-receiptcard-emulator.png new file mode 100644 index 00000000..ea34f065 Binary files /dev/null and b/client samples/contosoflowers/images/richcards-receiptcard-emulator.png differ diff --git a/client samples/contosoflowers/images/richcards-receiptcard-facebook.png b/client samples/contosoflowers/images/richcards-receiptcard-facebook.png new file mode 100644 index 00000000..b4082965 Binary files /dev/null and b/client samples/contosoflowers/images/richcards-receiptcard-facebook.png differ diff --git a/client samples/contosoflowers/images/richcards-receiptcard-skype.png b/client samples/contosoflowers/images/richcards-receiptcard-skype.png new file mode 100644 index 00000000..ed9bc741 Binary files /dev/null and b/client samples/contosoflowers/images/richcards-receiptcard-skype.png differ diff --git a/client samples/contosoflowers/images/state-settingsdialog-emulator.png b/client samples/contosoflowers/images/state-settingsdialog-emulator.png new file mode 100644 index 00000000..fd174c6a Binary files /dev/null and b/client samples/contosoflowers/images/state-settingsdialog-emulator.png differ diff --git a/client samples/contosoflowers/images/state-settingsdialog-facebook.png b/client samples/contosoflowers/images/state-settingsdialog-facebook.png new file mode 100644 index 00000000..20da1510 Binary files /dev/null and b/client samples/contosoflowers/images/state-settingsdialog-facebook.png differ diff --git a/client samples/contosoflowers/images/state-settingsdialog-skype.png b/client samples/contosoflowers/images/state-settingsdialog-skype.png new file mode 100644 index 00000000..853fe6f9 Binary files /dev/null and b/client samples/contosoflowers/images/state-settingsdialog-skype.png differ diff --git a/client samples/contosoflowers/images/welcomemessage-emulator.png b/client samples/contosoflowers/images/welcomemessage-emulator.png new file mode 100644 index 00000000..1100f63d Binary files /dev/null and b/client samples/contosoflowers/images/welcomemessage-emulator.png differ diff --git a/client samples/contosoflowers/package.json b/client samples/contosoflowers/package.json new file mode 100644 index 00000000..bdc391f3 --- /dev/null +++ b/client samples/contosoflowers/package.json @@ -0,0 +1,23 @@ +{ + "name": "botbuilder-sample-contosoflowers", + "version": "1.0.0", + "description": "Bot Builder Sample - Contoso Flowers", + "main": "index.js", + "scripts": { + "start": "node app.js" + }, + "author": "", + "license": "MIT", + "dependencies": { + "bluebird": "^3.4.5", + "body-parser": "^1.15.2", + "botbuilder": "^3.2.3", + "express": "^4.14.0", + "geobing": "^0.1.3", + "lodash": "^4.15.0", + "pug": "^2.0.0-beta6", + "serve-favicon": "^2.3.0", + "uuid": "^2.0.2", + "vorlon-node-wrapper": "0.0.3" + } +} diff --git a/client samples/contosoflowers/public/favicon.ico b/client samples/contosoflowers/public/favicon.ico new file mode 100644 index 00000000..bfe873eb Binary files /dev/null and b/client samples/contosoflowers/public/favicon.ico differ diff --git a/client samples/contosoflowers/public/stylesheets/style.css b/client samples/contosoflowers/public/stylesheets/style.css new file mode 100644 index 00000000..18799e94 --- /dev/null +++ b/client samples/contosoflowers/public/stylesheets/style.css @@ -0,0 +1,22 @@ +body { + padding: 50px; + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; +} + +a { + color: #00B7FF; +} + +fieldset { + border: 1px solid #ccc +} + +fieldset label { + display: block; + width: 400px; + clear: both; +} + +fieldset label input { + float: right; +} \ No newline at end of file diff --git a/client samples/contosoflowers/services/location.js b/client samples/contosoflowers/services/location.js new file mode 100644 index 00000000..5550ff2e --- /dev/null +++ b/client samples/contosoflowers/services/location.js @@ -0,0 +1,28 @@ +var _ = require('lodash'); +var Promise = require('bluebird'); +var geobing = require('geobing'); + +geobing.setKey(process.env.BING_MAPS_KEY); + +function parseAddress(inputAddress) { + return new Promise(function (resolve, reject) { + + geobing.geocode(inputAddress, (err, result) => { + if (err) { + return reject(err); + } + + var entities = result.resourceSets[0] ? result.resourceSets[0].resources : []; + var addressEntities = _.filter(entities, ['entityType', 'Address']); + resolve(addressEntities.map(extractAddress)); + }); + }); +} + +function extractAddress(bingEntity) { + return bingEntity.address.formattedAddress; +} + +module.exports = { + parseAddress: parseAddress +}; \ No newline at end of file diff --git a/client samples/contosoflowers/services/orders.js b/client samples/contosoflowers/services/orders.js new file mode 100644 index 00000000..e458b575 --- /dev/null +++ b/client samples/contosoflowers/services/orders.js @@ -0,0 +1,52 @@ +var uuid = require('uuid'); +var fs = require('fs'); +var _ = require('lodash'); +var Promise = require('bluebird'); + +const OrderService = { + placePendingOrder: function (order) { + order.id = uuid.v1(); + order.payed = false; + + var orders = this.load(); + orders.push(order); + this.save(orders); + + return Promise.resolve(order); + }, + retrieveOrder: function (orderId) { + var orders = this.load(); + var order = _.find(orders, ['id', orderId]); + + return Promise.resolve(order); + }, + confirmOrder: function (orderId, paymentDetails) { + var orders = this.load(); + var order = _.find(orders, ['id', orderId]); + if (!order) { + return Promise.reject({ error: 'Order ID not found' }); + } + + if (order.payed) { + return Promise.resolve(order); + } + + order.payed = true; + order.paymentDetails = paymentDetails; + this.save(orders); + + return Promise.resolve(order); + }, + + // persistence + load: function () { + var json = fs.readFileSync('./data/orders.json', { encoding: 'utf8' }); + return JSON.parse(json); + }, + save: function (orders) { + var json = JSON.stringify(orders); + fs.writeFileSync('./data/orders.json', json, { encoding: 'utf8' }); + } +}; + +module.exports = OrderService; \ No newline at end of file diff --git a/client samples/contosoflowers/services/products.js b/client samples/contosoflowers/services/products.js new file mode 100644 index 00000000..9c8e75eb --- /dev/null +++ b/client samples/contosoflowers/services/products.js @@ -0,0 +1,52 @@ +var _ = require('lodash'); +var Promise = require('bluebird'); + +const allCategories = _.times(5) + .map((i) => ({ + name: 'Flower ' + (i + 1), + imageUrl: 'https://placeholdit.imgix.net/~text?txtsize=48&txt=Flower%20' + (i + 1) + '&w=640&h=330' + })); + +const allProducts = _.times(17) + .map((i) => ({ + name: 'Bouquet ' + (i + 1) + '\u2122', + imageUrl: 'https://placeholdit.imgix.net/~text?txtsize=48&txt=Bouquet%20' + (i + 1) + '&w=640&h=330', + price: (Math.floor(Math.random() * 100) + 10) + .99 + })); + +const productsService = { + // Categories + getCategories: function (pageNumber, pageSize) { + return pageItems(pageNumber, pageSize, allCategories); + }, + + // Get Single Category + getCategory: function (categoryName) { + var category = _.find(allCategories, ['name', categoryName]); + return Promise.resolve(category); + }, + + // Products + getProducts: function (categoryName, pageNumber, pageSize) { + return pageItems(pageNumber, pageSize, allProducts); + }, + + // Get Single Product + getProduct: function (productName) { + var product = _.find(allProducts, ['name', productName]); + return Promise.resolve(product); + } +} + +// helpers +function pageItems(pageNumber, pageSize, items) { + var pageItems = _.take(_.drop(items, pageSize * (pageNumber - 1)), pageSize); + var totalCount = items.length; + return Promise.resolve({ + items: pageItems, + totalCount: totalCount + }); +} + +// export +module.exports = productsService; \ No newline at end of file diff --git a/client samples/contosoflowers/views/checkout/completed.pug b/client samples/contosoflowers/views/checkout/completed.pug new file mode 100644 index 00000000..4b43310d --- /dev/null +++ b/client samples/contosoflowers/views/checkout/completed.pug @@ -0,0 +1,12 @@ +extends ../layout + +block content + p + strong + | Your order #{order.id} has been processed! + p + | The #{order.selection.name} will be sent to #{order.details.recipient.firstName} #{order.details.recipient.lastName} with the following note: + blockquote + | #{order.details.note} + p + | Thanks you for using Contoso Flowers \ No newline at end of file diff --git a/client samples/contosoflowers/views/checkout/index.pug b/client samples/contosoflowers/views/checkout/index.pug new file mode 100644 index 00000000..052f0f18 --- /dev/null +++ b/client samples/contosoflowers/views/checkout/index.pug @@ -0,0 +1,24 @@ +extends ../layout + +block content + form(method="post") + fieldset + legend Checkout & Payment + + p + strong + | Total Price: $ #{order.selection.price} + + p + label(for="creditcard") Credit card number: + input(type="text", name="creditcard", placeholder="Any number will do") + p + label(for="fullname") Full name: + input(type="text", name="fullname", placeholder="Your full name") + + div + input(type="hidden", name="orderId", value=order.id) + input(type="hidden", name="address", value=address) + + p.buttons + input(type="submit", value="Checkout!") \ No newline at end of file diff --git a/client samples/contosoflowers/views/error.pug b/client samples/contosoflowers/views/error.pug new file mode 100644 index 00000000..51ec12c6 --- /dev/null +++ b/client samples/contosoflowers/views/error.pug @@ -0,0 +1,6 @@ +extends layout + +block content + h1= message + h2= error.status + pre #{error.stack} diff --git a/client samples/contosoflowers/views/index.pug b/client samples/contosoflowers/views/index.pug new file mode 100644 index 00000000..3d63b9a0 --- /dev/null +++ b/client samples/contosoflowers/views/index.pug @@ -0,0 +1,5 @@ +extends layout + +block content + h1= title + p Welcome to #{title} diff --git a/client samples/contosoflowers/views/layout.pug b/client samples/contosoflowers/views/layout.pug new file mode 100644 index 00000000..15af079b --- /dev/null +++ b/client samples/contosoflowers/views/layout.pug @@ -0,0 +1,7 @@ +doctype html +html + head + title= title + link(rel='stylesheet', href='/stylesheets/style.css') + body + block content diff --git a/client samples/nodejs/package.json b/client samples/nodejs/package.json index aca25469..d38cfeab 100644 --- a/client samples/nodejs/package.json +++ b/client samples/nodejs/package.json @@ -15,6 +15,7 @@ "openurl": "^1.1.1", "socket.io": "^1.3.7", "socket.io-client": "^1.3.7", + "url-join": "^1.1.0", "vorlon-node-wrapper": "0.0.2", "xhr2": "^0.1.3", "xmlhttprequest": "^1.8.0" diff --git a/gulpfile.js b/gulpfile.js index 3ad2adda..fc3b4f46 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -137,6 +137,35 @@ gulp.task('scripts-specific-plugins-plugins', ['scripts-plugins'], function() { ]) .pipe(concat('vorlon.babylonInspector.dashboard.min.js')) .pipe(gulp.dest('Plugins/release/plugins/babylonInspector/')); + + // Bot framework inspector + gulp.src([ + 'Plugins/release/plugins/botFrameworkInspector/vorlon.botFrameworkInspector.interfaces.js', + 'Plugins/release/plugins/botFrameworkInspector/vorlon.botFrameworkInspector.client.js' + ]) + .pipe(concat('vorlon.botFrameworkInspector.client.js')) + .pipe(gulp.dest('Plugins/release/plugins/botFrameworkInspector/')); + + gulp.src([ + 'Plugins/release/plugins/botFrameworkInspector/vorlon.botFrameworkInspector.interfaces.js', + 'Plugins/release/plugins/botFrameworkInspector/vorlon.botFrameworkInspector.dashboard.js' + ]) + .pipe(concat('vorlon.botFrameworkInspector.dashboard.js')) + .pipe(gulp.dest('Plugins/release/plugins/botFrameworkInspector/')); + + gulp.src([ + 'Plugins/release/plugins/botFrameworkInspector/vorlon.botFrameworkInspector.interfaces.min.js', + 'Plugins/release/plugins/botFrameworkInspector/vorlon.botFrameworkInspector.client.min.js' + ]) + .pipe(concat('vorlon.botFrameworkInspector.client.min.js')) + .pipe(gulp.dest('Plugins/release/plugins/botFrameworkInspector/')); + + gulp.src([ + 'Plugins/release/plugins/botFrameworkInspector/vorlon.botFrameworkInspector.interfaces.min.js', + 'Plugins/release/plugins/botFrameworkInspector/vorlon.botFrameworkInspector.dashboard.min.js' + ]) + .pipe(concat('vorlon.botFrameworkInspector.dashboard.min.js')) + .pipe(gulp.dest('Plugins/release/plugins/botFrameworkInspector/')); // Office gulp.src([ diff --git a/package.json b/package.json index ea896351..c3714aa0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vorlon", - "version": "0.4.0", + "version": "0.5.0", "description": "vorlon", "main": "Server/server.js", "dependencies": { diff --git a/whatsnew.md b/whatsnew.md index 21312839..50d6bca6 100644 --- a/whatsnew.md +++ b/whatsnew.md @@ -1,3 +1,14 @@ +## 0.5.0 +- Plugins + - Bot Framework Inspector + - Plugin creation + - Helps you inspect and debug bot created in Node.js using Bot Builder (https://github.com/Microsoft/BotBuilder) + - Features: Get dialogs list, See live dialog stacks and events, live graph showing path in the bot. +- Various fixes and improvements + - Add support for listening with host 0.0.0.0 (https://github.com/kkeybbs) + - Dashboard working correctly if baseUrl is set in config file (https://github.com/mboelter) + - Add missing baseURL inside templates and dashboard config (https://github.com/xiaodoudou) + ## 0.4.0 - Plugins - Dom Timeline