3

I'm trying to put a simple adaptive card in my chatbot that collects the user's name and email. I can't figure out how to actually get the input from the card.

In the waterfall step where I display the dialog. I can't figure out what property should have the JSON string returned from the Action.Submit button.

I've included the json dialog and my TypeScript files. My MainDialog starts ClientCheckDialog on line 146, ClientCheckDialog starts GetContactInfoDialog on line 86

This is the json file dialog:

{
  "$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
      }
    }
  ]
}

Bot File

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 file

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 });
    }
}

Thank you in advance

123me
  • 43
  • 1
  • 6

1 Answers1

2

UPDATE

Now that I've reviewed your code, I've found the answer. First, a few points:

  1. Virtual Assistant is still in Preview and the TypeScript version of it is a lower priority than C#. Expect bugs. Also, there's a better-than-zero chance it's going to change pretty drastically in the future. Personally, I'd use it for ideas, but develop by starting with something like CoreBot and then integrating in the stuff you actually need.
  2. I have no idea why adding this.onDialog(this.activityToText.bind(this)); results in a double-welcome message. I think it has to do with point 1.
  3. The reason this didn't work is my own fault. I included trim() because it mirrors the C# version of this answer, but it actually breaks it because it doesn't result in a falsy value. Sorry for the confusion and I'll edit my other answers.
  4. You don't need turnContext.activity.text = JSON.stringify(turnContext.activity.value); at the end.

More info about Point 4

I'm going to expand on this because it's an important aspect of Javascript to understand.

When you have:

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); is redundant because setting

const activity = turnContext.activity

is NOT saying:

const activity = copyOf(turnContext.activity)

Instead, it's saying:

const activity = memoryLocationOf(turnContext.activity)

So, when you call activity.text = JSON.stringify(activity.value);, it's changing the memory location of activity.text (which is turnContext.activity.text) to JSON.stringify(activity.value). So, you're actually changing both activity.text and turnContext.activity.text at the same time because they refer to the same location in memory.

I bring this up not because it's super super relevant, but because if you don't learn this now, you'll likely run into some real head-scratchers in the future.

The Answer

With all that being said:

  1. Delete this.onDialog(this.activityToText.bind(this));
  2. Delete activityToText()
  3. Make the ending of turn() look like this:
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();

and then you will get:

enter image description here

enter image description here


I'm only not going to flag this as a duplicate because it's in TypeScript and it's been awhile since I've answered this for TypeScript. That being said, I've answered this before and edited the original answer to keep it up-to-date (last link in this answer).

That being said, the principle is the same. See this answer.

I highly highly recommend reading the blog post linked at the bottom of that answer.

A slightly shorter version can be found here.


Specific to your code:

You can't simply use await stepContext.context.sendActivity(<card>) and then return await stepContext.next(undefined);. There's nothing telling the bot to wait for a reply. You either need to send a blank text prompt after the card (like in my example in the last link) or attach the card to a text prompt and send them both at the same time. Something like:

async displayCard(step) {
    const cardPrompt = MessageFactory.text('');
    cardPrompt.attachments = [yourAdaptiveCard];
    return await step.prompt('textPrompt', cardPrompt);
}
mdrichardson
  • 7,141
  • 1
  • 7
  • 21
  • Thank you for your help. I've looked at the JavaScript example and I haven't been able to get it to work in typescript. Because clicking the submit button isn't a text input it stays at the it doesn't go to the next step and if I use InputHints.IgnoringInput it moves to the next step before the user inputs anything into the AdaptiveCard so I can't get the results from the card – 123me Oct 25 '19 at 19:48
  • @123me Do you have the `activity.text = JSON.stringify(activity.value);` part in either `onTurn` or `onDialog`? – mdrichardson Oct 25 '19 at 20:02
  • @123me Also see the "Specific to your code" section I just added to my answer. That's likely your main issue. Please fully read the links I posted as they contain all the information you need. – mdrichardson Oct 25 '19 at 20:08
  • Thank you I didn't have the ```activity.text = JSON.stringify(activity.value);``` – 123me Oct 25 '19 at 20:30
  • np. I assume it's all working, then? If so, please "accept" and upvote it so others can quickly find the answer and I can clear this from my support tracker. If not, let me know how else I can help! – mdrichardson Oct 25 '19 at 22:15
  • I still can't get it to work. When the user enters into the adaptive card the bot just loads the cards again. I'm guessing that I must not be adding to the onDialog function correctly. – 123me Oct 28 '19 at 14:12
  • In the bot constructor I have ```this.onDialog(this.activityToText.bind(this))``` and the activityToText function is ```public async activityToText(turnContext: TurnContext, next: () => Promise): Promise { 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(); }``` – 123me Oct 28 '19 at 14:12
  • @123me Can you update your question with 1) your current whole dialog code, and 2) all of the code for the file(s) where the class extends ActivityHandler (in our samples, these are the ones under the `bot` directory). It's tough to read the code in the comment and I'd like to make sure I catch everything. If you can link me to your full code/repo, that works too. – mdrichardson Oct 28 '19 at 14:28
  • Hi @mdrichardson. I've made the changes to this post. Let me know if this is enough info – 123me Oct 29 '19 at 14:41
  • @123me I think your `onDialog` is fine. However, it doesn't look like you do anything with the card input in `getContactInfoDialog.secondStep`. You should process it with `stepContext.result` and do something with it. The dialog should then end with `return stepContext.endDialog()`. If you still need help, a link to your code would be useful (you can also email it to me). It's very difficult to debug complex dialog flows without being able to use a debugger. – mdrichardson Oct 29 '19 at 21:25
  • I tried to log ```stepContext.result``` but it never gets to the second step. Here is the link to my git hub repo https://github.com/jtc-bacic/DemoBot. When you start the bot a list of options comes up with a list of suggested commands. (adding the ```this.onDialog(this.activityToText.bind(this));``` to the bot file created an error where the list of options appears twice and you have to click the options a couple times to continue with the rest of the script (comment out line 33 of the dialogBot.ts file to see expected behavior). Thank you for your help. – 123me Oct 30 '19 at 20:02
  • @123me Got it. Might want to delete that repo since it contains your `appsettings.json`. – mdrichardson Oct 30 '19 at 20:39
  • @123me Updated with the answer – mdrichardson Oct 30 '19 at 21:42
  • Thank you so much! I really appreciate the effort you put into helping and the detail of your answer. – 123me Oct 31 '19 at 02:17
  • @123me No problem! I literally get paid for it. Sorry that my initial answer threw things off with `trim()` :(. And if you're *extra* appreciative, feel free to upvote the answer, too. We're a bit competitive around the office for SO rep ;) – mdrichardson Oct 31 '19 at 14:50
  • Haha! I did upvote it but I don't have enough of for stack overflow to show the upvote but if there is a place where I can write a review for you I would be happy to! – 123me Nov 01 '19 at 19:25
  • @123me Haha. Don't worry about it :) – mdrichardson Nov 01 '19 at 19:28