@inploi/plugin-chatbot 14.9.0 → 14.10.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.
Files changed (36) hide show
  1. package/cdn/index.js +7 -7
  2. package/cdn/style.css +1 -1
  3. package/dist/chatbot-body-b3e7212a.cjs +5 -0
  4. package/dist/chatbot-body-d3fd468f.js +4094 -0
  5. package/dist/chatbot.d.ts +6 -0
  6. package/dist/chatbot.idb.d.ts +2 -2
  7. package/dist/chatbot.resume.d.ts +15 -0
  8. package/dist/chatbot.resume.test.d.ts +1 -0
  9. package/dist/chatbot.state.d.ts +24 -406
  10. package/dist/chatbot.storage.test.d.ts +1 -0
  11. package/dist/chatbot.utils.d.ts +1 -255
  12. package/dist/components/chat-input/chat-input.address.d.ts +1 -14
  13. package/dist/components/chat-input/chat-input.boolean.d.ts +1 -13
  14. package/dist/components/chat-input/chat-input.d.ts +1 -12
  15. package/dist/components/chat-input/chat-input.file.d.ts +1 -12
  16. package/dist/components/chat-input/chat-input.multiple-choice.d.ts +1 -14
  17. package/dist/components/chat-input/chat-input.number.d.ts +1 -14
  18. package/dist/components/chat-input/chat-input.phone-number.d.ts +1 -17
  19. package/dist/components/chat-input/chat-input.submit.d.ts +1 -10
  20. package/dist/components/chat-input/chat-input.text.d.ts +1 -16
  21. package/dist/components/chatbot-body.lifecycle.test.d.ts +1 -0
  22. package/dist/components/message.link.d.ts +1 -1
  23. package/dist/components/useChatService.d.ts +1 -1
  24. package/dist/components/useChatService.test.d.ts +1 -0
  25. package/dist/i18n.d.ts +42 -0
  26. package/dist/index-4aad840b.cjs +5 -0
  27. package/dist/{index-75330fed.js → index-b8dab3ec.js} +1184 -1122
  28. package/dist/plugin-chatbot.cjs +1 -1
  29. package/dist/plugin-chatbot.js +1 -1
  30. package/dist/style/tokens.d.ts +4 -4
  31. package/dist/style.css +1 -1
  32. package/package.json +4 -4
  33. package/dist/chatbot-body-45ccac19.js +0 -4695
  34. package/dist/chatbot-body-b6a4eef8.cjs +0 -5
  35. package/dist/index-47f31542.cjs +0 -5
  36. package/dist/interpreter.d.ts +0 -67
