botql 1.1.2 → 1.2.2
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/LICENSE +30 -10
- package/Parser.js +5 -1
- package/RAG.js +1 -572
- package/README.md +44 -18
- package/Terminal.js +96 -0
- package/botql.browser.js +248 -526
- package/botql.js +4 -4
- package/createBot.js +88 -0
- package/package.json +3 -2
- package/Bootstrap.js +0 -74
package/botql.browser.js
CHANGED
|
@@ -22,23 +22,29 @@ var BotQL = (() => {
|
|
|
22
22
|
var TokenType = { KEYWORD: "KEYWORD", STRING: "STRING", NUMBER: "NUMBER", IDENT: "IDENT", SYMBOL: "SYMBOL", EOF: "EOF" };
|
|
23
23
|
var KEYWORDS = /* @__PURE__ */ new Set(["CREATE", "BOT", "PLATFORM", "CONNECT", "TABLE", "PREVENT", "DEFAULT", "ON", "START", "MESSAGE", "FROM", "WHEN", "CONTAINS", "OR", "OTHERWISE", "KEYWORDS", "THINK", "WAITING", "REPLY", "TO", "FORWARD", "PARSE", "SEND", "INSERT", "INTO", "VALUES", "UPDATE", "SET", "WHERE", "RUN", "IMPORT", "AS", "RESPONSE"]);
|
|
24
24
|
var Token = class {
|
|
25
|
-
constructor(e, t, s) {
|
|
26
|
-
this.type = e, this.value = t, this.line = s;
|
|
25
|
+
constructor(e, t, s, r, i) {
|
|
26
|
+
this.type = e, this.value = t, this.line = s, this.start = r, this.end = i;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var BotQLSyntaxError = class extends Error {
|
|
30
|
+
constructor(e, { line: t = null, start: s = 0, end: r = null, near: i = null } = {}) {
|
|
31
|
+
super(e), this.name = "BotQLSyntaxError", this.line = t, this.start = s, this.end = null === r ? s + 1 : r, this.near = i;
|
|
27
32
|
}
|
|
28
33
|
};
|
|
29
34
|
var Tokenizer = class {
|
|
30
35
|
constructor(e) {
|
|
31
36
|
this.source = e, this.pos = 0, this.line = 1, this.tokens = [];
|
|
32
37
|
}
|
|
33
|
-
error(e) {
|
|
34
|
-
|
|
38
|
+
error(e, t, s) {
|
|
39
|
+
let r = void 0 !== t ? t : this.pos;
|
|
40
|
+
throw new BotQLSyntaxError(`syntax error: ${e}`, { line: this.line, start: r, end: s });
|
|
35
41
|
}
|
|
36
42
|
peekChar(e = 0) {
|
|
37
43
|
return this.source[this.pos + e];
|
|
38
44
|
}
|
|
39
45
|
tokenize() {
|
|
40
46
|
for (; this.pos < this.source.length; ) {
|
|
41
|
-
let e = this.peekChar();
|
|
47
|
+
let e = this.peekChar(), t = this.pos;
|
|
42
48
|
if ("\n" === e) {
|
|
43
49
|
this.line++, this.pos++;
|
|
44
50
|
continue;
|
|
@@ -51,39 +57,24 @@ var BotQL = (() => {
|
|
|
51
57
|
for (; this.pos < this.source.length && "\n" !== this.peekChar(); ) this.pos++;
|
|
52
58
|
continue;
|
|
53
59
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
if (/[0-9]/.test(e)) {
|
|
59
|
-
this.tokens.push(this.readNumber());
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
if ("{}(),.=+*;".includes(e)) {
|
|
63
|
-
this.tokens.push(new Token(TokenType.SYMBOL, e, this.line)), this.pos++;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
if (/[A-Za-z_]/.test(e)) {
|
|
67
|
-
let t = this.readWord();
|
|
68
|
-
this.tokens.push(t);
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
this.error(`unexpected character: "${e}"`);
|
|
60
|
+
let s;
|
|
61
|
+
'"' === e || "'" === e ? s = this.readString(e) : /[0-9]/.test(e) ? s = this.readNumber() : "{}(),.=+*;".includes(e) ? (s = new Token(TokenType.SYMBOL, e, this.line), this.pos++) : /[A-Za-z_]/.test(e) ? s = this.readWord() : this.error(`unexpected character: "${e}"`, t, t + 1), s.start = t, s.end = this.pos, this.tokens.push(s);
|
|
72
62
|
}
|
|
73
|
-
|
|
63
|
+
let r = this.source.length, i = new Token(TokenType.EOF, null, this.line, r, r);
|
|
64
|
+
return this.tokens.push(i), this.tokens;
|
|
74
65
|
}
|
|
75
66
|
readString(e) {
|
|
76
|
-
let t = this.line;
|
|
67
|
+
let t = this.line, s = this.pos;
|
|
77
68
|
this.pos++;
|
|
78
|
-
let
|
|
69
|
+
let r = "";
|
|
79
70
|
for (; this.pos < this.source.length && this.peekChar() !== e; ) {
|
|
80
71
|
if ("\\" === this.peekChar()) {
|
|
81
|
-
this.pos++,
|
|
72
|
+
this.pos++, r += this.peekChar(), this.pos++;
|
|
82
73
|
continue;
|
|
83
74
|
}
|
|
84
|
-
"\n" === this.peekChar() && this.line++,
|
|
75
|
+
"\n" === this.peekChar() && this.line++, r += this.peekChar(), this.pos++;
|
|
85
76
|
}
|
|
86
|
-
return this.peekChar() !== e && this.error("unterminated string"), this.pos++, new Token(TokenType.STRING,
|
|
77
|
+
return this.peekChar() !== e && this.error("unterminated string", s, this.pos), this.pos++, new Token(TokenType.STRING, r, t);
|
|
87
78
|
}
|
|
88
79
|
readNumber() {
|
|
89
80
|
let e = this.line, t = "";
|
|
@@ -99,11 +90,11 @@ var BotQL = (() => {
|
|
|
99
90
|
};
|
|
100
91
|
var Parser = class {
|
|
101
92
|
constructor(e) {
|
|
102
|
-
this.tokens = new Tokenizer(e).tokenize(), this.pos = 0;
|
|
93
|
+
this.source = e, this.tokens = new Tokenizer(e).tokenize(), this.pos = 0;
|
|
103
94
|
}
|
|
104
95
|
error(e) {
|
|
105
|
-
let t = this.current();
|
|
106
|
-
throw
|
|
96
|
+
let t = this.current(), s = t && t.type !== TokenType.EOF ? t.value : "EOF", r = t ? t.start : this.source.length, i = t ? t.end : this.source.length;
|
|
97
|
+
throw new BotQLSyntaxError(`syntax error: ${e}, near "${s}"`, { line: t ? t.line : null, start: r, end: i, near: s });
|
|
107
98
|
}
|
|
108
99
|
current() {
|
|
109
100
|
return this.tokens[this.pos];
|
|
@@ -147,7 +138,7 @@ var BotQL = (() => {
|
|
|
147
138
|
return [this.parseStatement()];
|
|
148
139
|
}
|
|
149
140
|
parseStatement() {
|
|
150
|
-
return this.atKeyword("CREATE") ? this.parseCreate() : this.atKeyword("PLATFORM") ? this.parsePlatform() : this.atKeyword("CONNECT") ? this.parseConnect() : this.atKeyword("ON") ? this.parseOn() : this.atKeyword("WHEN") ? this.parseWhen() : this.atKeyword("OTHERWISE") ? this.parseOtherwise() : this.atKeyword("REPLY") ? this.parseReply() : this.atKeyword("THINK") ? this.parseThink() : this.atKeyword("FORWARD") ? this.parseForward() : this.atKeyword("PARSE") ? this.parseParseSignal() : this.atKeyword("SEND") ? this.parseSend() : this.atKeyword("INSERT") ? this.parseInsert() : this.atKeyword("UPDATE") ? this.parseUpdate() : this.atKeyword("RUN") ? this.parseRunBot() : this.atKeyword("IMPORT") ? this.parseImport() : void this.error("unexpected statement");
|
|
141
|
+
return this.atKeyword("CREATE") ? this.parseCreate() : this.atKeyword("PLATFORM") ? this.parsePlatform() : this.atKeyword("CONNECT") ? this.parseConnect() : this.atKeyword("ON") ? this.parseOn() : this.atKeyword("WHEN") ? this.parseWhen() : this.atKeyword("OTHERWISE") ? this.parseOtherwise() : this.atKeyword("REPLY") ? this.parseReply() : this.atKeyword("THINK") ? this.parseThink() : this.atKeyword("WAITING") ? this.parseWaitingStatement() : this.atKeyword("FORWARD") ? this.parseForward() : this.atKeyword("PARSE") ? this.parseParseSignal() : this.atKeyword("SEND") ? this.parseSend() : this.atKeyword("INSERT") ? this.parseInsert() : this.atKeyword("UPDATE") ? this.parseUpdate() : this.atKeyword("RUN") ? this.parseRunBot() : this.atKeyword("IMPORT") ? this.parseImport() : void this.error("unexpected statement");
|
|
151
142
|
}
|
|
152
143
|
parseCreate() {
|
|
153
144
|
if (this.expectKeyword("CREATE"), this.atKeyword("BOT")) {
|
|
@@ -270,8 +261,12 @@ var BotQL = (() => {
|
|
|
270
261
|
tryParseWaiting() {
|
|
271
262
|
if (!this.atKeyword("WAITING")) return null;
|
|
272
263
|
this.advance(), this.expect(TokenType.SYMBOL, "(");
|
|
273
|
-
let e = null, t = null;
|
|
274
|
-
return !this.at(TokenType.SYMBOL, ")") && (e = this.parseFileName(), this.at(TokenType.SYMBOL, ",") && (this.advance(),
|
|
264
|
+
let e = null, t = null, s = null;
|
|
265
|
+
return !this.at(TokenType.SYMBOL, ")") && (this.at(TokenType.STRING) ? t = this.advance().value : e = this.parseFileName(), this.at(TokenType.SYMBOL, ",") && (this.advance(), s = this.parseExpression())), this.expect(TokenType.SYMBOL, ")"), { file: e, text: t, seconds: s };
|
|
266
|
+
}
|
|
267
|
+
parseWaitingStatement() {
|
|
268
|
+
let e = this.tryParseWaiting();
|
|
269
|
+
return { type: "Waiting", file: e.file, text: e.text, seconds: e.seconds };
|
|
275
270
|
}
|
|
276
271
|
parseReply() {
|
|
277
272
|
this.expectKeyword("REPLY");
|
|
@@ -374,7 +369,7 @@ var BotQL = (() => {
|
|
|
374
369
|
this.error("invalid expression");
|
|
375
370
|
}
|
|
376
371
|
};
|
|
377
|
-
module.exports = { Parser, Tokenizer, TokenType, KEYWORDS };
|
|
372
|
+
module.exports = { Parser, Tokenizer, TokenType, KEYWORDS, BotQLSyntaxError };
|
|
378
373
|
}
|
|
379
374
|
});
|
|
380
375
|
|
|
@@ -530,505 +525,221 @@ var BotQL = (() => {
|
|
|
530
525
|
var require_RAG = __commonJS({
|
|
531
526
|
"RAG.js"(exports, module) {
|
|
532
527
|
"use strict";
|
|
533
|
-
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
534
|
-
|
|
535
|
-
"
|
|
536
|
-
"as",
|
|
537
|
-
"os",
|
|
538
|
-
"de",
|
|
539
|
-
"da",
|
|
540
|
-
"do",
|
|
541
|
-
"das",
|
|
542
|
-
"dos",
|
|
543
|
-
"e",
|
|
544
|
-
"ou",
|
|
545
|
-
"que",
|
|
546
|
-
"um",
|
|
547
|
-
"uma",
|
|
548
|
-
"uns",
|
|
549
|
-
"umas",
|
|
550
|
-
"em",
|
|
551
|
-
"no",
|
|
552
|
-
"na",
|
|
553
|
-
"nos",
|
|
554
|
-
"nas",
|
|
555
|
-
"por",
|
|
556
|
-
"para",
|
|
557
|
-
"com",
|
|
558
|
-
"sem",
|
|
559
|
-
"se",
|
|
560
|
-
"foi",
|
|
561
|
-
"ser",
|
|
562
|
-
"sao",
|
|
563
|
-
"esta",
|
|
564
|
-
"estao",
|
|
565
|
-
"ao",
|
|
566
|
-
"aos",
|
|
567
|
-
"mas",
|
|
568
|
-
"como",
|
|
569
|
-
"tem",
|
|
570
|
-
"ter",
|
|
571
|
-
"nao",
|
|
572
|
-
"sim",
|
|
573
|
-
"meu",
|
|
574
|
-
"minha",
|
|
575
|
-
"seu",
|
|
576
|
-
"sua",
|
|
577
|
-
"eu",
|
|
578
|
-
"tu",
|
|
579
|
-
"ele",
|
|
580
|
-
"ela",
|
|
581
|
-
"nos",
|
|
582
|
-
"vos",
|
|
583
|
-
"eles",
|
|
584
|
-
"elas",
|
|
585
|
-
"isso",
|
|
586
|
-
"isto",
|
|
587
|
-
"aquilo",
|
|
588
|
-
"quando",
|
|
589
|
-
"onde",
|
|
590
|
-
"porque",
|
|
591
|
-
"qual",
|
|
592
|
-
"quais",
|
|
593
|
-
"muito",
|
|
594
|
-
"muita",
|
|
595
|
-
"ja",
|
|
596
|
-
"so"
|
|
597
|
-
]);
|
|
598
|
-
function normalizar(texto) {
|
|
599
|
-
return texto.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
528
|
+
var STOPWORDS = /* @__PURE__ */ new Set(["a", "o", "as", "os", "de", "da", "do", "das", "dos", "e", "ou", "que", "um", "uma", "uns", "umas", "em", "no", "na", "nos", "nas", "por", "para", "com", "sem", "se", "foi", "ser", "sao", "esta", "estao", "ao", "aos", "mas", "como", "tem", "ter", "nao", "sim", "meu", "minha", "seu", "sua", "eu", "tu", "ele", "ela", "nos", "vos", "eles", "elas", "isso", "isto", "aquilo", "quando", "onde", "porque", "qual", "quais", "muito", "muita", "ja", "so"]);
|
|
529
|
+
function normalizar(e) {
|
|
530
|
+
return e.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
600
531
|
}
|
|
601
532
|
var SUFIXOS_ADJETIVO_ADVERBIO = ["issimamente", "issimo", "issima", "mente"];
|
|
602
533
|
var SUFIXOS_NOMINALIZACAO = ["acoes", "acao", "imentos", "imento", "idades", "idade"];
|
|
603
|
-
function stem(
|
|
604
|
-
let
|
|
605
|
-
for (
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
break;
|
|
609
|
-
}
|
|
534
|
+
function stem(e) {
|
|
535
|
+
let t = e;
|
|
536
|
+
for (let i of SUFIXOS_ADJETIVO_ADVERBIO) if (t.length > i.length + 3 && t.endsWith(i)) {
|
|
537
|
+
t = t.slice(0, -i.length);
|
|
538
|
+
break;
|
|
610
539
|
}
|
|
611
|
-
for (
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
break;
|
|
615
|
-
}
|
|
540
|
+
for (let o of SUFIXOS_NOMINALIZACAO) if (t.length > o.length + 3 && t.endsWith(o)) {
|
|
541
|
+
t = t.slice(0, -o.length);
|
|
542
|
+
break;
|
|
616
543
|
}
|
|
617
|
-
|
|
618
|
-
p = p.slice(0, -1);
|
|
619
|
-
}
|
|
620
|
-
return p;
|
|
544
|
+
return t.length > 4 && t.endsWith("s") && !t.endsWith("ns") && (t = t.slice(0, -1)), t;
|
|
621
545
|
}
|
|
622
|
-
function tokenizar(
|
|
623
|
-
return normalizar(
|
|
546
|
+
function tokenizar(e) {
|
|
547
|
+
return normalizar(e).toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((e2) => e2.length > 1 && !STOPWORDS.has(e2)).map(stem);
|
|
624
548
|
}
|
|
625
|
-
function distanciaEdicao(
|
|
626
|
-
if (
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
if (
|
|
630
|
-
|
|
631
|
-
let
|
|
632
|
-
for (let
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
linhaAtual[j] = Math.min(
|
|
638
|
-
linhaAtual[j - 1] + 1,
|
|
639
|
-
linhaAnterior[j] + 1,
|
|
640
|
-
linhaAnterior[j - 1] + custo
|
|
641
|
-
);
|
|
549
|
+
function distanciaEdicao(e, t) {
|
|
550
|
+
if (e === t) return 0;
|
|
551
|
+
let i = e.length, o = t.length;
|
|
552
|
+
if (0 === i) return o;
|
|
553
|
+
if (0 === o) return i;
|
|
554
|
+
let s = Array(o + 1);
|
|
555
|
+
for (let r = 0; r <= o; r++) s[r] = r;
|
|
556
|
+
for (let n = 1; n <= i; n++) {
|
|
557
|
+
let a = [n];
|
|
558
|
+
for (let l = 1; l <= o; l++) {
|
|
559
|
+
let c = e[n - 1] === t[l - 1] ? 0 : 1;
|
|
560
|
+
a[l] = Math.min(a[l - 1] + 1, s[l] + 1, s[l - 1] + c);
|
|
642
561
|
}
|
|
643
|
-
|
|
562
|
+
s = a;
|
|
644
563
|
}
|
|
645
|
-
return
|
|
564
|
+
return s[o];
|
|
646
565
|
}
|
|
647
|
-
function distanciaMaximaTolerada(
|
|
648
|
-
|
|
649
|
-
if (tamanho <= 7) return 1;
|
|
650
|
-
return 2;
|
|
566
|
+
function distanciaMaximaTolerada(e) {
|
|
567
|
+
return e <= 4 ? 0 : e <= 7 ? 1 : 2;
|
|
651
568
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
var CONFIANCA_ALTA = 0.55;
|
|
658
|
-
var CONFIANCA_MINIMA = 0.15;
|
|
659
|
-
var MARGEM_MINIMA = 0.12;
|
|
660
|
-
var SIMILARIDADE_DUPLICADA = 0.75;
|
|
661
|
-
function extrairNomesProprios(texto) {
|
|
662
|
-
const palavras = texto.split(/\s+/);
|
|
663
|
-
const encontrados = [];
|
|
664
|
-
for (let i = 1; i < palavras.length; i++) {
|
|
665
|
-
const limpa = palavras[i].replace(/^[(["']+|[.,;:!?)\]"']+$/g, "");
|
|
666
|
-
if (/^[A-ZÀ-Ý][a-zà-ÿ]+$/.test(limpa)) {
|
|
667
|
-
encontrados.push(limpa);
|
|
668
|
-
}
|
|
569
|
+
function extrairNomesProprios(e) {
|
|
570
|
+
let t = e.split(/\s+/), i = [];
|
|
571
|
+
for (let o = 1; o < t.length; o++) {
|
|
572
|
+
let s = t[o].replace(/^[(["']+|[.,;:!?)\]"']+$/g, "");
|
|
573
|
+
/^[A-ZÀ-Ý][a-zà-ÿ]+$/.test(s) && i.push(s);
|
|
669
574
|
}
|
|
670
|
-
return
|
|
575
|
+
return i;
|
|
671
576
|
}
|
|
672
577
|
var KnowledgeIndex = class _KnowledgeIndex {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
* @param {Record<string,string>} [options.synonyms] Mapa de termo -> termo
|
|
677
|
-
* canónico, para ligar manualmente palavras que o stemmer não junta
|
|
678
|
-
* sozinho (ex: { horas: 'horario' }).
|
|
679
|
-
*/
|
|
680
|
-
constructor(sourceText, options = {}) {
|
|
681
|
-
this.synonyms = {};
|
|
682
|
-
for (const [de, para] of Object.entries(options.synonyms || {})) {
|
|
683
|
-
this.synonyms[stem(normalizar(de.toLowerCase()))] = stem(normalizar(para.toLowerCase()));
|
|
684
|
-
}
|
|
685
|
-
this.blocks = _KnowledgeIndex.parseBlocks(sourceText);
|
|
686
|
-
this._buildIndex();
|
|
578
|
+
constructor(e, t = {}) {
|
|
579
|
+
for (let [i, o] of (this.synonyms = {}, Object.entries(t.synonyms || {}))) this.synonyms[stem(normalizar(i.toLowerCase()))] = stem(normalizar(o.toLowerCase()));
|
|
580
|
+
this.blocks = _KnowledgeIndex.parseBlocks(e), this._buildIndex();
|
|
687
581
|
}
|
|
688
|
-
static parseBlocks(
|
|
689
|
-
return
|
|
582
|
+
static parseBlocks(e) {
|
|
583
|
+
return e.split(/\n\s*\n/).map((e2) => e2.trim()).filter((e2) => e2.length > 0);
|
|
690
584
|
}
|
|
691
|
-
_aplicarSinonimos(
|
|
692
|
-
return
|
|
585
|
+
_aplicarSinonimos(e) {
|
|
586
|
+
return e.map((e2) => this.synonyms[e2] || e2);
|
|
693
587
|
}
|
|
694
588
|
_buildIndex() {
|
|
695
|
-
this.docs = this.blocks.map((
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
this.vocabulario = /* @__PURE__ */ new Set();
|
|
699
|
-
for (const tokens of this.docs) {
|
|
700
|
-
const vistas = new Set(tokens);
|
|
701
|
-
for (const palavra of vistas) {
|
|
702
|
-
this.docFreq.set(palavra, (this.docFreq.get(palavra) || 0) + 1);
|
|
703
|
-
this.vocabulario.add(palavra);
|
|
704
|
-
}
|
|
589
|
+
for (let e of (this.docs = this.blocks.map((e2) => tokenizar(e2)), this.docTextNormalizado = this.blocks.map((e2) => normalizar(e2).toLowerCase()), this.docPalavrasSignificativas = this.blocks.map((e2) => this._palavrasSignificativas(e2)), this.docFreq = /* @__PURE__ */ new Map(), this.vocabulario = /* @__PURE__ */ new Set(), this.docs)) {
|
|
590
|
+
let t = new Set(e);
|
|
591
|
+
for (let i of t) this.docFreq.set(i, (this.docFreq.get(i) || 0) + 1), this.vocabulario.add(i);
|
|
705
592
|
}
|
|
706
|
-
this.listaVocabulario = Array.from(this.vocabulario);
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
for (const candidato of this.listaVocabulario) {
|
|
721
|
-
if (Math.abs(candidato.length - termo.length) > tolerancia) continue;
|
|
722
|
-
const d = distanciaEdicao(termo, candidato);
|
|
723
|
-
if (d < melhorDist) {
|
|
724
|
-
melhorDist = d;
|
|
725
|
-
melhor = candidato;
|
|
726
|
-
}
|
|
593
|
+
this.listaVocabulario = Array.from(this.vocabulario), this.totalDocs = this.docs.length, this.avgDocLen = 0 === this.totalDocs ? 0 : this.docs.reduce((e, t) => e + t.length, 0) / this.totalDocs;
|
|
594
|
+
}
|
|
595
|
+
_idf(e) {
|
|
596
|
+
let t = this.docFreq.get(e) || 0;
|
|
597
|
+
return 0 === t ? 0 : Math.log(1 + (this.totalDocs - t + 0.5) / (t + 0.5));
|
|
598
|
+
}
|
|
599
|
+
_termoMaisProximo(e) {
|
|
600
|
+
let t = distanciaMaximaTolerada(e.length);
|
|
601
|
+
if (0 === t) return null;
|
|
602
|
+
let i = null, o = t + 1;
|
|
603
|
+
for (let s of this.listaVocabulario) {
|
|
604
|
+
if (Math.abs(s.length - e.length) > t) continue;
|
|
605
|
+
let r = distanciaEdicao(e, s);
|
|
606
|
+
r < o && (o = r, i = s);
|
|
727
607
|
}
|
|
728
|
-
return
|
|
729
|
-
}
|
|
730
|
-
_scoreBM25(
|
|
731
|
-
|
|
732
|
-
if (
|
|
733
|
-
|
|
734
|
-
for (
|
|
735
|
-
let
|
|
736
|
-
for (
|
|
737
|
-
let
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
if (proximo === null) continue;
|
|
743
|
-
tf = termFreq.get(proximo) || 0;
|
|
744
|
-
if (tf === 0) continue;
|
|
745
|
-
idf = this._idf(proximo);
|
|
746
|
-
peso = PESO_FUZZY;
|
|
608
|
+
return o <= t ? i : null;
|
|
609
|
+
}
|
|
610
|
+
_scoreBM25(e, t) {
|
|
611
|
+
let i = this.docs[t];
|
|
612
|
+
if (0 === i.length) return 0;
|
|
613
|
+
let o = /* @__PURE__ */ new Map();
|
|
614
|
+
for (let s of i) o.set(s, (o.get(s) || 0) + 1);
|
|
615
|
+
let r = 0;
|
|
616
|
+
for (let n of e) {
|
|
617
|
+
let a = o.get(n) || 0, l = this._idf(n), c = 1;
|
|
618
|
+
if (0 === a) {
|
|
619
|
+
let d = this._termoMaisProximo(n);
|
|
620
|
+
if (null === d || 0 === (a = o.get(d) || 0)) continue;
|
|
621
|
+
l = this._idf(d), c = 0.4;
|
|
747
622
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
score += peso * idf * (numerador / denominador);
|
|
623
|
+
let h = 2.5 * a, u = a + 1.5 * (0.7 + 0.3 * (i.length / this.avgDocLen));
|
|
624
|
+
r += c * l * (h / u);
|
|
751
625
|
}
|
|
752
|
-
return
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
626
|
+
return r;
|
|
627
|
+
}
|
|
628
|
+
_palavrasSignificativas(e) {
|
|
629
|
+
return normalizar(e).toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((e2) => e2.length > 1 && !STOPWORDS.has(e2));
|
|
630
|
+
}
|
|
631
|
+
_bonusFrase(e, t) {
|
|
632
|
+
let i = this._palavrasSignificativas(e);
|
|
633
|
+
if (i.length < 2) return 0;
|
|
634
|
+
let o = this.docPalavrasSignificativas[t], s = 0;
|
|
635
|
+
for (let r = Math.min(6, i.length); r >= 2; r--) {
|
|
636
|
+
for (let n = 0; n + r <= i.length; n++) {
|
|
637
|
+
let a = i.slice(n, n + r);
|
|
638
|
+
if (this._contemSequencia(o, a)) {
|
|
639
|
+
s = r;
|
|
640
|
+
break;
|
|
765
641
|
}
|
|
766
642
|
}
|
|
643
|
+
if (s > 0) break;
|
|
767
644
|
}
|
|
768
|
-
return
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
}
|
|
775
|
-
// Calcula o score de todos os blocos para a mensagem e devolve ordenado
|
|
776
|
-
// do maior para o menor. Base partilhada por search() e analyze().
|
|
777
|
-
_rankTodos(message) {
|
|
778
|
-
const queryTokens = this._aplicarSinonimos(tokenizar(message));
|
|
779
|
-
if (queryTokens.length === 0) return [];
|
|
780
|
-
const resultados = [];
|
|
781
|
-
for (let i = 0; i < this.totalDocs; i++) {
|
|
782
|
-
resultados.push({ index: i, score: this._scoreDoc(message, queryTokens, i) });
|
|
783
|
-
}
|
|
784
|
-
resultados.sort((a, b) => b.score - a.score);
|
|
785
|
-
return resultados;
|
|
786
|
-
}
|
|
787
|
-
/**
|
|
788
|
-
* Procura o bloco mais relevante para a mensagem recebida.
|
|
789
|
-
*
|
|
790
|
-
* @returns {{text: string, score: number, confidence: number, index: number} | null}
|
|
791
|
-
* `confidence` está sempre entre 0 e 1 — não é probabilidade
|
|
792
|
-
* estatística real, é uma escala interpretável para decidir um
|
|
793
|
-
* limiar no `.sql` (ex: "só responde se confidence > 0.3").
|
|
794
|
-
*/
|
|
795
|
-
search(message) {
|
|
796
|
-
if (this.totalDocs === 0) return null;
|
|
797
|
-
const ranking = this._rankTodos(message);
|
|
798
|
-
if (ranking.length === 0) return null;
|
|
799
|
-
const melhor = ranking[0];
|
|
800
|
-
if (melhor.score <= 0) return null;
|
|
801
|
-
return {
|
|
802
|
-
text: this.blocks[melhor.index],
|
|
803
|
-
score: melhor.score,
|
|
804
|
-
confidence: melhor.score / (melhor.score + 3),
|
|
805
|
-
index: melhor.index
|
|
806
|
-
};
|
|
807
|
-
}
|
|
808
|
-
// Jaccard sobre os tokens (já com stem aplicado) de dois blocos já
|
|
809
|
-
// indexados. Usado só pra detetar "mesmo conteúdo repetido" (valor
|
|
810
|
-
// alto) — dois blocos sobre o mesmo assunto mas com detalhes
|
|
811
|
-
// diferentes normalmente NÃO têm jaccard alto (a maior parte da frase
|
|
812
|
-
// difere), por isso não serve pra decidir se vale a pena tentar unir.
|
|
813
|
-
_similaridadeBlocos(indexA, indexB) {
|
|
814
|
-
const a = new Set(this.docs[indexA]);
|
|
815
|
-
const b = new Set(this.docs[indexB]);
|
|
816
|
-
if (a.size === 0 || b.size === 0) return 0;
|
|
817
|
-
let intersecao = 0;
|
|
818
|
-
for (const t of a) if (b.has(t)) intersecao++;
|
|
819
|
-
const uniao = a.size + b.size - intersecao;
|
|
820
|
-
return uniao === 0 ? 0 : intersecao / uniao;
|
|
821
|
-
}
|
|
822
|
-
// Quantos tokens (com stem) os dois blocos partilham, em termos
|
|
823
|
-
// absolutos. Usado como gatilho pra tentar unir: basta partilharem UM
|
|
824
|
-
// termo de assunto ("entregamos") — quem garante que a fusão é segura
|
|
825
|
-
// não é isto, é a regra de "exatamente um nome próprio diferente em
|
|
826
|
-
// cada bloco" dentro de _tentarUnir.
|
|
827
|
-
_termosPartilhados(indexA, indexB) {
|
|
828
|
-
const a = new Set(this.docs[indexA]);
|
|
829
|
-
const b = this.docs[indexB];
|
|
830
|
-
let count = 0;
|
|
831
|
-
for (const t of b) if (a.has(t)) count++;
|
|
832
|
-
return count;
|
|
833
|
-
}
|
|
834
|
-
// Compara dois blocos concorrentes pelo nome próprio que cada um
|
|
835
|
-
// menciona, pra decidir se são "a mesma informação" ou "informação
|
|
836
|
-
// complementar que dá pra unir":
|
|
837
|
-
// - mesmo nome próprio nos dois (ex: os dois falam de Luanda)
|
|
838
|
-
// -> { tipo: 'duplicado' }: é a mesma coisa dita de formas
|
|
839
|
-
// diferentes, não há o que unir, usa qualquer um dos dois
|
|
840
|
-
// - nomes próprios diferentes (ex: Luanda vs Huambo)
|
|
841
|
-
// -> { tipo: 'unido', texto: '...' }: funde numa frase só
|
|
842
|
-
// - não dá pra identificar com segurança (nenhum nome próprio, ou
|
|
843
|
-
// mais de um em algum dos blocos) -> null: quem chama decide o
|
|
844
|
-
// que fazer a seguir (normalmente: reanalisar, ou cair no fallback)
|
|
845
|
-
_tentarUnir(textoA, textoB) {
|
|
846
|
-
const entidadesA = extrairNomesProprios(textoA);
|
|
847
|
-
const entidadesB = extrairNomesProprios(textoB);
|
|
848
|
-
if (entidadesA.length !== 1 || entidadesB.length !== 1) return null;
|
|
849
|
-
const entidadeA = entidadesA[0];
|
|
850
|
-
const entidadeB = entidadesB[0];
|
|
851
|
-
if (normalizar(entidadeA).toLowerCase() === normalizar(entidadeB).toLowerCase()) {
|
|
852
|
-
return { tipo: "duplicado" };
|
|
853
|
-
}
|
|
854
|
-
const primeiraPalavra = textoA.trim().split(/\s+/)[0].replace(/[.,;:!?]+$/, "");
|
|
855
|
-
return { tipo: "unido", texto: `${primeiraPalavra} em v\xE1rios lugares, como ${entidadeA} e ${entidadeB}.` };
|
|
856
|
-
}
|
|
857
|
-
// Segunda tentativa de ranking, usada só quando a primeira ficou
|
|
858
|
-
// ambígua: descarta metade dos termos da query (os de menor IDF, ou
|
|
859
|
-
// seja, os mais genéricos/comuns) e refaz o ranking só com os termos
|
|
860
|
-
// mais raros/decisivos. Uma query mais focada às vezes desempata o
|
|
861
|
-
// que uma query "cheia" deixa embolado.
|
|
862
|
-
_reanalisarFocado(message) {
|
|
863
|
-
const tokens = this._aplicarSinonimos(tokenizar(message));
|
|
864
|
-
if (tokens.length <= 2) return null;
|
|
865
|
-
const comIdf = tokens.map((t) => ({ termo: t, idf: this._idf(t) }));
|
|
866
|
-
comIdf.sort((a, b) => b.idf - a.idf);
|
|
867
|
-
const focados = comIdf.slice(0, Math.max(1, Math.ceil(tokens.length / 2))).map((x) => x.termo);
|
|
868
|
-
const resultados = [];
|
|
869
|
-
for (let i = 0; i < this.totalDocs; i++) {
|
|
870
|
-
resultados.push({ index: i, score: this._scoreDoc(message, focados, i) });
|
|
871
|
-
}
|
|
872
|
-
resultados.sort((a, b) => b.score - a.score);
|
|
873
|
-
return resultados;
|
|
874
|
-
}
|
|
875
|
-
/**
|
|
876
|
-
* Versão completa da busca: além do melhor bloco, avalia a evidência
|
|
877
|
-
* (melhor x segundo colocado) e devolve uma decisão explícita, em vez
|
|
878
|
-
* de deixar o `.sql` decidir tudo com um único limiar de confidence.
|
|
879
|
-
*
|
|
880
|
-
* EvidenceEvaluator: compara o melhor resultado com o segundo. Se os
|
|
881
|
-
* dois estão muito próximos, o motor não tem certeza de qual bloco
|
|
882
|
-
* responde à pergunta — mesmo que o score absoluto seja alto.
|
|
883
|
-
*
|
|
884
|
-
* ConfidenceEngine: mesma fórmula de sempre (score / (score + 3)),
|
|
885
|
-
* aplicada só ao melhor resultado.
|
|
886
|
-
*
|
|
887
|
-
* DecisionEngine: cruza confidence com margem para decidir entre:
|
|
888
|
-
* - RESPONDER confidence alta e o melhor bloco se destaca do 2o,
|
|
889
|
-
* OU os dois concorrentes foram reconciliados (ver
|
|
890
|
-
* abaixo) — nesse caso `texto` já vem pronto pra usar
|
|
891
|
-
* - REANALISAR os blocos concorrentes são sobre assuntos diferentes
|
|
892
|
-
* demais pra reconciliar, e nem a retentativa focada
|
|
893
|
-
* resolveu — o `.sql` decide o que fazer (normalmente
|
|
894
|
-
* cair no fallback do OR REPLY)
|
|
895
|
-
* - UNKNOWN confidence baixa demais, não há bloco que sirva
|
|
896
|
-
*
|
|
897
|
-
* Reconciliação (só entra quando o resultado não é decisivo de cara):
|
|
898
|
-
* 1. Se o melhor e o segundo colocado são basicamente o mesmo
|
|
899
|
-
* conteúdo (alta similaridade) — não há nada pra unir, usa o
|
|
900
|
-
* melhor tal como está.
|
|
901
|
-
* 2. Se são blocos diferentes mas do mesmo assunto, e cada um tem
|
|
902
|
-
* exatamente um nome próprio diferente (ex: "entregamos em
|
|
903
|
-
* Luanda" / "entregamos no Huambo") — tenta fundir numa frase só
|
|
904
|
-
* ("entregamos em vários lugares, como Luanda e Huambo").
|
|
905
|
-
* 3. Se nada disso se aplica, tenta de novo com uma versão mais
|
|
906
|
-
* enxuta da pergunta (só os termos mais decisivos) antes de
|
|
907
|
-
* desistir.
|
|
908
|
-
*
|
|
909
|
-
* @returns {{
|
|
910
|
-
* decision: 'RESPONDER'|'REANALISAR'|'UNKNOWN',
|
|
911
|
-
* confidence: number,
|
|
912
|
-
* margem: number,
|
|
913
|
-
* texto: string | null,
|
|
914
|
-
* unificado: boolean,
|
|
915
|
-
* reanalisado: boolean,
|
|
916
|
-
* melhor: {text: string, score: number, index: number} | null,
|
|
917
|
-
* segundo: {text: string, score: number, index: number} | null
|
|
918
|
-
* }}
|
|
919
|
-
*/
|
|
920
|
-
analyze(message) {
|
|
921
|
-
const vazio = { decision: "UNKNOWN", confidence: 0, margem: 0, texto: null, unificado: false, reanalisado: false, melhor: null, segundo: null };
|
|
922
|
-
if (this.totalDocs === 0) return vazio;
|
|
923
|
-
const ranking = this._rankTodos(message);
|
|
924
|
-
if (ranking.length === 0 || ranking[0].score <= 0) return vazio;
|
|
925
|
-
const melhor = ranking[0];
|
|
926
|
-
const segundo = ranking[1] || { index: -1, score: 0 };
|
|
927
|
-
const margem = (melhor.score - segundo.score) / melhor.score;
|
|
928
|
-
const confidence = melhor.score / (melhor.score + 3);
|
|
929
|
-
const melhorInfo = { text: this.blocks[melhor.index], score: melhor.score, index: melhor.index };
|
|
930
|
-
const segundoInfo = segundo.index >= 0 ? { text: this.blocks[segundo.index], score: segundo.score, index: segundo.index } : null;
|
|
931
|
-
if (confidence < CONFIANCA_MINIMA) return vazio;
|
|
932
|
-
if (confidence >= CONFIANCA_ALTA && margem >= MARGEM_MINIMA) {
|
|
933
|
-
return {
|
|
934
|
-
decision: "RESPONDER",
|
|
935
|
-
confidence,
|
|
936
|
-
margem,
|
|
937
|
-
texto: melhorInfo.text,
|
|
938
|
-
unificado: false,
|
|
939
|
-
reanalisado: false,
|
|
940
|
-
melhor: melhorInfo,
|
|
941
|
-
segundo: segundoInfo
|
|
942
|
-
};
|
|
645
|
+
return 0 === s ? 0 : s / i.length;
|
|
646
|
+
}
|
|
647
|
+
_contemSequencia(e, t) {
|
|
648
|
+
outer: for (let i = 0; i + t.length <= e.length; i++) {
|
|
649
|
+
for (let o = 0; o < t.length; o++) if (e[i + o] !== t[o]) continue outer;
|
|
650
|
+
return true;
|
|
943
651
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
_bonusTitulo(e, t) {
|
|
655
|
+
if (0 === e.length) return 0;
|
|
656
|
+
let i = new Set(this.docPalavrasSignificativas[t].slice(0, 8)), o = new Set(e), s = 0;
|
|
657
|
+
for (let r of o) i.has(r) && s++;
|
|
658
|
+
return s / o.size;
|
|
659
|
+
}
|
|
660
|
+
_scoreDoc(e, t, i) {
|
|
661
|
+
let o = this._scoreBM25(t, i), s = this._bonusFrase(e, i), r = this._bonusTitulo(this._palavrasSignificativas(e), i);
|
|
662
|
+
return 1 * o + 4 * s + 3 * r;
|
|
663
|
+
}
|
|
664
|
+
_rankTodos(e) {
|
|
665
|
+
let t = this._aplicarSinonimos(tokenizar(e));
|
|
666
|
+
if (0 === t.length) return [];
|
|
667
|
+
let i = [];
|
|
668
|
+
for (let o = 0; o < this.totalDocs; o++) i.push({ index: o, score: this._scoreDoc(e, t, o) });
|
|
669
|
+
return i.sort((e2, t2) => t2.score - e2.score), i;
|
|
670
|
+
}
|
|
671
|
+
search(e) {
|
|
672
|
+
if (0 === this.totalDocs) return null;
|
|
673
|
+
let t = this._rankTodos(e);
|
|
674
|
+
if (0 === t.length) return null;
|
|
675
|
+
let i = t[0];
|
|
676
|
+
return i.score <= 0 ? null : { text: this.blocks[i.index], score: i.score, confidence: i.score / (i.score + 3), index: i.index };
|
|
677
|
+
}
|
|
678
|
+
_similaridadeBlocos(e, t) {
|
|
679
|
+
let i = new Set(this.docs[e]), o = new Set(this.docs[t]);
|
|
680
|
+
if (0 === i.size || 0 === o.size) return 0;
|
|
681
|
+
let s = 0;
|
|
682
|
+
for (let r of i) o.has(r) && s++;
|
|
683
|
+
let n = i.size + o.size - s;
|
|
684
|
+
return 0 === n ? 0 : s / n;
|
|
685
|
+
}
|
|
686
|
+
_termosPartilhados(e, t) {
|
|
687
|
+
let i = new Set(this.docs[e]), o = this.docs[t], s = 0;
|
|
688
|
+
for (let r of o) i.has(r) && s++;
|
|
689
|
+
return s;
|
|
690
|
+
}
|
|
691
|
+
_tentarUnir(e, t) {
|
|
692
|
+
let i = extrairNomesProprios(e), o = extrairNomesProprios(t);
|
|
693
|
+
if (1 !== i.length || 1 !== o.length) return null;
|
|
694
|
+
let s = i[0], r = o[0];
|
|
695
|
+
if (normalizar(s).toLowerCase() === normalizar(r).toLowerCase()) return { tipo: "duplicado" };
|
|
696
|
+
let n = e.trim().split(/\s+/)[0].replace(/[.,;:!?]+$/, "");
|
|
697
|
+
return { tipo: "unido", texto: `${n} em v\xE1rios lugares, como ${s} e ${r}.` };
|
|
698
|
+
}
|
|
699
|
+
_reanalisarFocado(e) {
|
|
700
|
+
let t = this._aplicarSinonimos(tokenizar(e));
|
|
701
|
+
if (t.length <= 2) return null;
|
|
702
|
+
let i = t.map((e2) => ({ termo: e2, idf: this._idf(e2) }));
|
|
703
|
+
i.sort((e2, t2) => t2.idf - e2.idf);
|
|
704
|
+
let o = i.slice(0, Math.max(1, Math.ceil(t.length / 2))).map((e2) => e2.termo), s = [];
|
|
705
|
+
for (let r = 0; r < this.totalDocs; r++) s.push({ index: r, score: this._scoreDoc(e, o, r) });
|
|
706
|
+
return s.sort((e2, t2) => t2.score - e2.score), s;
|
|
707
|
+
}
|
|
708
|
+
analyze(e) {
|
|
709
|
+
let t = { decision: "UNKNOWN", confidence: 0, margem: 0, texto: null, unificado: false, reanalisado: false, melhor: null, segundo: null };
|
|
710
|
+
if (0 === this.totalDocs) return t;
|
|
711
|
+
let i = this._rankTodos(e);
|
|
712
|
+
if (0 === i.length || i[0].score <= 0) return t;
|
|
713
|
+
let o = i[0], s = i[1] || { index: -1, score: 0 }, r = (o.score - s.score) / o.score, n = o.score / (o.score + 3), a = { text: this.blocks[o.index], score: o.score, index: o.index }, l = s.index >= 0 ? { text: this.blocks[s.index], score: s.score, index: s.index } : null;
|
|
714
|
+
if (n < 0.15) return t;
|
|
715
|
+
if (n >= 0.55 && r >= 0.12) return { decision: "RESPONDER", confidence: n, margem: r, texto: a.text, unificado: false, reanalisado: false, melhor: a, segundo: l };
|
|
716
|
+
if (l) {
|
|
717
|
+
let c = this._similaridadeBlocos(o.index, s.index);
|
|
718
|
+
if (c >= 0.75) return { decision: "RESPONDER", confidence: n, margem: r, texto: a.text, unificado: false, reanalisado: false, melhor: a, segundo: l };
|
|
719
|
+
let d = this._termosPartilhados(o.index, s.index);
|
|
720
|
+
if (d >= 1) {
|
|
721
|
+
let h = this._tentarUnir(a.text, l.text);
|
|
722
|
+
if (h && "duplicado" === h.tipo) return { decision: "RESPONDER", confidence: n, margem: r, texto: a.text, unificado: false, reanalisado: false, melhor: a, segundo: l };
|
|
723
|
+
if (h && "unido" === h.tipo) return { decision: "RESPONDER", confidence: n, margem: r, texto: h.texto, unificado: true, reanalisado: false, melhor: a, segundo: l };
|
|
985
724
|
}
|
|
986
725
|
}
|
|
987
|
-
|
|
988
|
-
if (
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
const margem2 = (melhor2.score - segundo2.score) / melhor2.score;
|
|
992
|
-
const confidence2 = melhor2.score / (melhor2.score + 3);
|
|
993
|
-
if (confidence2 >= CONFIANCA_ALTA && margem2 >= MARGEM_MINIMA) {
|
|
994
|
-
return {
|
|
995
|
-
decision: "RESPONDER",
|
|
996
|
-
confidence: confidence2,
|
|
997
|
-
margem: margem2,
|
|
998
|
-
texto: this.blocks[melhor2.index],
|
|
999
|
-
unificado: false,
|
|
1000
|
-
reanalisado: true,
|
|
1001
|
-
melhor: { text: this.blocks[melhor2.index], score: melhor2.score, index: melhor2.index },
|
|
1002
|
-
segundo: segundo2.index >= 0 ? { text: this.blocks[segundo2.index], score: segundo2.score, index: segundo2.index } : null
|
|
1003
|
-
};
|
|
1004
|
-
}
|
|
726
|
+
let u = this._reanalisarFocado(e);
|
|
727
|
+
if (u && u.length > 0 && u[0].score > 0) {
|
|
728
|
+
let f = u[0], $ = u[1] || { index: -1, score: 0 }, m = (f.score - $.score) / f.score, g = f.score / (f.score + 3);
|
|
729
|
+
if (g >= 0.55 && m >= 0.12) return { decision: "RESPONDER", confidence: g, margem: m, texto: this.blocks[f.index], unificado: false, reanalisado: true, melhor: { text: this.blocks[f.index], score: f.score, index: f.index }, segundo: $.index >= 0 ? { text: this.blocks[$.index], score: $.score, index: $.index } : null };
|
|
1005
730
|
}
|
|
1006
|
-
return {
|
|
1007
|
-
decision: "REANALISAR",
|
|
1008
|
-
confidence,
|
|
1009
|
-
margem,
|
|
1010
|
-
texto: null,
|
|
1011
|
-
unificado: false,
|
|
1012
|
-
reanalisado: false,
|
|
1013
|
-
melhor: melhorInfo,
|
|
1014
|
-
segundo: segundoInfo
|
|
1015
|
-
};
|
|
731
|
+
return { decision: "REANALISAR", confidence: n, margem: r, texto: null, unificado: false, reanalisado: false, melhor: a, segundo: l };
|
|
1016
732
|
}
|
|
1017
733
|
};
|
|
1018
734
|
var KnowledgeCache = class {
|
|
1019
|
-
constructor(
|
|
1020
|
-
this.fileSystem =
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
if (this.
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
}
|
|
1028
|
-
const texto = this.fileSystem.readFile(resolvedPath);
|
|
1029
|
-
const index = new KnowledgeIndex(texto, options);
|
|
1030
|
-
this.cache.set(resolvedPath, index);
|
|
1031
|
-
return index;
|
|
735
|
+
constructor(e) {
|
|
736
|
+
this.fileSystem = e, this.cache = /* @__PURE__ */ new Map();
|
|
737
|
+
}
|
|
738
|
+
get(e, t) {
|
|
739
|
+
if (this.cache.has(e)) return this.cache.get(e);
|
|
740
|
+
if (!this.fileSystem.exists(e)) throw Error(`BotQL/THINK: ficheiro de conhecimento n\xE3o encontrado: "${e}"`);
|
|
741
|
+
let i = this.fileSystem.readFile(e), o = new KnowledgeIndex(i, t);
|
|
742
|
+
return this.cache.set(e, o), o;
|
|
1032
743
|
}
|
|
1033
744
|
};
|
|
1034
745
|
module.exports = { KnowledgeIndex, KnowledgeCache, tokenizar, normalizar, stem, distanciaEdicao };
|
|
@@ -1042,10 +753,9 @@ var BotQL = (() => {
|
|
|
1042
753
|
var { MemoryDatabase: t } = require_Database();
|
|
1043
754
|
var { createDefaultFileSystem: s } = require_FileSystem();
|
|
1044
755
|
var { KnowledgeCache: i } = require_RAG();
|
|
1045
|
-
var WAITING_TEXTO_PADRAO = "Pensando...";
|
|
1046
756
|
var BotQLInterpreter = class _BotQLInterpreter {
|
|
1047
757
|
constructor(e2 = {}) {
|
|
1048
|
-
this.db = e2.db || new t(), this.onReply = e2.onReply || null, this.onForward = e2.onForward || null, this.onSend = e2.onSend || null, this.signalParser = e2.signalParser || ((e3) => e3), this.onThinking = e2.onThinking || null, this.botName = null, this.platform = null, this.connections = [], this.handlers = { START: [], MESSAGE: [], SIGNAL: [] }, this.running = false, this.responseConnections = /* @__PURE__ */ new Set(), this.onResponse = e2.onResponse || null, this.defaultMessageConfig = null, this.nativeFuncs = /* @__PURE__ */ new Map(), this.registerFunction("NOW", () => (/* @__PURE__ */ new Date()).toISOString()), this.fileSystem = e2.fileSystem || s();
|
|
758
|
+
this.db = e2.db || new t(), this.onReply = e2.onReply || null, this.onForward = e2.onForward || null, this.onSend = e2.onSend || null, this.signalParser = e2.signalParser || ((e3) => e3), this.onThinking = e2.onThinking || null, this.botName = null, this.platform = null, this.connections = [], this.handlers = { START: [], MESSAGE: [], SIGNAL: [] }, this.running = false, this.responseConnections = /* @__PURE__ */ new Set(), this._clientesOcupados = /* @__PURE__ */ new Set(), this.onBusy = e2.onBusy || null, this.onResponse = e2.onResponse || null, this.defaultMessageConfig = null, this.nativeFuncs = /* @__PURE__ */ new Map(), this.registerFunction("NOW", () => (/* @__PURE__ */ new Date()).toISOString()), this.fileSystem = e2.fileSystem || s();
|
|
1049
759
|
let n = "undefined" != typeof process && process.cwd ? process.cwd() : "";
|
|
1050
760
|
this._basePathStack = [e2.basePath || n], this._importedFiles = /* @__PURE__ */ new Set(), this._rootBasePath = e2.basePath || n, this._keywordsFileCache = /* @__PURE__ */ new Map(), this._replyFileCache = /* @__PURE__ */ new Map(), this.envValues = /* @__PURE__ */ new Map(), this.knowledgeCache = new i(this.fileSystem);
|
|
1051
761
|
}
|
|
@@ -1111,8 +821,8 @@ var BotQL = (() => {
|
|
|
1111
821
|
if (this._importedFiles.has(u)) continue;
|
|
1112
822
|
if (!this.fileSystem.exists(u)) throw Error(`runtime error: IMPORT failed, file not found: "${u}"`);
|
|
1113
823
|
this._importedFiles.add(u);
|
|
1114
|
-
let c = this.fileSystem.readFile(u),
|
|
1115
|
-
i2.push(...
|
|
824
|
+
let c = this.fileSystem.readFile(u), d = new e(c).parseProgram(), f = this._flattenImports(d.body, this.fileSystem.dirname(u));
|
|
825
|
+
i2.push(...f);
|
|
1116
826
|
}
|
|
1117
827
|
return [...i2, ...n];
|
|
1118
828
|
}
|
|
@@ -1126,10 +836,16 @@ var BotQL = (() => {
|
|
|
1126
836
|
}
|
|
1127
837
|
}
|
|
1128
838
|
async receiveMessage(e2, t2) {
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
839
|
+
if (this._clientesOcupados.has(e2)) return this.onBusy && await this.onBusy({ client: e2, message: t2 }), null;
|
|
840
|
+
this._clientesOcupados.add(e2);
|
|
841
|
+
try {
|
|
842
|
+
let s2 = this.createContext({ client: e2, message: t2 }), i2 = await this.greetIfNew(e2);
|
|
843
|
+
if (i2) return s2;
|
|
844
|
+
for (let n of this.handlers.MESSAGE) await this.run(n.body, s2);
|
|
845
|
+
return s2;
|
|
846
|
+
} finally {
|
|
847
|
+
this._clientesOcupados.delete(e2);
|
|
848
|
+
}
|
|
1133
849
|
}
|
|
1134
850
|
async greetIfNew(e2) {
|
|
1135
851
|
if (!this.defaultMessageConfig) return false;
|
|
@@ -1182,9 +898,10 @@ var BotQL = (() => {
|
|
|
1182
898
|
return this._keywordsFileCache.set(e2, i2), i2;
|
|
1183
899
|
}
|
|
1184
900
|
resolveWaitingConfig(e2, t2) {
|
|
1185
|
-
if (!e2) return { text:
|
|
1186
|
-
let s2 =
|
|
1187
|
-
if (e2.
|
|
901
|
+
if (!e2) return { text: null, seconds: 3 };
|
|
902
|
+
let s2 = null;
|
|
903
|
+
if (null !== e2.text && void 0 !== e2.text) s2 = e2.text;
|
|
904
|
+
else if (e2.file) {
|
|
1188
905
|
let i2 = this.fileSystem.resolve(this._rootBasePath, e2.file);
|
|
1189
906
|
if (!this.fileSystem.exists(i2)) throw Error(`runtime error: WAITING failed, file not found: "${i2}"`);
|
|
1190
907
|
s2 = this.fileSystem.readFile(i2).trim();
|
|
@@ -1217,11 +934,11 @@ var BotQL = (() => {
|
|
|
1217
934
|
}
|
|
1218
935
|
let h = Number(l[0]), u = a + l[0].length + 1;
|
|
1219
936
|
if ("{" === e2[u]) {
|
|
1220
|
-
let c = 1,
|
|
1221
|
-
for (;
|
|
937
|
+
let c = 1, d = u + 1, f = d;
|
|
938
|
+
for (; f < i2 && c > 0; ) "{" === e2[f] ? c++ : "}" === e2[f] && c--, f++;
|
|
1222
939
|
if (0 !== c) throw Error(`runtime error: unclosed "{" for entry ${h} in "${t2}"`);
|
|
1223
|
-
let p = e2.slice(
|
|
1224
|
-
s2.set(h, p), n =
|
|
940
|
+
let p = e2.slice(d, f - 1).trim();
|
|
941
|
+
s2.set(h, p), n = f;
|
|
1225
942
|
continue;
|
|
1226
943
|
}
|
|
1227
944
|
let m = e2.indexOf("\n", u), w = -1 === m ? i2 : m, g = e2.slice(u, w).trim();
|
|
@@ -1236,19 +953,24 @@ var BotQL = (() => {
|
|
|
1236
953
|
t2.vars.lastMsg = s2, this.onReply && await this.onReply({ target: e2.target, text: s2, client: t2.vars.client });
|
|
1237
954
|
break;
|
|
1238
955
|
}
|
|
956
|
+
case "Waiting": {
|
|
957
|
+
let i2 = this.resolveWaitingConfig(e2, t2);
|
|
958
|
+
this.onThinking && await this.onThinking({ client: t2.vars.client, text: i2.text }), await new Promise((e3) => setTimeout(e3, 1e3 * i2.seconds));
|
|
959
|
+
break;
|
|
960
|
+
}
|
|
1239
961
|
case "Think": {
|
|
1240
|
-
let
|
|
1241
|
-
this.onThinking && await this.onThinking({ client: t2.vars.client, text:
|
|
1242
|
-
let
|
|
1243
|
-
if (
|
|
1244
|
-
let
|
|
1245
|
-
t2.vars.lastMsg =
|
|
962
|
+
let n = this.resolveWaitingConfig(e2.waiting, t2), r = Date.now();
|
|
963
|
+
this.onThinking && await this.onThinking({ client: t2.vars.client, text: n.text });
|
|
964
|
+
let a = this.fileSystem.resolve(this._rootBasePath, e2.file), l = this.knowledgeCache.get(a), o = l.analyze(t2.vars.message), h = Date.now() - r, u = 1e3 * n.seconds - h;
|
|
965
|
+
if (u > 0 && await new Promise((e3) => setTimeout(e3, u)), "RESPONDER" === o.decision) {
|
|
966
|
+
let c = o.texto;
|
|
967
|
+
t2.vars.lastMsg = c, this.onReply && await this.onReply({ target: null, text: c, client: t2.vars.client });
|
|
1246
968
|
} else e2.fallback && await this.execStatement(e2.fallback, t2);
|
|
1247
969
|
break;
|
|
1248
970
|
}
|
|
1249
971
|
case "ForwardTo": {
|
|
1250
|
-
let
|
|
1251
|
-
this.onForward && await this.onForward({ target:
|
|
972
|
+
let d = String(this.evalExpr(e2.target, t2));
|
|
973
|
+
this.onForward && await this.onForward({ target: d, client: t2.vars.client });
|
|
1252
974
|
break;
|
|
1253
975
|
}
|
|
1254
976
|
case "ParseSignal":
|