diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/README.md b/samples/javascript_nodejs/49.qnamaker-all-features/README.md index 55d4f2ea36..03db12e056 100644 --- a/samples/javascript_nodejs/49.qnamaker-all-features/README.md +++ b/samples/javascript_nodejs/49.qnamaker-all-features/README.md @@ -1,6 +1,6 @@ # QnA Maker -Bot Framework v4 QnA Maker bot sample. This sample shows how to integrate Multiturn and Active learning in a QnA Maker bot with ASP.Net Core-2. Click [here][72] to know more about using follow-up prompts to create multiturn conversation. To know more about how to enable and use active learning, click [here][71]. +Bot Framework v4 QnA Maker bot sample. This sample shows how to integrate Multiturn and Active learning in a QnA Maker bot with Node.js. Click [here][72] to know more about using follow-up prompts to create multiturn conversation. To know more about how to enable and use active learning, click [here][71]. This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a bot that uses the [QnA Maker Cognitive AI](https://www.qnamaker.ai) service. @@ -117,13 +117,14 @@ This bot uses [QnA Maker Service](https://www.qnamaker.ai), an AI based cognitiv [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. -- Install the Bot Framework Emulator version 4.9.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +- Install the latest Bot Framework Emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) ### Connect to the bot using Bot Framework Emulator - Launch Bot Framework Emulator - File -> Open Bot -- Enter a Bot URL of `http://localhost:3999/api/messages` +- Enter a Bot URL of `http://localhost:3978/api/messages` # QnA Maker service QnA Maker enables you to power a question and answer service from your semi-structured content. diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/bots/QnABot.js b/samples/javascript_nodejs/49.qnamaker-all-features/bots/QnABot.js index 3becbd1e88..86955540ee 100644 --- a/samples/javascript_nodejs/49.qnamaker-all-features/bots/QnABot.js +++ b/samples/javascript_nodejs/49.qnamaker-all-features/bots/QnABot.js @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. const { ActivityHandler } = require('botbuilder'); diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/dialogs/qnamakerBaseDialog.js b/samples/javascript_nodejs/49.qnamaker-all-features/dialogs/qnamakerBaseDialog.js deleted file mode 100644 index 509443dd24..0000000000 --- a/samples/javascript_nodejs/49.qnamaker-all-features/dialogs/qnamakerBaseDialog.js +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -const { - QnAMakerDialog -} = require('botbuilder-ai'); - -const { - ActivityFactory, - MessageFactory -} = require('botbuilder'); - -// Default parameters -const DefaultThreshold = 0.3; -const DefaultTopN = 3; -const DefaultAnswer = 'No QnAMaker answers found.'; - -// Card parameters -const DefaultCardTitle = 'Did you mean:'; -const DefaultCardNoMatchText = 'None of the above.'; -const DefaultCardNoMatchResponse = 'Thanks for the feedback.'; - -/// QnA Maker dialog. -const QNAMAKER_BASE_DIALOG = 'qnamaker-base-dailog'; - -class QnAMakerBaseDialog extends QnAMakerDialog { - /** - * Core logic of QnA Maker dialog. - * @param {QnAMaker} qnaService A QnAMaker service object. - */ - constructor(knowledgebaseId, authkey, host, defaultAnswer) { - const defaultAnswerActivity = MessageFactory.text(!!defaultAnswer.trim() ? defaultAnswer : DefaultAnswer); - let filters = []; - super(knowledgebaseId, authkey, host, defaultAnswerActivity, DefaultThreshold, DefaultCardTitle, DefaultCardNoMatchText, - DefaultTopN, ActivityFactory.cardNoMatchResponse, filters, QNAMAKER_BASE_DIALOG); - this.id = QNAMAKER_BASE_DIALOG; - } -} - -module.exports.QnAMakerBaseDialog = QnAMakerBaseDialog; -module.exports.QNAMAKER_BASE_DIALOG = QNAMAKER_BASE_DIALOG; -module.exports.DefaultThreshold = DefaultThreshold; -module.exports.DefaultTopN = DefaultTopN; -module.exports.DefaultAnswer = DefaultAnswer; -module.exports.DefaultCardTitle = DefaultCardTitle; -module.exports.DefaultCardNoMatchText = DefaultCardNoMatchText; -module.exports.DefaultCardNoMatchResponse = DefaultCardNoMatchResponse; diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/dialogs/rootDialog.js b/samples/javascript_nodejs/49.qnamaker-all-features/dialogs/rootDialog.js index 859a6ddfaa..0326f68134 100644 --- a/samples/javascript_nodejs/49.qnamaker-all-features/dialogs/rootDialog.js +++ b/samples/javascript_nodejs/49.qnamaker-all-features/dialogs/rootDialog.js @@ -1,33 +1,50 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +const { QnAMakerDialog } = require('botbuilder-ai'); const { ComponentDialog, DialogSet, DialogTurnStatus, - WaterfallDialog + WaterfallDialog, } = require('botbuilder-dialogs'); - -const { - QnAMakerBaseDialog -} = require('./qnamakerBaseDialog'); +const { MessageFactory } = require('botbuilder'); const INITIAL_DIALOG = 'initial-dialog'; const ROOT_DIALOG = 'root-dialog'; -const QNAMAKER_BASE_DIALOG = 'qnamaker-base-dailog'; +const QNAMAKER_BASE_DIALOG = 'qnamaker-base-dialog'; + +/** + * Creates QnAMakerDialog instance with provided configuraton values. + */ +const createQnAMakerDialog = (knowledgeBaseId, endpointKey, endpointHostName, defaultAnswer) => { + let noAnswerActivity; + if (typeof defaultAnswer === 'string') { + noAnswerActivity = MessageFactory.text(defaultAnswer); + } + + const qnaMakerDialog = new QnAMakerDialog(knowledgeBaseId, endpointKey, endpointHostName, noAnswerActivity); + qnaMakerDialog.id = QNAMAKER_BASE_DIALOG; + + return qnaMakerDialog; +} class RootDialog extends ComponentDialog { /** - * Root dialog for this bot. - * @param {QnAMaker} qnaService A QnAMaker service object. + * Root dialog for this bot. Creates a QnAMakerDialog. + * @param {string} knowledgeBaseId Knowledge Base ID of the QnA Maker instance. + * @param {string} endpointKey Endpoint key needed to query QnA Maker. + * @param {string} endpointHostName Host name of the QnA Maker instance. + * @param {string} defaultAnswer (optional) Text used to create a fallback response when QnA Maker doesn't have an answer for a question. */ - constructor(knowledgebaseId, authkey, host, defaultAnswer) { + constructor(knowledgeBaseId, endpointKey, endpointHostName, defaultAnswer) { super(ROOT_DIALOG); // Initial waterfall dialog. this.addDialog(new WaterfallDialog(INITIAL_DIALOG, [ this.startInitialDialog.bind(this) ])); - this.addDialog(new QnAMakerBaseDialog(knowledgebaseId, authkey, host, defaultAnswer)); + + this.addDialog(createQnAMakerDialog(knowledgeBaseId, endpointKey, endpointHostName, defaultAnswer)); this.initialDialogId = INITIAL_DIALOG; } diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/index.js b/samples/javascript_nodejs/49.qnamaker-all-features/index.js index d0c1c553b5..e87309f2f5 100644 --- a/samples/javascript_nodejs/49.qnamaker-all-features/index.js +++ b/samples/javascript_nodejs/49.qnamaker-all-features/index.js @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // index.js is used to setup and configure your bot @@ -6,26 +6,19 @@ // Import required packages const path = require('path'); -// Note: Ensure you have a .env file and include QnAMakerKnowledgeBaseId, QnAMakerEndpointKey and QnAMakerHost. +// Note: Ensure you have a .env file and include QnAKnowledgebaseId, QnAEndpointKey and QnAEndpointHostName. const ENV_FILE = path.join(__dirname, '.env'); require('dotenv').config({ path: ENV_FILE }); const restify = require('restify'); -// Import required bot services. See https://aka.ms/bot-services to learn more about the different parts of a bot. +// Import required bot services. +// See https://aka.ms/bot-services to learn more about the different parts of a bot. const { BotFrameworkAdapter, ConversationState, MemoryStorage, UserState } = require('botbuilder'); const { QnABot } = require('./bots/QnABot'); const { RootDialog } = require('./dialogs/rootDialog'); -// Create HTTP server. -const server = restify.createServer(); -server.listen(process.env.port || process.env.PORT || 3978, function() { - console.log(`\n${ server.name } listening to ${ server.url }.`); - console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator'); - console.log('\nTo talk to your bot, open the emulator select "Open Bot"'); -}); - // Create adapter. // See https://aka.ms/about-bot-adapter to learn more about adapters. const adapter = new BotFrameworkAdapter({ @@ -56,9 +49,11 @@ adapter.onTurnError = async (context, error) => { // Define the state store for your bot. See https://aka.ms/about-bot-state to learn more about using MemoryStorage. // A bot requires a state storage system to persist the dialog and user state between messages. -const memoryStorage = new MemoryStorage(); -// Create conversation and user state with in-memory storage provider. +// For local development, in-memory storage is used. +// CAUTION: The Memory Storage used here is for local bot debugging only. When the bot +// is restarted, anything stored in memory will be gone. +const memoryStorage = new MemoryStorage(); const conversationState = new ConversationState(memoryStorage); const userState = new UserState(memoryStorage); @@ -71,13 +66,8 @@ if (!endpointHostName.includes('/v5.0') && !endpointHostName.endsWith('/qnamaker endpointHostName = endpointHostName + '/qnamaker'; } -var endpointKey = process.env.QnAEndpointKey; - -if (!endpointKey || 0 === endpointKey.length) -{ - // To support backward compatibility for Key Names - endpointKey = process.env.QnAAuthKey; -} +// To support backward compatibility for Key Names, fallback to process.env.QnAAuthKey. +const endpointKey = process.env.QnAEndpointKey || process.env.QnAAuthKey; // Create the main dialog. const dialog = new RootDialog(process.env.QnAKnowledgebaseId, endpointKey, endpointHostName, process.env.DefaultAnswer); @@ -85,6 +75,14 @@ const dialog = new RootDialog(process.env.QnAKnowledgebaseId, endpointKey, endpo // Create the bot's main handler. const bot = new QnABot(conversationState, userState, dialog); +// Create HTTP server. +const server = restify.createServer(); +server.listen(process.env.port || process.env.PORT || 3978, function() { + console.log(`\n${ server.name } listening to ${ server.url }.`); + console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator'); + console.log('\nTo talk to your bot, open the emulator select "Open Bot"'); +}); + // Listen for incoming requests. server.post('/api/messages', (req, res) => { adapter.processActivity(req, res, async (turnContext) => { diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/utils/qnaCardBuilder.js b/samples/javascript_nodejs/49.qnamaker-all-features/utils/qnaCardBuilder.js deleted file mode 100644 index 523c7f909d..0000000000 --- a/samples/javascript_nodejs/49.qnamaker-all-features/utils/qnaCardBuilder.js +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -const { CardFactory, ActionTypes } = require('botbuilder'); - -class QnACardBuilder { - /** - * Get multi-turn prompts card. - * @param {QnAMakerResult} result Result to be dispalyed as prompts. - * @param {string} cardNoMatchText No match text. - */ - static GetQnAPromptsCard(result) { - var cardActions = []; - result.context.prompts.forEach(prompt => { - cardActions.push({ - value: prompt.displayText, - type: ActionTypes.ImBack, - title: prompt.displayText - }); - }); - - var heroCard = CardFactory.heroCard('', '', [], CardFactory.actions(cardActions)); - return { text: result.answer, attachments: [heroCard] }; - } - - /** - * Get suggestion hero card to get user feedback. - * @param {Array} suggestionList A list of suggested questions strings. - * @param {string} cardTitle Title of the card. - * @param {string} cardNoMatchText No match text. - */ - static GetSuggestionCard(suggestionList, cardTitle, cardNoMatchText) { - var cardActions = []; - suggestionList.forEach(element => { - cardActions.push({ - value: element, - type: ActionTypes.ImBack, - title: element - }); - }); - - cardActions.push({ - value: cardNoMatchText, - type: ActionTypes.ImBack, - title: cardNoMatchText - }); - - var heroCard = CardFactory.heroCard( - '', - '', - [], - CardFactory.actions(cardActions)); - - return { text: cardTitle, attachments: [heroCard] }; - } -} - -module.exports.QnACardBuilder = QnACardBuilder;