wingbot 3.39.0-alpha.1 → 3.39.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/plugins/ai.wingbot.slotsContinue/plugin.js +44 -0
- package/plugins/ai.wingbot.slotsRegister/plugin.js +89 -0
- package/plugins/plugins.json +75 -0
- package/src/AiMatching.js +21 -16
- package/src/Processor.js +1 -1
- package/src/Request.js +9 -1
- package/src/Tester.js +46 -5
- package/src/resolvers/message.js +4 -0
- package/src/testTools/asserts.js +2 -2
- package/src/utils/getUpdate.js +1 -1
- package/src/utils/quickReplies.js +1 -0
- package/src/utils/slots.js +113 -0
- package/src/utils/stateVariables.js +17 -2
package/package.json
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const { StepState, StepType, getNextStep } = require('../../src/utils/slots');
|
|
2
|
+
const { vars } = require('../../src/utils/stateVariables');
|
|
3
|
+
|
|
4
|
+
/** @typedef {import('../../src/Router').Resolver} Resolver */
|
|
5
|
+
/** @typedef {import('../../src/Responder')} Responder */
|
|
6
|
+
/** @typedef {import('../../src/utils/slots').SlotBotState} SlotBotState */
|
|
7
|
+
/** @typedef {import('../../src/utils/slots').SlotsResolver} SlotsResolver */
|
|
8
|
+
/** @typedef {import('../../src/utils/slots').SlotsRequest} SlotsRequest */
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @returns {SlotsResolver}
|
|
12
|
+
*/
|
|
13
|
+
function slotContinue () {
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {SlotsRequest} req
|
|
17
|
+
* @param {Responder} res
|
|
18
|
+
* @param {Function} postBack
|
|
19
|
+
* @returns {Promise}
|
|
20
|
+
*/
|
|
21
|
+
async function slotContinuePlugin (req, res, postBack) {
|
|
22
|
+
const state = { ...req.state, ...res.newState };
|
|
23
|
+
|
|
24
|
+
const { _slotStep: step } = state;
|
|
25
|
+
let { _slotState: slotState } = state;
|
|
26
|
+
|
|
27
|
+
if (!step || !slotState) {
|
|
28
|
+
const msg = 'ERROR: slot filling was not initialized (use the "continue" plugin after the "initialize")';
|
|
29
|
+
res.text(msg);
|
|
30
|
+
throw new Error(msg);
|
|
31
|
+
}
|
|
32
|
+
slotState = slotState.map((s) => (s.e === step.entity
|
|
33
|
+
? { ...s, s: StepState.FILLED }
|
|
34
|
+
: s));
|
|
35
|
+
|
|
36
|
+
res.setState({ _slotState: slotState });
|
|
37
|
+
|
|
38
|
+
return getNextStep(req, res, postBack);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return slotContinuePlugin;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = slotContinue;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const { webalize } = require('webalize');
|
|
2
|
+
const { ai } = require('../../src/Ai');
|
|
3
|
+
const Router = require('../../src/Router');
|
|
4
|
+
const { StepState, StepType, getNextStep } = require('../../src/utils/slots');
|
|
5
|
+
const { vars } = require('../../src/utils/stateVariables');
|
|
6
|
+
|
|
7
|
+
/** @typedef {import('../../src/utils/slots').SlotsResolver} SlotsResolver */
|
|
8
|
+
/** @typedef {import('../../src/utils/slots').SlotPluginConfig} SlotPluginConfig */
|
|
9
|
+
/** @typedef {import('../../src/utils/slots').SlotBotState} SlotBotState */
|
|
10
|
+
/** @typedef {import('../../src/utils/slots').SlotState} SlotState */
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
*
|
|
14
|
+
* @param {SlotPluginConfig} params
|
|
15
|
+
* @returns {Router<SlotBotState>}
|
|
16
|
+
*/
|
|
17
|
+
function slotsRegister ({
|
|
18
|
+
steps = [],
|
|
19
|
+
doneAction,
|
|
20
|
+
intents = ''
|
|
21
|
+
}) {
|
|
22
|
+
|
|
23
|
+
const useIntents = intents.split(',')
|
|
24
|
+
.map((i) => i.trim());
|
|
25
|
+
|
|
26
|
+
/** @type {Router<SlotBotState>} */
|
|
27
|
+
const bot = new Router();
|
|
28
|
+
|
|
29
|
+
const setEntities = [];
|
|
30
|
+
|
|
31
|
+
/** @type {SlotsResolver} */
|
|
32
|
+
const handler = async (req, res, postBack) => {
|
|
33
|
+
// offer rooms or (does'nt matter)
|
|
34
|
+
|
|
35
|
+
/** @type {SlotState[]} */
|
|
36
|
+
const slotState = steps.map((step) => {
|
|
37
|
+
const entity = step.entity.replace(/^@/, '');
|
|
38
|
+
const entities = req.entities.filter((e) => e.entity === entity)
|
|
39
|
+
.map((e) => e.value);
|
|
40
|
+
|
|
41
|
+
if (entities.length === 0) {
|
|
42
|
+
return {
|
|
43
|
+
e: step.entity,
|
|
44
|
+
s: step.type === StepType.ADDITIONAL
|
|
45
|
+
? StepState.FILLED
|
|
46
|
+
: StepState.INITIALIZED
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (step.type === StepType.MULTI) {
|
|
51
|
+
setEntities.push(vars.dialogContext(`+${entity}`, entities, true));
|
|
52
|
+
} else {
|
|
53
|
+
setEntities.push(vars.dialogContext(step.entity, entities[0], true));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
e: step.entity,
|
|
58
|
+
s: step.validateAction ? StepState.FILLED : StepState.VALID
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
res.setState({
|
|
63
|
+
_slotState: slotState,
|
|
64
|
+
_slotSteps: steps,
|
|
65
|
+
_slotDone: doneAction,
|
|
66
|
+
...Object.fromEntries(
|
|
67
|
+
steps.map(({ entity, type }) => (type === StepType.MULTI
|
|
68
|
+
? [entity.replace(/^@/, '+'), []]
|
|
69
|
+
: [entity, null]))
|
|
70
|
+
),
|
|
71
|
+
...setEntities.reduce((o, r) => Object.assign(o, r), {})
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return getNextStep(req, res, postBack);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
bot.use(ai.global('/', useIntents), handler);
|
|
78
|
+
|
|
79
|
+
for (const step of steps) {
|
|
80
|
+
bot.use(
|
|
81
|
+
ai.global(webalize(step.entity), [...useIntents, step.entity]),
|
|
82
|
+
handler
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return bot;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = slotsRegister;
|
package/plugins/plugins.json
CHANGED
|
@@ -375,6 +375,81 @@
|
|
|
375
375
|
"label": "Text to test (keep empty to use user input)"
|
|
376
376
|
}
|
|
377
377
|
]
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
"id": "ai.wingbot.slotsRegister",
|
|
381
|
+
"name": "Slot filling: register",
|
|
382
|
+
"description": "Sets up slot filling conversation for a list of entities",
|
|
383
|
+
"availableSince": 3.39,
|
|
384
|
+
"editable": false,
|
|
385
|
+
"isFactory": true,
|
|
386
|
+
"category": "conversation",
|
|
387
|
+
"inputs": [
|
|
388
|
+
{
|
|
389
|
+
"type": "text",
|
|
390
|
+
"name": "intents",
|
|
391
|
+
"label": "Intent list (comma separated)",
|
|
392
|
+
"validations": [
|
|
393
|
+
{ "type": "regexp", "value": "^[^\\s]+$", "message": "intent for slot filling should not be empty" }
|
|
394
|
+
]
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
"type": "postback",
|
|
398
|
+
"name": "doneAction",
|
|
399
|
+
"label": "Continue here, when finished"
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
"type": "array",
|
|
403
|
+
"name": "steps",
|
|
404
|
+
"label": "Slot",
|
|
405
|
+
"keyPropertyName": "entity",
|
|
406
|
+
"inputs": [
|
|
407
|
+
{
|
|
408
|
+
"type": "text",
|
|
409
|
+
"name": "entity",
|
|
410
|
+
"label": "Entity (with @)",
|
|
411
|
+
"validations": [
|
|
412
|
+
{ "type": "regexp", "value": "^@[a-zA-Z0-9-]+$", "message": "the entity for the slot filling should be valid" }
|
|
413
|
+
]
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
"type": "select",
|
|
417
|
+
"name": "type",
|
|
418
|
+
"label": "Question",
|
|
419
|
+
"options": [
|
|
420
|
+
{ "label": "Required", "value": "req" },
|
|
421
|
+
{ "label": "Multi value", "value": "mul" },
|
|
422
|
+
{ "label": "Additional", "value": "add" }
|
|
423
|
+
]
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
"type": "postback",
|
|
427
|
+
"name": "askAction",
|
|
428
|
+
"label": "Question"
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
"type": "postback",
|
|
432
|
+
"optional": true,
|
|
433
|
+
"name": "validateAction",
|
|
434
|
+
"label": "Validation"
|
|
435
|
+
}
|
|
436
|
+
]
|
|
437
|
+
}
|
|
438
|
+
],
|
|
439
|
+
"items": [
|
|
440
|
+
]
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
"id": "ai.wingbot.slotsContinue",
|
|
444
|
+
"name": "Slot filling: continue",
|
|
445
|
+
"description": "move the slot filling to the next step",
|
|
446
|
+
"availableSince": 3.39,
|
|
447
|
+
"editable": false,
|
|
448
|
+
"isFactory": true,
|
|
449
|
+
"inputs": [
|
|
450
|
+
],
|
|
451
|
+
"items": [
|
|
452
|
+
]
|
|
378
453
|
}
|
|
379
454
|
],
|
|
380
455
|
"categories": [
|
package/src/AiMatching.js
CHANGED
|
@@ -191,7 +191,7 @@ class AiMatching {
|
|
|
191
191
|
return returnIfEmpty;
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
_hbsOrFn (value
|
|
194
|
+
_hbsOrFn (value) {
|
|
195
195
|
if (typeof value === 'string') {
|
|
196
196
|
let useValue = value;
|
|
197
197
|
if (useValue.match(/^\$[a-zA-Z0-9_-]+$/)) {
|
|
@@ -199,13 +199,12 @@ class AiMatching {
|
|
|
199
199
|
}
|
|
200
200
|
if (useValue.match(/\{\{.+\}\}/)) {
|
|
201
201
|
const compiler = handlebars.compile(useValue);
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
};
|
|
202
|
+
// @ts-ignore
|
|
203
|
+
compiler.template = useValue;
|
|
204
|
+
return compiler;
|
|
206
205
|
}
|
|
207
206
|
}
|
|
208
|
-
return
|
|
207
|
+
return value;
|
|
209
208
|
}
|
|
210
209
|
|
|
211
210
|
_normalizeComparisonArray (compare, op) {
|
|
@@ -220,7 +219,7 @@ class AiMatching {
|
|
|
220
219
|
const [val] = arr;
|
|
221
220
|
|
|
222
221
|
return [
|
|
223
|
-
this._hbsOrFn(val
|
|
222
|
+
this._hbsOrFn(val)
|
|
224
223
|
];
|
|
225
224
|
}
|
|
226
225
|
|
|
@@ -228,12 +227,12 @@ class AiMatching {
|
|
|
228
227
|
const [min, max] = arr;
|
|
229
228
|
|
|
230
229
|
return [
|
|
231
|
-
this._hbsOrFn(min
|
|
232
|
-
this._hbsOrFn(max
|
|
230
|
+
this._hbsOrFn(min),
|
|
231
|
+
this._hbsOrFn(max)
|
|
233
232
|
];
|
|
234
233
|
}
|
|
235
234
|
|
|
236
|
-
return arr.map((cmp) => this._hbsOrFn(cmp
|
|
235
|
+
return arr.map((cmp) => this._hbsOrFn(cmp));
|
|
237
236
|
}
|
|
238
237
|
|
|
239
238
|
_stringOpToOperation (op) {
|
|
@@ -285,6 +284,7 @@ class AiMatching {
|
|
|
285
284
|
*/
|
|
286
285
|
getSetStateForEntityRules ({ entities }) {
|
|
287
286
|
return entities.reduce((o, rule) => {
|
|
287
|
+
|
|
288
288
|
if (rule instanceof RegExp) {
|
|
289
289
|
return o;
|
|
290
290
|
}
|
|
@@ -297,13 +297,13 @@ class AiMatching {
|
|
|
297
297
|
|
|
298
298
|
if (rule.op === COMPARE.EQUAL
|
|
299
299
|
&& rule.compare
|
|
300
|
-
&& rule.compare.length === 1
|
|
301
|
-
&& typeof rule.compare[0] !== 'function') {
|
|
300
|
+
&& rule.compare.length === 1) {
|
|
302
301
|
|
|
303
302
|
const key = `@${rule.entity}`;
|
|
304
303
|
const value = rule.compare[0];
|
|
305
304
|
|
|
306
|
-
|
|
305
|
+
// @ts-ignore
|
|
306
|
+
return vars.dialogContext(key, value && (value.template || value));
|
|
307
307
|
}
|
|
308
308
|
return o;
|
|
309
309
|
}, {});
|
|
@@ -732,11 +732,15 @@ class AiMatching {
|
|
|
732
732
|
: false;
|
|
733
733
|
}
|
|
734
734
|
|
|
735
|
-
|
|
735
|
+
let useCmp = (compare || [])
|
|
736
736
|
.map((c) => (typeof c === 'function'
|
|
737
737
|
? c(requestState)
|
|
738
738
|
: c));
|
|
739
739
|
|
|
740
|
+
if ([COMPARE.EQUAL, COMPARE.NOT_EQUAL].includes(operation)) {
|
|
741
|
+
useCmp = useCmp.map((c) => (typeof c === 'string' ? c : `${c}`));
|
|
742
|
+
}
|
|
743
|
+
|
|
740
744
|
switch (operation) {
|
|
741
745
|
case COMPARE.EQUAL:
|
|
742
746
|
return useCmp.length === 0 || useCmp.includes(`${value}`);
|
|
@@ -748,7 +752,8 @@ class AiMatching {
|
|
|
748
752
|
if (normalized === null) {
|
|
749
753
|
return false;
|
|
750
754
|
}
|
|
751
|
-
return normalized >= min
|
|
755
|
+
return normalized >= this._normalizeToNumber(min, -Infinity)
|
|
756
|
+
&& normalized <= this._normalizeToNumber(max, Infinity);
|
|
752
757
|
}
|
|
753
758
|
case COMPARE.GT:
|
|
754
759
|
case COMPARE.LT:
|
|
@@ -759,7 +764,7 @@ class AiMatching {
|
|
|
759
764
|
if (normalized === null) {
|
|
760
765
|
return false;
|
|
761
766
|
}
|
|
762
|
-
return this._numberComparison(op, cmp, normalized);
|
|
767
|
+
return this._numberComparison(op, this._normalizeToNumber(cmp, 0), normalized);
|
|
763
768
|
}
|
|
764
769
|
default:
|
|
765
770
|
return true;
|
package/src/Processor.js
CHANGED
package/src/Request.js
CHANGED
|
@@ -1137,7 +1137,15 @@ class Request {
|
|
|
1137
1137
|
let { setState = {} } = res;
|
|
1138
1138
|
for (const gi of this.globalIntents.values()) {
|
|
1139
1139
|
if (gi.action === res.action) {
|
|
1140
|
-
|
|
1140
|
+
entitiesSetState = { ...gi.entitiesSetState };
|
|
1141
|
+
|
|
1142
|
+
const values = Array.from(Object.values(entitiesSetState));
|
|
1143
|
+
|
|
1144
|
+
for (const value of values) {
|
|
1145
|
+
if (typeof value === 'function') {
|
|
1146
|
+
Object.assign(entitiesSetState, value(stateData(this)));
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1141
1149
|
}
|
|
1142
1150
|
}
|
|
1143
1151
|
const newState = {
|
package/src/Tester.js
CHANGED
|
@@ -290,6 +290,14 @@ class Tester {
|
|
|
290
290
|
|| (!botAction.match(/\*/) && actionMatches(botAction, path));
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
_actionsDebug (matchRequestActions = false) {
|
|
294
|
+
const set = new Set();
|
|
295
|
+
return this.actions
|
|
296
|
+
.filter((a) => !a.isReqAction || matchRequestActions)
|
|
297
|
+
.map((a) => (a.doNotTrack ? `(system interaction) ${a.action}` : a.action))
|
|
298
|
+
.filter((a) => !set.has(a) && set.add(a));
|
|
299
|
+
}
|
|
300
|
+
|
|
293
301
|
/**
|
|
294
302
|
* Checks, that request passed an interaction
|
|
295
303
|
*
|
|
@@ -305,11 +313,7 @@ class Tester {
|
|
|
305
313
|
&& this._actionMatches(action.action, path));
|
|
306
314
|
let actual;
|
|
307
315
|
if (!ok) {
|
|
308
|
-
|
|
309
|
-
actual = this.actions
|
|
310
|
-
.filter((a) => !a.isReqAction || matchRequestActions)
|
|
311
|
-
.map((a) => (a.doNotTrack ? `(system interaction) ${a.action}` : a.action))
|
|
312
|
-
.filter((a) => !set.has(a) && set.add(a));
|
|
316
|
+
actual = this._actionsDebug(matchRequestActions);
|
|
313
317
|
assert.fail(asserts.ex('Interaction was not passed', path, actual));
|
|
314
318
|
}
|
|
315
319
|
return this;
|
|
@@ -419,6 +423,22 @@ class Tester {
|
|
|
419
423
|
.intentWithEntity(this.senderId, text, intent, entity, value, score));
|
|
420
424
|
}
|
|
421
425
|
|
|
426
|
+
/**
|
|
427
|
+
* Makes recognised AI request with entity
|
|
428
|
+
*
|
|
429
|
+
* @param {string} entity
|
|
430
|
+
* @param {string} [value]
|
|
431
|
+
* @param {string} [text]
|
|
432
|
+
* @param {number} [score]
|
|
433
|
+
* @returns {Promise}
|
|
434
|
+
*
|
|
435
|
+
* @memberOf Tester
|
|
436
|
+
*/
|
|
437
|
+
entity (entity, value = entity, text = value, score = 1) {
|
|
438
|
+
return this.processMessage(Request
|
|
439
|
+
.intentWithEntity(this.senderId, text, `random-${Date.now()}`, entity, value, score));
|
|
440
|
+
}
|
|
441
|
+
|
|
422
442
|
/**
|
|
423
443
|
* Make optin call
|
|
424
444
|
*
|
|
@@ -523,6 +543,27 @@ class Tester {
|
|
|
523
543
|
.postBack(this.senderId, action, data, refAction, refData, null));
|
|
524
544
|
}
|
|
525
545
|
|
|
546
|
+
/**
|
|
547
|
+
* Prints last conversation turnaround
|
|
548
|
+
*
|
|
549
|
+
* @param {boolean} [showPrivateKeys]
|
|
550
|
+
*/
|
|
551
|
+
debug (showPrivateKeys = false) {
|
|
552
|
+
// eslint-disable-next-line no-console
|
|
553
|
+
console.log(
|
|
554
|
+
'\n===== actions =====\n',
|
|
555
|
+
this._actionsDebug(true),
|
|
556
|
+
'\n---- responses ----\n',
|
|
557
|
+
this.responses.map(({ messaging_type: m, recipient, ...o }) => o),
|
|
558
|
+
'\n------ state ------\n',
|
|
559
|
+
Object.fromEntries(
|
|
560
|
+
Object.entries(this.getState().state)
|
|
561
|
+
.filter((e) => showPrivateKeys || !e[0].startsWith('_'))
|
|
562
|
+
),
|
|
563
|
+
'\n===================\n'
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
526
567
|
}
|
|
527
568
|
|
|
528
569
|
module.exports = Tester;
|
package/src/resolvers/message.js
CHANGED
package/src/testTools/asserts.js
CHANGED
|
@@ -30,8 +30,8 @@ function m (text, actual = null, expected = null) {
|
|
|
30
30
|
|
|
31
31
|
function ex (message, expected, actual) {
|
|
32
32
|
const actuals = Array.isArray(actual) ? actual : [actual];
|
|
33
|
-
return `${message}\n + expected: "${expected}"\n - actual:
|
|
34
|
-
.map((a) => `"${a}"`).join('\n
|
|
33
|
+
return `${message}\n + expected: "${expected}"\n - actual: ${actuals
|
|
34
|
+
.map((a) => `"${a}"`).join('\n ')}`;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function getText (response) {
|
package/src/utils/getUpdate.js
CHANGED
|
@@ -13,7 +13,7 @@ try {
|
|
|
13
13
|
handlebars = { compile: (text) => () => text };
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const ATTR_REGEX = /^([
|
|
16
|
+
const ATTR_REGEX = /^([^.]*)\.?(.+)?$/;
|
|
17
17
|
const SCALAR_TYPES = ['string', 'number', 'boolean'];
|
|
18
18
|
const SUBSCRIBE = '_$subscribe';
|
|
19
19
|
const UNSUBSCRIBE = '_$unsubscribe';
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author {David Menger}
|
|
3
|
+
*/
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const Router = require('../Router');
|
|
7
|
+
const Request = require('../Request'); // eslint-disable-line no-unused-vars
|
|
8
|
+
|
|
9
|
+
/** @typedef {import('../Responder')} Responder */
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @readonly
|
|
13
|
+
* @enum {string}
|
|
14
|
+
*/
|
|
15
|
+
const StepType = {
|
|
16
|
+
REQUIRED: 'req',
|
|
17
|
+
MULTI: 'mul',
|
|
18
|
+
ADDITIONAL: 'add'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @readonly
|
|
23
|
+
* @enum {string}
|
|
24
|
+
*/
|
|
25
|
+
const StepState = {
|
|
26
|
+
INITIALIZED: 'i',
|
|
27
|
+
FILLED: 'f',
|
|
28
|
+
VALID: 'v'
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {object} SlotStep
|
|
33
|
+
* @prop {string} entity
|
|
34
|
+
* @prop {StepType} type
|
|
35
|
+
* @prop {string} [validateAction]
|
|
36
|
+
* @prop {string} askAction
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {object} SlotState
|
|
41
|
+
* @prop {string} e
|
|
42
|
+
* @prop {StepState} s
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {object} SlotBotState
|
|
47
|
+
* @prop {SlotState[]} _slotState
|
|
48
|
+
* @prop {SlotStep} [_slotStep]
|
|
49
|
+
* @prop {SlotStep[]} [_slotSteps]
|
|
50
|
+
* @prop {string} [_slotDone]
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @typedef {object} SlotPluginConfig
|
|
55
|
+
* @prop {SlotStep[]} steps
|
|
56
|
+
* @prop {string} doneAction
|
|
57
|
+
* @prop {string} intents
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/** @typedef {import('../Router').Resolver<SlotBotState>} SlotsResolver */
|
|
61
|
+
/** @typedef {Request<SlotBotState>} SlotsRequest */
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {Request<SlotBotState>} req
|
|
65
|
+
* @param {Responder} res
|
|
66
|
+
* @param {Function} postBack
|
|
67
|
+
* @returns {Promise}
|
|
68
|
+
*/
|
|
69
|
+
async function getNextStep (req, res, postBack) {
|
|
70
|
+
let invalid = null;
|
|
71
|
+
const {
|
|
72
|
+
_slotState: slotState, _slotSteps: steps, _slotDone: doneAction
|
|
73
|
+
} = { ...req.state, ...res.newState };
|
|
74
|
+
|
|
75
|
+
for (const slot of slotState) {
|
|
76
|
+
const step = steps.find((s) => s.entity === slot.e);
|
|
77
|
+
if (slot.s === StepState.FILLED && step && step.validateAction) {
|
|
78
|
+
// eslint-disable-next-line
|
|
79
|
+
await postBack(step.validateAction, {}, true);
|
|
80
|
+
if (res.finalMessageSent) {
|
|
81
|
+
invalid = step;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
slot.s = StepState.VALID;
|
|
85
|
+
} else if (slot.s === StepState.FILLED) {
|
|
86
|
+
slot.s = StepState.VALID;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
res.setState({ _slotState: slotState });
|
|
91
|
+
|
|
92
|
+
if (invalid) {
|
|
93
|
+
res.setState({ _slotStep: invalid });
|
|
94
|
+
return Router.END;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const nextStep = slotState.find((s) => s.s !== StepState.VALID);
|
|
98
|
+
|
|
99
|
+
if (!nextStep) {
|
|
100
|
+
postBack(doneAction);
|
|
101
|
+
return Router.END;
|
|
102
|
+
}
|
|
103
|
+
const stepConfig = steps.find((s) => s.entity === nextStep.e);
|
|
104
|
+
res.setState({ _slotStep: stepConfig });
|
|
105
|
+
postBack(stepConfig.askAction);
|
|
106
|
+
return Router.END;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = {
|
|
110
|
+
getNextStep,
|
|
111
|
+
StepState,
|
|
112
|
+
StepType
|
|
113
|
+
};
|
|
@@ -26,15 +26,19 @@ const vars = {
|
|
|
26
26
|
*
|
|
27
27
|
* @param {string} key
|
|
28
28
|
* @param {*} value
|
|
29
|
+
* @param {string} [path]
|
|
29
30
|
* @returns {object}
|
|
30
31
|
* @example
|
|
31
32
|
* const { vars } = require('wingbot');
|
|
32
33
|
* res.setState(vars.dialogContext('myKey', 'foovalue'))
|
|
33
34
|
*/
|
|
34
|
-
dialogContext (key, value) {
|
|
35
|
+
dialogContext (key, value, path = null) {
|
|
35
36
|
return {
|
|
36
37
|
[key]: value,
|
|
37
|
-
[`_~${key}`]: {
|
|
38
|
+
[`_~${key}`]: {
|
|
39
|
+
t: DIALOG_CONTEXT,
|
|
40
|
+
...(path && { p: path })
|
|
41
|
+
}
|
|
38
42
|
};
|
|
39
43
|
},
|
|
40
44
|
|
|
@@ -166,6 +170,17 @@ function mergeState (previousState, req, res, senderStateUpdate, firstInTurnover
|
|
|
166
170
|
case DIALOG_CONTEXT: {
|
|
167
171
|
// compare state
|
|
168
172
|
|
|
173
|
+
if (!lastInTurnover || previousState._lastVisitedPath === undefined) {
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (value.p === true) {
|
|
178
|
+
state[key].p = res.newState._lastVisitedPath;
|
|
179
|
+
} else if (value.p !== res.newState._lastVisitedPath) {
|
|
180
|
+
delete state[key];
|
|
181
|
+
delete state[referencedKey];
|
|
182
|
+
}
|
|
183
|
+
|
|
169
184
|
if (lastInTurnover
|
|
170
185
|
&& previousState._lastVisitedPath !== undefined
|
|
171
186
|
&& value.p !== res.newState._lastVisitedPath) {
|