我正在尝试使用Typescript开发虚拟助手。我已经按照以下文件 https://microsoft.github.io/botframework-solutions/tutorials/typescript/create-assistant/1_intro/
当我运行npm start并在Botframework模拟器中对其进行测试时,该机器人没有响应任何消息。 但是Bot正在以新用户问候自适应卡片消息的形式开放
我已尝试在此文档页面后编辑自适应贺卡 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();
}
}