@kavchat/core 0.0.1 → 0.0.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/dist/index.d.mts +14 -2
- package/dist/index.d.ts +14 -2
- package/dist/index.js +147 -10
- package/dist/index.mjs +146 -10
- package/i18n/th.json +39 -0
- package/package.json +1 -1
- package/test-vectors/conversation.json +364 -0
- package/test-vectors/fixtures.json +124 -0
- package/test-vectors/match.json +131 -0
- package/test-vectors/normalize.json +123 -0
package/dist/index.d.mts
CHANGED
|
@@ -80,11 +80,16 @@ type ClientRelease = {
|
|
|
80
80
|
* - אותיות קטנות, הסרת ניקוד וטעמים (עברית), תשכיל (ערבית) וסימני הטעמה לטיניים (é → e)
|
|
81
81
|
* - אותיות סופיות בעברית (ם → מ), צורות אליף בערבית (أ → ا), ё → е, ß → ss
|
|
82
82
|
* - מילות קישור לכל שפה, והסרת תחיליות בעברית (ו, ה, ב...) ובערבית (ال, و, ب...)
|
|
83
|
+
* - תאית ושאר הכתבים בלי רווחים בין מילים (לאו, חמרית, בורמזית, יפנית וסינית): ראו isSpacelessScript
|
|
83
84
|
*
|
|
84
85
|
* הפורטים ל-Dart, Swift ו-Kotlin חייבים לעבור את test-vectors/normalize.json
|
|
85
86
|
*/
|
|
86
|
-
declare const SUPPORTED_LOCALES: readonly ["he", "en", "ar", "ru", "es", "fr", "de", "pt", "it", "tr"];
|
|
87
|
+
declare const SUPPORTED_LOCALES: readonly ["he", "en", "ar", "ru", "es", "fr", "de", "pt", "it", "tr", "th"];
|
|
87
88
|
type Locale = (typeof SUPPORTED_LOCALES)[number];
|
|
89
|
+
/**
|
|
90
|
+
* מילה שיש בה תו מכתב בלי רווחים. מילות מפתח כאלה מותאמות כתת-מחרוזת של השאלה, ולא כמילה שלמה (ראו match.ts)
|
|
91
|
+
*/
|
|
92
|
+
declare function isSpacelessScript(word: string): boolean;
|
|
88
93
|
/** מנקה ומחזיר רשימת מילים */
|
|
89
94
|
declare function normalizeWords(text: string): string[];
|
|
90
95
|
/**
|
|
@@ -110,6 +115,13 @@ declare const toBubbles: (text: string) => string[];
|
|
|
110
115
|
* התאמה של שאלה חופשית לשלב בתסריט
|
|
111
116
|
* ציון: מילה מובילה = 3, שם התסריט או כפתור שמוביל לשלב = 2, שגיאת כתיב של אות אחת = 0.5.
|
|
112
117
|
* שלב אחד עם ציון כפול מהבא = single. כמה קרובים = multi (עד 3). אין כלום = none
|
|
118
|
+
*
|
|
119
|
+
* כתבים בלי רווחים (תאית, לאו, חמרית, בורמזית, יפנית, סינית): "ใบเสร็จอยู่ที่ไหน" היא מילה אחת בעיני normalizeWords,
|
|
120
|
+
* ולכן השוואת מילים שלמות לא תמצא בה את "ใบเสร็จ". מילת מפתח, שם תסריט או כפתור שיש בהם תו מכתב כזה
|
|
121
|
+
* מותאמים כתת-מחרוזת של כל השאלה המנורמלת, אחרי חיבור המילים בלי רווחים (משתמשים מוסיפים רווחים באמצע).
|
|
122
|
+
* המשקלות זהים (3 ו-2). בתוך שלב, מונח שמוכל במונח ארוך יותר שנמצא, עם משקל שווה או גבוה, לא נספר שוב.
|
|
123
|
+
* אין סובלנות לשגיאות כתיב בכתבים האלה: השאלה היא רצף אחד בלי גבולות מילים, ושינוי של סימן אחד
|
|
124
|
+
* (למשל טון: ข้าว ו-ข่าว) הוא מילה אחרת. שגיאה של אות אחת בתת-מחרוזת קצרה הייתה מתאימה כמעט לכל שאלה
|
|
113
125
|
*/
|
|
114
126
|
type MatchReason = {
|
|
115
127
|
kind: 'keyword' | 'title' | 'typo';
|
|
@@ -307,5 +319,5 @@ declare function eventsPayload(state: BotState, sessionId: string, releaseVersio
|
|
|
307
319
|
}[];
|
|
308
320
|
};
|
|
309
321
|
|
|
310
|
-
export { AI_ANSWER_MAX, AI_QUESTION_MAX, RTL_LOCALES, SUPPORTED_LOCALES, UI_TEXTS, aiQuestion, answerWithAi, configureEngine, eventsPayload, format, groupKey, isRtl, keyTerms, matchQuestion, normalizeWords, pendingQuestion, pressChip, sendText, startConversation, stripPrefixes, toBubbles, transcriptOf, uiTexts, withinOneEdit };
|
|
322
|
+
export { AI_ANSWER_MAX, AI_QUESTION_MAX, RTL_LOCALES, SUPPORTED_LOCALES, UI_TEXTS, aiQuestion, answerWithAi, configureEngine, eventsPayload, format, groupKey, isRtl, isSpacelessScript, keyTerms, matchQuestion, normalizeWords, pendingQuestion, pressChip, sendText, startConversation, stripPrefixes, toBubbles, transcriptOf, uiTexts, withinOneEdit };
|
|
311
323
|
export type { BotAction, BotButton, BotChip, BotEffect, BotMatch, BotMatchResult, BotMessage, BotOutcome, BotPathEntry, BotPosition, BotResult, BotScript, BotScriptContent, BotSettings, BotState, BotStep, ChipKind, ClientRelease, EngineOptions, Locale, MatchReason, TranscriptEntry, UiKey, UiTexts };
|
package/dist/index.d.ts
CHANGED
|
@@ -80,11 +80,16 @@ type ClientRelease = {
|
|
|
80
80
|
* - אותיות קטנות, הסרת ניקוד וטעמים (עברית), תשכיל (ערבית) וסימני הטעמה לטיניים (é → e)
|
|
81
81
|
* - אותיות סופיות בעברית (ם → מ), צורות אליף בערבית (أ → ا), ё → е, ß → ss
|
|
82
82
|
* - מילות קישור לכל שפה, והסרת תחיליות בעברית (ו, ה, ב...) ובערבית (ال, و, ب...)
|
|
83
|
+
* - תאית ושאר הכתבים בלי רווחים בין מילים (לאו, חמרית, בורמזית, יפנית וסינית): ראו isSpacelessScript
|
|
83
84
|
*
|
|
84
85
|
* הפורטים ל-Dart, Swift ו-Kotlin חייבים לעבור את test-vectors/normalize.json
|
|
85
86
|
*/
|
|
86
|
-
declare const SUPPORTED_LOCALES: readonly ["he", "en", "ar", "ru", "es", "fr", "de", "pt", "it", "tr"];
|
|
87
|
+
declare const SUPPORTED_LOCALES: readonly ["he", "en", "ar", "ru", "es", "fr", "de", "pt", "it", "tr", "th"];
|
|
87
88
|
type Locale = (typeof SUPPORTED_LOCALES)[number];
|
|
89
|
+
/**
|
|
90
|
+
* מילה שיש בה תו מכתב בלי רווחים. מילות מפתח כאלה מותאמות כתת-מחרוזת של השאלה, ולא כמילה שלמה (ראו match.ts)
|
|
91
|
+
*/
|
|
92
|
+
declare function isSpacelessScript(word: string): boolean;
|
|
88
93
|
/** מנקה ומחזיר רשימת מילים */
|
|
89
94
|
declare function normalizeWords(text: string): string[];
|
|
90
95
|
/**
|
|
@@ -110,6 +115,13 @@ declare const toBubbles: (text: string) => string[];
|
|
|
110
115
|
* התאמה של שאלה חופשית לשלב בתסריט
|
|
111
116
|
* ציון: מילה מובילה = 3, שם התסריט או כפתור שמוביל לשלב = 2, שגיאת כתיב של אות אחת = 0.5.
|
|
112
117
|
* שלב אחד עם ציון כפול מהבא = single. כמה קרובים = multi (עד 3). אין כלום = none
|
|
118
|
+
*
|
|
119
|
+
* כתבים בלי רווחים (תאית, לאו, חמרית, בורמזית, יפנית, סינית): "ใบเสร็จอยู่ที่ไหน" היא מילה אחת בעיני normalizeWords,
|
|
120
|
+
* ולכן השוואת מילים שלמות לא תמצא בה את "ใบเสร็จ". מילת מפתח, שם תסריט או כפתור שיש בהם תו מכתב כזה
|
|
121
|
+
* מותאמים כתת-מחרוזת של כל השאלה המנורמלת, אחרי חיבור המילים בלי רווחים (משתמשים מוסיפים רווחים באמצע).
|
|
122
|
+
* המשקלות זהים (3 ו-2). בתוך שלב, מונח שמוכל במונח ארוך יותר שנמצא, עם משקל שווה או גבוה, לא נספר שוב.
|
|
123
|
+
* אין סובלנות לשגיאות כתיב בכתבים האלה: השאלה היא רצף אחד בלי גבולות מילים, ושינוי של סימן אחד
|
|
124
|
+
* (למשל טון: ข้าว ו-ข่าว) הוא מילה אחרת. שגיאה של אות אחת בתת-מחרוזת קצרה הייתה מתאימה כמעט לכל שאלה
|
|
113
125
|
*/
|
|
114
126
|
type MatchReason = {
|
|
115
127
|
kind: 'keyword' | 'title' | 'typo';
|
|
@@ -307,5 +319,5 @@ declare function eventsPayload(state: BotState, sessionId: string, releaseVersio
|
|
|
307
319
|
}[];
|
|
308
320
|
};
|
|
309
321
|
|
|
310
|
-
export { AI_ANSWER_MAX, AI_QUESTION_MAX, RTL_LOCALES, SUPPORTED_LOCALES, UI_TEXTS, aiQuestion, answerWithAi, configureEngine, eventsPayload, format, groupKey, isRtl, keyTerms, matchQuestion, normalizeWords, pendingQuestion, pressChip, sendText, startConversation, stripPrefixes, toBubbles, transcriptOf, uiTexts, withinOneEdit };
|
|
322
|
+
export { AI_ANSWER_MAX, AI_QUESTION_MAX, RTL_LOCALES, SUPPORTED_LOCALES, UI_TEXTS, aiQuestion, answerWithAi, configureEngine, eventsPayload, format, groupKey, isRtl, isSpacelessScript, keyTerms, matchQuestion, normalizeWords, pendingQuestion, pressChip, sendText, startConversation, stripPrefixes, toBubbles, transcriptOf, uiTexts, withinOneEdit };
|
|
311
323
|
export type { BotAction, BotButton, BotChip, BotEffect, BotMatch, BotMatchResult, BotMessage, BotOutcome, BotPathEntry, BotPosition, BotResult, BotScript, BotScriptContent, BotSettings, BotState, BotStep, ChipKind, ClientRelease, EngineOptions, Locale, MatchReason, TranscriptEntry, UiKey, UiTexts };
|
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ __export(index_exports, {
|
|
|
32
32
|
format: () => format,
|
|
33
33
|
groupKey: () => groupKey,
|
|
34
34
|
isRtl: () => isRtl,
|
|
35
|
+
isSpacelessScript: () => isSpacelessScript,
|
|
35
36
|
keyTerms: () => keyTerms,
|
|
36
37
|
matchQuestion: () => matchQuestion,
|
|
37
38
|
normalizeWords: () => normalizeWords,
|
|
@@ -48,7 +49,7 @@ __export(index_exports, {
|
|
|
48
49
|
module.exports = __toCommonJS(index_exports);
|
|
49
50
|
|
|
50
51
|
// src/normalize.ts
|
|
51
|
-
var SUPPORTED_LOCALES = ["he", "en", "ar", "ru", "es", "fr", "de", "pt", "it", "tr"];
|
|
52
|
+
var SUPPORTED_LOCALES = ["he", "en", "ar", "ru", "es", "fr", "de", "pt", "it", "tr", "th"];
|
|
52
53
|
var FOLD = {
|
|
53
54
|
// עברית: אותיות סופיות
|
|
54
55
|
\u05DA: "\u05DB",
|
|
@@ -74,6 +75,13 @@ var FOLD = {
|
|
|
74
75
|
function isCombiningMark(code) {
|
|
75
76
|
return code >= 768 && code <= 879 || code >= 1425 && code <= 1479 || code >= 1552 && code <= 1562 || code >= 1611 && code <= 1631 || code === 1648 || code >= 1750 && code <= 1773;
|
|
76
77
|
}
|
|
78
|
+
function isSpacelessLetter(code) {
|
|
79
|
+
return code >= 3585 && code <= 3630 || code >= 3632 && code <= 3642 || code >= 3648 && code <= 3653 || code >= 3655 && code <= 3662 || code >= 3713 && code <= 3758 || code >= 3760 && code <= 3781 || code >= 3784 && code <= 3790 || code >= 3804 && code <= 3807 || code >= 4096 && code <= 4169 || code >= 4176 && code <= 4253 || code >= 6016 && code <= 6099 || code === 6108 || code === 6109 || code === 12293 || code >= 12353 && code <= 12438 || code >= 12441 && code <= 12442 || code >= 12445 && code <= 12447 || code >= 12449 && code <= 12538 || code >= 12540 && code <= 12543 || code >= 13312 && code <= 19903 || code >= 19968 && code <= 40959;
|
|
80
|
+
}
|
|
81
|
+
function isSpacelessScript(word) {
|
|
82
|
+
for (let i = 0; i < word.length; i++) if (isSpacelessLetter(word.charCodeAt(i))) return true;
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
77
85
|
function isWordChar(ch) {
|
|
78
86
|
const code = ch.charCodeAt(0);
|
|
79
87
|
if (code >= 48 && code <= 57) return true;
|
|
@@ -81,9 +89,75 @@ function isWordChar(ch) {
|
|
|
81
89
|
if (code >= 1488 && code <= 1514) return true;
|
|
82
90
|
if (code >= 1568 && code <= 1610) return true;
|
|
83
91
|
if (code >= 1649 && code <= 1747) return true;
|
|
84
|
-
if (code
|
|
92
|
+
if (isSpacelessLetter(code)) return true;
|
|
85
93
|
return ch.toLowerCase() !== ch.toUpperCase();
|
|
86
94
|
}
|
|
95
|
+
var isZeroWidth = (code) => code >= 8203 && code <= 8205 || code === 8288 || code === 65279;
|
|
96
|
+
var DIGIT_ZEROS = [3664, 3792, 4160, 6112];
|
|
97
|
+
function asciiDigit(code) {
|
|
98
|
+
for (const zero of DIGIT_ZEROS) if (code >= zero && code <= zero + 9) return String.fromCharCode(48 + code - zero);
|
|
99
|
+
return void 0;
|
|
100
|
+
}
|
|
101
|
+
var MARK_ORDER = {
|
|
102
|
+
3640: 103,
|
|
103
|
+
3641: 103,
|
|
104
|
+
3642: 9,
|
|
105
|
+
3656: 107,
|
|
106
|
+
3657: 107,
|
|
107
|
+
3658: 107,
|
|
108
|
+
3659: 107,
|
|
109
|
+
3768: 118,
|
|
110
|
+
3769: 118,
|
|
111
|
+
3770: 9,
|
|
112
|
+
3784: 122,
|
|
113
|
+
3785: 122,
|
|
114
|
+
3786: 122,
|
|
115
|
+
3787: 122,
|
|
116
|
+
4151: 7,
|
|
117
|
+
4153: 9,
|
|
118
|
+
4154: 9,
|
|
119
|
+
4237: 220,
|
|
120
|
+
6098: 9,
|
|
121
|
+
6109: 230,
|
|
122
|
+
12441: 8,
|
|
123
|
+
12442: 8
|
|
124
|
+
};
|
|
125
|
+
var isThaiTone = (code) => code >= 3656 && code <= 3659;
|
|
126
|
+
function orderMarks(text) {
|
|
127
|
+
if (!/[\u0e33\u0e38-\u0e3a\u0e48-\u0e4b\u0e4d\u0eb8-\u0eba\u0ec8-\u0ecb\u1037\u1039\u103a\u108d\u17d2\u17dd\u3099\u309a]/.test(text)) return text;
|
|
128
|
+
const units = [];
|
|
129
|
+
for (let i = 0; i < text.length; i++) units.push(text.charCodeAt(i));
|
|
130
|
+
for (let i = 0; i < units.length; i++) {
|
|
131
|
+
if (!MARK_ORDER[units[i]]) continue;
|
|
132
|
+
let end = i;
|
|
133
|
+
while (end < units.length && MARK_ORDER[units[end]]) end++;
|
|
134
|
+
for (let a = i + 1; a < end; a++) {
|
|
135
|
+
const v = units[a];
|
|
136
|
+
let b = a - 1;
|
|
137
|
+
while (b >= i && MARK_ORDER[units[b]] > MARK_ORDER[v]) {
|
|
138
|
+
units[b + 1] = units[b];
|
|
139
|
+
b--;
|
|
140
|
+
}
|
|
141
|
+
units[b + 1] = v;
|
|
142
|
+
}
|
|
143
|
+
i = end - 1;
|
|
144
|
+
}
|
|
145
|
+
let out = "";
|
|
146
|
+
for (let i = 0; i < units.length; i++) {
|
|
147
|
+
const c = units[i];
|
|
148
|
+
if (c === 3661 && units[i + 1] === 3634) {
|
|
149
|
+
out += "\u0E33";
|
|
150
|
+
i++;
|
|
151
|
+
} else if (c === 3661 && isThaiTone(units[i + 1]) && units[i + 2] === 3634) {
|
|
152
|
+
out += String.fromCharCode(units[i + 1], 3635);
|
|
153
|
+
i += 2;
|
|
154
|
+
} else if (c === 3635 && isThaiTone(units[i + 1])) {
|
|
155
|
+
out += String.fromCharCode(units[i + 1], 3635);
|
|
156
|
+
i++;
|
|
157
|
+
} else out += String.fromCharCode(c);
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
87
161
|
var decompose = (s) => {
|
|
88
162
|
try {
|
|
89
163
|
return s.normalize("NFD");
|
|
@@ -92,16 +166,16 @@ var decompose = (s) => {
|
|
|
92
166
|
}
|
|
93
167
|
};
|
|
94
168
|
function normalizeWords(text) {
|
|
95
|
-
var _a;
|
|
169
|
+
var _a, _b;
|
|
96
170
|
const source = decompose(String(text != null ? text : "").toLowerCase());
|
|
97
171
|
let out = "";
|
|
98
172
|
for (const raw of source) {
|
|
99
173
|
const code = raw.charCodeAt(0);
|
|
100
|
-
if (isCombiningMark(code)) continue;
|
|
101
|
-
const ch = (_a = FOLD[raw]) != null ? _a : raw;
|
|
174
|
+
if (isCombiningMark(code) || isZeroWidth(code)) continue;
|
|
175
|
+
const ch = (_b = (_a = FOLD[raw]) != null ? _a : asciiDigit(code)) != null ? _b : raw;
|
|
102
176
|
out += ch.length === 1 && !isWordChar(ch) ? " " : ch;
|
|
103
177
|
}
|
|
104
|
-
const cleaned = out.replace(/\s+/g, " ").trim();
|
|
178
|
+
const cleaned = orderMarks(out).replace(/\s+/g, " ").trim();
|
|
105
179
|
return cleaned ? cleaned.split(" ") : [];
|
|
106
180
|
}
|
|
107
181
|
var STOP_WORDS = {
|
|
@@ -114,7 +188,9 @@ var STOP_WORDS = {
|
|
|
114
188
|
de: ["wie", "was", "ich", "mein", "meine", "mir", "der", "die", "das", "ein", "eine", "und", "oder", "in", "mit", "fur", "von", "zu", "ist", "kann", "mochte", "wann", "wo", "warum", "nicht", "es", "den", "dem"],
|
|
115
189
|
pt: ["como", "que", "eu", "meu", "minha", "o", "a", "os", "as", "um", "uma", "de", "do", "da", "em", "no", "na", "com", "para", "por", "e", "ou", "posso", "quero", "quando", "onde", "porque", "nao"],
|
|
116
190
|
it: ["come", "che", "cosa", "io", "mio", "mia", "il", "lo", "la", "i", "gli", "le", "un", "una", "di", "del", "della", "in", "con", "per", "e", "o", "posso", "voglio", "quando", "dove", "perche", "non", "l"],
|
|
117
|
-
tr: ["nasil", "ne", "ben", "benim", "bir", "ve", "veya", "ile", "icin", "bu", "su", "mi", "mu", "nerede", "ne zaman", "neden", "kac", "istiyorum", "var", "yok", "de", "da"]
|
|
191
|
+
tr: ["nasil", "ne", "ben", "benim", "bir", "ve", "veya", "ile", "icin", "bu", "su", "mi", "mu", "nerede", "ne zaman", "neden", "kac", "istiyorum", "var", "yok", "de", "da"],
|
|
192
|
+
// בתאית מילות קישור נמחקות רק כשהן מילה נפרדת (מוקפות ברווחים). רוב השאלות בלי רווחים, וההתאמה שם לפי תת-מחרוזת
|
|
193
|
+
th: ["\u0E09\u0E31\u0E19", "\u0E1C\u0E21", "\u0E14\u0E34\u0E09\u0E31\u0E19", "\u0E04\u0E38\u0E13", "\u0E04\u0E23\u0E31\u0E1A", "\u0E04\u0E48\u0E30", "\u0E04\u0E30", "\u0E19\u0E30", "\u0E44\u0E2B\u0E21", "\u0E21\u0E31\u0E49\u0E22", "\u0E2D\u0E30\u0E44\u0E23", "\u0E2D\u0E22\u0E48\u0E32\u0E07\u0E44\u0E23", "\u0E22\u0E31\u0E07\u0E44\u0E07", "\u0E17\u0E35\u0E48", "\u0E41\u0E25\u0E30", "\u0E2B\u0E23\u0E37\u0E2D", "\u0E02\u0E2D\u0E07", "\u0E44\u0E14\u0E49", "\u0E08\u0E30", "\u0E43\u0E19", "\u0E01\u0E31\u0E1A", "\u0E40\u0E1B\u0E47\u0E19", "\u0E21\u0E35", "\u0E44\u0E21\u0E48", "\u0E43\u0E2B\u0E49", "\u0E27\u0E48\u0E32", "\u0E2D\u0E22\u0E32\u0E01", "\u0E15\u0E49\u0E2D\u0E07\u0E01\u0E32\u0E23", "\u0E40\u0E21\u0E37\u0E48\u0E2D\u0E44\u0E2B\u0E23\u0E48", "\u0E17\u0E35\u0E48\u0E44\u0E2B\u0E19", "\u0E17\u0E33\u0E44\u0E21", "\u0E40\u0E17\u0E48\u0E32\u0E44\u0E2B\u0E23\u0E48"]
|
|
118
194
|
};
|
|
119
195
|
var NORMALIZED_STOP_WORDS = {};
|
|
120
196
|
for (const locale of SUPPORTED_LOCALES) {
|
|
@@ -189,6 +265,8 @@ function matchQuestion(scripts, text, locale = "en") {
|
|
|
189
265
|
const terms = Array.from(new Set(keyTerms(text, locale)));
|
|
190
266
|
if (terms.length === 0) return { kind: "none", matches: [] };
|
|
191
267
|
const terms_ = (values) => values.flatMap((v) => keyTerms(v, locale));
|
|
268
|
+
const wordTerms = terms.filter((t) => !isSpacelessScript(t));
|
|
269
|
+
const haystack = normalizeWords(text).join("");
|
|
192
270
|
const candidates = [];
|
|
193
271
|
for (const script of scripts) {
|
|
194
272
|
if (script.kind === "opening") continue;
|
|
@@ -206,20 +284,25 @@ function matchQuestion(scripts, text, locale = "en") {
|
|
|
206
284
|
const isRoot = step.id === content.rootStepId;
|
|
207
285
|
const strong = terms_([...step.keywords, ...isRoot ? content.keywords : []]);
|
|
208
286
|
const weak = terms_([...isRoot ? [content.name] : [], ...(_b = labelsTo.get(step.id)) != null ? _b : []]);
|
|
287
|
+
const typoPool = [...strong, ...weak].filter((w) => !isSpacelessScript(w));
|
|
209
288
|
let score = 0;
|
|
210
289
|
const reasons = [];
|
|
211
|
-
for (const term of
|
|
290
|
+
for (const term of wordTerms) {
|
|
212
291
|
if (strong.includes(term)) {
|
|
213
292
|
score += 3;
|
|
214
293
|
reasons.push({ kind: "keyword", term });
|
|
215
294
|
} else if (weak.includes(term)) {
|
|
216
295
|
score += 2;
|
|
217
296
|
reasons.push({ kind: "title", term });
|
|
218
|
-
} else if (
|
|
297
|
+
} else if (typoPool.some((w) => withinOneEdit(term, w))) {
|
|
219
298
|
score += 0.5;
|
|
220
299
|
reasons.push({ kind: "typo", term });
|
|
221
300
|
}
|
|
222
301
|
}
|
|
302
|
+
for (const hit of substringHits(strong, weak, haystack)) {
|
|
303
|
+
score += hit.weight;
|
|
304
|
+
reasons.push({ kind: hit.weight === 3 ? "keyword" : "title", term: hit.term });
|
|
305
|
+
}
|
|
223
306
|
if (score > 0) candidates.push({ scriptId: script.id, stepId: step.id, score, reasons });
|
|
224
307
|
}
|
|
225
308
|
}
|
|
@@ -229,6 +312,18 @@ function matchQuestion(scripts, text, locale = "en") {
|
|
|
229
312
|
if (!second || top.score >= second.score * 2) return { kind: "single", matches: [top] };
|
|
230
313
|
return { kind: "multi", matches: candidates.filter((c) => c.score >= top.score / 2).slice(0, 3) };
|
|
231
314
|
}
|
|
315
|
+
function substringHits(strong, weak, haystack) {
|
|
316
|
+
const found = [];
|
|
317
|
+
const seen = /* @__PURE__ */ new Set();
|
|
318
|
+
for (const [list, weight] of [[strong, 3], [weak, 2]]) {
|
|
319
|
+
for (const term of list) {
|
|
320
|
+
if (seen.has(term) || !isSpacelessScript(term)) continue;
|
|
321
|
+
seen.add(term);
|
|
322
|
+
if (haystack.includes(term)) found.push({ term, weight });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return found.filter((a) => !found.some((b) => b.term !== a.term && b.weight >= a.weight && b.term.includes(a.term)));
|
|
326
|
+
}
|
|
232
327
|
|
|
233
328
|
// i18n/en.json
|
|
234
329
|
var en_default = {
|
|
@@ -640,8 +735,49 @@ var tr_default = {
|
|
|
640
735
|
"form.backToChat": "Sohbete d\xF6n"
|
|
641
736
|
};
|
|
642
737
|
|
|
738
|
+
// i18n/th.json
|
|
739
|
+
var th_default = {
|
|
740
|
+
"chat.askAnswered": "\u0E04\u0E33\u0E15\u0E2D\u0E1A\u0E19\u0E35\u0E49\u0E0A\u0E48\u0E27\u0E22\u0E15\u0E2D\u0E1A\u0E04\u0E33\u0E16\u0E32\u0E21\u0E02\u0E2D\u0E07\u0E04\u0E38\u0E13\u0E2B\u0E23\u0E37\u0E2D\u0E44\u0E21\u0E48",
|
|
741
|
+
"chat.yes": "\u0E43\u0E0A\u0E48 \u0E02\u0E2D\u0E1A\u0E04\u0E38\u0E13",
|
|
742
|
+
"chat.no": "\u0E44\u0E21\u0E48\u0E43\u0E0A\u0E48 \u0E15\u0E49\u0E2D\u0E07\u0E01\u0E32\u0E23\u0E04\u0E27\u0E32\u0E21\u0E0A\u0E48\u0E27\u0E22\u0E40\u0E2B\u0E25\u0E37\u0E2D",
|
|
743
|
+
"chat.thanks": "\u0E22\u0E34\u0E19\u0E14\u0E35\u0E17\u0E35\u0E48\u0E44\u0E14\u0E49\u0E0A\u0E48\u0E27\u0E22\u0E40\u0E2B\u0E25\u0E37\u0E2D",
|
|
744
|
+
"chat.newQuestion": "\u0E16\u0E32\u0E21\u0E04\u0E33\u0E16\u0E32\u0E21\u0E43\u0E2B\u0E21\u0E48",
|
|
745
|
+
"chat.back": "\u0E22\u0E49\u0E2D\u0E19\u0E01\u0E25\u0E31\u0E1A",
|
|
746
|
+
"chat.home": "\u0E40\u0E21\u0E19\u0E39\u0E2B\u0E25\u0E31\u0E01",
|
|
747
|
+
"chat.didYouMean": "\u0E04\u0E38\u0E13\u0E2B\u0E21\u0E32\u0E22\u0E16\u0E36\u0E07\u0E02\u0E49\u0E2D\u0E43\u0E14",
|
|
748
|
+
"chat.notFound": "\u0E02\u0E2D\u0E2D\u0E20\u0E31\u0E22 \u0E44\u0E21\u0E48\u0E1E\u0E1A\u0E04\u0E33\u0E15\u0E2D\u0E1A\u0E2A\u0E33\u0E2B\u0E23\u0E31\u0E1A\u0E04\u0E33\u0E16\u0E32\u0E21\u0E19\u0E35\u0E49 \u0E01\u0E23\u0E38\u0E13\u0E32\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E2B\u0E31\u0E27\u0E02\u0E49\u0E2D\u0E08\u0E32\u0E01\u0E40\u0E21\u0E19\u0E39 \u0E2B\u0E23\u0E37\u0E2D\u0E15\u0E34\u0E14\u0E15\u0E48\u0E2D\u0E17\u0E35\u0E21\u0E07\u0E32\u0E19\u0E02\u0E2D\u0E07\u0E40\u0E23\u0E32",
|
|
749
|
+
"chat.notFoundTwice": "\u0E02\u0E2D\u0E2D\u0E20\u0E31\u0E22 \u0E44\u0E21\u0E48\u0E1E\u0E1A\u0E04\u0E33\u0E15\u0E2D\u0E1A \u0E17\u0E35\u0E21\u0E07\u0E32\u0E19\u0E02\u0E2D\u0E07\u0E40\u0E23\u0E32\u0E22\u0E34\u0E19\u0E14\u0E35\u0E0A\u0E48\u0E27\u0E22\u0E40\u0E2B\u0E25\u0E37\u0E2D \u0E41\u0E25\u0E30\u0E44\u0E14\u0E49\u0E43\u0E2A\u0E48\u0E04\u0E33\u0E16\u0E32\u0E21\u0E02\u0E2D\u0E07\u0E04\u0E38\u0E13\u0E44\u0E27\u0E49\u0E43\u0E19\u0E41\u0E1A\u0E1A\u0E1F\u0E2D\u0E23\u0E4C\u0E21\u0E41\u0E25\u0E49\u0E27",
|
|
750
|
+
"chat.contactChip": "\u0E15\u0E34\u0E14\u0E15\u0E48\u0E2D\u0E17\u0E35\u0E21\u0E07\u0E32\u0E19",
|
|
751
|
+
"chat.openingContact": "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E40\u0E1B\u0E34\u0E14\u0E41\u0E1A\u0E1A\u0E1F\u0E2D\u0E23\u0E4C\u0E21\u0E15\u0E34\u0E14\u0E15\u0E48\u0E2D \u0E1A\u0E17\u0E2A\u0E19\u0E17\u0E19\u0E32\u0E19\u0E35\u0E49\u0E08\u0E30\u0E41\u0E19\u0E1A\u0E44\u0E1B\u0E01\u0E31\u0E1A\u0E04\u0E33\u0E02\u0E2D\u0E02\u0E2D\u0E07\u0E04\u0E38\u0E13",
|
|
752
|
+
"chat.chooseTopic": "\u0E21\u0E35\u0E2D\u0E30\u0E44\u0E23\u0E43\u0E2B\u0E49\u0E0A\u0E48\u0E27\u0E22\u0E44\u0E2B\u0E21",
|
|
753
|
+
"chat.aiLabel": "\u0E04\u0E33\u0E15\u0E2D\u0E1A\u0E08\u0E32\u0E01 AI",
|
|
754
|
+
"widget.open": "\u0E40\u0E1B\u0E34\u0E14\u0E41\u0E0A\u0E15",
|
|
755
|
+
"widget.close": "\u0E1B\u0E34\u0E14\u0E41\u0E0A\u0E15",
|
|
756
|
+
"widget.placeholder": "\u0E1E\u0E34\u0E21\u0E1E\u0E4C\u0E04\u0E33\u0E16\u0E32\u0E21\u0E02\u0E2D\u0E07\u0E04\u0E38\u0E13...",
|
|
757
|
+
"widget.send": "\u0E2A\u0E48\u0E07",
|
|
758
|
+
"widget.poweredBy": "\u0E02\u0E31\u0E1A\u0E40\u0E04\u0E25\u0E37\u0E48\u0E2D\u0E19\u0E42\u0E14\u0E22 Kavchat",
|
|
759
|
+
"widget.offline": "\u0E04\u0E38\u0E13\u0E2D\u0E2D\u0E1F\u0E44\u0E25\u0E19\u0E4C\u0E2D\u0E22\u0E39\u0E48 \u0E01\u0E33\u0E25\u0E31\u0E07\u0E41\u0E2A\u0E14\u0E07\u0E04\u0E33\u0E15\u0E2D\u0E1A\u0E17\u0E35\u0E48\u0E1A\u0E31\u0E19\u0E17\u0E36\u0E01\u0E44\u0E27\u0E49",
|
|
760
|
+
"widget.loading": "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E42\u0E2B\u0E25\u0E14...",
|
|
761
|
+
"widget.unavailable": "\u0E1C\u0E39\u0E49\u0E0A\u0E48\u0E27\u0E22\u0E44\u0E21\u0E48\u0E1E\u0E23\u0E49\u0E2D\u0E21\u0E43\u0E2B\u0E49\u0E1A\u0E23\u0E34\u0E01\u0E32\u0E23\u0E43\u0E19\u0E02\u0E13\u0E30\u0E19\u0E35\u0E49",
|
|
762
|
+
"form.title": "\u0E15\u0E34\u0E14\u0E15\u0E48\u0E2D\u0E40\u0E23\u0E32",
|
|
763
|
+
"form.name": "\u0E0A\u0E37\u0E48\u0E2D",
|
|
764
|
+
"form.email": "\u0E2D\u0E35\u0E40\u0E21\u0E25",
|
|
765
|
+
"form.phone": "\u0E42\u0E17\u0E23\u0E28\u0E31\u0E1E\u0E17\u0E4C",
|
|
766
|
+
"form.topic": "\u0E2B\u0E31\u0E27\u0E02\u0E49\u0E2D",
|
|
767
|
+
"form.message": "\u0E02\u0E49\u0E2D\u0E04\u0E27\u0E32\u0E21",
|
|
768
|
+
"form.submit": "\u0E2A\u0E48\u0E07",
|
|
769
|
+
"form.cancel": "\u0E22\u0E01\u0E40\u0E25\u0E34\u0E01",
|
|
770
|
+
"form.sending": "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E2A\u0E48\u0E07...",
|
|
771
|
+
"form.sent": "\u0E02\u0E2D\u0E1A\u0E04\u0E38\u0E13 \u0E40\u0E23\u0E32\u0E44\u0E14\u0E49\u0E23\u0E31\u0E1A\u0E04\u0E33\u0E02\u0E2D\u0E2B\u0E21\u0E32\u0E22\u0E40\u0E25\u0E02 {number} \u0E02\u0E2D\u0E07\u0E04\u0E38\u0E13\u0E41\u0E25\u0E49\u0E27 \u0E41\u0E25\u0E30\u0E08\u0E30\u0E15\u0E34\u0E14\u0E15\u0E48\u0E2D\u0E01\u0E25\u0E31\u0E1A\u0E42\u0E14\u0E22\u0E40\u0E23\u0E47\u0E27",
|
|
772
|
+
"form.error": "\u0E40\u0E01\u0E34\u0E14\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14 \u0E01\u0E23\u0E38\u0E13\u0E32\u0E25\u0E2D\u0E07\u0E2D\u0E35\u0E01\u0E04\u0E23\u0E31\u0E49\u0E07",
|
|
773
|
+
"form.required": "\u0E08\u0E33\u0E40\u0E1B\u0E47\u0E19\u0E15\u0E49\u0E2D\u0E07\u0E01\u0E23\u0E2D\u0E01",
|
|
774
|
+
"form.contactRequired": "\u0E01\u0E23\u0E38\u0E13\u0E32\u0E23\u0E30\u0E1A\u0E38\u0E2D\u0E35\u0E40\u0E21\u0E25\u0E2B\u0E23\u0E37\u0E2D\u0E2B\u0E21\u0E32\u0E22\u0E40\u0E25\u0E02\u0E42\u0E17\u0E23\u0E28\u0E31\u0E1E\u0E17\u0E4C \u0E40\u0E1E\u0E37\u0E48\u0E2D\u0E43\u0E2B\u0E49\u0E40\u0E23\u0E32\u0E15\u0E34\u0E14\u0E15\u0E48\u0E2D\u0E01\u0E25\u0E31\u0E1A\u0E44\u0E14\u0E49",
|
|
775
|
+
"form.emailInvalid": "\u0E2D\u0E35\u0E40\u0E21\u0E25\u0E44\u0E21\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07",
|
|
776
|
+
"form.backToChat": "\u0E01\u0E25\u0E31\u0E1A\u0E44\u0E1B\u0E17\u0E35\u0E48\u0E41\u0E0A\u0E15"
|
|
777
|
+
};
|
|
778
|
+
|
|
643
779
|
// src/i18n.ts
|
|
644
|
-
var UI_TEXTS = { en: en_default, he: he_default, ar: ar_default, ru: ru_default, es: es_default, fr: fr_default, de: de_default, pt: pt_default, it: it_default, tr: tr_default };
|
|
780
|
+
var UI_TEXTS = { en: en_default, he: he_default, ar: ar_default, ru: ru_default, es: es_default, fr: fr_default, de: de_default, pt: pt_default, it: it_default, tr: tr_default, th: th_default };
|
|
645
781
|
var RTL_LOCALES = ["he", "ar"];
|
|
646
782
|
var isRtl = (locale) => RTL_LOCALES.includes(baseLocale(locale));
|
|
647
783
|
var baseLocale = (locale) => String(locale || "en").toLowerCase().split(/[-_]/)[0].replace(/^iw$/, "he");
|
|
@@ -925,6 +1061,7 @@ function eventsPayload(state, sessionId, releaseVersion, startedAt) {
|
|
|
925
1061
|
format,
|
|
926
1062
|
groupKey,
|
|
927
1063
|
isRtl,
|
|
1064
|
+
isSpacelessScript,
|
|
928
1065
|
keyTerms,
|
|
929
1066
|
matchQuestion,
|
|
930
1067
|
normalizeWords,
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/normalize.ts
|
|
2
|
-
var SUPPORTED_LOCALES = ["he", "en", "ar", "ru", "es", "fr", "de", "pt", "it", "tr"];
|
|
2
|
+
var SUPPORTED_LOCALES = ["he", "en", "ar", "ru", "es", "fr", "de", "pt", "it", "tr", "th"];
|
|
3
3
|
var FOLD = {
|
|
4
4
|
// עברית: אותיות סופיות
|
|
5
5
|
\u05DA: "\u05DB",
|
|
@@ -25,6 +25,13 @@ var FOLD = {
|
|
|
25
25
|
function isCombiningMark(code) {
|
|
26
26
|
return code >= 768 && code <= 879 || code >= 1425 && code <= 1479 || code >= 1552 && code <= 1562 || code >= 1611 && code <= 1631 || code === 1648 || code >= 1750 && code <= 1773;
|
|
27
27
|
}
|
|
28
|
+
function isSpacelessLetter(code) {
|
|
29
|
+
return code >= 3585 && code <= 3630 || code >= 3632 && code <= 3642 || code >= 3648 && code <= 3653 || code >= 3655 && code <= 3662 || code >= 3713 && code <= 3758 || code >= 3760 && code <= 3781 || code >= 3784 && code <= 3790 || code >= 3804 && code <= 3807 || code >= 4096 && code <= 4169 || code >= 4176 && code <= 4253 || code >= 6016 && code <= 6099 || code === 6108 || code === 6109 || code === 12293 || code >= 12353 && code <= 12438 || code >= 12441 && code <= 12442 || code >= 12445 && code <= 12447 || code >= 12449 && code <= 12538 || code >= 12540 && code <= 12543 || code >= 13312 && code <= 19903 || code >= 19968 && code <= 40959;
|
|
30
|
+
}
|
|
31
|
+
function isSpacelessScript(word) {
|
|
32
|
+
for (let i = 0; i < word.length; i++) if (isSpacelessLetter(word.charCodeAt(i))) return true;
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
28
35
|
function isWordChar(ch) {
|
|
29
36
|
const code = ch.charCodeAt(0);
|
|
30
37
|
if (code >= 48 && code <= 57) return true;
|
|
@@ -32,9 +39,75 @@ function isWordChar(ch) {
|
|
|
32
39
|
if (code >= 1488 && code <= 1514) return true;
|
|
33
40
|
if (code >= 1568 && code <= 1610) return true;
|
|
34
41
|
if (code >= 1649 && code <= 1747) return true;
|
|
35
|
-
if (code
|
|
42
|
+
if (isSpacelessLetter(code)) return true;
|
|
36
43
|
return ch.toLowerCase() !== ch.toUpperCase();
|
|
37
44
|
}
|
|
45
|
+
var isZeroWidth = (code) => code >= 8203 && code <= 8205 || code === 8288 || code === 65279;
|
|
46
|
+
var DIGIT_ZEROS = [3664, 3792, 4160, 6112];
|
|
47
|
+
function asciiDigit(code) {
|
|
48
|
+
for (const zero of DIGIT_ZEROS) if (code >= zero && code <= zero + 9) return String.fromCharCode(48 + code - zero);
|
|
49
|
+
return void 0;
|
|
50
|
+
}
|
|
51
|
+
var MARK_ORDER = {
|
|
52
|
+
3640: 103,
|
|
53
|
+
3641: 103,
|
|
54
|
+
3642: 9,
|
|
55
|
+
3656: 107,
|
|
56
|
+
3657: 107,
|
|
57
|
+
3658: 107,
|
|
58
|
+
3659: 107,
|
|
59
|
+
3768: 118,
|
|
60
|
+
3769: 118,
|
|
61
|
+
3770: 9,
|
|
62
|
+
3784: 122,
|
|
63
|
+
3785: 122,
|
|
64
|
+
3786: 122,
|
|
65
|
+
3787: 122,
|
|
66
|
+
4151: 7,
|
|
67
|
+
4153: 9,
|
|
68
|
+
4154: 9,
|
|
69
|
+
4237: 220,
|
|
70
|
+
6098: 9,
|
|
71
|
+
6109: 230,
|
|
72
|
+
12441: 8,
|
|
73
|
+
12442: 8
|
|
74
|
+
};
|
|
75
|
+
var isThaiTone = (code) => code >= 3656 && code <= 3659;
|
|
76
|
+
function orderMarks(text) {
|
|
77
|
+
if (!/[\u0e33\u0e38-\u0e3a\u0e48-\u0e4b\u0e4d\u0eb8-\u0eba\u0ec8-\u0ecb\u1037\u1039\u103a\u108d\u17d2\u17dd\u3099\u309a]/.test(text)) return text;
|
|
78
|
+
const units = [];
|
|
79
|
+
for (let i = 0; i < text.length; i++) units.push(text.charCodeAt(i));
|
|
80
|
+
for (let i = 0; i < units.length; i++) {
|
|
81
|
+
if (!MARK_ORDER[units[i]]) continue;
|
|
82
|
+
let end = i;
|
|
83
|
+
while (end < units.length && MARK_ORDER[units[end]]) end++;
|
|
84
|
+
for (let a = i + 1; a < end; a++) {
|
|
85
|
+
const v = units[a];
|
|
86
|
+
let b = a - 1;
|
|
87
|
+
while (b >= i && MARK_ORDER[units[b]] > MARK_ORDER[v]) {
|
|
88
|
+
units[b + 1] = units[b];
|
|
89
|
+
b--;
|
|
90
|
+
}
|
|
91
|
+
units[b + 1] = v;
|
|
92
|
+
}
|
|
93
|
+
i = end - 1;
|
|
94
|
+
}
|
|
95
|
+
let out = "";
|
|
96
|
+
for (let i = 0; i < units.length; i++) {
|
|
97
|
+
const c = units[i];
|
|
98
|
+
if (c === 3661 && units[i + 1] === 3634) {
|
|
99
|
+
out += "\u0E33";
|
|
100
|
+
i++;
|
|
101
|
+
} else if (c === 3661 && isThaiTone(units[i + 1]) && units[i + 2] === 3634) {
|
|
102
|
+
out += String.fromCharCode(units[i + 1], 3635);
|
|
103
|
+
i += 2;
|
|
104
|
+
} else if (c === 3635 && isThaiTone(units[i + 1])) {
|
|
105
|
+
out += String.fromCharCode(units[i + 1], 3635);
|
|
106
|
+
i++;
|
|
107
|
+
} else out += String.fromCharCode(c);
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
38
111
|
var decompose = (s) => {
|
|
39
112
|
try {
|
|
40
113
|
return s.normalize("NFD");
|
|
@@ -43,16 +116,16 @@ var decompose = (s) => {
|
|
|
43
116
|
}
|
|
44
117
|
};
|
|
45
118
|
function normalizeWords(text) {
|
|
46
|
-
var _a;
|
|
119
|
+
var _a, _b;
|
|
47
120
|
const source = decompose(String(text != null ? text : "").toLowerCase());
|
|
48
121
|
let out = "";
|
|
49
122
|
for (const raw of source) {
|
|
50
123
|
const code = raw.charCodeAt(0);
|
|
51
|
-
if (isCombiningMark(code)) continue;
|
|
52
|
-
const ch = (_a = FOLD[raw]) != null ? _a : raw;
|
|
124
|
+
if (isCombiningMark(code) || isZeroWidth(code)) continue;
|
|
125
|
+
const ch = (_b = (_a = FOLD[raw]) != null ? _a : asciiDigit(code)) != null ? _b : raw;
|
|
53
126
|
out += ch.length === 1 && !isWordChar(ch) ? " " : ch;
|
|
54
127
|
}
|
|
55
|
-
const cleaned = out.replace(/\s+/g, " ").trim();
|
|
128
|
+
const cleaned = orderMarks(out).replace(/\s+/g, " ").trim();
|
|
56
129
|
return cleaned ? cleaned.split(" ") : [];
|
|
57
130
|
}
|
|
58
131
|
var STOP_WORDS = {
|
|
@@ -65,7 +138,9 @@ var STOP_WORDS = {
|
|
|
65
138
|
de: ["wie", "was", "ich", "mein", "meine", "mir", "der", "die", "das", "ein", "eine", "und", "oder", "in", "mit", "fur", "von", "zu", "ist", "kann", "mochte", "wann", "wo", "warum", "nicht", "es", "den", "dem"],
|
|
66
139
|
pt: ["como", "que", "eu", "meu", "minha", "o", "a", "os", "as", "um", "uma", "de", "do", "da", "em", "no", "na", "com", "para", "por", "e", "ou", "posso", "quero", "quando", "onde", "porque", "nao"],
|
|
67
140
|
it: ["come", "che", "cosa", "io", "mio", "mia", "il", "lo", "la", "i", "gli", "le", "un", "una", "di", "del", "della", "in", "con", "per", "e", "o", "posso", "voglio", "quando", "dove", "perche", "non", "l"],
|
|
68
|
-
tr: ["nasil", "ne", "ben", "benim", "bir", "ve", "veya", "ile", "icin", "bu", "su", "mi", "mu", "nerede", "ne zaman", "neden", "kac", "istiyorum", "var", "yok", "de", "da"]
|
|
141
|
+
tr: ["nasil", "ne", "ben", "benim", "bir", "ve", "veya", "ile", "icin", "bu", "su", "mi", "mu", "nerede", "ne zaman", "neden", "kac", "istiyorum", "var", "yok", "de", "da"],
|
|
142
|
+
// בתאית מילות קישור נמחקות רק כשהן מילה נפרדת (מוקפות ברווחים). רוב השאלות בלי רווחים, וההתאמה שם לפי תת-מחרוזת
|
|
143
|
+
th: ["\u0E09\u0E31\u0E19", "\u0E1C\u0E21", "\u0E14\u0E34\u0E09\u0E31\u0E19", "\u0E04\u0E38\u0E13", "\u0E04\u0E23\u0E31\u0E1A", "\u0E04\u0E48\u0E30", "\u0E04\u0E30", "\u0E19\u0E30", "\u0E44\u0E2B\u0E21", "\u0E21\u0E31\u0E49\u0E22", "\u0E2D\u0E30\u0E44\u0E23", "\u0E2D\u0E22\u0E48\u0E32\u0E07\u0E44\u0E23", "\u0E22\u0E31\u0E07\u0E44\u0E07", "\u0E17\u0E35\u0E48", "\u0E41\u0E25\u0E30", "\u0E2B\u0E23\u0E37\u0E2D", "\u0E02\u0E2D\u0E07", "\u0E44\u0E14\u0E49", "\u0E08\u0E30", "\u0E43\u0E19", "\u0E01\u0E31\u0E1A", "\u0E40\u0E1B\u0E47\u0E19", "\u0E21\u0E35", "\u0E44\u0E21\u0E48", "\u0E43\u0E2B\u0E49", "\u0E27\u0E48\u0E32", "\u0E2D\u0E22\u0E32\u0E01", "\u0E15\u0E49\u0E2D\u0E07\u0E01\u0E32\u0E23", "\u0E40\u0E21\u0E37\u0E48\u0E2D\u0E44\u0E2B\u0E23\u0E48", "\u0E17\u0E35\u0E48\u0E44\u0E2B\u0E19", "\u0E17\u0E33\u0E44\u0E21", "\u0E40\u0E17\u0E48\u0E32\u0E44\u0E2B\u0E23\u0E48"]
|
|
69
144
|
};
|
|
70
145
|
var NORMALIZED_STOP_WORDS = {};
|
|
71
146
|
for (const locale of SUPPORTED_LOCALES) {
|
|
@@ -140,6 +215,8 @@ function matchQuestion(scripts, text, locale = "en") {
|
|
|
140
215
|
const terms = Array.from(new Set(keyTerms(text, locale)));
|
|
141
216
|
if (terms.length === 0) return { kind: "none", matches: [] };
|
|
142
217
|
const terms_ = (values) => values.flatMap((v) => keyTerms(v, locale));
|
|
218
|
+
const wordTerms = terms.filter((t) => !isSpacelessScript(t));
|
|
219
|
+
const haystack = normalizeWords(text).join("");
|
|
143
220
|
const candidates = [];
|
|
144
221
|
for (const script of scripts) {
|
|
145
222
|
if (script.kind === "opening") continue;
|
|
@@ -157,20 +234,25 @@ function matchQuestion(scripts, text, locale = "en") {
|
|
|
157
234
|
const isRoot = step.id === content.rootStepId;
|
|
158
235
|
const strong = terms_([...step.keywords, ...isRoot ? content.keywords : []]);
|
|
159
236
|
const weak = terms_([...isRoot ? [content.name] : [], ...(_b = labelsTo.get(step.id)) != null ? _b : []]);
|
|
237
|
+
const typoPool = [...strong, ...weak].filter((w) => !isSpacelessScript(w));
|
|
160
238
|
let score = 0;
|
|
161
239
|
const reasons = [];
|
|
162
|
-
for (const term of
|
|
240
|
+
for (const term of wordTerms) {
|
|
163
241
|
if (strong.includes(term)) {
|
|
164
242
|
score += 3;
|
|
165
243
|
reasons.push({ kind: "keyword", term });
|
|
166
244
|
} else if (weak.includes(term)) {
|
|
167
245
|
score += 2;
|
|
168
246
|
reasons.push({ kind: "title", term });
|
|
169
|
-
} else if (
|
|
247
|
+
} else if (typoPool.some((w) => withinOneEdit(term, w))) {
|
|
170
248
|
score += 0.5;
|
|
171
249
|
reasons.push({ kind: "typo", term });
|
|
172
250
|
}
|
|
173
251
|
}
|
|
252
|
+
for (const hit of substringHits(strong, weak, haystack)) {
|
|
253
|
+
score += hit.weight;
|
|
254
|
+
reasons.push({ kind: hit.weight === 3 ? "keyword" : "title", term: hit.term });
|
|
255
|
+
}
|
|
174
256
|
if (score > 0) candidates.push({ scriptId: script.id, stepId: step.id, score, reasons });
|
|
175
257
|
}
|
|
176
258
|
}
|
|
@@ -180,6 +262,18 @@ function matchQuestion(scripts, text, locale = "en") {
|
|
|
180
262
|
if (!second || top.score >= second.score * 2) return { kind: "single", matches: [top] };
|
|
181
263
|
return { kind: "multi", matches: candidates.filter((c) => c.score >= top.score / 2).slice(0, 3) };
|
|
182
264
|
}
|
|
265
|
+
function substringHits(strong, weak, haystack) {
|
|
266
|
+
const found = [];
|
|
267
|
+
const seen = /* @__PURE__ */ new Set();
|
|
268
|
+
for (const [list, weight] of [[strong, 3], [weak, 2]]) {
|
|
269
|
+
for (const term of list) {
|
|
270
|
+
if (seen.has(term) || !isSpacelessScript(term)) continue;
|
|
271
|
+
seen.add(term);
|
|
272
|
+
if (haystack.includes(term)) found.push({ term, weight });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return found.filter((a) => !found.some((b) => b.term !== a.term && b.weight >= a.weight && b.term.includes(a.term)));
|
|
276
|
+
}
|
|
183
277
|
|
|
184
278
|
// i18n/en.json
|
|
185
279
|
var en_default = {
|
|
@@ -591,8 +685,49 @@ var tr_default = {
|
|
|
591
685
|
"form.backToChat": "Sohbete d\xF6n"
|
|
592
686
|
};
|
|
593
687
|
|
|
688
|
+
// i18n/th.json
|
|
689
|
+
var th_default = {
|
|
690
|
+
"chat.askAnswered": "\u0E04\u0E33\u0E15\u0E2D\u0E1A\u0E19\u0E35\u0E49\u0E0A\u0E48\u0E27\u0E22\u0E15\u0E2D\u0E1A\u0E04\u0E33\u0E16\u0E32\u0E21\u0E02\u0E2D\u0E07\u0E04\u0E38\u0E13\u0E2B\u0E23\u0E37\u0E2D\u0E44\u0E21\u0E48",
|
|
691
|
+
"chat.yes": "\u0E43\u0E0A\u0E48 \u0E02\u0E2D\u0E1A\u0E04\u0E38\u0E13",
|
|
692
|
+
"chat.no": "\u0E44\u0E21\u0E48\u0E43\u0E0A\u0E48 \u0E15\u0E49\u0E2D\u0E07\u0E01\u0E32\u0E23\u0E04\u0E27\u0E32\u0E21\u0E0A\u0E48\u0E27\u0E22\u0E40\u0E2B\u0E25\u0E37\u0E2D",
|
|
693
|
+
"chat.thanks": "\u0E22\u0E34\u0E19\u0E14\u0E35\u0E17\u0E35\u0E48\u0E44\u0E14\u0E49\u0E0A\u0E48\u0E27\u0E22\u0E40\u0E2B\u0E25\u0E37\u0E2D",
|
|
694
|
+
"chat.newQuestion": "\u0E16\u0E32\u0E21\u0E04\u0E33\u0E16\u0E32\u0E21\u0E43\u0E2B\u0E21\u0E48",
|
|
695
|
+
"chat.back": "\u0E22\u0E49\u0E2D\u0E19\u0E01\u0E25\u0E31\u0E1A",
|
|
696
|
+
"chat.home": "\u0E40\u0E21\u0E19\u0E39\u0E2B\u0E25\u0E31\u0E01",
|
|
697
|
+
"chat.didYouMean": "\u0E04\u0E38\u0E13\u0E2B\u0E21\u0E32\u0E22\u0E16\u0E36\u0E07\u0E02\u0E49\u0E2D\u0E43\u0E14",
|
|
698
|
+
"chat.notFound": "\u0E02\u0E2D\u0E2D\u0E20\u0E31\u0E22 \u0E44\u0E21\u0E48\u0E1E\u0E1A\u0E04\u0E33\u0E15\u0E2D\u0E1A\u0E2A\u0E33\u0E2B\u0E23\u0E31\u0E1A\u0E04\u0E33\u0E16\u0E32\u0E21\u0E19\u0E35\u0E49 \u0E01\u0E23\u0E38\u0E13\u0E32\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E2B\u0E31\u0E27\u0E02\u0E49\u0E2D\u0E08\u0E32\u0E01\u0E40\u0E21\u0E19\u0E39 \u0E2B\u0E23\u0E37\u0E2D\u0E15\u0E34\u0E14\u0E15\u0E48\u0E2D\u0E17\u0E35\u0E21\u0E07\u0E32\u0E19\u0E02\u0E2D\u0E07\u0E40\u0E23\u0E32",
|
|
699
|
+
"chat.notFoundTwice": "\u0E02\u0E2D\u0E2D\u0E20\u0E31\u0E22 \u0E44\u0E21\u0E48\u0E1E\u0E1A\u0E04\u0E33\u0E15\u0E2D\u0E1A \u0E17\u0E35\u0E21\u0E07\u0E32\u0E19\u0E02\u0E2D\u0E07\u0E40\u0E23\u0E32\u0E22\u0E34\u0E19\u0E14\u0E35\u0E0A\u0E48\u0E27\u0E22\u0E40\u0E2B\u0E25\u0E37\u0E2D \u0E41\u0E25\u0E30\u0E44\u0E14\u0E49\u0E43\u0E2A\u0E48\u0E04\u0E33\u0E16\u0E32\u0E21\u0E02\u0E2D\u0E07\u0E04\u0E38\u0E13\u0E44\u0E27\u0E49\u0E43\u0E19\u0E41\u0E1A\u0E1A\u0E1F\u0E2D\u0E23\u0E4C\u0E21\u0E41\u0E25\u0E49\u0E27",
|
|
700
|
+
"chat.contactChip": "\u0E15\u0E34\u0E14\u0E15\u0E48\u0E2D\u0E17\u0E35\u0E21\u0E07\u0E32\u0E19",
|
|
701
|
+
"chat.openingContact": "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E40\u0E1B\u0E34\u0E14\u0E41\u0E1A\u0E1A\u0E1F\u0E2D\u0E23\u0E4C\u0E21\u0E15\u0E34\u0E14\u0E15\u0E48\u0E2D \u0E1A\u0E17\u0E2A\u0E19\u0E17\u0E19\u0E32\u0E19\u0E35\u0E49\u0E08\u0E30\u0E41\u0E19\u0E1A\u0E44\u0E1B\u0E01\u0E31\u0E1A\u0E04\u0E33\u0E02\u0E2D\u0E02\u0E2D\u0E07\u0E04\u0E38\u0E13",
|
|
702
|
+
"chat.chooseTopic": "\u0E21\u0E35\u0E2D\u0E30\u0E44\u0E23\u0E43\u0E2B\u0E49\u0E0A\u0E48\u0E27\u0E22\u0E44\u0E2B\u0E21",
|
|
703
|
+
"chat.aiLabel": "\u0E04\u0E33\u0E15\u0E2D\u0E1A\u0E08\u0E32\u0E01 AI",
|
|
704
|
+
"widget.open": "\u0E40\u0E1B\u0E34\u0E14\u0E41\u0E0A\u0E15",
|
|
705
|
+
"widget.close": "\u0E1B\u0E34\u0E14\u0E41\u0E0A\u0E15",
|
|
706
|
+
"widget.placeholder": "\u0E1E\u0E34\u0E21\u0E1E\u0E4C\u0E04\u0E33\u0E16\u0E32\u0E21\u0E02\u0E2D\u0E07\u0E04\u0E38\u0E13...",
|
|
707
|
+
"widget.send": "\u0E2A\u0E48\u0E07",
|
|
708
|
+
"widget.poweredBy": "\u0E02\u0E31\u0E1A\u0E40\u0E04\u0E25\u0E37\u0E48\u0E2D\u0E19\u0E42\u0E14\u0E22 Kavchat",
|
|
709
|
+
"widget.offline": "\u0E04\u0E38\u0E13\u0E2D\u0E2D\u0E1F\u0E44\u0E25\u0E19\u0E4C\u0E2D\u0E22\u0E39\u0E48 \u0E01\u0E33\u0E25\u0E31\u0E07\u0E41\u0E2A\u0E14\u0E07\u0E04\u0E33\u0E15\u0E2D\u0E1A\u0E17\u0E35\u0E48\u0E1A\u0E31\u0E19\u0E17\u0E36\u0E01\u0E44\u0E27\u0E49",
|
|
710
|
+
"widget.loading": "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E42\u0E2B\u0E25\u0E14...",
|
|
711
|
+
"widget.unavailable": "\u0E1C\u0E39\u0E49\u0E0A\u0E48\u0E27\u0E22\u0E44\u0E21\u0E48\u0E1E\u0E23\u0E49\u0E2D\u0E21\u0E43\u0E2B\u0E49\u0E1A\u0E23\u0E34\u0E01\u0E32\u0E23\u0E43\u0E19\u0E02\u0E13\u0E30\u0E19\u0E35\u0E49",
|
|
712
|
+
"form.title": "\u0E15\u0E34\u0E14\u0E15\u0E48\u0E2D\u0E40\u0E23\u0E32",
|
|
713
|
+
"form.name": "\u0E0A\u0E37\u0E48\u0E2D",
|
|
714
|
+
"form.email": "\u0E2D\u0E35\u0E40\u0E21\u0E25",
|
|
715
|
+
"form.phone": "\u0E42\u0E17\u0E23\u0E28\u0E31\u0E1E\u0E17\u0E4C",
|
|
716
|
+
"form.topic": "\u0E2B\u0E31\u0E27\u0E02\u0E49\u0E2D",
|
|
717
|
+
"form.message": "\u0E02\u0E49\u0E2D\u0E04\u0E27\u0E32\u0E21",
|
|
718
|
+
"form.submit": "\u0E2A\u0E48\u0E07",
|
|
719
|
+
"form.cancel": "\u0E22\u0E01\u0E40\u0E25\u0E34\u0E01",
|
|
720
|
+
"form.sending": "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E2A\u0E48\u0E07...",
|
|
721
|
+
"form.sent": "\u0E02\u0E2D\u0E1A\u0E04\u0E38\u0E13 \u0E40\u0E23\u0E32\u0E44\u0E14\u0E49\u0E23\u0E31\u0E1A\u0E04\u0E33\u0E02\u0E2D\u0E2B\u0E21\u0E32\u0E22\u0E40\u0E25\u0E02 {number} \u0E02\u0E2D\u0E07\u0E04\u0E38\u0E13\u0E41\u0E25\u0E49\u0E27 \u0E41\u0E25\u0E30\u0E08\u0E30\u0E15\u0E34\u0E14\u0E15\u0E48\u0E2D\u0E01\u0E25\u0E31\u0E1A\u0E42\u0E14\u0E22\u0E40\u0E23\u0E47\u0E27",
|
|
722
|
+
"form.error": "\u0E40\u0E01\u0E34\u0E14\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14 \u0E01\u0E23\u0E38\u0E13\u0E32\u0E25\u0E2D\u0E07\u0E2D\u0E35\u0E01\u0E04\u0E23\u0E31\u0E49\u0E07",
|
|
723
|
+
"form.required": "\u0E08\u0E33\u0E40\u0E1B\u0E47\u0E19\u0E15\u0E49\u0E2D\u0E07\u0E01\u0E23\u0E2D\u0E01",
|
|
724
|
+
"form.contactRequired": "\u0E01\u0E23\u0E38\u0E13\u0E32\u0E23\u0E30\u0E1A\u0E38\u0E2D\u0E35\u0E40\u0E21\u0E25\u0E2B\u0E23\u0E37\u0E2D\u0E2B\u0E21\u0E32\u0E22\u0E40\u0E25\u0E02\u0E42\u0E17\u0E23\u0E28\u0E31\u0E1E\u0E17\u0E4C \u0E40\u0E1E\u0E37\u0E48\u0E2D\u0E43\u0E2B\u0E49\u0E40\u0E23\u0E32\u0E15\u0E34\u0E14\u0E15\u0E48\u0E2D\u0E01\u0E25\u0E31\u0E1A\u0E44\u0E14\u0E49",
|
|
725
|
+
"form.emailInvalid": "\u0E2D\u0E35\u0E40\u0E21\u0E25\u0E44\u0E21\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07",
|
|
726
|
+
"form.backToChat": "\u0E01\u0E25\u0E31\u0E1A\u0E44\u0E1B\u0E17\u0E35\u0E48\u0E41\u0E0A\u0E15"
|
|
727
|
+
};
|
|
728
|
+
|
|
594
729
|
// src/i18n.ts
|
|
595
|
-
var UI_TEXTS = { en: en_default, he: he_default, ar: ar_default, ru: ru_default, es: es_default, fr: fr_default, de: de_default, pt: pt_default, it: it_default, tr: tr_default };
|
|
730
|
+
var UI_TEXTS = { en: en_default, he: he_default, ar: ar_default, ru: ru_default, es: es_default, fr: fr_default, de: de_default, pt: pt_default, it: it_default, tr: tr_default, th: th_default };
|
|
596
731
|
var RTL_LOCALES = ["he", "ar"];
|
|
597
732
|
var isRtl = (locale) => RTL_LOCALES.includes(baseLocale(locale));
|
|
598
733
|
var baseLocale = (locale) => String(locale || "en").toLowerCase().split(/[-_]/)[0].replace(/^iw$/, "he");
|
|
@@ -875,6 +1010,7 @@ export {
|
|
|
875
1010
|
format,
|
|
876
1011
|
groupKey,
|
|
877
1012
|
isRtl,
|
|
1013
|
+
isSpacelessScript,
|
|
878
1014
|
keyTerms,
|
|
879
1015
|
matchQuestion,
|
|
880
1016
|
normalizeWords,
|
package/i18n/th.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"chat.askAnswered": "คำตอบนี้ช่วยตอบคำถามของคุณหรือไม่",
|
|
3
|
+
"chat.yes": "ใช่ ขอบคุณ",
|
|
4
|
+
"chat.no": "ไม่ใช่ ต้องการความช่วยเหลือ",
|
|
5
|
+
"chat.thanks": "ยินดีที่ได้ช่วยเหลือ",
|
|
6
|
+
"chat.newQuestion": "ถามคำถามใหม่",
|
|
7
|
+
"chat.back": "ย้อนกลับ",
|
|
8
|
+
"chat.home": "เมนูหลัก",
|
|
9
|
+
"chat.didYouMean": "คุณหมายถึงข้อใด",
|
|
10
|
+
"chat.notFound": "ขออภัย ไม่พบคำตอบสำหรับคำถามนี้ กรุณาเลือกหัวข้อจากเมนู หรือติดต่อทีมงานของเรา",
|
|
11
|
+
"chat.notFoundTwice": "ขออภัย ไม่พบคำตอบ ทีมงานของเรายินดีช่วยเหลือ และได้ใส่คำถามของคุณไว้ในแบบฟอร์มแล้ว",
|
|
12
|
+
"chat.contactChip": "ติดต่อทีมงาน",
|
|
13
|
+
"chat.openingContact": "กำลังเปิดแบบฟอร์มติดต่อ บทสนทนานี้จะแนบไปกับคำขอของคุณ",
|
|
14
|
+
"chat.chooseTopic": "มีอะไรให้ช่วยไหม",
|
|
15
|
+
"chat.aiLabel": "คำตอบจาก AI",
|
|
16
|
+
"widget.open": "เปิดแชต",
|
|
17
|
+
"widget.close": "ปิดแชต",
|
|
18
|
+
"widget.placeholder": "พิมพ์คำถามของคุณ...",
|
|
19
|
+
"widget.send": "ส่ง",
|
|
20
|
+
"widget.poweredBy": "ขับเคลื่อนโดย Kavchat",
|
|
21
|
+
"widget.offline": "คุณออฟไลน์อยู่ กำลังแสดงคำตอบที่บันทึกไว้",
|
|
22
|
+
"widget.loading": "กำลังโหลด...",
|
|
23
|
+
"widget.unavailable": "ผู้ช่วยไม่พร้อมให้บริการในขณะนี้",
|
|
24
|
+
"form.title": "ติดต่อเรา",
|
|
25
|
+
"form.name": "ชื่อ",
|
|
26
|
+
"form.email": "อีเมล",
|
|
27
|
+
"form.phone": "โทรศัพท์",
|
|
28
|
+
"form.topic": "หัวข้อ",
|
|
29
|
+
"form.message": "ข้อความ",
|
|
30
|
+
"form.submit": "ส่ง",
|
|
31
|
+
"form.cancel": "ยกเลิก",
|
|
32
|
+
"form.sending": "กำลังส่ง...",
|
|
33
|
+
"form.sent": "ขอบคุณ เราได้รับคำขอหมายเลข {number} ของคุณแล้ว และจะติดต่อกลับโดยเร็ว",
|
|
34
|
+
"form.error": "เกิดข้อผิดพลาด กรุณาลองอีกครั้ง",
|
|
35
|
+
"form.required": "จำเป็นต้องกรอก",
|
|
36
|
+
"form.contactRequired": "กรุณาระบุอีเมลหรือหมายเลขโทรศัพท์ เพื่อให้เราติดต่อกลับได้",
|
|
37
|
+
"form.emailInvalid": "อีเมลไม่ถูกต้อง",
|
|
38
|
+
"form.backToChat": "กลับไปที่แชต"
|
|
39
|
+
}
|
package/package.json
CHANGED