Виртуальный помощник, созданный с помощью Typscript, запущенный в эмуляторе Bot Framework, не отвечает - PullRequest
0 голосов
/ 10 октября 2019

Я пытаюсь разработать виртуальный помощник, используя машинопись. я следовал этому документу ниже https://microsoft.github.io/botframework-solutions/tutorials/typescript/create-assistant/1_intro/

Когда я запускаю npm start и тестирую его в эмуляторе Botframework, бот не отвечает ни на одно сообщение. Но бот открывается новым сообщением адаптивного приветствия пользователя

Я попытался отредактировать адаптивную открытку после этой страницы документа https://microsoft.github.io/botframework-solutions/tutorials/typescript/customize-assistant/2_edit_your_greeting/

, но хотя бот не отвечает ни на одно сообщение

`[11:53:22]Emulator listening on http://localhost:49963`

[11:53:22]ngrok listening on https://b2915c2d.ngrok.io [11:53:22]ngrok traffic inspector:http://127.0.0.1:4040 [11:53:22]Will bypass ngrok for local addresses [11:53:23]<- messageapplication/vnd.microsoft.card.adaptive [11:53:23]POST200conversations.replyToActivity [11:53:23]POST200directline.conversationUpdate [11:53:23]POST200directline.conversationUpdate

ожидаемые и фактические результаты: следует спросить, как вас зовут после подключения иначать разговор ===================================================================================== mainDialog.ts

import {
   BotFrameworkAdapter,
   BotTelemetryClient,
   RecognizerResult,
   StatePropertyAccessor } from 'botbuilder';
import { LuisRecognizer, LuisRecognizerTelemetryClient, QnAMakerResult,     QnAMakerTelemetryClient } from 'botbuilder-ai';
import {
DialogContext,
DialogTurnResult,
DialogTurnStatus } from 'botbuilder-dialogs';
import {
ISkillManifest,
SkillContext,
SkillDialog,
SkillRouter } from 'botbuilder-skills';
import {
ICognitiveModelSet,
InterruptionAction,
RouterDialog,
TokenEvents } from 'botbuilder-solutions';
import { TokenStatus } from 'botframework-connector';
import {
Activity,
ActivityTypes } from 'botframework-schema';
import i18next from 'i18next';
import { IOnboardingState } from '../models/onboardingState';
import { CancelResponses } from '../responses/cancelResponses';
import { MainResponses } from '../responses/mainResponses';
import { BotServices } from '../services/botServices';
import { IBotSettings } from '../services/botSettings';
import { CancelDialog } from './cancelDialog';
import { EscalateDialog } from './escalateDialog';
import { OnboardingDialog } from './onboardingDialog';

enum Events {
timeZoneEvent = 'va.timeZone',
locationEvent = 'va.location'
}

