This repository has been archived by the owner on Oct 21, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
bot.js
220 lines (177 loc) · 7.64 KB
/
bot.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/**
* Copyright 2018 Artificial Solutions. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const { ActivityTypes } = require('botbuilder');
const TIE = require('@artificialsolutions/tie-api-client');
const TENEO_OUTPUTSEGMENTS_PARAM = "outputTextSegmentIndexes";
const dotenv = require('dotenv');
dotenv.config();
// Teneo engine url
const teneoEngineUrl = process.env.TENEO_ENGINE_URL;
// property to store sessionId in conversation state object
const SESSION_ID_PROPERTY = 'sessionId';
const WELCOMED_USER = 'welcomedUserProperty';
// initialize a Teneo client for interacting with TeneoEengine
const teneoApi = TIE.init(teneoEngineUrl);
class MyBot {
/**
*
* @param {ConversationState} conversation state object
*/
constructor(conversationState) {
// Creates a new state accessor property.
// See https://aka.ms/about-bot-state-accessors to learn more about the bot state and state accessors.
this.sessionIdProperty = conversationState.createProperty(SESSION_ID_PROPERTY);
this.welcomedUserProperty = conversationState.createProperty(WELCOMED_USER);
this.conversationState = conversationState;
}
/**
*
* @param {TurnContext} on turn context object.
*/
async onTurn(turnContext) {
// See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types.
if (turnContext.activity.type === ActivityTypes.Message) {
// send user input to engine and store sessionId in state in case not stored yet
await this.handleMessage(turnContext);
} else if (turnContext.activity.type === ActivityTypes.ConversationUpdate) {
// Conversation update activities describe a change in a conversation's members, description, existence, or otherwise.
// We want to send a welcome message to conversation members when they join the conversation
console.log(turnContext.activity.membersAdded)
// Iterate over all new members added to the conversation
for (const idx in turnContext.activity.membersAdded) {
// Only sent message to conversation members who aren't the bot
if (turnContext.activity.membersAdded[idx].id !== turnContext.activity.recipient.id) {
// send empty input to engine to receive Teneo greeting message and store sessionId in state
await this.handleMessage(turnContext);
}
}
} else {
console.log(`[${turnContext.activity.type} event detected]`);
}
// Save state changes
await this.conversationState.saveChanges(turnContext);
}
/**
*
* @param {TurnContext} on turn context object.
*/
async handleMessage(turnContext) {
const message = turnContext.activity;
// console.log(message);
try {
let messageText = ""
if (message.text) {
messageText = message.text;
}
console.log(`Got message '${messageText}' from channel ${message.channelId}`);
// find engine session id
const sessionId = await this.sessionIdProperty.get(turnContext);
let inputDetails = {
text: messageText,
channel: 'botframework-' + message.channelId
}
if (message.attachments) {
inputDetails["botframeworkAttachments"] = JSON.stringify(message.attachments);
}
// send message to engine using sessionId
const teneoResponse = await teneoApi.sendInput(sessionId, inputDetails);
console.log(`Got Teneo Engine response '${teneoResponse.output.text}' for session ${teneoResponse.sessionId}`);
// store egnine sessionId in conversation state
await this.sessionIdProperty.set(turnContext, teneoResponse.sessionId);
// separate the answer text into segments if applicable
let answerTextSegments = this.getOutputTextSegments(teneoResponse);
for (let index = 0; index < answerTextSegments.length; index++) {
const segmentText = answerTextSegments[index];
const reply = {};
reply.text = segmentText;
// only send bot framework actions for the last segment/message
if (index + 1 === answerTextSegments.length) {
// check if an output parameter 'msbotframework' exists in engine response
// if so, check if it should be added as attachment/card or suggestion action
if (teneoResponse.output.parameters.msbotframework) {
try {
const extension = JSON.parse(
teneoResponse.output.parameters.msbotframework
);
// suggested actions have an 'actions' key
if (extension.actions) {
reply.suggestedActions = extension;
} else {
// we assume the extension code matches that of an attachment or rich card
reply.attachments = [extension];
}
} catch (attachError) {
console.error(`Failed when parsing attachment JSON`, attachError);
}
}
}
// send response to bot framework.
await turnContext.sendActivity(reply);
}
} catch (error) {
console.error(`Failed when sending input to Teneo Engine @ ${teneoEngineUrl}`, error);
}
}
/**
* A Teneo response can contain an output parameter that
* indicates how the output text can be split in multiple
* segments or messages. The value of the parameter is a list
* with start and end index pairs that looks like this:
* [[0, 39], [40, 67], [70, 96]]
* getOutputTextSegments will split the output text into
* its segments and return them as a list of strings.
**/
getOutputTextSegments(teneoResponse) {
const teneoAnswerText = teneoResponse.output.text;
let segments = [];
let segmentRanges;
// get the output param with boundaries for each message
try {
const segmentsString = teneoResponse.output.parameters[TENEO_OUTPUTSEGMENTS_PARAM];
if (segmentsString) {
segmentRanges = JSON.parse(segmentsString);
}
} catch(err) {
console.log('Error: Unable to parse segmentsString JSON')
}
if (segmentRanges && Array.isArray(segmentRanges)) {
// each segmentRange in the list contains the start and end index for a message
segmentRanges.forEach((segmentRange) => {
try {
// get the start and end index for this segment
var segmentStartIndex = segmentRange[0];
var segmentEndIndex = segmentRange[1];
// if start and end seem valid
if (!isNaN(segmentStartIndex) && !isNaN(segmentEndIndex)) {
// get the substring from the answer text that needs to appear in a bubble
var segmentText = teneoAnswerText.substring(segmentStartIndex,segmentEndIndex).trim();
// add the segment to the list of segments, but only if it is not empty
if (segmentText) {
segments.push(segmentText)
}
}
} catch (err) {
console.log('Error: unexpected segment range')
}
});
} else {
// message does not need to be segmented, the only chunk is the full answer text
segments.push(teneoAnswerText)
}
return segments;
}
}
module.exports.MyBot = MyBot;