【问题标题】:Get User Input from Adaptive Card using Waterfall Dialog in Botframework v4使用 Botframework v4 中的瀑布对话框从自适应卡获取用户输入
【发布时间】:2020-02-21 21:40:49
【问题描述】:

我正在尝试在我的聊天机器人中放置一张简单的自适应卡片,用于收集用户的姓名和电子邮件。我不知道如何实际从卡中获取输入。

在我显示对话框的瀑布步骤中。我不知道从 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 });
    }
}

提前谢谢你

【问题讨论】:

    标签: typescript botframework adaptive-cards


    【解决方案1】:

    更新

    现在我查看了您的代码,找到了答案。先说几点:

    1. 虚拟助手仍处于预览阶段,其 TypeScript 版本的优先级低于 C#。期待错误。此外,它在未来发生巨大变化的可能性大于零。就我个人而言,我会将它用于创意,但要从 CoreBot 之类的东西开始开发,然后集成到您实际需要的东西中。
    2. 我不知道为什么添加this.onDialog(this.activityToText.bind(this)); 会导致双重欢迎消息。我认为这与第 1 点有关。
    3. 这不起作用的原因是我自己的错。我包括了trim(),因为它反映了这个答案的 C# 版本,但它实际上破坏了它,因为它不会产生虚假值。很抱歉造成混乱,我会编辑我的其他答案。
    4. 最后你不需要turnContext.activity.text = JSON.stringify(turnContext.activity.value);

    关于第 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.textturnContext.activity.text,因为它们引用了内存中的相同位置。

    我提出这个不是因为它超级超级相关,而是因为如果你现在不学习这个,你将来可能会遇到一些真正令人头疼的问题。

    答案

    说了这么多:

    1. 删除this.onDialog(this.activityToText.bind(this));
    2. 删除activityToText()
    3. 使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(&lt;card&gt;) 然后return await stepContext.next(undefined);。没有什么告诉机器人等待回复。您要么需要在卡片之后发送空白文本提示(如我在最后一个链接中的示例),要么将卡片附加到文本提示并同时发送它们。比如:

    async displayCard(step) {
        const cardPrompt = MessageFactory.text('');
        cardPrompt.attachments = [yourAdaptiveCard];
        return await step.prompt('textPrompt', cardPrompt);
    }
    

    【讨论】:

    • 感谢您的帮助。我查看了 JavaScript 示例,但无法让它在 typescript 中工作。因为单击提交按钮不是文本输入,所以它停留在它不会进入下一步,如果我使用 InputHints.IgnoringInput 它会在用户将任何内容输入到 AdaptiveCard 之前移动到下一步,所以我不能从卡片中获取结果
    • @123me 你在onTurnonDialog 中有activity.text = JSON.stringify(activity.value); 部分吗?
    • @123me 另请参阅我刚刚添加到答案中的“特定于您的代码”部分。这可能是你的主要问题。请完整阅读我发布的链接,因为它们包含您需要的所有信息。
    • 谢谢我没有activity.text = JSON.stringify(activity.value);
    • np。我想这一切都在工作,然后呢?如果是这样,请“接受”并投票,以便其他人可以快速找到答案,我可以从我的支持跟踪器中清除它。如果没有,请告诉我我还能提供哪些帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-07
    • 1970-01-01
    • 2019-09-24
    • 1970-01-01
    • 1970-01-01
    • 2019-11-22
    相关资源
    最近更新 更多