@kavchat/core 0.0.0-stage → 0.0.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 maxsave
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,18 @@
1
- # Temporary Holding Version
1
+ # @kavchat/core
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The shared engine behind every Kavchat SDK: text normalization for 10 languages, matching a typed question to a
4
+ script (keywords, titles, typo tolerance), the conversation state machine and the UI texts. It has no dependencies
5
+ and does no network calls.
6
+
7
+ You normally do not install it directly. It comes with the SDKs:
8
+
9
+ ```sh
10
+ yarn add @kavchat/widget # website
11
+ yarn add @kavchat/react-native # React Native, with or without Expo
12
+ ```
13
+
14
+ The same behavior is ported to Dart, Swift and Kotlin; `test-vectors/` holds the shared cases every port must pass.
15
+
16
+ Kavchat is a scripted customer-support chat for websites and apps: https://kavchat.com · Docs: https://kavchat.com/docs
17
+
18
+ License: MIT
@@ -0,0 +1,311 @@
1
+ /**
2
+ * מבנה התוכן שהשרת מפרסם ושכל לקוח (ווידג'ט, React Native, Flutter, iOS, Android) מריץ
3
+ * זהה ל-ClientRelease במפרט ה-OpenAPI
4
+ */
5
+ type BotAction = {
6
+ type: 'step';
7
+ stepId: string;
8
+ } | {
9
+ type: 'goto';
10
+ stepId: string;
11
+ scriptId?: string;
12
+ } | {
13
+ type: 'contact';
14
+ } | {
15
+ type: 'screen';
16
+ screen: string;
17
+ targetId?: string;
18
+ } | {
19
+ type: 'link';
20
+ url: string;
21
+ } | {
22
+ type: 'script';
23
+ scriptId: string;
24
+ };
25
+ interface BotButton {
26
+ id: string;
27
+ label: string;
28
+ action: BotAction | null;
29
+ }
30
+ interface BotStep {
31
+ id: string;
32
+ text: string;
33
+ imageUrl?: string;
34
+ keywords: string[];
35
+ buttons: BotButton[];
36
+ }
37
+ interface BotScriptContent {
38
+ name: string;
39
+ keywords: string[];
40
+ contactTopicId?: string;
41
+ rootStepId: string;
42
+ steps: BotStep[];
43
+ }
44
+ interface BotScript {
45
+ id: string;
46
+ kind: 'opening' | 'topic';
47
+ sortOrder: number;
48
+ content: BotScriptContent;
49
+ }
50
+ interface BotSettings {
51
+ botName: string;
52
+ avatarText: string;
53
+ avatarImageUrl: string | null;
54
+ subtitle: string;
55
+ primaryColor: string;
56
+ position: 'start' | 'end';
57
+ contactTopics: Array<{
58
+ id: string;
59
+ name: string;
60
+ }>;
61
+ /** false בתוכנית החינמית: הסמל "Powered by Kavchat" חייב להופיע */
62
+ removeBranding?: boolean;
63
+ /** true כשבעל סביבת העבודה הפעיל תשובות AI (פרימיום, על המפתח שלו). שואלים את /ai/answer רק כשאין התאמה */
64
+ aiAnswers?: boolean;
65
+ }
66
+ type ClientRelease = {
67
+ enabled: false;
68
+ } | {
69
+ enabled: true;
70
+ version: number;
71
+ locale: string;
72
+ settings: BotSettings;
73
+ openingScriptId: string;
74
+ scripts: BotScript[];
75
+ };
76
+
77
+ /**
78
+ * נרמול טקסט לכל השפות הנתמכות, בלי ביטויים עם \p{...}, כי Hermes הישן לא תומך בהם
79
+ *
80
+ * - אותיות קטנות, הסרת ניקוד וטעמים (עברית), תשכיל (ערבית) וסימני הטעמה לטיניים (é → e)
81
+ * - אותיות סופיות בעברית (ם → מ), צורות אליף בערבית (أ → ا), ё → е, ß → ss
82
+ * - מילות קישור לכל שפה, והסרת תחיליות בעברית (ו, ה, ב...) ובערבית (ال, و, ب...)
83
+ *
84
+ * הפורטים ל-Dart, Swift ו-Kotlin חייבים לעבור את test-vectors/normalize.json
85
+ */
86
+ declare const SUPPORTED_LOCALES: readonly ["he", "en", "ar", "ru", "es", "fr", "de", "pt", "it", "tr"];
87
+ type Locale = (typeof SUPPORTED_LOCALES)[number];
88
+ /** מנקה ומחזיר רשימת מילים */
89
+ declare function normalizeWords(text: string): string[];
90
+ /**
91
+ * מסיר תחיליות כל עוד נשארות לפחות 3 אותיות ("והתשלום" ← "תשלומ", "والدفع" ← "دفع"),
92
+ * ובאותיות לטיניות מסיר s של רבים ("arrives" ← "arrive"), חוץ מ-ss, us ו-is
93
+ */
94
+ declare function stripPrefixes(word: string): string;
95
+ /**
96
+ * מילים משמעותיות: בלי מילות קישור של שפת התוכן ושל אנגלית (משתמשים מערבבים), ואחרי הסרת תחיליות
97
+ */
98
+ declare function keyTerms(text: string, locale?: string): string[];
99
+ /** מפתח קיבוץ לשאלות דומות בדוחות */
100
+ declare function groupKey(text: string, locale?: string): string;
101
+ /**
102
+ * שגיאת כתיב אחת לכל היותר, רק במילים של 4 אותיות ומעלה:
103
+ * אות חסרה, אות מיותרת, אות מוחלפת, או שתי אותיות סמוכות שהתחלפו ("trakcing")
104
+ */
105
+ declare function withinOneEdit(a: string, b: string): boolean;
106
+ /** טקסט של שלב, מפוצל לבועות לפי שורה ריקה */
107
+ declare const toBubbles: (text: string) => string[];
108
+
109
+ /**
110
+ * התאמה של שאלה חופשית לשלב בתסריט
111
+ * ציון: מילה מובילה = 3, שם התסריט או כפתור שמוביל לשלב = 2, שגיאת כתיב של אות אחת = 0.5.
112
+ * שלב אחד עם ציון כפול מהבא = single. כמה קרובים = multi (עד 3). אין כלום = none
113
+ */
114
+ type MatchReason = {
115
+ kind: 'keyword' | 'title' | 'typo';
116
+ term: string;
117
+ };
118
+ interface BotMatch {
119
+ scriptId: string;
120
+ stepId: string;
121
+ score: number;
122
+ reasons: MatchReason[];
123
+ }
124
+ interface BotMatchResult {
125
+ kind: 'single' | 'multi' | 'none';
126
+ matches: BotMatch[];
127
+ }
128
+ declare function matchQuestion(scripts: BotScript[], text: string, locale?: string): BotMatchResult;
129
+
130
+ declare const _exports: {
131
+ "chat.askAnswered": string;
132
+ "chat.yes": string;
133
+ "chat.no": string;
134
+ "chat.thanks": string;
135
+ "chat.newQuestion": string;
136
+ "chat.back": string;
137
+ "chat.home": string;
138
+ "chat.didYouMean": string;
139
+ "chat.notFound": string;
140
+ "chat.notFoundTwice": string;
141
+ "chat.contactChip": string;
142
+ "chat.openingContact": string;
143
+ "chat.chooseTopic": string;
144
+ "chat.aiLabel": string;
145
+ "widget.open": string;
146
+ "widget.close": string;
147
+ "widget.placeholder": string;
148
+ "widget.send": string;
149
+ "widget.poweredBy": string;
150
+ "widget.offline": string;
151
+ "widget.loading": string;
152
+ "widget.unavailable": string;
153
+ "form.title": string;
154
+ "form.name": string;
155
+ "form.email": string;
156
+ "form.phone": string;
157
+ "form.topic": string;
158
+ "form.message": string;
159
+ "form.submit": string;
160
+ "form.cancel": string;
161
+ "form.sending": string;
162
+ "form.sent": string;
163
+ "form.error": string;
164
+ "form.required": string;
165
+ "form.contactRequired": string;
166
+ "form.emailInvalid": string;
167
+ "form.backToChat": string;
168
+ };
169
+
170
+ /**
171
+ * הטקסטים של משתמש הקצה: הבוט, הווידג'ט וטופס הפנייה
172
+ * מקור האמת הוא קובצי ה-JSON ב-i18n/, שגם Flutter, iOS ו-Android טוענים
173
+ */
174
+ type UiKey = keyof typeof _exports;
175
+ type UiTexts = Record<UiKey, string>;
176
+ declare const UI_TEXTS: Record<string, UiTexts>;
177
+ declare const RTL_LOCALES: string[];
178
+ declare const isRtl: (locale: string) => boolean;
179
+ /** הטקסטים בשפה המבוקשת, עם השלמה מאנגלית לכל מפתח חסר */
180
+ declare function uiTexts(locale: string, overrides?: Partial<UiTexts>): UiTexts;
181
+ declare const format: (template: string, params: Record<string, string | number>) => string;
182
+
183
+ /**
184
+ * מנוע השיחה: פונקציות טהורות בלי ממשק
185
+ * כל פעולה מחזירה { state, effect }. effect הוא מה שהלקוח צריך לעשות מחוץ לצ'אט:
186
+ * לפתוח טופס פנייה, מסך באפליקציה או קישור.
187
+ * הפורטים ל-Dart, Swift ו-Kotlin חייבים לעבור את test-vectors/conversation.json
188
+ */
189
+ interface BotMessage {
190
+ id: string;
191
+ from: 'bot' | 'user';
192
+ text: string;
193
+ at: number;
194
+ stepId?: string;
195
+ scriptId?: string;
196
+ imageUrl?: string;
197
+ /** תשובה שנוצרה ב-AI. הלקוח מציג לידה תווית, כדי שהמשתמש יידע שהיא לא מתסריט */
198
+ ai?: boolean;
199
+ }
200
+ type ChipKind = 'button' | 'back' | 'home' | 'yes' | 'no' | 'contact' | 'pick';
201
+ interface BotChip {
202
+ id: string;
203
+ label: string;
204
+ kind: ChipKind;
205
+ action?: BotAction | null;
206
+ prefill?: string;
207
+ target?: {
208
+ scriptId: string;
209
+ stepId: string;
210
+ };
211
+ }
212
+ interface BotPosition {
213
+ scriptId: string;
214
+ stepId: string;
215
+ }
216
+ interface BotPathEntry {
217
+ scriptId: string;
218
+ stepId: string;
219
+ via: 'start' | 'button' | 'text' | 'back' | 'home';
220
+ at: string;
221
+ }
222
+ type BotOutcome = 'open' | 'answered' | 'contact';
223
+ interface BotState {
224
+ scripts: Record<string, BotScript>;
225
+ openingId: string;
226
+ locale: string;
227
+ userName: string;
228
+ messages: BotMessage[];
229
+ chips: BotChip[];
230
+ position: BotPosition | null;
231
+ history: BotPosition[];
232
+ path: BotPathEntry[];
233
+ unmatched: {
234
+ text: string;
235
+ at: string;
236
+ }[];
237
+ misses: number;
238
+ outcome: BotOutcome;
239
+ lastScriptId: string | null;
240
+ lastMatch: BotMatchResult | null;
241
+ /** מונה פנימי למזהי הודעות, כדי שהמנוע יישאר דטרמיניסטי */
242
+ seq: number;
243
+ }
244
+ interface TranscriptEntry {
245
+ from: 'bot' | 'user';
246
+ text: string;
247
+ stepId?: string;
248
+ at: string;
249
+ }
250
+ type BotEffect = {
251
+ type: 'contact';
252
+ contactTopicId: string | null;
253
+ scriptId: string | null;
254
+ prefill: string;
255
+ transcript: TranscriptEntry[];
256
+ } | Extract<BotAction, {
257
+ type: 'screen';
258
+ } | {
259
+ type: 'link';
260
+ }>;
261
+ interface BotResult {
262
+ state: BotState;
263
+ effect?: BotEffect;
264
+ }
265
+ /** שעון ומחולל זמן ניתנים להחלפה, כדי שהבדיקות יהיו דטרמיניסטיות */
266
+ interface EngineOptions {
267
+ now?: () => number;
268
+ texts?: Partial<UiTexts>;
269
+ }
270
+ declare function configureEngine(opts: EngineOptions): void;
271
+ declare function transcriptOf(state: BotState): TranscriptEntry[];
272
+ declare function startConversation(opts: {
273
+ scripts: BotScript[];
274
+ openingScriptId: string;
275
+ locale?: string;
276
+ userName?: string;
277
+ }): BotState;
278
+ declare function pressChip(state: BotState, chip: BotChip): BotResult;
279
+ declare function sendText(state: BotState, rawText: string): BotResult;
280
+ /** אורך השאלה המרבי שנשלח ל-/ai/answer */
281
+ declare const AI_QUESTION_MAX = 300;
282
+ /** אורך התשובה המרבי שמוצג. השרת כבר חותך ל-600 */
283
+ declare const AI_ANSWER_MAX = 600;
284
+ /**
285
+ * השאלה לשליחה ל-/ai/answer, או null כשהטקסט ריק או שתסריט עונה עליו.
286
+ * הלקוח קורא לזה רק כש-settings.aiAnswers דלוק
287
+ */
288
+ declare function aiQuestion(state: BotState, rawText: string): string | null;
289
+ /** מה מציגים בזמן שמחכים לתשובת AI: ההודעה של המשתמש, בלי כפתורים */
290
+ declare function pendingQuestion(state: BotState, rawText: string): BotState;
291
+ /**
292
+ * תשובת AI לשאלה שאין לה תסריט. נקרא על המצב שלפני השאלה, במקום sendText.
293
+ * הדיווח לשרת זהה ל-sendText: השאלה נרשמת ב-unmatched.
294
+ * תשובה ריקה מחזירה בדיוק את sendText, כלומר את הודעת "לא נמצא" הרגילה
295
+ */
296
+ declare function answerWithAi(state: BotState, rawText: string, answer: string | null | undefined, scriptId?: string | null): BotResult;
297
+ /** הדיווח לשרת (POST /v1/client/:pk/events). שולחים את כל המסלול בכל פעם */
298
+ declare function eventsPayload(state: BotState, sessionId: string, releaseVersion: number, startedAt: string): {
299
+ sessionId: string;
300
+ releaseVersion: number;
301
+ startedAt: string;
302
+ outcome: BotOutcome;
303
+ path: BotPathEntry[];
304
+ unmatched: {
305
+ text: string;
306
+ at: string;
307
+ }[];
308
+ };
309
+
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 };
311
+ 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 };
@@ -0,0 +1,311 @@
1
+ /**
2
+ * מבנה התוכן שהשרת מפרסם ושכל לקוח (ווידג'ט, React Native, Flutter, iOS, Android) מריץ
3
+ * זהה ל-ClientRelease במפרט ה-OpenAPI
4
+ */
5
+ type BotAction = {
6
+ type: 'step';
7
+ stepId: string;
8
+ } | {
9
+ type: 'goto';
10
+ stepId: string;
11
+ scriptId?: string;
12
+ } | {
13
+ type: 'contact';
14
+ } | {
15
+ type: 'screen';
16
+ screen: string;
17
+ targetId?: string;
18
+ } | {
19
+ type: 'link';
20
+ url: string;
21
+ } | {
22
+ type: 'script';
23
+ scriptId: string;
24
+ };
25
+ interface BotButton {
26
+ id: string;
27
+ label: string;
28
+ action: BotAction | null;
29
+ }
30
+ interface BotStep {
31
+ id: string;
32
+ text: string;
33
+ imageUrl?: string;
34
+ keywords: string[];
35
+ buttons: BotButton[];
36
+ }
37
+ interface BotScriptContent {
38
+ name: string;
39
+ keywords: string[];
40
+ contactTopicId?: string;
41
+ rootStepId: string;
42
+ steps: BotStep[];
43
+ }
44
+ interface BotScript {
45
+ id: string;
46
+ kind: 'opening' | 'topic';
47
+ sortOrder: number;
48
+ content: BotScriptContent;
49
+ }
50
+ interface BotSettings {
51
+ botName: string;
52
+ avatarText: string;
53
+ avatarImageUrl: string | null;
54
+ subtitle: string;
55
+ primaryColor: string;
56
+ position: 'start' | 'end';
57
+ contactTopics: Array<{
58
+ id: string;
59
+ name: string;
60
+ }>;
61
+ /** false בתוכנית החינמית: הסמל "Powered by Kavchat" חייב להופיע */
62
+ removeBranding?: boolean;
63
+ /** true כשבעל סביבת העבודה הפעיל תשובות AI (פרימיום, על המפתח שלו). שואלים את /ai/answer רק כשאין התאמה */
64
+ aiAnswers?: boolean;
65
+ }
66
+ type ClientRelease = {
67
+ enabled: false;
68
+ } | {
69
+ enabled: true;
70
+ version: number;
71
+ locale: string;
72
+ settings: BotSettings;
73
+ openingScriptId: string;
74
+ scripts: BotScript[];
75
+ };
76
+
77
+ /**
78
+ * נרמול טקסט לכל השפות הנתמכות, בלי ביטויים עם \p{...}, כי Hermes הישן לא תומך בהם
79
+ *
80
+ * - אותיות קטנות, הסרת ניקוד וטעמים (עברית), תשכיל (ערבית) וסימני הטעמה לטיניים (é → e)
81
+ * - אותיות סופיות בעברית (ם → מ), צורות אליף בערבית (أ → ا), ё → е, ß → ss
82
+ * - מילות קישור לכל שפה, והסרת תחיליות בעברית (ו, ה, ב...) ובערבית (ال, و, ب...)
83
+ *
84
+ * הפורטים ל-Dart, Swift ו-Kotlin חייבים לעבור את test-vectors/normalize.json
85
+ */
86
+ declare const SUPPORTED_LOCALES: readonly ["he", "en", "ar", "ru", "es", "fr", "de", "pt", "it", "tr"];
87
+ type Locale = (typeof SUPPORTED_LOCALES)[number];
88
+ /** מנקה ומחזיר רשימת מילים */
89
+ declare function normalizeWords(text: string): string[];
90
+ /**
91
+ * מסיר תחיליות כל עוד נשארות לפחות 3 אותיות ("והתשלום" ← "תשלומ", "والدفع" ← "دفع"),
92
+ * ובאותיות לטיניות מסיר s של רבים ("arrives" ← "arrive"), חוץ מ-ss, us ו-is
93
+ */
94
+ declare function stripPrefixes(word: string): string;
95
+ /**
96
+ * מילים משמעותיות: בלי מילות קישור של שפת התוכן ושל אנגלית (משתמשים מערבבים), ואחרי הסרת תחיליות
97
+ */
98
+ declare function keyTerms(text: string, locale?: string): string[];
99
+ /** מפתח קיבוץ לשאלות דומות בדוחות */
100
+ declare function groupKey(text: string, locale?: string): string;
101
+ /**
102
+ * שגיאת כתיב אחת לכל היותר, רק במילים של 4 אותיות ומעלה:
103
+ * אות חסרה, אות מיותרת, אות מוחלפת, או שתי אותיות סמוכות שהתחלפו ("trakcing")
104
+ */
105
+ declare function withinOneEdit(a: string, b: string): boolean;
106
+ /** טקסט של שלב, מפוצל לבועות לפי שורה ריקה */
107
+ declare const toBubbles: (text: string) => string[];
108
+
109
+ /**
110
+ * התאמה של שאלה חופשית לשלב בתסריט
111
+ * ציון: מילה מובילה = 3, שם התסריט או כפתור שמוביל לשלב = 2, שגיאת כתיב של אות אחת = 0.5.
112
+ * שלב אחד עם ציון כפול מהבא = single. כמה קרובים = multi (עד 3). אין כלום = none
113
+ */
114
+ type MatchReason = {
115
+ kind: 'keyword' | 'title' | 'typo';
116
+ term: string;
117
+ };
118
+ interface BotMatch {
119
+ scriptId: string;
120
+ stepId: string;
121
+ score: number;
122
+ reasons: MatchReason[];
123
+ }
124
+ interface BotMatchResult {
125
+ kind: 'single' | 'multi' | 'none';
126
+ matches: BotMatch[];
127
+ }
128
+ declare function matchQuestion(scripts: BotScript[], text: string, locale?: string): BotMatchResult;
129
+
130
+ declare const _exports: {
131
+ "chat.askAnswered": string;
132
+ "chat.yes": string;
133
+ "chat.no": string;
134
+ "chat.thanks": string;
135
+ "chat.newQuestion": string;
136
+ "chat.back": string;
137
+ "chat.home": string;
138
+ "chat.didYouMean": string;
139
+ "chat.notFound": string;
140
+ "chat.notFoundTwice": string;
141
+ "chat.contactChip": string;
142
+ "chat.openingContact": string;
143
+ "chat.chooseTopic": string;
144
+ "chat.aiLabel": string;
145
+ "widget.open": string;
146
+ "widget.close": string;
147
+ "widget.placeholder": string;
148
+ "widget.send": string;
149
+ "widget.poweredBy": string;
150
+ "widget.offline": string;
151
+ "widget.loading": string;
152
+ "widget.unavailable": string;
153
+ "form.title": string;
154
+ "form.name": string;
155
+ "form.email": string;
156
+ "form.phone": string;
157
+ "form.topic": string;
158
+ "form.message": string;
159
+ "form.submit": string;
160
+ "form.cancel": string;
161
+ "form.sending": string;
162
+ "form.sent": string;
163
+ "form.error": string;
164
+ "form.required": string;
165
+ "form.contactRequired": string;
166
+ "form.emailInvalid": string;
167
+ "form.backToChat": string;
168
+ };
169
+
170
+ /**
171
+ * הטקסטים של משתמש הקצה: הבוט, הווידג'ט וטופס הפנייה
172
+ * מקור האמת הוא קובצי ה-JSON ב-i18n/, שגם Flutter, iOS ו-Android טוענים
173
+ */
174
+ type UiKey = keyof typeof _exports;
175
+ type UiTexts = Record<UiKey, string>;
176
+ declare const UI_TEXTS: Record<string, UiTexts>;
177
+ declare const RTL_LOCALES: string[];
178
+ declare const isRtl: (locale: string) => boolean;
179
+ /** הטקסטים בשפה המבוקשת, עם השלמה מאנגלית לכל מפתח חסר */
180
+ declare function uiTexts(locale: string, overrides?: Partial<UiTexts>): UiTexts;
181
+ declare const format: (template: string, params: Record<string, string | number>) => string;
182
+
183
+ /**
184
+ * מנוע השיחה: פונקציות טהורות בלי ממשק
185
+ * כל פעולה מחזירה { state, effect }. effect הוא מה שהלקוח צריך לעשות מחוץ לצ'אט:
186
+ * לפתוח טופס פנייה, מסך באפליקציה או קישור.
187
+ * הפורטים ל-Dart, Swift ו-Kotlin חייבים לעבור את test-vectors/conversation.json
188
+ */
189
+ interface BotMessage {
190
+ id: string;
191
+ from: 'bot' | 'user';
192
+ text: string;
193
+ at: number;
194
+ stepId?: string;
195
+ scriptId?: string;
196
+ imageUrl?: string;
197
+ /** תשובה שנוצרה ב-AI. הלקוח מציג לידה תווית, כדי שהמשתמש יידע שהיא לא מתסריט */
198
+ ai?: boolean;
199
+ }
200
+ type ChipKind = 'button' | 'back' | 'home' | 'yes' | 'no' | 'contact' | 'pick';
201
+ interface BotChip {
202
+ id: string;
203
+ label: string;
204
+ kind: ChipKind;
205
+ action?: BotAction | null;
206
+ prefill?: string;
207
+ target?: {
208
+ scriptId: string;
209
+ stepId: string;
210
+ };
211
+ }
212
+ interface BotPosition {
213
+ scriptId: string;
214
+ stepId: string;
215
+ }
216
+ interface BotPathEntry {
217
+ scriptId: string;
218
+ stepId: string;
219
+ via: 'start' | 'button' | 'text' | 'back' | 'home';
220
+ at: string;
221
+ }
222
+ type BotOutcome = 'open' | 'answered' | 'contact';
223
+ interface BotState {
224
+ scripts: Record<string, BotScript>;
225
+ openingId: string;
226
+ locale: string;
227
+ userName: string;
228
+ messages: BotMessage[];
229
+ chips: BotChip[];
230
+ position: BotPosition | null;
231
+ history: BotPosition[];
232
+ path: BotPathEntry[];
233
+ unmatched: {
234
+ text: string;
235
+ at: string;
236
+ }[];
237
+ misses: number;
238
+ outcome: BotOutcome;
239
+ lastScriptId: string | null;
240
+ lastMatch: BotMatchResult | null;
241
+ /** מונה פנימי למזהי הודעות, כדי שהמנוע יישאר דטרמיניסטי */
242
+ seq: number;
243
+ }
244
+ interface TranscriptEntry {
245
+ from: 'bot' | 'user';
246
+ text: string;
247
+ stepId?: string;
248
+ at: string;
249
+ }
250
+ type BotEffect = {
251
+ type: 'contact';
252
+ contactTopicId: string | null;
253
+ scriptId: string | null;
254
+ prefill: string;
255
+ transcript: TranscriptEntry[];
256
+ } | Extract<BotAction, {
257
+ type: 'screen';
258
+ } | {
259
+ type: 'link';
260
+ }>;
261
+ interface BotResult {
262
+ state: BotState;
263
+ effect?: BotEffect;
264
+ }
265
+ /** שעון ומחולל זמן ניתנים להחלפה, כדי שהבדיקות יהיו דטרמיניסטיות */
266
+ interface EngineOptions {
267
+ now?: () => number;
268
+ texts?: Partial<UiTexts>;
269
+ }
270
+ declare function configureEngine(opts: EngineOptions): void;
271
+ declare function transcriptOf(state: BotState): TranscriptEntry[];
272
+ declare function startConversation(opts: {
273
+ scripts: BotScript[];
274
+ openingScriptId: string;
275
+ locale?: string;
276
+ userName?: string;
277
+ }): BotState;
278
+ declare function pressChip(state: BotState, chip: BotChip): BotResult;
279
+ declare function sendText(state: BotState, rawText: string): BotResult;
280
+ /** אורך השאלה המרבי שנשלח ל-/ai/answer */
281
+ declare const AI_QUESTION_MAX = 300;
282
+ /** אורך התשובה המרבי שמוצג. השרת כבר חותך ל-600 */
283
+ declare const AI_ANSWER_MAX = 600;
284
+ /**
285
+ * השאלה לשליחה ל-/ai/answer, או null כשהטקסט ריק או שתסריט עונה עליו.
286
+ * הלקוח קורא לזה רק כש-settings.aiAnswers דלוק
287
+ */
288
+ declare function aiQuestion(state: BotState, rawText: string): string | null;
289
+ /** מה מציגים בזמן שמחכים לתשובת AI: ההודעה של המשתמש, בלי כפתורים */
290
+ declare function pendingQuestion(state: BotState, rawText: string): BotState;
291
+ /**
292
+ * תשובת AI לשאלה שאין לה תסריט. נקרא על המצב שלפני השאלה, במקום sendText.
293
+ * הדיווח לשרת זהה ל-sendText: השאלה נרשמת ב-unmatched.
294
+ * תשובה ריקה מחזירה בדיוק את sendText, כלומר את הודעת "לא נמצא" הרגילה
295
+ */
296
+ declare function answerWithAi(state: BotState, rawText: string, answer: string | null | undefined, scriptId?: string | null): BotResult;
297
+ /** הדיווח לשרת (POST /v1/client/:pk/events). שולחים את כל המסלול בכל פעם */
298
+ declare function eventsPayload(state: BotState, sessionId: string, releaseVersion: number, startedAt: string): {
299
+ sessionId: string;
300
+ releaseVersion: number;
301
+ startedAt: string;
302
+ outcome: BotOutcome;
303
+ path: BotPathEntry[];
304
+ unmatched: {
305
+ text: string;
306
+ at: string;
307
+ }[];
308
+ };
309
+
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 };
311
+ 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 };