import { Activity , ActivityTypes , CardAction , ConversationState , MessageFactory , Middleware , StoreItem , TurnContext } from 'botbuilder-core' ; const DEFAULT_FEEDBACK_ACTIONS = [ '👍 good answer' , '👎 bad answer' ] ; const DEFAULT_FEEDBACK_RESPONSE = 'Thanks for your feedback!' ; const DEFAULT_DISMISS_ACTION = 'dismiss' ; const DEFAULT_FREE_FORM_PROMPT = 'Please add any additional comments in the chat' ; const DEFAULT_PROMPT_FREE_FORM = false ; const TRACE_TYPE = 'https://www.example.org/schemas/feedback/trace' ; const TRACE_NAME = 'Feedback' ; const TRACE_LABEL = 'User Feedback' ; export type Message = string | { text : string ; speak ? : string ; } ; const feedbackAction = ( action ) => typeof action === 'string' ? action : feedbackValue ( action ) ; const feedbackValue = ( activity ) => activity . value || activity . text ; /** StoreItem to track feedback state */ export interface FeedbackState extends StoreItem { feedback : FeedbackRecord ; } export interface FeedbackContext { feedback : FeedbackOptions & { conversationState : ConversationState ; } ; } /** Options for Feedback Middleware */ export interface FeedbackOptions { /** Custom feedback choices for the user. Default values are: `['👍 good answer', '👎 bad answer']` */ feedbackActions ? : FeedbackAction [ ] ; /** Message to show when a user provides some feedback. Default value is `'Thanks for your feedback!'` */ feedbackResponse ? : Message ; /** Text to show on button that allows user to hide/ignore the feedback request. Default value is `'dismiss'` */ dismissAction ? : FeedbackAction ; /** Optionally enable prompting for free-form comments for all or select feedback choices (free-form prompt is shown after user selects a preset choice) */ promptFreeForm ? : boolean | string [ ] ; /** Message to show when `promptFreeForm` is enabled. Default value is `'Please add any additional comments in the chat'` */ freeFormPrompt ? : Message ; } /** Record of feedback received */ export interface FeedbackRecord { /** arbitrary feedback tagging, for analytics purposes */ tag : string ; /** activity sent by the user that triggered the feedback request */ request : Partial < Activity > ; /** bot text or value for which feedback is being requested */ response : string | any ; /** user's feedback selection */ feedback : string | any ; /** user's free-form comments, if enabled */ comments : string ; } export type FeedbackAction = string | CardAction ; export type ContextWithFeedback = TurnContext & FeedbackContext ; /** Middleware that managegs user feedback prompts, and stores responses in the transcript log */ export class Feedback implements Middleware { param context param textOrActivity param tag static createFeedbackMessage ( context , textOrActivity , tag ? ) { const feedbackContext = context as ContextWithFeedback ; const state = feedbackContext . feedback . conversationState . get ( context ) as StoreItem & FeedbackState ; const actions = feedbackContext . feedback . feedbackActions . concat ( feedbackContext . feedback . dismissAction ) . filter ( ( x ) => ! ! x ) ; state . feedback = { tag , request : context . activity , response : typeof textOrActivity === 'string' ? textOrActivity : feedbackValue ( textOrActivity ) , feedback : null , comments : null , } ; if ( typeof textOrActivity === 'string' ) { const text = textOrActivity ; return MessageFactory . suggestedActions ( actions , text ) ; } else { const activity = textOrActivity ; const suggestedActions = MessageFactory . suggestedActions ( actions ) . suggestedActions ; if ( activity . suggestedActions ) { activity . suggestedActions . actions = activity . suggestedActions . actions . concat ( suggestedActions . actions ) ; } else { activity . suggestedActions = suggestedActions ; } return activity ; } } param context param textOrActivity param tag static sendFeedbackActivity ( context , textOrActivity , tag ? ) { const message = Feedback . createFeedbackMessage ( context , textOrActivity , tag ) ; return context . sendActivity ( message ) ; } param conversationState param options constructor ( private conversationState , private options ? ) { this . options = options || { } ; if ( ! this . options . feedbackActions ) { this . options . feedbackActions = DEFAULT_FEEDBACK_ACTIONS ; } if ( ! this . options . feedbackResponse ) { this . options . feedbackResponse = DEFAULT_FEEDBACK_RESPONSE ; } if ( ! this . options . dismissAction ) { this . options . dismissAction = DEFAULT_DISMISS_ACTION ; } if ( ! this . options . freeFormPrompt ) { this . options . freeFormPrompt = DEFAULT_FREE_FORM_PROMPT ; } if ( ! this . options . promptFreeForm ) { this . options . promptFreeForm = DEFAULT_PROMPT_FREE_FORM ; } } async onTurn ( context , next ) { context . feedback = Object . assign ( { } , this . options , { conversationState : this . conversationState } ) ; const record = await this . getFeedbackState ( context ) ; if ( record ) { const canGiveComments = this . userCanGiveComments ( context ) ; if ( record . feedback && this . options . promptFreeForm ) { record . comments = context . activity . text ; await this . storeFeedback ( context ) ; return ; } else if ( this . userGaveFeedback ( context ) ) { record . feedback = feedbackValue ( context . activity ) ; if ( canGiveComments ) { await this . sendMessage ( context , this . options . freeFormPrompt ) ; } else { await this . storeFeedback ( context ) ; } return ; } else { this . clearFeedbackState ( context ) ; if ( ! this . userDismissed ( context ) ) { return next ( ) ; } } } else { return next ( ) ; } } private async getFeedbackState ( context ) { const state = await this . conversationState . load ( context ) ; if ( ! state ) { throw new Error ( 'Feedback middleware cannot find a BotState instance. Make sure that your ConversationState middleware is added to your bot before Feedback' ) ; } return state . feedback ; } private clearFeedbackState ( context ) { const state = this . conversationState . get ( context ) ; delete state . feedback ; } private sendMessage ( context , message ) { if ( typeof message === 'string' ) { return context . sendActivity ( message ) ; } else { return context . sendActivity ( message . text , message . speak ) ; } } private async storeFeedback ( context ) { const record = await this . getFeedbackState ( context ) ; this . clearFeedbackState ( context ) ; await context . sendActivity ( { type : ActivityTypes . Trace , valueType : TRACE_TYPE , name : TRACE_NAME , label : TRACE_LABEL , value : record , } ) ; if ( this . options . feedbackResponse ) { await this . sendMessage ( context , this . options . feedbackResponse ) ; } } private userCanGiveComments ( context ) { return this . options . promptFreeForm === true || ( Array . isArray ( this . options . promptFreeForm ) && this . options . promptFreeForm . some ( ( x ) => x === feedbackValue ( context . activity ) ) ) ; } private userGaveFeedback ( context ) { return ! this . userDismissed ( context ) && this . options . feedbackActions . some ( ( x ) => feedbackAction ( x ) === feedbackValue ( context . activity ) ) ; } private userDismissed ( context ) { return feedbackValue ( context . activity ) === feedbackAction ( this . options . dismissAction ) ; } }