export class MainDialog extends RouterDialog {
// Fields
private readonly luisServiceGeneral: string = 'general';
private readonly luisServiceFaq: string = 'faq';
private readonly luisServiceChitchat: string = 'chitchat';
private readonly settings: Partial<IBotSettings>;
private readonly services: BotServices;
private readonly skillContextAccessor:    StatePropertyAccessor<SkillContext>;  
private readonly onboardingAccessor: StatePropertyAccessor<IOnboardingState>;
private readonly responder: MainResponses = new MainResponses();

// Constructor
public constructor(
    settings: Partial<IBotSettings>,
    services: BotServices,
    onboardingDialog: OnboardingDialog,
    escalateDialog: EscalateDialog,
    cancelDialog: CancelDialog,
    skillDialogs: SkillDialog[],
    skillContextAccessor: StatePropertyAccessor<SkillContext>,
    onboardingAccessor: StatePropertyAccessor<IOnboardingState>,
    telemetryClient: BotTelemetryClient
) {
    super(MainDialog.name, telemetryClient);
    this.settings = settings;
    this.services = services;
    this.onboardingAccessor = onboardingAccessor;
    this.skillContextAccessor = skillContextAccessor;
    this.telemetryClient = telemetryClient;

    this.addDialog(onboardingDialog);
    this.addDialog(escalateDialog);
    this.addDialog(cancelDialog);

    skillDialogs.forEach((skillDialog: SkillDialog): void => {
        this.addDialog(skillDialog);
    });
}

protected async onStart(dc: DialogContext): Promise<void> {
    const view: MainResponses = new MainResponses();
    const onboardingState: IOnboardingState|undefined = await this.onboardingAccessor.get(dc.context);
    if (onboardingState === undefined || onboardingState.name === undefined || onboardingState.name === '') {
        await view.replyWith(dc.context, MainResponses.responseIds.newUserGreeting);
    } else {
        await view.replyWith(dc.context, MainResponses.responseIds.returningUserGreeting);
    }
}

protected async route(dc: DialogContext): Promise<void> {
    // Get cognitive models for locale
    const locale: string = i18next.language.substring(0, 2);
    const cognitiveModels: ICognitiveModelSet | undefined = this.services.cognitiveModelSets.get(locale);

    if (cognitiveModels === undefined) {
        throw new Error('There is no value in cognitiveModels');
    }
    // Check dispatch result
    const dispatchResult: RecognizerResult = await cognitiveModels.dispatchService.recognize(dc.context);
    const intent: string = LuisRecognizer.topIntent(dispatchResult);

    if (this.settings.skills === undefined) {
        throw new Error('There is no skills in settings value');
    }
    // Identify if the dispatch intent matches any Action within a Skill if so, we pass to the appropriate SkillDialog to hand-off
    const identifiedSkill: ISkillManifest | undefined = SkillRouter.isSkill(this.settings.skills, intent);
    if (identifiedSkill !== undefined) {
        // We have identified a skill so initialize the skill connection with the target skill
        const result: DialogTurnResult = await dc.beginDialog(identifiedSkill.id);

        if (result.status === DialogTurnStatus.complete) {
            await this.complete(dc);
        }
    } else if (intent === 'l_general') {
        // If dispatch result is general luis model
        const luisService: LuisRecognizerTelemetryClient | undefined = cognitiveModels.luisServices.get(this.luisServiceGeneral);
        if (luisService === undefined) {
            throw new Error('The specified LUIS Model could not be found in your Bot Services configuration.');
        } else {
            const result: RecognizerResult = await luisService.recognize(dc.context);
            if (result !== undefined) {
                const generalIntent: string = LuisRecognizer.topIntent(result);

                // switch on general intents
                switch (generalIntent) {
                    case 'Escalate': {
                        // start escalate dialog
                        await dc.beginDialog(EscalateDialog.name);
                        break;
                    }
                    case 'None':
                    default: {
                        // No intent was identified, send confused message
                        await this.responder.replyWith(dc.context, MainResponses.responseIds.confused);
                    }
                }
            }
        }
    } else if (intent === 'q_faq') {
        const qnaService: QnAMakerTelemetryClient | undefined = cognitiveModels.qnaServices.get(this.luisServiceFaq);

        if (qnaService === undefined) {
            throw new Error('The specified QnA Maker Service could not be found in your Bot Services configuration.');
        } else {
            const answers: QnAMakerResult[] = await qnaService.getAnswers(dc.context);
            if (answers !== undefined && answers.length > 0) {
                await dc.context.sendActivity(answers[0].answer, answers[0].answer);
            } else {
                await this.responder.replyWith(dc.context, MainResponses.responseIds.confused);
            }
        }
    } else if (intent === 'q_chitchat') {
        const qnaService: QnAMakerTelemetryClient | undefined = cognitiveModels.qnaServices.get(this.luisServiceChitchat);

        if (qnaService === undefined) {
            throw new Error('The specified QnA Maker Service could not be found in your Bot Services configuration.');
        } else {
            const answers: QnAMakerResult[] = await qnaService.getAnswers(dc.context);
            if (answers !== undefined && answers.length > 0) {
                await dc.context.sendActivity(answers[0].answer, answers[0].answer);
            } else {
                await this.responder.replyWith(dc.context, MainResponses.responseIds.confused);
            }
        }
    } else {
        // If dispatch intent does not map to configured models, send 'confused' response.
        await this.responder.replyWith(dc.context, MainResponses.responseIds.confused);
    }
}

protected async onEvent(dc: DialogContext): Promise<void> {
    // Check if there was an action submitted from intro card
    if (dc.context.activity.value) {
        // tslint:disable-next-line: no-unsafe-any
        if (dc.context.activity.value.action === 'startOnboarding') {
            await dc.beginDialog(OnboardingDialog.name);

            return;
        }
    }

    let forward: boolean = true;
    const ev: Activity = dc.context.activity;
    if (ev.name !== undefined && ev.name.trim().length > 0) {
        switch (ev.name) {
            case Events.timeZoneEvent: {
                try {
                    const timezone: string = <string> ev.value;
                    const tz: string = new Date().toLocaleString(timezone);
                    const timeZoneObj: {
                        timezone: string;
                    } = {
                        timezone: tz
                    };

                    const skillContext: SkillContext = await this.skillContextAccessor.get(dc.context, new SkillContext());
                    skillContext.setObj(timezone, timeZoneObj);

                    await this.skillContextAccessor.set(dc.context, skillContext);
                } catch {
                    await dc.context.sendActivity(
                        {
                            type: ActivityTypes.Trace,
                            text: `"Timezone passed could not be mapped to a valid Timezone. Property not set."`
                        }
                    );
                }
                forward = false;
                break;
            }
            case Events.locationEvent: {
                const location: string = <string> ev.value;
                const locationObj: {
                    location: string;
                } = {
                    location: location
                };

                const skillContext: SkillContext = await this.skillContextAccessor.get(dc.context, new SkillContext());
                skillContext.setObj(location, locationObj);

                await this.skillContextAccessor.set(dc.context, skillContext);

                forward = true;
                break;
            }
            case TokenEvents.tokenResponseEventName: {
                forward = true;
                break;
            }
            default: {
                await dc.context.sendActivity(
                    {
                        type: ActivityTypes.Trace,
                        text: `"Unknown Event ${ ev.name } was received but not processed."`
                    }
                );
                forward = false;
            }
        }
    }

    if (forward) {
        const result: DialogTurnResult = await dc.continueDialog();

        if (result.status === DialogTurnStatus.complete) {
            await this.complete(dc);
        }
    }
}

protected async complete(dc: DialogContext, result?: DialogTurnResult): Promise<void> {
    // The active dialog's stack ended with a complete status
    await this.responder.replyWith(dc.context, MainResponses.responseIds.completed);
}

protected async onInterruptDialog(dc: DialogContext): Promise<InterruptionAction> {
    if (dc.context.activity.type === ActivityTypes.Message) {
        const locale: string = i18next.language.substring(0, 2);
        const cognitiveModels: ICognitiveModelSet | undefined = this.services.cognitiveModelSets.get(locale);

        if (cognitiveModels === undefined) {
            throw new Error('There is no cognitiveModels value');
        }
        // check luis intent
        const luisService: LuisRecognizerTelemetryClient | undefined = cognitiveModels.luisServices.get(this.luisServiceGeneral);

        if (luisService === undefined) {
            throw new Error('The general LUIS Model could not be found in your Bot Services configuration.');
        } else {
            const luisResult: RecognizerResult = await luisService.recognize(dc.context);
            const intent: string = LuisRecognizer.topIntent(luisResult);

            // Only triggers interruption if confidence level is high
            if (luisResult.intents[intent] !== undefined && luisResult.intents[intent].score > 0.5) {
                switch (intent) {
                    case 'Cancel': {
                        return this.onCancel(dc);
                    }
                    case 'Help': {
                        return this.onHelp(dc);
                    }
                    case 'Logout': {
                        return this.onLogout(dc);
                    }
                    default:
                }
            }
        }
    }

    return InterruptionAction.NoAction;
}

private async onCancel(dc: DialogContext): Promise<InterruptionAction> {
    if (dc.activeDialog !== undefined && dc.activeDialog.id !== CancelDialog.name) {
        // Don't start restart cancel dialog
        await dc.beginDialog(CancelDialog.name);

        // Signal that the dialog is waiting on user response
        return InterruptionAction.StartedDialog;
    }

    const view: CancelResponses = new CancelResponses();
    await view.replyWith(dc.context, CancelResponses.responseIds.nothingToCancelMessage);

    return InterruptionAction.StartedDialog;
}

private async onHelp(dc: DialogContext): Promise<InterruptionAction> {
    await this.responder.replyWith(dc.context, MainResponses.responseIds.help);

    // Signal the conversation was interrupted and should immediately continue
    return InterruptionAction.MessageSentToUser;
}

private async onLogout(dc: DialogContext): Promise<InterruptionAction> {
    let adapter: BotFrameworkAdapter;
    const supported: boolean = dc.context.adapter instanceof BotFrameworkAdapter;
    if (!supported) {
        throw new Error('OAuthPrompt.SignOutUser(): not supported by the current adapter');
    } else {
        adapter = <BotFrameworkAdapter> dc.context.adapter;
    }

    await dc.cancelAllDialogs();

    // Sign out user
    // PENDING check adapter.getTokenStatusAsync
    const tokens: TokenStatus[] = [];
    tokens.forEach(async (token: TokenStatus): Promise<void> => {
        if (token.connectionName !== undefined) {
            await adapter.signOutUser(dc.context, token.connectionName);
        }
    });
    await dc.context.sendActivity(i18next.t('main.logOut'));

    return InterruptionAction.StartedDialog;
}

}

