我正在尝试在聊天机器人中放入一个简单的自适应卡,以收集用户的姓名和电子邮件。我不知道如何实际从卡中获取输入。
在显示对话框的瀑布步骤中。我无法弄清楚哪个属性应该具有从Action.Submit按钮返回的JSON字符串。
我包含了json对话框和TypeScript文件。 我的MainDialog在第146行启动ClientCheckDialog,ClientCheckDialog在第86行启动GetContactInfoDialog
这是json文件对话框:
{
"$schema": "https://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"body": [
{
"type": "TextBlock",
"text": "Name",
"wrap": true
},
{
"type": "Input.Text",
"id": "id_name"
},
{
"type": "TextBlock",
"text": "Email Address",
"wrap": true
},
{
"type": "Input.Text",
"id": "id_email",
"style": "email",
"placeholder": "youremail@example.com"
}
],
"actions": [
{
"type": "Action.Submit",
"title": "Submit",
"data": {
"clickedSubmit" : true
}
}
]
}
启动文件
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 = 'tcsBot';
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));
this.onDialog(this.activityToText.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();
}
public async activityToText(turnContext: TurnContext, next: () => Promise<void>): Promise<any> {
const activity = turnContext.activity;
if (!activity.text.trim() && activity.value) {
activity.text = JSON.stringify(activity.value);
}
turnContext.activity.text = JSON.stringify(turnContext.activity.value);
await next();
}
}
index.ts文件
import {
BotFrameworkAdapterSettings,
BotTelemetryClient,
ConversationState,
NullTelemetryClient,
TurnContext,
UserState
} from 'botbuilder';
import { ApplicationInsightsTelemetryClient, ApplicationInsightsWebserverMiddleware } from 'botbuilder-applicationinsights';
import { LuisApplication } from 'botbuilder-ai';
import {
CosmosDbStorage,
CosmosDbStorageSettings
} from 'botbuilder-azure';
import { Dialog } from 'botbuilder-dialogs';
import {
ISkillManifest
} from 'botbuilder-skills';
import {
ICognitiveModelConfiguration,
Locales
} from 'botbuilder-solutions';;
import i18next from 'i18next';
import i18nextNodeFsBackend from 'i18next-node-fs-backend';
import * as path from 'path';
import * as restify from 'restify';
import { DefaultAdapter } from './adapters/defaultAdapter';
import * as appsettings from './appsettings.json';
import { DialogBot } from './bots/dialogBot';
import * as cognitiveModelsRaw from './cognitivemodels.json';
import { MainDialog } from './dialogs/mainDialog';
import { IBotSettings } from './services/botSettings';
import { skills as skillsRaw } from './skills.json';
import { WelcomeDialog } from './dialogs/welcomeDialog'
import { GetContactInfoDialog } from './dialogs/getContactInfoDialog'
import { ServicesDialog } from './dialogs/servicesDialog'
import { ClientCheckDialog } from './dialogs/clientCheckDialog'
// Configure internationalization and default locale
// tslint:disable-next-line: no-floating-promises
i18next.use(i18nextNodeFsBackend)
.init({
fallbackLng: 'en',
preload: ['en', 'fr'],
backend: {
loadPath: path.join(__dirname, 'locales', '{{lng}}.json')
}
})
.then(async (): Promise<void> => {
await Locales.addResourcesFromPath(i18next, 'common');
});
const skills: ISkillManifest[] = skillsRaw;
const cognitiveModels: Map<string, ICognitiveModelConfiguration> = new Map();
const cognitiveModelDictionary: { [key: string]: Object } = cognitiveModelsRaw.cognitiveModels;
const cognitiveModelMap: Map<string, Object> = new Map(Object.entries(cognitiveModelDictionary));
cognitiveModelMap.forEach((value: Object, key: string): void => {
cognitiveModels.set(key, <ICognitiveModelConfiguration>value);
});
const botSettings: Partial<IBotSettings> = {
appInsights: appsettings.appInsights,
blobStorage: appsettings.blobStorage,
cognitiveModels: cognitiveModels,
cosmosDb: appsettings.cosmosDb,
defaultLocale: cognitiveModelsRaw.defaultLocale,
microsoftAppId: appsettings.microsoftAppId,
microsoftAppPassword: appsettings.microsoftAppPassword,
skills: skills
};
function getTelemetryClient(settings: Partial<IBotSettings>): BotTelemetryClient {
if (settings !== undefined && settings.appInsights !== undefined && settings.appInsights.instrumentationKey !== undefined) {
const instrumentationKey: string = settings.appInsights.instrumentationKey;
return new ApplicationInsightsTelemetryClient(instrumentationKey);
}
return new NullTelemetryClient();
}
const telemetryClient: BotTelemetryClient = getTelemetryClient(botSettings);
const adapterSettings: Partial<BotFrameworkAdapterSettings> = {
appId: botSettings.microsoftAppId,
appPassword: botSettings.microsoftAppPassword
};
let cosmosDbStorageSettings: CosmosDbStorageSettings;
if (botSettings.cosmosDb === undefined) {
throw new Error();
}
cosmosDbStorageSettings = {
authKey: botSettings.cosmosDb.authKey,
collectionId: botSettings.cosmosDb.collectionId,
databaseId: botSettings.cosmosDb.databaseId,
serviceEndpoint: botSettings.cosmosDb.cosmosDBEndpoint
};
const storage: CosmosDbStorage = new CosmosDbStorage(cosmosDbStorageSettings);
const userState: UserState = new UserState(storage);
const conversationState: ConversationState = new ConversationState(storage);
const adapter: DefaultAdapter = new DefaultAdapter(
botSettings,
adapterSettings,
telemetryClient,
userState,
conversationState
);
let bot: DialogBot<Dialog>;
try {
const luisConfig: LuisApplication = { applicationId: appsettings.luis.appId, endpointKey: appsettings.luis.key, endpoint: appsettings.luis.endpoint };
const welcomeDialog: WelcomeDialog = new WelcomeDialog();
const servicesDialog: ServicesDialog = new ServicesDialog();
const getContactInfoDialog: GetContactInfoDialog = new GetContactInfoDialog()
const clientCheckDialog: ClientCheckDialog = new ClientCheckDialog(getContactInfoDialog)
const mainDialog: MainDialog = new MainDialog(
luisConfig, welcomeDialog, servicesDialog, clientCheckDialog
);
bot = new DialogBot(conversationState, telemetryClient, mainDialog);
} catch (err) {
throw err;
}
// Create server
const server: restify.Server = restify.createServer();
// Enable the Application Insights middleware, which helps correlate all activity
// based on the incoming request.
server.use(restify.plugins.bodyParser());
// tslint:disable-next-line:no-unsafe-any
server.use(ApplicationInsightsWebserverMiddleware);
server.listen(process.env.port || process.env.PORT || '3979', (): void => {
// tslint:disable-next-line:no-console
console.log(`${server.name} listening to ${server.url}`);
// tslint:disable-next-line:no-console
console.log(`Get the Emulator: https://aka.ms/botframework-emulator`);
// tslint:disable-next-line:no-console
console.log(`To talk to your bot, open your '.bot' file in the Emulator`);
});
// Listen for incoming requests
server.post('/api/messages', async (req: restify.Request, res: restify.Response): Promise<void> => {
// Route received a request to adapter for processing
await adapter.processActivity(req, res, async (turnContext: TurnContext): Promise<void> => {
// route to bot activity handler.
await bot.run(turnContext);
});
});
mainDialog.ts
import { InputHints, MessageFactory, StatePropertyAccessor, TurnContext } from 'botbuilder';
import { LuisApplication, LuisRecognizer } from 'botbuilder-ai';
import {
ComponentDialog,
DialogSet,
DialogState,
DialogTurnResult,
DialogTurnStatus,
TextPrompt,
WaterfallDialog,
WaterfallStepContext,
ChoicePrompt,
ListStyle,
ConfirmPrompt
} from 'botbuilder-dialogs';
import { WelcomeDialog } from '../dialogs/welcomeDialog'
import { ClientCheckDialog } from '../dialogs/clientCheckDialog'
import { ServicesDialog } from '../dialogs/servicesDialog'
import { Conversation } from './conversation'
import msg from '../resources/enMsg.json';
import { ClientInfo } from './clientInfo';
const CHOICE_PROMPT = 'choicePrompt';
const MAIN_WATERFALL_DIALOG = 'mainWaterfallDialog';
const TEXT_PROMPT = 'textPrompt';
const CONFIRM_PROMPT = 'confirmPrompt';
export class MainDialog extends ComponentDialog {
private luisRecognizer: LuisRecognizer;
private conversation: Conversation;
private clientInfo: ClientInfo;
public constructor(config: LuisApplication, welcomeDialog: WelcomeDialog, servicesDialog: ServicesDialog, clientCheckDialog: ClientCheckDialog) {
super('MainDialog');
const luisIsConfigured = config && config.applicationId && config.endpoint && config.endpointKey;
if (luisIsConfigured) {
this.luisRecognizer = new LuisRecognizer(config, {}, true);
}
else {
throw new Error('[MainDialog]: Missing parameter \'luisRecognizer\' is required');
}
this.conversation = new Conversation()
this.clientInfo = new ClientInfo()
const choicePrompt = new ChoicePrompt(CHOICE_PROMPT);
choicePrompt.style = ListStyle.suggestedAction;
this.addDialog(new TextPrompt(TEXT_PROMPT))
.addDialog(new ConfirmPrompt(CONFIRM_PROMPT))
.addDialog(choicePrompt)
.addDialog(welcomeDialog)
.addDialog(servicesDialog)
.addDialog(clientCheckDialog)
.addDialog(new WaterfallDialog(MAIN_WATERFALL_DIALOG, [
this.introStep1.bind(this),
this.introStep2.bind(this),
this.getIntentStep.bind(this),
this.followUpStep.bind(this),
this.checkForContactInfo.bind(this),
this.checkIfHelpfulStep.bind(this),
this.finalStep.bind(this)
]));
this.initialDialogId = MAIN_WATERFALL_DIALOG;
}
public async run(context: TurnContext, accessor: StatePropertyAccessor<DialogState>) {
const dialogSet = new DialogSet(accessor);
dialogSet.add(this);
const dialogContext = await dialogSet.createContext(context);
const results = await dialogContext.continueDialog();
if (results.status === DialogTurnStatus.empty) {
await dialogContext.beginDialog(this.id);
}
}
private async introStep1(stepContext: WaterfallStepContext): Promise<DialogTurnResult> {
if (!this.luisRecognizer) {
const luisConfigMsg = 'NOTE: LUIS is not configured. To enable all capabilities, add `LuisAppId`, `LuisAPIKey` and `LuisAPIHostName` to the .env file.';
await stepContext.context.sendActivity(luisConfigMsg);
return await stepContext.next();
}
const messageText = (stepContext.options as any).restartMsg ? (stepContext.options as any).restartMsg : msg.welcome;
this.conversation.addSpeech(Conversation.Speaker.Bot, messageText)
return await stepContext.beginDialog('welcomeDialog', { messageText: messageText })
}
private async introStep2(stepContext: WaterfallStepContext): Promise<DialogTurnResult> {
var messageText = msg.clickOrType
const promptMessage = MessageFactory.text(messageText, messageText, InputHints.ExpectingInput);
return await stepContext.prompt(TEXT_PROMPT, { prompt: promptMessage });
}
private async getIntentStep(stepContext: WaterfallStepContext): Promise<DialogTurnResult> {
this.conversation.addSpeech(Conversation.Speaker.Client, stepContext.result)
this.clientInfo.question = stepContext.result
if (this.luisRecognizer) {
const luisResult = await this.luisRecognizer.recognize(stepContext.context);
switch (LuisRecognizer.topIntent(luisResult)) {
case 'Services':
this.clientInfo.intent = ClientInfo.Intent.Services
break
default:
this.clientInfo.intent = ClientInfo.Intent.Other
// Catch all for unhandled intents
return await stepContext.replaceDialog(this.initialDialogId, { restartMsg: msg.didNotUnderstandIntent });
}
if (this.clientInfo.intent === ClientInfo.Intent.Services) {
return await stepContext.beginDialog('servicesDialog', { clientInfo: this.clientInfo, repeat: false })
}
}
return await stepContext.next();
}
private async followUpStep(stepContext: WaterfallStepContext): Promise<DialogTurnResult> {
if (stepContext.result) {
var getIntentResult = stepContext.result as { clientInfo: ClientInfo | undefined; conversation: Conversation };
if (getIntentResult.clientInfo)
this.clientInfo = getIntentResult.clientInfo
this.conversation.addSubConversation(getIntentResult.conversation)
if (getIntentResult.clientInfo) {
if (getIntentResult.clientInfo.intent === ClientInfo.Intent.Services) {
return await stepContext.beginDialog('checkClientDialog', this.clientInfo)
}
}
}
return await stepContext.next();
}
private async checkForContactInfo(stepContext: WaterfallStepContext): Promise<DialogTurnResult> {
if (stepContext.result) {
var followUpResult = stepContext.result as { clientInfo: ClientInfo | undefined; conversation: Conversation };
this.conversation.addSubConversation(followUpResult.conversation)
}
return await stepContext.next();
}
//ask user if bot was able to help them
private async checkIfHelpfulStep(stepContext: WaterfallStepContext): Promise<DialogTurnResult> {
const messageText = msg.wasThisHelpful
const message = MessageFactory.text(messageText, messageText, InputHints.ExpectingInput);
this.conversation.addSpeech(Conversation.Speaker.Bot, messageText)
return await stepContext.prompt(CONFIRM_PROMPT, { prompt: message });
}
//restart
private async finalStep(stepContext: WaterfallStepContext): Promise<DialogTurnResult> {
this.clientInfo.wasHelpful = stepContext.result
// Restart the main dialog waterfall with a different message the second time around
return await stepContext.replaceDialog(this.initialDialogId, { restartMsg: msg.restartMain });
}
}
clientCheck.ts
import {
ComponentDialog,
DialogTurnResult,
WaterfallDialog,
WaterfallStepContext,
ChoiceFactory,
ConfirmPrompt
} from 'botbuilder-dialogs';
import { ClientInfo } from './clientInfo';
import { InputHints, MessageFactory } from 'botbuilder';
import { GetContactInfoDialog } from '../dialogs/getContactInfoDialog'
import { Conversation } from './conversation'
import msg from '../resources/enMsg.json';
const CONFIRM_PROMPT = 'confirmPrompt'
const WATERFALL_DIALOG = 'waterfallDialog';
export class ClientCheckDialog extends ComponentDialog {
private conversation: Conversation;
// Constructor
public constructor(getContactInfoDialog: GetContactInfoDialog) {
super('ClientCheckDialog');
this.conversation = new Conversation()
this.addDialog(new ConfirmPrompt(CONFIRM_PROMPT))
.addDialog(getContactInfoDialog)
.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.introStep.bind(this),
this.generalInfoStep.bind(this),
this.getContactInfoStep.bind(this),
this.finalStep.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
}
private async introStep(stepContext: WaterfallStepContext): Promise<DialogTurnResult> {
const messageText = msg.workWithUs
const message = MessageFactory.text(messageText, messageText, InputHints.ExpectingInput);
this.conversation.addSpeech(Conversation.Speaker.Bot, messageText)
return await stepContext.prompt(CONFIRM_PROMPT, { prompt: message });
}
private async generalInfoStep(stepContext: WaterfallStepContext): Promise<DialogTurnResult> {
const clientInfo = stepContext.options as ClientInfo;
this.conversation.addSpeech(Conversation.Speaker.Client, stepContext.result)
clientInfo.isQualified = stepContext.result
//start list of recources
var bulletPoints = [msg.benefit1, msg.benefit2, msg.benefit3]
//check for more cases to add info
const messageText1 = msg.general
const message = ChoiceFactory.list(bulletPoints, messageText1, InputHints.IgnoringInput);
//collecting bot output for conversation
var botOutput = messageText1
for (var point in bulletPoints) {
botOutput.concat(" -", point)
}
this.conversation.addSpeech(Conversation.Speaker.Bot, botOutput)
await stepContext.context.sendActivity(message);
if (clientInfo.isQualified) {
const messageText2 = msg.becomeAClient
const messageContact = MessageFactory.text(messageText2, messageText2, InputHints.ExpectingInput);
this.conversation.addSpeech(Conversation.Speaker.Bot, messageText2)
return await stepContext.prompt(CONFIRM_PROMPT, { prompt: messageContact });
}
else {
return await stepContext.endDialog({ clientInfo: clientInfo, conversation: this.conversation });
}
}
private async getContactInfoStep(stepContext: WaterfallStepContext): Promise<DialogTurnResult> {
this.conversation.addSpeech(Conversation.Speaker.Client, stepContext.result)
const clientInfo = stepContext.options as ClientInfo;
if (stepContext.result) {
return await stepContext.beginDialog("getContactInfoDialog")
}
return await stepContext.endDialog({ clientInfo: clientInfo, conversation: this.conversation });
}
private async finalStep(stepContext: WaterfallStepContext): Promise<DialogTurnResult> {
const clientInfo = stepContext.options as ClientInfo;
return await stepContext.endDialog({ clientInfo: clientInfo, conversation: this.conversation });
}
}
getContactInfoDialog.ts:
import {
ComponentDialog,
DialogTurnResult,
WaterfallDialog,
WaterfallStepContext,
TextPrompt
} from 'botbuilder-dialogs';
import { CardFactory, MessageFactory } from 'botbuilder';
const WATERFALL_DIALOG = 'waterfallDialog';
const TEXT_PROMPT = 'textPrompt';
import getContactInfoCard from '../cards/getContactInfoCard.json'
export class GetContactInfoDialog extends ComponentDialog {
public constructor() {
super('getContactInfoDialog')
this.addDialog(new TextPrompt(TEXT_PROMPT))
this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.firstStep.bind(this),
this.secondStep.bind(this)
]))
this.initialDialogId = WATERFALL_DIALOG;
}
public async firstStep(stepContext: WaterfallStepContext): Promise<DialogTurnResult> {
const cardPrompt = MessageFactory.text('');
cardPrompt.attachments = [CardFactory.adaptiveCard(getContactInfoCard)];
return await stepContext.prompt(TEXT_PROMPT, cardPrompt);
}
public async secondStep(stepContext: WaterfallStepContext): Promise<DialogTurnResult> {
//process adaptive card input here
const messageText = 'What else can I do for you?'
const messageContact = MessageFactory.text(messageText, messageText);
return await stepContext.prompt(TEXT_PROMPT, { prompt: messageContact });
}
}
提前谢谢
答案 0 :(得分:0)
现在,我已经审查了您的代码,找到了答案。首先,几点:
this.onDialog(this.activityToText.bind(this));
会产生双重欢迎消息。我认为这与第1点有关。trim()
是因为它反映了此答案的C#版本,但实际上由于它不会导致虚假的值而将其破坏。抱歉造成混乱,我将编辑其他答案。turnContext.activity.text = JSON.stringify(turnContext.activity.value);
。有关Point 4的更多信息
我将对此进行扩展,因为这是理解Javascript的重要方面。
如果有:
const activity = turnContext.activity;
if (!activity.text && activity.value) {
activity.text = JSON.stringify(activity.value);
}
turnContext.activity.text = JSON.stringify(turnContext.activity.value);
... turnContext.activity.text = JSON.stringify(turnContext.activity.value);
是多余的,因为设置
const activity = turnContext.activity
不是在说:
const activity = copyOf(turnContext.activity)
相反,它是在说:
const activity = memoryLocationOf(turnContext.activity)
因此,当您调用activity.text = JSON.stringify(activity.value);
时,它会将activity.text
(即turnContext.activity.text
)的内存位置更改为JSON.stringify(activity.value)
。因此,实际上您同时在更改activity.text
和turnContext.activity.text
,因为它们指向内存中的相同位置。
之所以提出这一点,并不是因为它具有超级相关性,而是因为如果您现在不学习这一点,将来您可能会遇到一些真正的难题。
话虽如此:
this.onDialog(this.activityToText.bind(this));
activityToText()
turn()
的结尾看起来像这样:if (dc.activeDialog !== undefined) {
const activity = turnContext.activity;
if (!activity.text && activity.value) {
activity.text = JSON.stringify(activity.value);
}
await dc.continueDialog();
} else {
await dc.beginDialog(this.rootDialogId);
}
await next();
然后您将得到:
我不会将其标记为重复项,因为它在TypeScript中,并且距离我为TypeScript回答了已经有一段时间了。话虽如此,我之前已经回答了这个问题,并编辑了原始答案以使其保持最新(此答案中的最后一个链接)。
话虽如此,原理是相同的。参见this answer。
我强烈建议您阅读答案底部链接的博客文章。
一个稍短的版本可以是found here。
特定于您的代码:
您不能简单地先使用await stepContext.context.sendActivity(<card>)
然后使用return await stepContext.next(undefined);
。没有任何内容告诉机器人等待回复。您要么需要在卡片后发送空白文本提示(如上一个链接中的示例中所示),要么将卡片附加到文本提示中,然后同时发送它们。像这样:
async displayCard(step) {
const cardPrompt = MessageFactory.text('');
cardPrompt.attachments = [yourAdaptiveCard];
return await step.prompt('textPrompt', cardPrompt);
}