wingbot 3.53.4 → 3.54.0
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.openai/README.md +1 -0
- package/plugins/ai.wingbot.openai/plugin.js +119 -0
- package/plugins/plugins.json +80 -0
- package/src/AiMatching.js +83 -27
- package/src/Processor.js +2 -1
- package/src/Responder.js +38 -3
- package/src/ReturnSender.js +20 -2
- package/src/transcript/transcriptFromHistory.js +3 -0
package/package.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
> This plugin allows you to use chatGPT
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author wingbot.ai
|
|
3
|
+
*/
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const fetch = require('node-fetch').default;
|
|
7
|
+
const compileWithState = require('../../src/utils/compileWithState');
|
|
8
|
+
|
|
9
|
+
function chatgptPlugin (params) {
|
|
10
|
+
|
|
11
|
+
async function chatgpt (req, res) {
|
|
12
|
+
const content = req.text();
|
|
13
|
+
|
|
14
|
+
const token = compileWithState(req, res, params.token).trim();
|
|
15
|
+
|
|
16
|
+
// gpt-3.5-turbo-0301 gpt-3.5-turbo
|
|
17
|
+
const model = compileWithState(req, res, params.model) || 'gpt-3.5-turbo';
|
|
18
|
+
|
|
19
|
+
const system = compileWithState(req, res, params.system).trim();
|
|
20
|
+
|
|
21
|
+
// 0 - 2
|
|
22
|
+
const temperature = parseFloat(compileWithState(req, res, params.temperature).trim().replace(',', '.') || '1') || 1;
|
|
23
|
+
// presence_penalty between -2.0 and 2.0
|
|
24
|
+
const presence = parseFloat(compileWithState(req, res, params.presence).trim().replace(',', '.') || '0') || 0;
|
|
25
|
+
|
|
26
|
+
const maxTokens = parseFloat(compileWithState(req, res, params.maxTokens).trim() || '512') || 512;
|
|
27
|
+
|
|
28
|
+
const limit = parseInt(compileWithState(req, res, params.maxTokens).trim() || '10', 10) || 10;
|
|
29
|
+
|
|
30
|
+
const user = `${req.pageId}|${req.senderId}`;
|
|
31
|
+
|
|
32
|
+
const systemAfter = compileWithState(req, res, params.systemAfter).trim();
|
|
33
|
+
|
|
34
|
+
let body;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
res.typingOn();
|
|
38
|
+
|
|
39
|
+
body = {
|
|
40
|
+
model,
|
|
41
|
+
frequency_penalty: 0,
|
|
42
|
+
presence_penalty: presence,
|
|
43
|
+
max_tokens: maxTokens,
|
|
44
|
+
temperature,
|
|
45
|
+
user
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const ts = await res.getTranscript(limit);
|
|
49
|
+
|
|
50
|
+
const messages = [
|
|
51
|
+
...(system ? [{ role: 'system', content: system }] : []),
|
|
52
|
+
...ts.map((t) => ({ role: t.fromBot ? 'assistant' : 'user', content: t.text })),
|
|
53
|
+
{ role: 'user', content },
|
|
54
|
+
...(systemAfter ? [{ role: 'system', content: systemAfter }] : [])
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
Object.assign(body, { messages });
|
|
58
|
+
|
|
59
|
+
const useFetch = params.fetch || fetch;
|
|
60
|
+
|
|
61
|
+
const response = await useFetch('https://api.openai.com/v1/chat/completions', {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: {
|
|
64
|
+
'Content-Type': 'application/json',
|
|
65
|
+
Authorization: `Bearer ${token}`
|
|
66
|
+
},
|
|
67
|
+
body: JSON.stringify(body)
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const data = await response.json();
|
|
71
|
+
|
|
72
|
+
if (response.status !== 200
|
|
73
|
+
|| !Array.isArray(data.choices)) {
|
|
74
|
+
const { status, statusText } = response;
|
|
75
|
+
// eslint-disable-next-line no-console
|
|
76
|
+
console.log('chat gpt error', { status, statusText, data });
|
|
77
|
+
throw new Error(`Chat GPT ${status}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// eslint-disable-next-line no-console
|
|
81
|
+
console.log('chat gpt', JSON.stringify(data));
|
|
82
|
+
|
|
83
|
+
const sent = data.choices
|
|
84
|
+
.filter((ch) => ch.message && ch.message.role === 'assistant' && ch.message.content)
|
|
85
|
+
.map((ch) => {
|
|
86
|
+
let filtered = ch.message.content
|
|
87
|
+
.replace(/\n\n/g, '\n')
|
|
88
|
+
.split(/\n+(?!-)/g)
|
|
89
|
+
.filter((t) => !!t.trim());
|
|
90
|
+
|
|
91
|
+
if (filtered.length > 2) {
|
|
92
|
+
filtered = filtered.slice(0, filtered.length - 1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
filtered
|
|
96
|
+
.forEach((t) => res.text(t.trim()));
|
|
97
|
+
|
|
98
|
+
return ch;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (sent.length === 0) {
|
|
102
|
+
const { status, statusText } = response;
|
|
103
|
+
// eslint-disable-next-line no-console
|
|
104
|
+
console.log('chat gpt nothing to send', { status, statusText, data });
|
|
105
|
+
throw new Error('Chat GPT empty');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
} catch (e) {
|
|
109
|
+
// eslint-disable-next-line no-console
|
|
110
|
+
console.error('chat gpt fail', e, body);
|
|
111
|
+
await res.run('fallback');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return chatgpt;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = chatgptPlugin;
|
package/plugins/plugins.json
CHANGED
|
@@ -484,6 +484,86 @@
|
|
|
484
484
|
],
|
|
485
485
|
"items": [
|
|
486
486
|
]
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
"id": "ai.wingbot.openai",
|
|
490
|
+
"name": "OpenAI - Chat",
|
|
491
|
+
"description": "Use within fallback to attach ChatGPT to your chatbot",
|
|
492
|
+
"availableSince": 3.54,
|
|
493
|
+
"editable": false,
|
|
494
|
+
"isFactory": true,
|
|
495
|
+
"category": "conversation",
|
|
496
|
+
"inputs": [
|
|
497
|
+
{
|
|
498
|
+
"name": "token",
|
|
499
|
+
"label": "API Key",
|
|
500
|
+
"type": "text",
|
|
501
|
+
"validations": [
|
|
502
|
+
{ "type": "regexp", "value": "^.+$", "message": "the token should be filled" }
|
|
503
|
+
]
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
"type": "select",
|
|
507
|
+
"name": "model",
|
|
508
|
+
"label": "Chat GPT model",
|
|
509
|
+
"options": [
|
|
510
|
+
{ "value": "gpt-3.5-turbo", "label": "gpt-3.5-turbo" },
|
|
511
|
+
{ "value": "gpt-3.5-turbo-0301", "label": "gpt-3.5-turbo-0301" }
|
|
512
|
+
]
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
"type": "textarea",
|
|
516
|
+
"name": "system",
|
|
517
|
+
"label": "Initial preset message (system)"
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
"type": "text",
|
|
521
|
+
"name": "temperature",
|
|
522
|
+
"label": "Temperature: 0.0 - 2.0 (1.0) - randomness of answers",
|
|
523
|
+
"validations": [
|
|
524
|
+
{
|
|
525
|
+
"type": "regexp",
|
|
526
|
+
"value": "^[0-9]*([.,][0-9]+)?$",
|
|
527
|
+
"message": "Temperature should be a valid float number"
|
|
528
|
+
}
|
|
529
|
+
]
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
"type": "text",
|
|
533
|
+
"name": "presence",
|
|
534
|
+
"label": "Presence: -2.0 - 2.0 (0.0) - likelihood to talk about new topics",
|
|
535
|
+
"validations": [
|
|
536
|
+
{
|
|
537
|
+
"type": "regexp",
|
|
538
|
+
"value": "^-?[0-9]*([.,][0-9]+)?$",
|
|
539
|
+
"message": "Presence should be a valid float number"
|
|
540
|
+
}
|
|
541
|
+
]
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
"type": "text",
|
|
545
|
+
"name": "maxTokens",
|
|
546
|
+
"label": "Max Tokens: 0 - 4096 (512) - max. number of returned tokens",
|
|
547
|
+
"validations": [
|
|
548
|
+
{
|
|
549
|
+
"type": "regexp",
|
|
550
|
+
"value": "^[0-9]*$",
|
|
551
|
+
"message": "Max tokens should be a valid number"
|
|
552
|
+
}
|
|
553
|
+
]
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
"type": "textarea",
|
|
557
|
+
"name": "systemAfter",
|
|
558
|
+
"label": "Current message (system)"
|
|
559
|
+
}
|
|
560
|
+
],
|
|
561
|
+
"items": [
|
|
562
|
+
{
|
|
563
|
+
"id": "fallback",
|
|
564
|
+
"description": "If API call fails"
|
|
565
|
+
}
|
|
566
|
+
]
|
|
487
567
|
}
|
|
488
568
|
],
|
|
489
569
|
"categories": [
|
package/src/AiMatching.js
CHANGED
|
@@ -23,6 +23,16 @@ const FULL_EMOJI_REGEX = /^#((?:[\u2600-\u27bf].?|(?:\ud83c[\udde6-\uddff]){2}|[
|
|
|
23
23
|
const HAS_CLOSING_HASH = /^#(.+)#$/;
|
|
24
24
|
const ENTITY_REGEX = /^@([^=><!?]+)(\?)?([!=><]{1,2})?([^=><!]+)?$/i;
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {object} EntityMatchingResult
|
|
28
|
+
* @prop {number} score
|
|
29
|
+
* @prop {number} handicap
|
|
30
|
+
* @prop {number} fromState
|
|
31
|
+
* @prop {number} minScore
|
|
32
|
+
* @prop {number} metl
|
|
33
|
+
* @prop {Entity[]} matched
|
|
34
|
+
*/
|
|
35
|
+
|
|
26
36
|
/**
|
|
27
37
|
* RegExp to test a string for a ISO 8601 Date spec
|
|
28
38
|
* YYYY
|
|
@@ -449,6 +459,7 @@ class AiMatching {
|
|
|
449
459
|
|
|
450
460
|
const noIntentHandicap = req.intents.length === 0 ? 0 : this.redundantIntentHandicap;
|
|
451
461
|
const regexpScore = this._matchRegexp(req, regexps, noIntentHandicap);
|
|
462
|
+
const textLength = req.text().trim().length;
|
|
452
463
|
|
|
453
464
|
if (regexpScore !== 0 || (intents.length === 0 && regexps.length === 0)) {
|
|
454
465
|
|
|
@@ -477,8 +488,11 @@ class AiMatching {
|
|
|
477
488
|
useState = stateData(req);
|
|
478
489
|
}
|
|
479
490
|
|
|
480
|
-
const {
|
|
491
|
+
const {
|
|
492
|
+
score, handicap, matched, metl
|
|
493
|
+
} = this
|
|
481
494
|
._entityMatching(
|
|
495
|
+
textLength,
|
|
482
496
|
entities,
|
|
483
497
|
reqEntities,
|
|
484
498
|
useState
|
|
@@ -499,30 +513,19 @@ class AiMatching {
|
|
|
499
513
|
? score - noIntentHandicap
|
|
500
514
|
: (regexpScore + score) / 2;
|
|
501
515
|
|
|
502
|
-
|
|
503
|
-
typeof entity.end === 'number' && typeof entity.start === 'number' && tot !== null
|
|
504
|
-
? (tot + (entity.end - entity.start))
|
|
505
|
-
: null
|
|
506
|
-
), 0);
|
|
507
|
-
const textLength = req.text().trim().length;
|
|
508
|
-
|
|
509
|
-
const useHandicap = textLength <= matchedEntitiesTextLength
|
|
510
|
-
? Math.max(-this.redundantEntityHandicap, baseScore - 1)
|
|
511
|
-
: handicap;
|
|
512
|
-
|
|
513
|
-
let finalScore = (baseScore - useHandicap)
|
|
516
|
+
let finalScore = (baseScore - handicap)
|
|
514
517
|
* (this.multiMatchGain ** countOfAdditionalItems);
|
|
515
518
|
|
|
516
|
-
if (
|
|
519
|
+
if (metl && textLength) {
|
|
517
520
|
const remainingScore = Math.max(0, Math.min(1, finalScore) - (
|
|
518
521
|
this._ai.confidence + this.redundantEntityHandicap
|
|
519
522
|
));
|
|
520
523
|
|
|
521
|
-
const remainingTextLen = (textLength -
|
|
524
|
+
const remainingTextLen = (textLength - metl);
|
|
522
525
|
const minus = (remainingTextLen / textLength) * remainingScore;
|
|
523
526
|
|
|
524
527
|
// eslint-disable-next-line max-len,object-curly-newline
|
|
525
|
-
// console.log({ minus,
|
|
528
|
+
// console.log({ minus, metl, textLength, remainingScore })
|
|
526
529
|
|
|
527
530
|
finalScore -= minus;
|
|
528
531
|
}
|
|
@@ -552,7 +555,14 @@ class AiMatching {
|
|
|
552
555
|
let max = total;
|
|
553
556
|
for (const requestIntent of req.intents) {
|
|
554
557
|
const { score, entities: matchedEntities } = this
|
|
555
|
-
._intentMatchingScore(
|
|
558
|
+
._intentMatchingScore(
|
|
559
|
+
textLength,
|
|
560
|
+
wanted,
|
|
561
|
+
requestIntent,
|
|
562
|
+
entities,
|
|
563
|
+
req,
|
|
564
|
+
stateless
|
|
565
|
+
);
|
|
556
566
|
|
|
557
567
|
if (score > max) {
|
|
558
568
|
max = score;
|
|
@@ -579,6 +589,7 @@ class AiMatching {
|
|
|
579
589
|
/**
|
|
580
590
|
*
|
|
581
591
|
* @private
|
|
592
|
+
* @param {number} textLength
|
|
582
593
|
* @param {string} wantedIntent
|
|
583
594
|
* @param {Intent} requestIntent
|
|
584
595
|
* @param {EntityExpression[]} wantedEntities
|
|
@@ -586,7 +597,14 @@ class AiMatching {
|
|
|
586
597
|
* @param {boolean} stateless
|
|
587
598
|
* @returns {{score:number,entities:Entity[]}}
|
|
588
599
|
*/
|
|
589
|
-
_intentMatchingScore (
|
|
600
|
+
_intentMatchingScore (
|
|
601
|
+
textLength,
|
|
602
|
+
wantedIntent,
|
|
603
|
+
requestIntent,
|
|
604
|
+
wantedEntities,
|
|
605
|
+
req,
|
|
606
|
+
stateless = false
|
|
607
|
+
) {
|
|
590
608
|
if (wantedIntent !== requestIntent.intent) {
|
|
591
609
|
return { score: 0, entities: [] };
|
|
592
610
|
}
|
|
@@ -604,12 +622,14 @@ class AiMatching {
|
|
|
604
622
|
score: entitiesScore, handicap, matched, minScore, fromState
|
|
605
623
|
} = this
|
|
606
624
|
._entityMatching(
|
|
625
|
+
textLength,
|
|
607
626
|
wantedEntities,
|
|
608
627
|
useEntities,
|
|
609
628
|
stateless ? {} : req.state,
|
|
610
629
|
requestIntent.entities
|
|
611
630
|
? (x) => Math.atan((x - 0.76) * 40) / Math.atan((1 - 0.76) * 40)
|
|
612
|
-
: (x) => x
|
|
631
|
+
: (x) => x,
|
|
632
|
+
req.entities
|
|
613
633
|
);
|
|
614
634
|
|
|
615
635
|
// eslint-disable-next-line max-len,object-curly-newline
|
|
@@ -639,13 +659,23 @@ class AiMatching {
|
|
|
639
659
|
/**
|
|
640
660
|
*
|
|
641
661
|
* @private
|
|
662
|
+
* @param {number} textLen
|
|
642
663
|
* @param {EntityExpression[]} wantedEntities
|
|
643
664
|
* @param {Entity[]} requestEntities
|
|
644
665
|
* @param {object} [requestState]
|
|
645
666
|
* @param {Function} [scoreFn]
|
|
646
|
-
* @
|
|
667
|
+
* @param {Entity[]} allEntities
|
|
668
|
+
*
|
|
669
|
+
* @returns {EntityMatchingResult}
|
|
647
670
|
*/
|
|
648
|
-
_entityMatching (
|
|
671
|
+
_entityMatching (
|
|
672
|
+
textLen,
|
|
673
|
+
wantedEntities,
|
|
674
|
+
requestEntities = [],
|
|
675
|
+
requestState = {},
|
|
676
|
+
scoreFn = (x) => x,
|
|
677
|
+
allEntities = requestEntities
|
|
678
|
+
) {
|
|
649
679
|
const occurences = new Map();
|
|
650
680
|
|
|
651
681
|
const matched = [];
|
|
@@ -653,6 +683,7 @@ class AiMatching {
|
|
|
653
683
|
let sum = 0;
|
|
654
684
|
let minScore = 1;
|
|
655
685
|
let fromState = 0;
|
|
686
|
+
let metl = 0;
|
|
656
687
|
|
|
657
688
|
for (const wanted of wantedEntities) {
|
|
658
689
|
const usedIndexes = occurences.has(wanted.entity)
|
|
@@ -705,7 +736,7 @@ class AiMatching {
|
|
|
705
736
|
|
|
706
737
|
if (!matching && (!wanted.optional || entityExists)) {
|
|
707
738
|
return {
|
|
708
|
-
score: 0, handicap: 0, matched: [], minScore, fromState
|
|
739
|
+
score: 0, handicap: 0, matched: [], minScore, fromState, metl
|
|
709
740
|
};
|
|
710
741
|
}
|
|
711
742
|
|
|
@@ -731,6 +762,10 @@ class AiMatching {
|
|
|
731
762
|
}
|
|
732
763
|
|
|
733
764
|
if (requestEntity) {
|
|
765
|
+
if (typeof requestEntity.end === 'number' && typeof requestEntity.start === 'number') {
|
|
766
|
+
metl += requestEntity.end - requestEntity.start;
|
|
767
|
+
}
|
|
768
|
+
|
|
734
769
|
matched.push(requestEntity);
|
|
735
770
|
sum += scoreFn(requestEntity.score);
|
|
736
771
|
if (index !== -1) {
|
|
@@ -747,16 +782,37 @@ class AiMatching {
|
|
|
747
782
|
}
|
|
748
783
|
}
|
|
749
784
|
|
|
785
|
+
const withCoveringEntity = textLen && textLen <= metl;
|
|
786
|
+
|
|
750
787
|
// eslint-disable-next-line max-len
|
|
751
|
-
// console.log({ wantedEntities, sum, handicap, rl: requestEntities.length, ml: matched.length });
|
|
788
|
+
// console.log({ metl, withCoveringEntity, wantedEntities, sum, handicap, rl: requestEntities.length, ml: matched.length });
|
|
789
|
+
|
|
790
|
+
if (withCoveringEntity) {
|
|
791
|
+
handicap -= this.redundantEntityHandicap;
|
|
792
|
+
} else {
|
|
793
|
+
const otherEntitiesTextLen = allEntities
|
|
794
|
+
.filter((re) => !matched.some((e) => e.entity === re.entity))
|
|
795
|
+
.reduce((tot, entity) => (
|
|
796
|
+
typeof entity.end === 'number' && typeof entity.start === 'number'
|
|
797
|
+
? (tot + (entity.end - entity.start))
|
|
798
|
+
: 0
|
|
799
|
+
), 0);
|
|
800
|
+
|
|
801
|
+
const coveringHandicap = textLen && otherEntitiesTextLen >= textLen
|
|
802
|
+
? 1
|
|
803
|
+
: 0;
|
|
752
804
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
805
|
+
handicap += (requestEntities.length + fromState - matched.length + coveringHandicap)
|
|
806
|
+
* this.redundantEntityHandicap;
|
|
807
|
+
|
|
808
|
+
// eslint-disable-next-line max-len
|
|
809
|
+
// console.log({ requestEntities, matched, handicap, coveringHandicap, otherEntitiesTextLen });
|
|
810
|
+
|
|
811
|
+
}
|
|
756
812
|
const score = matched.length === 0 ? 0 : sum / matched.length;
|
|
757
813
|
|
|
758
814
|
return {
|
|
759
|
-
score, handicap, matched, minScore, fromState
|
|
815
|
+
score, handicap, matched, minScore, fromState, metl
|
|
760
816
|
};
|
|
761
817
|
}
|
|
762
818
|
|
package/src/Processor.js
CHANGED
|
@@ -646,7 +646,8 @@ class Processor extends EventEmitter {
|
|
|
646
646
|
const options = {
|
|
647
647
|
...this.options,
|
|
648
648
|
state: Object.freeze({ ...state }),
|
|
649
|
-
features
|
|
649
|
+
features,
|
|
650
|
+
pageId
|
|
650
651
|
};
|
|
651
652
|
|
|
652
653
|
res = new Responder(senderId, messageSender, token, options, responderData);
|
package/src/Responder.js
CHANGED
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
FEATURE_SSML,
|
|
17
17
|
FEATURE_PHRASES
|
|
18
18
|
} = require('./features');
|
|
19
|
+
const transcriptFromHistory = require('./transcript/transcriptFromHistory');
|
|
19
20
|
|
|
20
21
|
const TYPE_RESPONSE = 'RESPONSE';
|
|
21
22
|
const TYPE_UPDATE = 'UPDATE';
|
|
@@ -26,6 +27,7 @@ const EXCEPTION_HOPCOUNT_THRESHOLD = 5;
|
|
|
26
27
|
/** @typedef {import('./ReturnSender').UploadResult} UploadResult */
|
|
27
28
|
/** @typedef {import('./analytics/consts').TrackingCategory} TrackingCategory */
|
|
28
29
|
/** @typedef {import('./analytics/consts').TrackingType} TrackingType */
|
|
30
|
+
/** @typedef {import('./transcript/transcriptFromHistory').Transcript} Transcript */
|
|
29
31
|
|
|
30
32
|
/**
|
|
31
33
|
* @enum {string} ExpectedInput
|
|
@@ -89,6 +91,7 @@ class Responder {
|
|
|
89
91
|
constructor (senderId, messageSender, token = null, options = {}, data = {}) {
|
|
90
92
|
this._messageSender = messageSender;
|
|
91
93
|
this._senderId = senderId;
|
|
94
|
+
this._pageId = options.pageId;
|
|
92
95
|
this.token = token;
|
|
93
96
|
|
|
94
97
|
/**
|
|
@@ -178,6 +181,33 @@ class Responder {
|
|
|
178
181
|
this._recipient = { id: senderId };
|
|
179
182
|
|
|
180
183
|
this._textResponses = [];
|
|
184
|
+
|
|
185
|
+
this._typingSent = false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
*
|
|
190
|
+
* Returns current conversation transcript
|
|
191
|
+
*
|
|
192
|
+
* @param {number} [limit]
|
|
193
|
+
* @returns {Promise<Transcript[]>}
|
|
194
|
+
*/
|
|
195
|
+
async getTranscript (limit = 10) {
|
|
196
|
+
const { chatLogStorage } = this._messageSender;
|
|
197
|
+
if (!chatLogStorage) {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
const transcript = await transcriptFromHistory(
|
|
201
|
+
chatLogStorage,
|
|
202
|
+
this._senderId,
|
|
203
|
+
this._pageId,
|
|
204
|
+
limit
|
|
205
|
+
);
|
|
206
|
+
const { responseTexts = [] } = chatLogStorage;
|
|
207
|
+
transcript.push(...responseTexts.map((text) => ({
|
|
208
|
+
fromBot: true, text
|
|
209
|
+
})));
|
|
210
|
+
return transcript;
|
|
181
211
|
}
|
|
182
212
|
|
|
183
213
|
/**
|
|
@@ -287,6 +317,7 @@ class Responder {
|
|
|
287
317
|
});
|
|
288
318
|
}
|
|
289
319
|
this.startedOutput = true;
|
|
320
|
+
this._typingSent = data.sender_action === 'typing_on';
|
|
290
321
|
this._messageSender.send(data);
|
|
291
322
|
return this;
|
|
292
323
|
}
|
|
@@ -979,10 +1010,11 @@ class Responder {
|
|
|
979
1010
|
/**
|
|
980
1011
|
* Sends "typing..." information
|
|
981
1012
|
*
|
|
1013
|
+
* @param {boolean} [force] - send even if was recently sent
|
|
982
1014
|
* @returns {this}
|
|
983
1015
|
*/
|
|
984
|
-
typingOn () {
|
|
985
|
-
return this._senderAction('typing_on');
|
|
1016
|
+
typingOn (force = false) {
|
|
1017
|
+
return this._senderAction('typing_on', force);
|
|
986
1018
|
}
|
|
987
1019
|
|
|
988
1020
|
/**
|
|
@@ -1243,7 +1275,10 @@ class Responder {
|
|
|
1243
1275
|
return this._textResponses;
|
|
1244
1276
|
}
|
|
1245
1277
|
|
|
1246
|
-
_senderAction (action) {
|
|
1278
|
+
_senderAction (action, force = false) {
|
|
1279
|
+
if (action === 'typing_on' && this._typingSent && !force) {
|
|
1280
|
+
return this;
|
|
1281
|
+
}
|
|
1247
1282
|
const messageData = {
|
|
1248
1283
|
sender_action: action
|
|
1249
1284
|
};
|
package/src/ReturnSender.js
CHANGED
|
@@ -12,10 +12,21 @@ const extractText = require('./transcript/extractText');
|
|
|
12
12
|
/** @typedef {import('./Responder')} Responder */
|
|
13
13
|
/** @typedef {import('./Processor').TrackingObject} TrackingObject */
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @callback GetInteractions
|
|
17
|
+
* @param {string} senderId
|
|
18
|
+
* @param {string} pageId
|
|
19
|
+
* @param {number} [limit]
|
|
20
|
+
* @param {number} [endAt] - iterate backwards to history
|
|
21
|
+
* @param {number} [startAt] - iterate forward to last interaction
|
|
22
|
+
* @returns {Promise<object[]>}
|
|
23
|
+
*/
|
|
24
|
+
|
|
15
25
|
/**
|
|
16
26
|
* @typedef {object} ChatLogStorage
|
|
17
|
-
* @
|
|
18
|
-
* @
|
|
27
|
+
* @prop {Function} log
|
|
28
|
+
* @prop {Function} error
|
|
29
|
+
* @prop {GetInteractions} [getInteractions]
|
|
19
30
|
*/
|
|
20
31
|
|
|
21
32
|
/**
|
|
@@ -165,6 +176,13 @@ class ReturnSender {
|
|
|
165
176
|
.filter((t) => t && `${t}`.trim());
|
|
166
177
|
}
|
|
167
178
|
|
|
179
|
+
/**
|
|
180
|
+
* @returns {ChatLogStorage}
|
|
181
|
+
*/
|
|
182
|
+
get chatLogStorage () {
|
|
183
|
+
return this._logger;
|
|
184
|
+
}
|
|
185
|
+
|
|
168
186
|
_gotAnotherEvent () {
|
|
169
187
|
if (this._gotAnotherEventDefer) {
|
|
170
188
|
this._gotAnotherEventDefer();
|
|
@@ -33,6 +33,9 @@ const extractText = require('./extractText');
|
|
|
33
33
|
* @returns {Promise<Transcript[]>}
|
|
34
34
|
*/
|
|
35
35
|
async function transcriptFromHistory (chatLogStorage, senderId, pageId, limit = 20) {
|
|
36
|
+
if (typeof chatLogStorage.getInteractions !== 'function') {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
36
39
|
const data = await chatLogStorage.getInteractions(senderId, pageId, limit);
|
|
37
40
|
|
|
38
41
|
return data
|