================================================================================= onboardingDialog.ts

import {
BotTelemetryClient,
StatePropertyAccessor,
TurnContext } from 'botbuilder';
import {
ComponentDialog,
DialogTurnResult,
TextPrompt,
WaterfallDialog,
WaterfallStepContext } from 'botbuilder-dialogs';
import { IOnboardingState } from '../models/onboardingState';
import { OnboardingResponses } from '../responses/onboardingResponses';
import { BotServices } from '../services/botServices';

enum DialogIds {
namePrompt = 'namePrompt',
emailPrompt = 'emailPrompt',
locationPrompt =  'locationPrompt'
}

export class OnboardingDialog extends ComponentDialog {

// Fields
private static readonly responder: OnboardingResponses = new    OnboardingResponses();
private readonly accessor: StatePropertyAccessor<IOnboardingState>;
private state!: IOnboardingState;

// Constructor
public constructor(botServices: BotServices, accessor:  StatePropertyAccessor<IOnboardingState>, telemetryClient: BotTelemetryClient) {
    super(OnboardingDialog.name);
    this.accessor = accessor;
    this.initialDialogId = OnboardingDialog.name;
    const onboarding: ((sc: WaterfallStepContext<IOnboardingState>) =>  Promise<DialogTurnResult>)[] = [
        this.askForName.bind(this),
        this.finishOnboardingDialog.bind(this)
    ];

    // To capture built-in waterfall dialog telemetry, set the telemetry  client
    // to the new waterfall dialog and add it to the component dialog
    this.telemetryClient = telemetryClient;
    this.addDialog(new WaterfallDialog(this.initialDialogId, onboarding));
    this.addDialog(new TextPrompt(DialogIds.namePrompt));
   }

public async askForName(sc: WaterfallStepContext<IOnboardingState>): Promise<DialogTurnResult> {
    this.state = await this.getStateFromAccessor(sc.context);

    if (this.state.name !== undefined && this.state.name.trim().length > 0)     {
        return sc.next(this.state.name);
    }

    return sc.prompt(DialogIds.namePrompt, {
        prompt: await OnboardingDialog.responder.renderTemplate(
            sc.context,
            OnboardingResponses.responseIds.namePrompt,
            <string> sc.context.activity.locale)
    });
  }

public async finishOnboardingDialog(sc: WaterfallStepContext<IOnboardingState>): Promise<DialogTurnResult> {
    this.state = await this.getStateFromAccessor(sc.context);
    this.state.name = <string> sc.result;
    await this.accessor.set(sc.context, this.state);

    await OnboardingDialog.responder.replyWith(
        sc.context,
        OnboardingResponses.responseIds.haveNameMessage,
        {
            name: this.state.name
        });

    return sc.endDialog();
  }

