-
Notifications
You must be signed in to change notification settings - Fork 9
/
loadOnDemand.js
218 lines (179 loc) · 6.69 KB
/
loadOnDemand.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
var path = require('path');
var express = require('express');
var builder = require('botbuilder');
var GraphDialog = require('bot-graph-dialog');
var config = require('./config');
var fs = require('fs');
var port = process.env.PORT || 3978;
var app = express();
var microsoft_app_id = config.get('MICROSOFT_APP_ID');
var microsoft_app_password = config.get('MICROSOFT_APP_PASSWORD');
var connector = new builder.ChatConnector({
appId: microsoft_app_id,
appPassword: microsoft_app_password,
});
var bot = new builder.UniversalBot(connector);
var intents = new builder.IntentDialog();
var scenariosPath = path.join(__dirname, 'bot', 'scenarios');
var handlersPath = path.join(__dirname, 'bot', 'handlers');
var dialogsMapById = {};
var dialogsMapByPath = {};
bot.dialog('/', intents);
intents.matches(/^(help|hi|hello)/i, [
function (session) {
session.send('Hi, how can I help you?');
}
]);
// dynamically load dialog from a remote datasource
// create a GraphDialog instance and bind it on the bot
async function loadDialog(dialog) {
console.log(`loading scenario: ${dialog.scenario} for regex: ${dialog.regex}`);
var re = new RegExp(dialog.regex, 'i');
intents.matches(re, [
function (session) {
session.beginDialog(dialog.path);
}
]);
try {
var graphDialog = await GraphDialog.create({
bot,
scenario: dialog.scenario,
loadScenario,
loadHandler,
customTypeHandlers: getCustomTypeHandlers(),
onBeforeProcessingStep
});
}
catch(err) {
console.error(`error loading dialog: ${err.message}`);
throw err;
}
dialog.graphDialog = graphDialog;
dialogsMapById[graphDialog.getDialogId()] = dialog;
dialogsMapByPath[dialog.path] = dialog;
bot.dialog(dialog.path, graphDialog.getDialog());
console.log(`graph dialog loaded successfully: scenario ${dialog.scenario} version ${graphDialog.getDialogVersion()} for regExp: ${dialog.regex} on path ${dialog.path}`);
}
// trigger dynamic load of the dialogs
loadDialogs()
.then(dialogs => dialogs.forEach(dialog => loadDialog(dialog)))
.catch(err => console.error(`error loading dialogs dynamically: ${err.message}`));
// intercept change in scenario version before processing each dialog step
// if there was a change in the version, restart the dialog
// TODO: think about adding this internally to the GraphDialog so users gets this as default behaviour.
function onBeforeProcessingStep(session, args, next) {
session.sendTyping();
var dialogVersion = this.getDialogVersion();
if (!session.privateConversationData._dialogVersion) {
session.privateConversationData._dialogVersion = dialogVersion;
}
if (session.privateConversationData._dialogVersion !== dialogVersion) {
session.send("Dialog updated. We'll have to start over.");
return this.restartDialog(session);
}
return next();
}
// this allows you to extend the json with more custom node types,
// by providing your implementation to processing each custom type.
// in the end of your implemention you should call the next callbacks
// to allow the framework to continue with the dialog.
// refer to the customTypeStepDemo node in the stomachPain.json scenario for an example.
function getCustomTypeHandlers() {
return [
{
name: 'myCustomType',
execute: (session, next, data) => {
console.log(`in custom node type handler: customTypeStepDemo, data: ${data.someData}`);
return next();
}
}
];
}
// this is the handler for loading scenarios from external datasource
// in this implementation we're just reading it from a file
// but it can come from any external datasource like a file, db, etc.
function loadScenario(scenario) {
return new Promise((resolve, reject) => {
console.log('loading scenario', scenario);
// implement loadScenario from external datasource.
// in this example we're loading from local file
var scenarioPath = path.join(scenariosPath, scenario + '.json');
return fs.readFile(scenarioPath, 'utf8', (err, content) => {
if (err) {
console.error("error loading json: " + scenarioPath);
return reject(err);
}
var scenarioObj = JSON.parse(content);
// simulating long load period
setTimeout(() => {
console.log('resolving scenario', scenarioPath);
resolve(scenarioObj);
}, Math.random() * 3000);
});
});
}
// this is the handler for loading handlers from external datasource
// in this implementation we're just reading it from a file
// but it can come from any external datasource like a file, db, etc.
//
// NOTE: handlers can also be embeded in the scenario json. See scenarios/botGames.json for an example.
function loadHandler(handler) {
return new Promise((resolve, reject) => {
console.log('loading handler', handler);
// implement loadHandler from external datasource.
// in this example we're loading from local file
var handlerPath = path.join(handlersPath, handler);
var handlerString = null;
return fs.readFile(handlerPath, 'utf8', (err, content) => {
if (err) {
console.error("error loading handler: " + handlerPath);
return reject(err);
}
// simulating long load period
setTimeout(() => {
console.log('resolving handler', handler);
resolve(content);
}, Math.random() * 3000);
});
});
}
// this is the handler for loading scenarios from external datasource
// in this implementation we're just reading it from the file scnearios/dialogs.json
// but it can come from any external datasource like a file, db, etc.
function loadDialogs() {
return new Promise((resolve, reject) => {
console.log('loading dialogs');
var dialogsPath = path.join(scenariosPath, "dialogs.json");
return fs.readFile(dialogsPath, 'utf8', (err, content) => {
if (err) {
console.error("error loading json: " + dialogsPath);
return reject(err);
}
var dialogs = JSON.parse(content);
// simulating long load period
setTimeout(() => {
console.log('resolving dialogs', dialogsPath);
resolve(dialogs.dialogs);
}, Math.random() * 3000);
});
});
}
app.post('/api/messages', connector.listen());
// endpoint for reloading scenario on demand
app.get('/api/load/:scenario', async (req, res) => {
var scenario = req.params.scenario;
console.log(`reloading scenario: ${scenario}`);
var dialog = dialogsMapById[scenario];
try {
await dialog.graphDialog.reload();
var msg = `scenario id '${scenario}' reloaded`;
console.log(msg);
return res.end(msg);
}
catch(err) {
return res.end(`error loading dialog: ${err.message}`)
}
});
app.listen(port, () => {
console.log('listening on port %s', port);
});