diff --git a/MESSAGE_FACTORY.md b/MESSAGE_FACTORY.md index d94e2d3..1bb91e5 100644 --- a/MESSAGE_FACTORY.md +++ b/MESSAGE_FACTORY.md @@ -406,11 +406,11 @@ When using TypeScript, you can cast to the proper message subclass to get design if (context.getRequest().state === context.getRequest().previousState) { const um = context.getUserMessage(); if (um instanceof TextMessage) { - const utm = um as typeof TextMessage; + const utm = um as TextMessage; const text = utm.getText(); // handle text } else if (um instanceof PostbackMessage) { - const upm = um as typeof PostbackMessage; + const upm = um as PostbackMessage; const postback = upm.getPostback(); // handle postback payload ... diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e1dd807..debbeec 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,6 @@ # Release Notes +- [Version 2.6.8](#v268) - [Version 2.6.7](#v267) - [Version 2.6.6](#v266) - [Version 2.6.5](#v265) @@ -17,6 +18,13 @@ - [Version 2.4.3](#v243) - [Version 2.4.2](#v242) +## Version 2.6.8 + +### Fixed Issues + +- Documentation fixes +- Fixed issue in baseContext.translate method for TypeScript. + ## Version 2.6.7 ### New Features diff --git a/bin/templates/components/dataqueryeventhandler/template.js b/bin/templates/components/dataqueryeventhandler/template.js deleted file mode 100644 index 3e3eb94..0000000 --- a/bin/templates/components/dataqueryeventhandler/template.js +++ /dev/null @@ -1,49 +0,0 @@ -/* eslint-disable no-unused-vars */ - -'use strict'; - -// You can use your favorite http client package to make REST calls, however, the node fetch API is pre-installed with the bots-node-sdk. -// Documentation can be found at https://www.npmjs.com/package/node-fetch -// Un-comment the next line if you want to make REST calls using node-fetch. -// const fetch = require("node-fetch"); - -module.exports = { - metadata: { - name: '{{name}}', - eventHandlerType: '{{eventHandlerType}}' - }, - handlers: { - entity: { - /** - * Default message handler that includes acknowledgements when a bag item is updated - * or a bag item value is provided while the user was prompted for another item - * @param {ChangeUISettingsEvent} event - * @param {DataQueryResolutionContext} context - */ - changeUISettings: async (event, context) => { - return event.settings; - }, - - changeResponseData: async (event, context) => { - return context.getQueryResult(); - }, - - changeBotMessages: async (event, context) => { - return event.messages; - } - }, - attributes: { - SomeAttributemName: { // TODO change to a valid attribute name - // add attribute level event handlers here - } - // add more attributes and their handlers here - }, - custom: { - // add custom event handlers here - } - } -}; - -/* eslint-enable no-unused-vars */ - - diff --git a/bin/templates/components/dataqueryeventhandler/template.ts b/bin/templates/components/dataqueryeventhandler/template.ts deleted file mode 100644 index 8ded1f0..0000000 --- a/bin/templates/components/dataqueryeventhandler/template.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { EntityResolutionContext - , EntityEventHandler - , EntityEventHandlers - , EntityEventHandlerMetadata - , EntityBaseEvent - , EntityPublishMessageEvent -} from '@oracle/bots-node-sdk/lib'; - -// You can use your favorite http client package to make REST calls, however, the node fetch API is pre-installed with the bots-node-sdk. -// Documentation can be found at https://www.npmjs.com/package/node-fetch -// Un-comment the next line if you want to make REST calls using node-fetch. -// import fetch from 'node-fetch'; - -export class {{className}} implements EntityEventHandler { - - public metadata(): EntityEventHandlerMetadata { - return { - name: '{{name}}', - eventHandlerType: '{{eventHandlerType}}', - supportedActions: [] // string array of transition actions that might be set by the event handler - }; - } - - public handlers(): EntityEventHandlers { - return { - - entity: { - /** - * Default message handler that includes acknowledgements when a bag item is updated - * or a bag item value is provided while the user was prompted for another item - */ - publishMessage: async (event: EntityPublishMessageEvent, context: EntityResolutionContext) => { - updatedItemsMessage(context); - outOfOrderItemsMessage(context); - context.addCandidateMessages(); - }, - - /** - * This handler is called when the composite bag entity is resolved - */ - resolved: async (event: EntityBaseEvent, context: EntityResolutionContext) => { // eslint-disable-line no-unused-vars - // add your back-end REST API call here - } - // add more entity level event handlers here - }, - - items: { - SomeBagItemName: { // TODO change to a valid bag item name - // add item level event handlers here - } - // add more bag items and their handlers here - }, - - custom: { - // add custom event handlers here - } - - }; - } - -} - -/** - * Helper function to show acknowledgement message when a bag item value is updated. - */ -function updatedItemsMessage(context: EntityResolutionContext) { - if (context.getItemsUpdated().length > 0) { - let message = "I have updated" + context.getItemsUpdated().map((item, i) => (i !== 0 ? " and the " : " the ") + item.toLowerCase() + " to " + context.getDisplayValue(item)); - context.addMessage(message); - } -} - -/** - * Helper function to show acknowledgement message when a bag item value is provided when user was prompted for anther bag item. - */ -function outOfOrderItemsMessage(context: EntityResolutionContext) { - if (context.getItemsMatchedOutOfOrder().length > 0) { - let message = "I got" + context.getItemsMatchedOutOfOrder().map((item, i) => (i !== 0 ? " and the " : " the ") + item.toLowerCase() + " " + context.getDisplayValue(item)); - context.addMessage(message); - } -} diff --git a/lib/dataquery/dataQueryContext.js b/lib/dataquery/dataQueryContext.js index 7e4c2b5..9f2d40e 100644 --- a/lib/dataquery/dataQueryContext.js +++ b/lib/dataquery/dataQueryContext.js @@ -176,6 +176,44 @@ class DataQueryContext extends BaseContext { return this.getVariable('system.isFollowupResponse'); } + /** + * The end flow action that is set that can be used to transition to a different flow by defining a mapping for this action in the main flow. + * @param {string} action - the end flow action that can be used to transition in the main flow + */ + setEndFlowAction(action) { + this.setVariable('system.sqlDialog.endFlowAction', action); + this.getResponse().transitionAction = 'system.sqlDialog.endFlow'; + } + + /** + * Creates a postback action that ends the flow with the specified end flow action. + * @param {string} the label of the postback button + * @param {string} action - the end flow action that can be used to transition in the main flow + */ + createEndFlowPostbackAction(label, action) { + const mf = this.getMessageFactory(); + return mf.createPostbackAction(label, {'action': 'system.sqlDialog.endFlow', 'variables': {'system.sqlDialog.endFlowAction': action}}); + } + + /** + * Invoke another flow + * @param {string} flowName - name of the flow to invoke + */ + invokeFlow(flowName) { + this.setVariable('system.sqlDialog.invokeFlowName', flowName); + this.getResponse().transitionAction = 'system.sqlDialog.invokeFlow'; + } + + /** + * Creates a postback action that invokes the specified flow. + * @param {string} the label of the postback button + * @param {string} flowName - name of the flow to invoke + */ + createInvokeFlowPostbackAction(label, flowName) { + const mf = this.getMessageFactory(); + return mf.createPostbackAction(label, {'action': 'system.sqlDialog.invokeFlow', 'variables': {'system.sqlDialog.invokeFlowName': flowName}}); + } + } module.exports = { DataQueryContext } \ No newline at end of file diff --git a/lib/restservice/restServiceContext.js b/lib/restservice/restServiceContext.js index f9c124e..ff12bd3 100644 --- a/lib/restservice/restServiceContext.js +++ b/lib/restservice/restServiceContext.js @@ -79,6 +79,36 @@ class RestServiceContext extends BaseContext { this.getResponse().responsePayload = payload; } + /** + * Adds a message to the bot response sent to the user. + * NOTE: This method can only be used in the validateResponsePayload handler + * @param {object} payload - can take a string message, or a message created using the MessageFactory + */ + addMessage(payload) { + this.getResponse().messages = this.getResponse().messages || []; + this.getResponse().messages.push(super.constructMessagePayload(payload)); + } + + /** + * Set a transition action. When you use this function, the dialog engine will transition to the state defined for this transition action. + *

+ * NOTE: This method can only be used in the validateResponsePayload handler + * @param {string} action - name of the transition action + */ + setTransitionAction(action) { + this.getResponse().transitionAction = action; + } + + /** + * Sets an LLM prompt that will be sent to the LLM + *

+ * NOTE: This method can only be used in the validateResponsePayload handler + * @param {string} prompt - the text of the prompt + */ + setLLMPrompt(prompt) { + this.getResponse().llmPrompt = prompt; + } + } module.exports = { RestServiceContext } \ No newline at end of file diff --git a/lib/restservice/utils.js b/lib/restservice/utils.js index 1bf2adc..fff15b5 100644 --- a/lib/restservice/utils.js +++ b/lib/restservice/utils.js @@ -16,22 +16,27 @@ async function invokeRestServiceEventHandlers(component, context) { // event handlers can be async (returning a promise), but we dont want to enforce // every event handler is async, hence Promise.resolve wrapping of invocation if (eventName === `transformRequestPayload`) { - let payload = context.getRequestPayload(); - event.properties = {'payload': payload}; logger.debug(`Invoking event handler ${eventName}`); let returnValue = await Promise.resolve(handler(event.properties, context)); if (returnValue) { context.setRequestPayload(returnValue); } } else if (eventName === `transformResponsePayload` || eventName === `transformErrorResponsePayload`) { - let payload = context.getResponsePayload(); - event.properties = {'payload': payload}; logger.debug(`Invoking event handler ${eventName}`); let returnValue = await Promise.resolve(handler(event.properties, context)); if (returnValue) { context.setResponsePayload(returnValue); } + } else if (eventName === `validateResponsePayload`) { + logger.debug(`Invoking event handler ${eventName}`); + let returnValue = await Promise.resolve(handler(event.properties, context)); + // make sure return value is a boolean + let retValue = returnValue === undefined ? true : (returnValue + '' === 'true') + logger.debug(`${eventName} returned ${retValue}`); + context.getResponse().valid = retValue; } + + } else { logger.debug(`No handler found for event: ${eventName}`); } diff --git a/package-lock.json b/package-lock.json index 47b18b6..dc1d850 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@oracle/bots-node-sdk", - "version": "2.6.7", + "version": "2.6.8", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2108351..349258f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@oracle/bots-node-sdk", - "version": "2.6.7", + "version": "2.6.8", "description": "Oracle Digital Assistant SDK for custom component development and webhook integrations", "main": "index.js", "browser": "index-browser.js", diff --git a/ts/lib/component/baseContext.ts b/ts/lib/component/baseContext.ts index d4c848c..6725e5a 100644 --- a/ts/lib/component/baseContext.ts +++ b/ts/lib/component/baseContext.ts @@ -360,10 +360,10 @@ export abstract class BaseContext { */ translate(rbKey: string, ...rbArgs: string[]) { // create freemarker expression that will be resolved in runtime after event handler or custom component response is received - let exp = '${rb(\'' + rbKey + '\''; + let exp = '${rb("' + rbKey + '"'; for (let arg of rbArgs) { // MIECS-38051: only string args should be enclosed in quotes - typeof arg === 'string' ? exp += ',\'' + arg + '\'' : exp += ',' + arg; + typeof arg === 'string' ? exp += ',"' + arg + '"' : exp += ',' + arg; } exp += ')}'; return exp; diff --git a/ts/lib/dataquery/dataQueryContext.ts b/ts/lib/dataquery/dataQueryContext.ts index 34ec625..5946ef4 100644 --- a/ts/lib/dataquery/dataQueryContext.ts +++ b/ts/lib/dataquery/dataQueryContext.ts @@ -179,4 +179,43 @@ export class DataQueryContext extends BaseContext { return this.getVariable('system.isFollowupResponse'); } + /** + * The end flow action that is set that can be used to transition to a different flow by defining a mapping for this action in the + * main flow. + * @param {string} action - the end flow action that can be used to transition in the main flow + */ + setEndFlowAction(action: string): void { + this.setVariable('system.sqlDialog.endFlowAction', action); + this.getResponse().transitionAction = 'system.sqlDialog.endFlow'; + } + + /** + * Creates a postback action that ends the flow with the specified end flow action. + * @param {string} the label of the postback button + * @param {string} action - the end flow action that can be used to transition in the main flow + */ + createEndFlowPostbackAction(label: string, action: string): PostbackAction { + const mf = this.getMessageFactory(); + return mf.createPostbackAction(label, {'action': 'system.sqlDialog.endFlow', 'variables': {'system.sqlDialog.endFlowAction': action}}); + } + + /** + * Invoke another flow + * @param {string} flowName - name of the flow to invoke + */ + invokeFlow(flowName: string): void { + this.setVariable('system.sqlDialog.invokeFlowName', flowName); + this.getResponse().transitionAction = 'system.sqlDialog.invokeFlow'; + } + + /** + * Creates a postback action that invokes the specified flow. + * @param {string} the label of the postback button + * @param {string} flowName - name of the flow to invoke + */ + createInvokeFlowPostbackAction(label, flowName): PostbackAction { + const mf = this.getMessageFactory(); + return mf.createPostbackAction(label, {'action': 'system.sqlDialog.invokeFlow', + 'variables': {'system.sqlDialog.invokeFlowName': flowName}}); + } } diff --git a/ts/lib/restservice/restServiceContext.ts b/ts/lib/restservice/restServiceContext.ts index b9a8bfc..751bab9 100644 --- a/ts/lib/restservice/restServiceContext.ts +++ b/ts/lib/restservice/restServiceContext.ts @@ -1,4 +1,5 @@ import { BaseContext } from '../component/baseContext'; +import { MessagePayload } from '../message'; // Response template const RESPONSE = { @@ -74,8 +75,38 @@ export class RestServiceContext extends BaseContext { /** * Set the response payload */ - setResponsePayload(payload: any) { + setResponsePayload(payload: any): void { this.getResponse().responsePayload = payload; } + /** + * Adds a message to the bot response sent to the user. + * NOTE: This method can only be used in the validateResponsePayload handler + * @param {object} payload - can take a string message, or a message created using the MessageFactory + */ + addMessage(payload: string | MessagePayload): void { + this.getResponse().messages = this.getResponse().messages || []; + this.getResponse().messages.push(super.constructMessagePayload(payload)); + } + + /** + * Set a transition action. When you use this function, the dialog engine will transition to the state defined for this transition action. + *

+ * NOTE: This method can only be used in the validateResponsePayload handler + * @param {string} action - name of the transition action + */ + setTransitionAction(action: string): void { + this.getResponse().transitionAction = action; + } + + /** + * Sets an LLM prompt that will be sent to the LLM + *

+ * NOTE: This method can only be used in the validateResponsePayload handler + * @param {string} prompt - the text of the prompt + */ + setLLMPrompt(prompt: string): void { + this.getResponse().llmPrompt = prompt; + } + } diff --git a/ts/lib/restservice/restServiceTypes.ts b/ts/lib/restservice/restServiceTypes.ts index 674e1f4..c8a8aad 100644 --- a/ts/lib/restservice/restServiceTypes.ts +++ b/ts/lib/restservice/restServiceTypes.ts @@ -20,4 +20,14 @@ export interface TransformPayloadEvent { payload: any } +export interface ChatEntry { + role: 'user' | 'system' | 'assistant'; + content: string; +} + +export interface ValidateResponseEvent { + payload: string; + chatHistory: ChatEntry[]; + entityMatches?: Map +} diff --git a/ts/lib/restservice/utils.ts b/ts/lib/restservice/utils.ts index 0f90d42..284b985 100644 --- a/ts/lib/restservice/utils.ts +++ b/ts/lib/restservice/utils.ts @@ -18,21 +18,24 @@ export async function invokeRestServiceEventHandlers(component: RestServiceEvent // event handlers can be async (returning a promise), but we dont want to enforce // every event handler is async, hence Promise.resolve wrapping of invocation if (eventName === `transformRequestPayload`) { - let payload = context.getRequestPayload(); - event.properties = {'payload': payload}; logger.debug(`Invoking event handler ${eventName}`); let returnValue = await Promise.resolve(handler(event.properties, context)); if (returnValue) { context.setRequestPayload(returnValue); } } else if (eventName === `transformResponsePayload` || eventName === `transformErrorResponsePayload`) { - let payload = context.getResponsePayload(); - event.properties = {'payload': payload}; logger.debug(`Invoking event handler ${eventName}`); let returnValue = await Promise.resolve(handler(event.properties, context)); if (returnValue) { context.setResponsePayload(returnValue); } + } else if (eventName === `validateResponsePayload`) { + logger.debug(`Invoking event handler ${eventName}`); + let returnValue = await Promise.resolve(handler(event.properties, context)); + // make sure return value is a boolean + let retValue = returnValue === undefined ? true : (returnValue + '' === 'true') + logger.debug(`${eventName} returned ${retValue}`); + context.getResponse().valid = retValue; } } else { logger.debug(`No handler found for event: ${eventName}`);