@inploi/plugin-chatbot 3.8.0 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
- import { _, N, i as invariant, o as o$1, c as clsx, a as _$1, p, b as parse, d as picklist, y, e as application, h, k, C as Cn, f as parseAsync, V as ValiError, g as object, t as transform, m as maxLength, j as minLength, r as record, l as boolean, s as string, n as email, u as url, q as regex, T, v as inputHeight, w as m, A as AnimatePresence, F, x as viewState, E as ERROR_MESSAGES } from "./index-b616116f.js";
1
+ import { N, _, h as hasProp, i as invariant, o as o$1, c as clsx, a as _$1, p, b as parse, d as picklist, y, e as application, f as h, k, C as Cn, g as parseAsync, V as ValiError, j as object, t as transform, m as maxLength, l as minLength, r as record, n as boolean, s as string, q as email, u as url, v as regex, T, w as inputHeight, x as m, A as AnimatePresence, F, z as viewState, E as ERROR_MESSAGES } from "./index-122b2522.js";
2
2
  import "@inploi/sdk";
3
+ const kbToReadableSize = (kb) => N(kb).with(_.number.lte(1e3), () => `${Math.round(kb)}KB`).with(_.number.lt(1e3 * 10), () => `${(kb / 1e3).toFixed(1)}MB`).otherwise(() => `${Math.round(kb / 1e3)}MB`);
3
4
  const getHeadOrThrow = (nodes) => {
4
5
  const head = nodes.find((n2) => n2.isHead);
5
6
  if (!head)
@@ -229,14 +230,17 @@ async function interpretSubmitNode({
229
230
  variant: "info",
230
231
  text: "Submitting your application…"
231
232
  });
233
+ const {
234
+ anonymous_id,
235
+ session_id
236
+ } = analytics.getSessionInfo();
232
237
  const response = await apiClient.fetch(`/flow/apply`, {
233
238
  method: "POST",
234
239
  body: JSON.stringify({
235
240
  ...context,
236
- type: node.data.type,
237
- ats: node.data.ats,
238
241
  integration_id: node.data.integrationId,
239
- anonymous_id: analytics.getAnonymousId(),
242
+ anonymous_id,
243
+ session_id,
240
244
  submissions: getApplicationSubmissionsPayload(submissions || {})
241
245
  })
242
246
  }).catch((e) => e);
@@ -250,10 +254,9 @@ async function interpretSubmitNode({
250
254
  author: "bot",
251
255
  text: "Almost there! Please finalise your application on our partner’s website."
252
256
  });
253
- const currentAnonymousId = analytics.getAnonymousId();
254
257
  const href = new URL(response2.ats_data.redirect_url);
255
- if (currentAnonymousId && !href.searchParams.has("anonymous_id")) {
256
- href.searchParams.set("anonymous_id", currentAnonymousId);
258
+ if (anonymous_id && !href.searchParams.has("anonymous_id")) {
259
+ href.searchParams.set("anonymous_id", anonymous_id);
257
260
  }
258
261
  logApplyComplete();
259
262
  await chat.sendMessage({
@@ -285,12 +288,13 @@ async function interpretSubmitNode({
285
288
  async function interpretLinkNode({
286
289
  chat,
287
290
  next,
288
- node
291
+ node,
292
+ submissions
289
293
  }) {
290
294
  await chat.sendMessage({
291
295
  type: "link",
292
296
  href: node.data.href,
293
- text: node.data.cta
297
+ text: interpolateString(node.data.cta, submissions)
294
298
  });
295
299
  next(node.nextId);
296
300
  }
@@ -305,12 +309,13 @@ async function interpretIfBlockNode({
305
309
  async function interpretTextNode({
306
310
  chat,
307
311
  next,
308
- node
312
+ node,
313
+ submissions
309
314
  }) {
310
315
  await chat.sendMessage({
311
316
  author: "bot",
312
317
  type: "text",
313
- text: node.data.text
318
+ text: interpolateString(node.data.text, submissions)
314
319
  });
315
320
  next(node.nextId);
316
321
  }
@@ -331,12 +336,13 @@ async function interpretImageNode({
331
336
  async function interpretQuestionTextNode({
332
337
  chat,
333
338
  next,
334
- node
339
+ node,
340
+ submissions
335
341
  }) {
336
342
  await chat.sendMessage({
337
343
  author: "bot",
338
344
  type: "text",
339
- text: node.data.question
345
+ text: interpolateString(node.data.question, submissions)
340
346
  });
341
347
  const reply = await chat.userInput({
342
348
  key: node.data.key,
@@ -365,12 +371,13 @@ async function interpretQuestionTextNode({
365
371
  async function interpretQuestionNumberNode({
366
372
  chat,
367
373
  next,
368
- node
374
+ node,
375
+ submissions
369
376
  }) {
370
377
  await chat.sendMessage({
371
378
  author: "bot",
372
379
  type: "text",
373
- text: node.data.question
380
+ text: interpolateString(node.data.question, submissions)
374
381
  });
375
382
  const reply = await chat.userInput({
376
383
  key: node.data.key,
@@ -399,12 +406,13 @@ async function interpretQuestionNumberNode({
399
406
  async function interpretQuestionEnumNode({
400
407
  chat,
401
408
  next,
402
- node
409
+ node,
410
+ submissions
403
411
  }) {
404
412
  await chat.sendMessage({
405
413
  author: "bot",
406
414
  type: "text",
407
- text: node.data.question
415
+ text: interpolateString(node.data.question, submissions)
408
416
  });
409
417
  const reply = await chat.userInput({
410
418
  key: node.data.key,
@@ -429,12 +437,13 @@ async function interpretQuestionEnumNode({
429
437
  async function interpretQuestionBooleanNode({
430
438
  chat,
431
439
  next,
432
- node
440
+ node,
441
+ submissions
433
442
  }) {
434
443
  await chat.sendMessage({
435
444
  author: "bot",
436
445
  type: "text",
437
- text: node.data.question
446
+ text: interpolateString(node.data.question, submissions)
438
447
  });
439
448
  const input = await chat.userInput({
440
449
  key: node.data.key,
@@ -466,27 +475,205 @@ async function interpretQuestionBooleanNode({
466
475
  }
467
476
  next(node.nextId);
468
477
  }
478
+ const dummyDivBecauseGoogleRequiresIt = document.createElement("div");
479
+ const keyToAddressComponnents = {
480
+ line1: ["street_number", "floor", "room", "premise"],
481
+ line2: ["subpremise", "street_address", "route"],
482
+ line3: ["sublocality", "neighborhood"],
483
+ city: ["locality", "postal_town"],
484
+ state: ["administrative_area_level_1"],
485
+ postcode: ["postal_code"],
486
+ country: ["country"]
487
+ };
488
+ const fieldMapKeys = Object.keys(keyToAddressComponnents);
469
489
  async function interpretQuestionAddressNode({
470
490
  chat,
471
491
  next,
472
- node
492
+ node,
493
+ logger
473
494
  }) {
495
+ const {
496
+ google
497
+ } = window;
474
498
  await chat.sendMessage({
475
499
  author: "bot",
476
500
  type: "text",
477
- text: "Address questions are not implemented yet"
501
+ text: node.data.question
478
502
  });
479
- next(node.nextId);
503
+ const askForAddress = async (defaultValues) => {
504
+ const addressFields = [{
505
+ label: "Postcode",
506
+ key: node.data.keys.postcode,
507
+ optional: false,
508
+ defaultValue: defaultValues.postcode
509
+ }, {
510
+ label: "Line 1",
511
+ key: node.data.keys.line1,
512
+ optional: false,
513
+ defaultValue: defaultValues.line1
514
+ }, {
515
+ label: "Line 2",
516
+ key: node.data.keys.line2,
517
+ optional: true,
518
+ defaultValue: defaultValues.line2
519
+ }, {
520
+ label: "Line 3",
521
+ key: node.data.keys.line3,
522
+ optional: true,
523
+ defaultValue: defaultValues.line3
524
+ }, {
525
+ label: "City",
526
+ key: node.data.keys.city,
527
+ optional: false,
528
+ defaultValue: defaultValues.city
529
+ }, {
530
+ label: "State/County/Province",
531
+ key: node.data.keys.state,
532
+ optional: true,
533
+ defaultValue: defaultValues.state
534
+ }, {
535
+ label: "Country",
536
+ key: node.data.keys.country,
537
+ optional: false,
538
+ defaultValue: defaultValues.country
539
+ }];
540
+ for (const field of addressFields) {
541
+ await chat.sendMessage({
542
+ author: "bot",
543
+ type: "text",
544
+ text: field.label
545
+ });
546
+ const {
547
+ value
548
+ } = await chat.userInput({
549
+ type: "text",
550
+ key: field.key,
551
+ config: {
552
+ format: "text",
553
+ optional: field.optional,
554
+ defaultValue: field.defaultValue
555
+ }
556
+ });
557
+ if (value === null) {
558
+ await chat.sendMessage({
559
+ type: "system",
560
+ variant: "info",
561
+ text: "Skipped"
562
+ });
563
+ } else {
564
+ await chat.sendMessage({
565
+ author: "user",
566
+ type: "text",
567
+ text: value
568
+ });
569
+ }
570
+ }
571
+ };
572
+ if (!hasProp(window, "google") || !hasProp(window.google, "maps") || !hasProp(window.google.maps, "places")) {
573
+ logger.warn("Google maps not available, falling back to manual input.");
574
+ logger.info("If you’d like to use the address autocomplete, please insert the google maps API snippet in your website and make sure it has access to the *places* library.");
575
+ await askForAddress({});
576
+ return next(node.nextId);
577
+ }
578
+ const autocomplete = new google.maps.places.AutocompleteService();
579
+ const places = new google.maps.places.PlacesService(dummyDivBecauseGoogleRequiresIt);
580
+ const {
581
+ value: search
582
+ } = await chat.userInput({
583
+ type: "text",
584
+ key: "_internal-address-search",
585
+ config: {
586
+ format: "text",
587
+ optional: false,
588
+ placeholder: "Search for your address"
589
+ }
590
+ });
591
+ if (search === null)
592
+ return next(node.id);
593
+ await chat.sendMessage({
594
+ author: "user",
595
+ type: "text",
596
+ text: `Search for “${search}”`
597
+ });
598
+ const {
599
+ predictions
600
+ } = await autocomplete.getPlacePredictions({
601
+ input: search
602
+ });
603
+ const {
604
+ value: [selected]
605
+ } = await chat.userInput({
606
+ type: "multiple-choice",
607
+ key: void 0,
608
+ config: {
609
+ options: predictions.slice(0, 4).map((p2) => ({
610
+ label: p2.description,
611
+ value: p2.place_id
612
+ })).concat({
613
+ label: "None of these",
614
+ value: "none"
615
+ }),
616
+ maxSelected: 1,
617
+ minSelected: 1
618
+ }
619
+ });
620
+ if (!selected || selected === "none") {
621
+ return next(node.id);
622
+ }
623
+ const result = await new Promise((resolve, reject) => places.getDetails({
624
+ placeId: selected,
625
+ fields: ["address_components"]
626
+ }, (result2, status) => {
627
+ if (status !== google.maps.places.PlacesServiceStatus["OK"])
628
+ return reject(status);
629
+ if (result2 === null)
630
+ return reject("ZERO_RESULTS");
631
+ return resolve({
632
+ ok: true,
633
+ place: result2
634
+ });
635
+ })).catch(async (e) => {
636
+ logger.error("Failed to get address details", e);
637
+ return {
638
+ ok: false
639
+ };
640
+ });
641
+ if (result.ok === false) {
642
+ await chat.sendMessage({
643
+ type: "system",
644
+ variant: "error",
645
+ text: "Failed to get address details"
646
+ });
647
+ await askForAddress({});
648
+ return next(node.id);
649
+ }
650
+ const addressComponents = result.place.address_components;
651
+ const autoFilledInputs = addressComponents ? fieldMapKeys.reduce((acc, key) => {
652
+ const componentTypes = keyToAddressComponnents[key];
653
+ const value = addressComponents.filter((component) => component.types.some((type) => componentTypes.includes(type))).map((component) => component.long_name).join(", ");
654
+ if (value) {
655
+ acc[key] = value;
656
+ }
657
+ return acc;
658
+ }, {}) : {};
659
+ await chat.sendMessage({
660
+ author: "bot",
661
+ type: "text",
662
+ text: "Please confirm or adjust your address:"
663
+ });
664
+ await askForAddress(autoFilledInputs);
665
+ return next(node.nextId);
480
666
  }
481
667
  async function interpretQuestionFileNode({
482
668
  node,
483
669
  chat,
484
- next
670
+ next,
671
+ submissions
485
672
  }) {
486
673
  await chat.sendMessage({
487
674
  author: "bot",
488
675
  type: "text",
489
- text: node.data.question
676
+ text: interpolateString(node.data.question, submissions)
490
677
  });
491
678
  const files = await chat.userInput({
492
679
  key: node.data.key,
@@ -575,6 +762,27 @@ const isIfBlockConditionMet = (ifBlock, submissions) => {
575
762
  }
576
763
  }, () => false).exhaustive();
577
764
  };
765
+ const interpolateString = (str, context) => {
766
+ const regex2 = /{{\s*([^}]+?)\s*(?:\|\s*([^}]+?)\s*)?}}/g;
767
+ return str.replace(regex2, (match2, key, defaultValue = "") => {
768
+ key = key.trim();
769
+ const submission = context == null ? void 0 : context[key];
770
+ if (!submission)
771
+ return defaultValue;
772
+ switch (submission.type) {
773
+ case "boolean":
774
+ return submission.value === "true" ? "true" : "false";
775
+ case "file":
776
+ if (!submission.value)
777
+ return "no files";
778
+ return submission.value.map((file) => `${file.name} (${kbToReadableSize(file.sizeKb)})`).join(", ");
779
+ case "multiple-choice":
780
+ return submission.value.join(", ");
781
+ default:
782
+ return submission.value || defaultValue;
783
+ }
784
+ });
785
+ };
578
786
  const SendButton = ({
579
787
  class: className,
580
788
  ...props
@@ -739,7 +947,6 @@ const toBase64 = (file) => new Promise((resolve, reject) => {
739
947
  };
740
948
  reader.onerror = reject;
741
949
  });
742
- const kbToReadableSize = (kb) => N(kb).with(_.number.lte(1e3), () => `${Math.round(kb)}KB`).with(_.number.lt(1e3 * 10), () => `${(kb / 1e3).toFixed(1)}MB`).otherwise(() => `${Math.round(kb / 1e3)}MB`);
743
950
  const addFileSizesKb = (files) => files.reduce((acc, cur) => acc + cur.sizeKb, 0);
744
951
  const isFileSubmission = isSubmissionOfType("file");
745
952
  const FILENAMES_TO_SHOW_QTY = 3;
@@ -776,6 +983,19 @@ const FilenameBadge = ({
776
983
  class: clsx("outline-neutral-6 text-neutral-11 bg-neutral-1 block rounded-md px-1 py-0.5 text-xs outline outline-1", className),
777
984
  ...props
778
985
  });
986
+ const getFileExtension = (fileName) => {
987
+ const extension = fileName.split(".").pop();
988
+ if (!extension)
989
+ throw new Error("No file extension found");
990
+ return extension ? "." + extension : "";
991
+ };
992
+ const validateExtensions = ({
993
+ allowedExtensions,
994
+ files
995
+ }) => {
996
+ const normalisedExtensions = allowedExtensions.map((ext) => ext.toLowerCase());
997
+ return files.every((file) => normalisedExtensions.includes(getFileExtension(file.name).toLowerCase()));
998
+ };
779
999
  const ChatInputFile = ({
780
1000
  input,
781
1001
  onSubmitSuccess,
@@ -799,6 +1019,15 @@ const ChatInputFile = ({
799
1019
  message: "Please select a file"
800
1020
  });
801
1021
  }
1022
+ if (input.config.extensions.length > 0 && !validateExtensions({
1023
+ allowedExtensions: input.config.extensions,
1024
+ files
1025
+ })) {
1026
+ return setError({
1027
+ type: "validate",
1028
+ message: `Please upload ${input.config.extensions.join(", ")} files only`
1029
+ });
1030
+ }
802
1031
  if (input.config.fileSizeLimitKib && totalSize > input.config.fileSizeLimitKib) {
803
1032
  return setError({
804
1033
  type: "max",
@@ -2532,6 +2761,7 @@ const ChatInputText = ({
2532
2761
  }) => {
2533
2762
  var _a;
2534
2763
  const submission = input.key ? (_a = application.current$.value.application) == null ? void 0 : _a.data.submissions[input.key] : void 0;
2764
+ const defaultValue = input.config.defaultValue;
2535
2765
  const {
2536
2766
  register,
2537
2767
  handleSubmit,
@@ -2540,7 +2770,7 @@ const ChatInputText = ({
2540
2770
  }
2541
2771
  } = useForm({
2542
2772
  defaultValues: {
2543
- text: isTextSubmission(submission) ? submission.value : ""
2773
+ text: defaultValue ? defaultValue : isTextSubmission(submission) ? submission.value : ""
2544
2774
  },
2545
2775
  resolver: getResolver(input.config)
2546
2776
  });
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const index = require("./index-efb34443.cjs");
3
+ const index = require("./index-2d0cac15.cjs");
4
4
  require("@inploi/sdk");
5
5
  exports.chatbotPlugin = index.chatbotPlugin;
@@ -1,5 +1,5 @@
1
- import { z } from "./index-b616116f.js";
1
+ import { B } from "./index-122b2522.js";
2
2
  import "@inploi/sdk";
3
3
  export {
4
- z as chatbotPlugin
4
+ B as chatbotPlugin
5
5
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inploi/plugin-chatbot",
3
- "version": "3.8.0",
3
+ "version": "3.10.0",
4
4
  "type": "module",
5
5
  "main": "dist/plugin-chatbot.js",
6
6
  "types": "dist/index.d.ts",
@@ -33,17 +33,18 @@
33
33
  "@total-typescript/ts-reset": "^0.5.1",
34
34
  "@types/bun": "^1.0.8",
35
35
  "@types/culori": "^2.0.4",
36
+ "@types/google.maps": "^3.55.4",
36
37
  "@types/node": "^20.10.0",
37
38
  "@types/react-transition-group": "^4.4.9",
38
39
  "autoprefixer": "^10.4.16",
39
40
  "class-variance-authority": "^0.7.0",
40
41
  "clsx": "^2.0.0",
41
- "eslint-plugin-tailwindcss": "^3.14.1",
42
42
  "concurrently": "^8.2.2",
43
43
  "culori": "^3.3.0",
44
44
  "dotenv": "^16.3.1",
45
45
  "eslint": "^7.32.0",
46
46
  "eslint-plugin-react-hooks": "^4.6.0",
47
+ "eslint-plugin-tailwindcss": "^3.14.1",
47
48
  "happy-dom": "^12.6.0",
48
49
  "idb-keyval": "^6.2.1",
49
50
  "msw": "^2.0.10",
@@ -64,9 +65,9 @@
64
65
  "vite": "^4.4.5",
65
66
  "vite-plugin-dts": "^3.7.0",
66
67
  "vite-tsconfig-paths": "^4.2.1",
67
- "@inploi/core": "1.11.4",
68
- "@inploi/sdk": "1.11.4",
68
+ "@inploi/core": "1.11.5",
69
69
  "@inploi/design-tokens": "0.2.0",
70
+ "@inploi/sdk": "1.12.0",
70
71
  "eslint-config-custom": "0.1.0",
71
72
  "tsconfig": "0.1.0"
72
73
  },