package/dist/chatbot.d.ts CHANGED
@@ -78,6 +78,12 @@ export declare const chatbotPlugin: ({ _internal_domManager: dom, theme, terms,
78
78
  fetchFlowByJobId: (jobId: string, additionalProperties?: FetchFlowByJobIdAdditionalProperties) => Promise<OpenChatbotParams>;
79
79
  fetchFlowById: (flowId: string, additionalProperties?: FetchFlowByIdAdditionalProperties) => Promise<OpenChatbotParams>;
80
80
  open: (params: OpenChatbotParams | Promise<OpenChatbotParams>) => Promise<void>;
81
+ /**
82
+ * Whether this browser holds an application in progress that `open(params)` would load: the same saved state
83
+ * under the same key, unfinished, with an answer, including one whose submit started. The candidate loader
84
+ * keeps such a candidate on this chatbot rather than move them mid-application (PROD-7342).
85
+ */
86
+ hasApplicationInProgress: (params: OpenChatbotParams) => Promise<boolean>;
81
87
  close: () => Promise<void>;
82
88
  destroy: () => void;
83
89
  };
@@ -1,6 +1,6 @@
1
+ import { ApplicationRecovery, ChatInput, ChatMessage, KeyToSubmissionMap, SubmitCooldown } from '@inploi/flows/engine';
1
2
  import { Flow } from './chatbot.api';
2
- import { ApplicationRecovery, ChatMessage, KeyToSubmissionMap, StartedFlow, SubmitCooldown } from './chatbot.state';
3
- import { ChatInput } from './components/chat-input/chat-input';
3
+ import { StartedFlow } from './chatbot.state';
4
4
 
5
5
  export declare const idb: {
6
6
  getStateData: (flow: Flow, flowKeys: string[], context: Record<string, unknown>) => Promise<{
@@ -0,0 +1,15 @@
1
+ import { ChatService } from '@inploi/flows/engine';
2
+ import { TFunction } from './i18n';
3
+
4
+ /** The resume prompt's messages share this group, so they can be removed once answered or when left behind. */
5
+ export declare const RESUME_PROMPT_GROUP_ID = "resume-prompt";
6
+ /**
7
+ * Asks a candidate who came back to a saved, unsubmitted application whether to continue it or start again
8
+ * (PROD-7338). A bot message, announced by the conversation's live region, then two buttons, the first focused. The
9
+ * prompt is removed once answered, so it never builds up in the saved conversation. Resolves `true` to continue.
10
+ */
11
+ export declare const askToResume: ({ chatService, t, signal, }: {
12
+ chatService: Pick<ChatService, "send" | "input">;
13
+ t: TFunction;
14
+ signal: AbortSignal;
15
+ }) => Promise<boolean>;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
+ import { ApplicationRecovery, ChatInput, ChatMessage, FlowStateData, FlowSubmission } from '@inploi/flows/engine';
1
2
  import { OpenChatbotParams } from '.';
2
3
  import { Flow } from './chatbot.api';
3
- import { ChatInput } from './components/chat-input/chat-input';
4
4
  import { ChatbotFlowProgress } from './progress';
5
5
 
6
6
  export declare const getCacheKey: (flow: Pick<StartedFlow, "flowKeys" | "id" | "version" | "build" | "context">) => string;
@@ -14,8 +14,25 @@ export type StartedFlow = Flow & {
14
14
  locationOrigin: OpenChatbotParams['locationOrigin'];
15
15
  nodeIdToProgress: Record<string, ChatbotFlowProgress>;
16
16
  analytics: OpenChatbotParams['analytics'];
17
- };
18
- export declare const getCorrectionNodeId: (flow: StartedFlow) => string | undefined;
17
+ /**
18
+ * The candidate came back to a saved, unsubmitted application and has not yet chosen to continue it or start again
19
+ * (PROD-7338). Memory only, never in browser storage: asked on the first open after the page loads, not on a reopen.
20
+ */
21
+ offerResume?: boolean;
22
+ };
23
+ /**
24
+ * A saved application worth asking about when the candidate comes back: the candidate answered something, it is not finished, and no
25
+ * submit has started. A started write may have landed or succeeded, and its own guard already says so. Caches from
26
+ * before the write barrier existed count a visited submit node as started, as the interpreter does.
27
+ */
28
+ export declare const hasUnfinishedApplication: ({ data, nodes }: Pick<StartedFlow, "data" | "nodes">) => boolean;
29
+ /**
30
+ * A saved application still in the chatbot's hands: not finished, and the candidate answered something or a submit
31
+ * started (all questions may have been optional). Unlike the resume prompt's test, a started, pending or unknown
32
+ * submit counts, because only the chatbot holds its write barrier and recovery state (PROD-7342: such an application
33
+ * finishes in the chatbot).
34
+ */
35
+ export declare const hasApplicationInProgress: ({ data, nodes }: Pick<StartedFlow, "data" | "nodes">) => boolean;
19
36
  type CurrentFlow = {
20
37
  state: 'idle';
21
38
  flow?: never;
@@ -35,279 +52,11 @@ type CurrentFlow = {
35
52
  };
36
53
  export declare const store: {
37
54
  current$: import('@preact/signals').Signal<CurrentFlow>;
38
- viewState$: import('@preact/signals').Signal<"closed" | "maximised" | "minimised">;
39
- prepareFlow: ({ flow, context, flowKeys, title, analytics, job, locationOrigin }: OpenChatbotParams) => Promise<{
40
- data: FlowStateData;
41
- startedAt: Date;
42
- nodeIdToProgress: Record<string, ChatbotFlowProgress>;
43
- flowKeys: string[];
44
- id: string;
45
- version: number;
46
- nodes: ({
47
- type: "flow-section";
48
- id: string;
49
- data: {
50
- title: string;
51
- };
52
- isHead?: boolean | undefined;
53
- nextId?: string | undefined;
54
- } | {
55
- type: "pre-apply-result";
56
- id: string;
57
- data: {
58
- key: string;
59
- participationKey: string;
60
- messages: {
61
- hard: string;
62
- advisory: string;
63
- matched: string;
64
- };
65
- continueLabel: string;
66
- alternativeLink?: {
67
- href: string;
68
- label: string;
69
- } | undefined;
70
- };
71
- isHead?: boolean | undefined;
72
- nextId?: string | undefined;
73
- } | {
74
- type: "text";
75
- id: string;
76
- data: {
77
- text: string;
78
- };
79
- isHead?: boolean | undefined;
80
- nextId?: string | undefined;
81
- } | {
82
- type: "end-flow";
83
- id: string;
84
- data: {
85
- systemMessage: string;
86
- };
87
- isHead?: boolean | undefined;
88
- nextId?: string | undefined;
89
- } | {
90
- type: "integration-application-submit";
91
- id: string;
92
- data: {
93
- integrationId: string;
94
- skipConfirmation: boolean;
95
- submitLabel: string;
96
- submitKeys: "all" | string[];
97
- key?: string | undefined;
98
- successMessage?: string | undefined;
99
- captcha?: boolean | undefined;
100
- };
101
- isHead?: boolean | undefined;
102
- nextId?: string | undefined;
103
- } | {
104
- type: "integration-workflow-get";
105
- id: string;
106
- data: {
107
- integrationId: string;
108
- };
109
- isHead?: boolean | undefined;
110
- nextId?: string | undefined;
111
- } | {
112
- type: "link";
113
- id: string;
114
- data: {
115
- href: string;
116
- cta: string;
117
- };
118
- isHead?: boolean | undefined;
119
- nextId?: string | undefined;
120
- } | {
121
- type: "image";
122
- id: string;
123
- data: {
124
- url: string;
125
- width: number;
126
- height: number;
127
- };
128
- isHead?: boolean | undefined;
129
- nextId?: string | undefined;
130
- } | {
131
- type: "question-boolean";
132
- id: string;
133
- data: {
134
- key: string;
135
- question: string;
136
- trueLabel: string;
137
- falseLabel: string;
138
- optional: boolean;
139
- preApply?: {
140
- severity: "hard" | "advisory";
141
- mismatchMessage: string;
142
- expectedValue: boolean;
143
- } | undefined;
144
- };
145
- isHead?: boolean | undefined;
146
- nextId?: string | undefined;
147
- } | {
148
- type: "question-text";
149
- id: string;
150
- data: {
151
- key: string;
152
- question: string;
153
- optional: boolean;
154
- format: "date" | "text" | "url" | "email" | "phone";
155
- placeholder?: string | undefined;
156
- maxChars?: number | undefined;
157
- minChars?: number | undefined;
158
- multiline?: boolean | undefined;
159
- };
160
- isHead?: boolean | undefined;
161
- nextId?: string | undefined;
162
- } | {
163
- type: "question-number";
164
- id: string;
165
- data: {
166
- key: string;
167
- question: string;
168
- optional: boolean;
169
- placeholder?: string | undefined;
170
- decimalCases?: number | undefined;
171
- min?: number | undefined;
172
- max?: number | undefined;
173
- };
174
- isHead?: boolean | undefined;
175
- nextId?: string | undefined;
176
- } | {
177
- type: "question-phone";
178
- id: string;
179
- data: {
180
- key: string;
181
- question: string;
182
- optional: boolean;
183
- placeholder: string;
184
- minChars: number;
185
- defaultCountryCode: string;
186
- };
187
- isHead?: boolean | undefined;
188
- nextId?: string | undefined;
189
- } | {
190
- type: "question-enum";
191
- id: string;
192
- data: {
193
- options: {
194
- value: string;
195
- label: string;
196
- }[];
197
- key: string;
198
- question: string;
199
- maxSelected: number;
200
- minSelected: number;
201
- preApply?: {
202
- severity: "hard" | "advisory";
203
- mismatchMessage: string;
204
- expectedValue: string;
205
- } | undefined;
206
- };
207
- isHead?: boolean | undefined;
208
- nextId?: string | undefined;
209
- } | {
210
- type: "question-file";
211
- id: string;
212
- data: {
213
- key: string;
214
- question: string;
215
- optional: boolean;
216
- extensions: string[];
217
- multiple?: boolean | undefined;
218
- maxSizeKb?: number | undefined;
219
- };
220
- isHead?: boolean | undefined;
221
- nextId?: string | undefined;
222
- } | {
223
- type: "question-address";
224
- id: string;
225
- data: {
226
- keys: {
227
- line1: string | null;
228
- line2: string | null;
229
- line3: string | null;
230
- city: string | null;
231
- state: string | null;
232
- postcode: string | null;
233
- country: string | null;
234
- };
235
- key: string;
236
- question: string;
237
- optional: boolean;
238
- placeholder?: string | undefined;
239
- line2Required?: boolean | undefined;
240
- };
241
- isHead?: boolean | undefined;
242
- nextId?: string | undefined;
243
- } | {
244
- type: "if-block";
245
- id: string;
246
- data: {
247
- compareKey: string;
248
- compareValue: string;
249
- compare: "equals" | "notEquals" | "contains" | "notContains" | "greaterThan" | "lessThan" | "greaterThanOrEqualTo" | "lessThanOrEqualTo";
250
- } | {
251
- combinator: "and" | "or";
252
- conditions: {
253
- compareKey: string;
254
- compareValue: string;
255
- compare: "equals" | "notEquals" | "contains" | "notContains" | "greaterThan" | "lessThan" | "greaterThanOrEqualTo" | "lessThanOrEqualTo";
256
- }[];
257
- };
258
- isHead?: boolean | undefined;
259
- nextId?: string | undefined;
260
- branchId?: string | undefined;
261
- } | {
262
- type: "jump";
263
- id: string;
264
- data: {
265
- targetId: string;
266
- };
267
- isHead?: boolean | undefined;
268
- nextId?: string | undefined;
269
- } | {
270
- type: "add-submission";
271
- id: string;
272
- data: {
273
- value: string;
274
- key: string;
275
- };
276
- isHead?: boolean | undefined;
277
- nextId?: string | undefined;
278
- } | {
279
- type: "check-duplicate";
280
- id: string;
281
- data: {
282
- key: string;
283
- email: string;
284
- scope: "job" | "company";
285
- };
286
- isHead?: boolean | undefined;
287
- nextId?: string | undefined;
288
- } | {
289
- type: "feedback";
290
- id: string;
291
- data: {};
292
- isHead?: boolean | undefined;
293
- nextId?: string | undefined;
294
- })[];
295
- build: number;
296
- context: Record<string, unknown>;
297
- title: string;
298
- analytics: {
299
- customProperties?: Record<string, unknown>;
300
- } | undefined;
301
- job: {
302
- id: string;
303
- id_type: "internal" | "external";
304
- } | undefined;
305
- locationOrigin: {
306
- lat?: number;
307
- lng?: number;
308
- } | undefined;
309
- }>;
55
+ viewState$: import('@preact/signals').Signal<"maximised" | "minimised" | "closed">;
56
+ prepareFlow: ({ flow, context, flowKeys, title, analytics, job, locationOrigin, }: OpenChatbotParams) => Promise<StartedFlow>;
310
57
  commitFlow: (flow: StartedFlow) => void;
58
+ /** The candidate chose to continue or start again, so this page does not ask about this application again. */
59
+ settleResume: (flow: StartedFlow) => void;
311
60
  cancelCurrentFlow: () => void;
312
61
  markAsFinished: () => void;
313
62
  markAsInteracted: () => void;
@@ -321,135 +70,4 @@ export declare const store: {
321
70
  setInput: (input: ChatInput | undefined) => void;
322
71
  persistWriteBarrier: (flow: StartedFlow) => Promise<void>;
323
72
  };
324
- export type MessageAuthor = 'bot' | 'user';
325
- type SystemMessage = {
326
- type: 'system';
327
- text: string;
328
- variant: 'info' | 'warning' | 'error' | 'success';
329
- };
330
- type TextMessage = {
331
- author: MessageAuthor;
332
- type: 'text';
333
- text: string;
334
- };
335
- export type LinkMessage = {
336
- type: 'link';
337
- href: string;
338
- text: string;
339
- onClick?: () => void;
340
- };
341
- type ImageMessage = {
342
- author: MessageAuthor;
343
- type: 'image';
344
- url: string;
345
- width: number;
346
- height: number;
347
- };
348
- type FileMessage = {
349
- author: MessageAuthor;
350
- type: 'file';
351
- fileName: string;
352
- fileSizeKb: number;
353
- };
354
- export type LoadingMessage = {
355
- type: 'loading';
356
- /** Shown beside the indicator and announced to screen readers. */ text?: string;
357
- };
358
- export type ChatMessage = TextMessage | ImageMessage | SystemMessage | FileMessage | LinkMessage | LoadingMessage;
359
- export type FileToUpload = {
360
- name: string;
361
- data: string;
362
- sizeKb: number;
363
- };
364
- export type FlowSubmissionAddress = {
365
- value: Record<string, string | undefined>;
366
- type: 'address';
367
- };
368
- export type FlowSubmissionFile = {
369
- value: FileToUpload[];
370
- type: 'file';
371
- };
372
- export type FlowSubmissionString = {
373
- value: string;
374
- type: 'string';
375
- };
376
- export type FlowSubmissionBoolean = {
377
- value: boolean;
378
- type: 'boolean';
379
- };
380
- export type FlowSubmissionNumber = {
381
- value: number;
382
- type: 'number';
383
- };
384
- export type FlowSubmissionPhone = {
385
- value: {
386
- countryCode: string;
387
- phoneNumber: string;
388
- };
389
- type: 'phone';
390
- };
391
- export type FlowSubmissionEnum = {
392
- value: string[];
393
- type: 'enum';
394
- };
395
- export type FlowSubmissionIntegration = {
396
- value: unknown;
397
- type: 'integration';
398
- };
399
- export type FlowSubmission = FlowSubmissionString | FlowSubmissionBoolean | FlowSubmissionNumber | FlowSubmissionPhone | FlowSubmissionEnum | FlowSubmissionAddress | FlowSubmissionFile | FlowSubmissionIntegration;
400
- export type KeyToSubmissionMap = {
401
- [key: string]: FlowSubmission;
402
- };
403
- export type ApplicationRecovery = {
404
- fileQuestionNodeId: string;
405
- submitNodeId: string;
406
- };
407
- export type SubmitCooldown = {
408
- key: string;
409
- until: number;
410
- /** Consecutive throttles or temporary outages behind this wait, when it follows one; it sets the next one (PROD-6636, PROD-7172). */
411
- throttles?: number;
412
- };
413
- /** Dynamic part of a flow */
414
- export type FlowStateData = {
415
- /** History of messages left in the chat */
416
- messages: (ChatMessage & {
417
- groupId?: string;
418
- })[];
419
- submissions: KeyToSubmissionMap;
420
- nodeHistory: string[];
421
- /**
422
- * Browser replay barrier: submit nodes whose write has started, flushed to the flow cache before the POST so
423
- * Undo and reload cannot resend a pending, accepted or unknown attempt. It is not proof that the provider
424
- * accepted the application and not server idempotency: independent tabs, cleared storage and the memory-only
425
- * fallback are not coordinated. An explicit not_submitted result releases only the attempt that added the
426
- * entry. Caches from before this field infer it conservatively from visited submit nodes.
427
- */
428
- startedWriteNodeIds?: string[];
429
- /**
430
- * When the most recent unsuccessful submit of any kind, other than a duplicate, happened (epoch ms), by submit
431
- * node (and job, when known) key. A duplicate within an hour of it is most likely that attempt having landed
432
- * (PROD-7208). Kept across Restart, which is how a candidate retries after a failure; browser-local, like the barrier.
433
- */
434
- failedAttemptAt?: Record<string, number>;
435
- /**
436
- * Legacy, read-only: keys of attempts with an unknown or missing verdict, written before failures carried a time
437
- * (PROD-7055). Still in candidates' browser storage; with no time to compare, a key here counts as a recent
438
- * failure until a new failure on that key replaces it with a time in `failedAttemptAt`. Never added to.
439
- */
440
- unconfirmedAttemptKeys?: string[];
441
- /**
442
- * When submit becomes available again after an unsuccessful attempt, for the attempt key above (PROD-7112),
443
- * and how many throttles or outages in a row led to it (PROD-6636, PROD-7172). Cleared by a success. Kept across Restart so neither
444
- * Restart nor a reload skips the wait or resets the escalation; browser-local, like the barrier.
445
- */
446
- submitCooldown?: SubmitCooldown;
447
- applicationRecovery?: ApplicationRecovery;
448
- /** Needs to be separate because a node can have many inputs */
449
- currentInput: ChatInput | null;
450
- isFinished: boolean;
451
- hasInteracted?: boolean;
452
- sequence: number;
453
- flowSessionId: string;
454
- };
455
73
  export {};
@@ -0,0 +1 @@
1
+ export {};