 private async getStateFromAccessor(context: TurnContext):  Promise<IOnboardingState>  {
    const state: IOnboardingState | undefined = await this.accessor.get(context);
    if (state === undefined) {
        const newState: IOnboardingState = {
            email: '',
            location: '',
            name: ''
        };
        await this.accessor.set(context, newState);

        return newState;
    }

    return state;
}

} ================================================================================= dialogBot.ts

import {
ActivityHandler,
BotTelemetryClient,
ConversationState,
EndOfConversationCodes,
Severity,
TurnContext } from 'botbuilder';
import {
Dialog,
DialogContext,
DialogSet,
DialogState } from 'botbuilder-dialogs';

export class DialogBot<T extends Dialog> extends ActivityHandler {
private readonly telemetryClient: BotTelemetryClient;
private readonly solutionName: string = 'samplevirtualassistant';
private readonly rootDialogId: string;
private readonly dialogs: DialogSet;

public constructor(
    conversationState: ConversationState,
    telemetryClient: BotTelemetryClient,
    dialog: T) {
    super();

    this.rootDialogId = dialog.id;
    this.telemetryClient = telemetryClient;
    this.dialogs = new DialogSet(conversationState.createProperty<DialogState>(this.solutionName));
    this.dialogs.add(dialog);
    this.onTurn(this.turn.bind(this));
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/tslint/config
public async turn(turnContext: TurnContext, next: () => Promise<void>): Promise<any> {
    // Client notifying this bot took to long to respond (timed out)
    if (turnContext.activity.code === EndOfConversationCodes.BotTimedOut) {
        this.telemetryClient.trackTrace({
            message: `Timeout in ${ turnContext.activity.channelId } channel: Bot took too long to respond`,
            severityLevel: Severity.Information
        });

        return;
    }

    const dc: DialogContext = await this.dialogs.createContext(turnContext);

    if (dc.activeDialog !== undefined) {
        await dc.continueDialog();
    } else {
        await dc.beginDialog(this.rootDialogId);
    }

    await next();
}

}

...