@nuskin/react-search-sdk 1.0.0-comx-2233.4 → 1.0.0-trying-again.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/dist/index.d.mts +127 -122
- package/dist/index.d.ts +127 -122
- package/dist/index.js +451 -981
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +431 -956
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -3
package/dist/index.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var reactSearchComponents = require('@nuskin/react-search-components');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
8
6
|
|
|
9
7
|
// src/constants.ts
|
|
10
8
|
var STOREFRONT_SHOPPING_CONTEXT = "storefront";
|
|
@@ -18,9 +16,6 @@ var DEBOUNCE_MS = 220;
|
|
|
18
16
|
var MAX_VISIBLE_SUGGESTIONS = 6;
|
|
19
17
|
var SEARCH_PLACEHOLDER = "Search by product name, ingredient, benefits, or SKU";
|
|
20
18
|
var TRIGGER_PLACEHOLDER = "Product Search";
|
|
21
|
-
var DEFAULT_AI_CHAT_BASE_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/embed";
|
|
22
|
-
var DEFAULT_AI_CHAT_CONVERSATIONS_URL = `${DEFAULT_AI_CHAT_BASE_URL}/conversations`;
|
|
23
|
-
var DEFAULT_AI_CHAT_MESSAGES_URL = `${DEFAULT_AI_CHAT_BASE_URL}/messages`;
|
|
24
19
|
|
|
25
20
|
// src/services/SearchApiService.ts
|
|
26
21
|
var DEFAULT_SEARCH_PLACEHOLDER = TRIGGER_PLACEHOLDER;
|
|
@@ -406,549 +401,6 @@ function createSearchService(options = {}) {
|
|
|
406
401
|
return service;
|
|
407
402
|
}
|
|
408
403
|
|
|
409
|
-
// node_modules/eventsource-parser/dist/errors.js
|
|
410
|
-
var ParseError = class extends Error {
|
|
411
|
-
constructor(message, options) {
|
|
412
|
-
super(message);
|
|
413
|
-
this.name = "ParseError";
|
|
414
|
-
this.type = options.type;
|
|
415
|
-
this.field = options.field;
|
|
416
|
-
this.value = options.value;
|
|
417
|
-
this.line = options.line;
|
|
418
|
-
}
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
// node_modules/eventsource-parser/dist/parse.js
|
|
422
|
-
var LF = 10;
|
|
423
|
-
var CR = 13;
|
|
424
|
-
var SPACE = 32;
|
|
425
|
-
var MAX_FIELD_PREFIX_LENGTH = 6;
|
|
426
|
-
function noop(_arg) {
|
|
427
|
-
}
|
|
428
|
-
function createParser(config) {
|
|
429
|
-
if (typeof config === "function") {
|
|
430
|
-
throw new TypeError("`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?");
|
|
431
|
-
}
|
|
432
|
-
const { onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize } = config;
|
|
433
|
-
const pendingFragments = [];
|
|
434
|
-
let pendingFragmentsLength = 0;
|
|
435
|
-
let isFirstChunk = true;
|
|
436
|
-
let id;
|
|
437
|
-
let data = "";
|
|
438
|
-
let dataLines = 0;
|
|
439
|
-
let eventType;
|
|
440
|
-
let terminated = false;
|
|
441
|
-
let skippingLine = false;
|
|
442
|
-
let skipNextLineFeed = false;
|
|
443
|
-
function feed(chunk) {
|
|
444
|
-
if (terminated) {
|
|
445
|
-
throw new Error("Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing.");
|
|
446
|
-
}
|
|
447
|
-
if (isFirstChunk) {
|
|
448
|
-
isFirstChunk = false;
|
|
449
|
-
if (chunk.charCodeAt(0) === 65279) {
|
|
450
|
-
chunk = chunk.slice(1);
|
|
451
|
-
} else if (chunk.charCodeAt(0) === 239 && chunk.charCodeAt(1) === 187 && chunk.charCodeAt(2) === 191) {
|
|
452
|
-
chunk = chunk.slice(3);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
if (skippingLine || skipNextLineFeed) {
|
|
456
|
-
chunk = resumeAfterSkip(chunk);
|
|
457
|
-
if (!chunk) {
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
if (!pendingFragments.length) {
|
|
462
|
-
const trailing = processLines(chunk);
|
|
463
|
-
if (trailing !== "") {
|
|
464
|
-
storeTrailing(trailing);
|
|
465
|
-
}
|
|
466
|
-
checkBufferSize();
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
if (chunk.indexOf("\n") === -1 && chunk.indexOf("\r") === -1) {
|
|
470
|
-
if (pendingFragmentsLength < MAX_FIELD_PREFIX_LENGTH) {
|
|
471
|
-
const head = pendingFragments.join("") + chunk.slice(0, MAX_FIELD_PREFIX_LENGTH - pendingFragmentsLength);
|
|
472
|
-
if (!shouldBufferTrailing(head)) {
|
|
473
|
-
pendingFragments.length = 0;
|
|
474
|
-
pendingFragmentsLength = 0;
|
|
475
|
-
skippingLine = true;
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
pendingFragments.push(chunk);
|
|
480
|
-
pendingFragmentsLength += chunk.length;
|
|
481
|
-
checkBufferSize();
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
pendingFragments.push(chunk);
|
|
485
|
-
const input = pendingFragments.join("");
|
|
486
|
-
pendingFragments.length = 0;
|
|
487
|
-
pendingFragmentsLength = 0;
|
|
488
|
-
storeTrailing(processLines(input));
|
|
489
|
-
checkBufferSize();
|
|
490
|
-
}
|
|
491
|
-
function resumeAfterSkip(chunk) {
|
|
492
|
-
if (chunk.length === 0) {
|
|
493
|
-
return chunk;
|
|
494
|
-
}
|
|
495
|
-
if (skipNextLineFeed) {
|
|
496
|
-
skipNextLineFeed = false;
|
|
497
|
-
return chunk.charCodeAt(0) === LF ? chunk.slice(1) : chunk;
|
|
498
|
-
}
|
|
499
|
-
const crIndex = chunk.indexOf("\r");
|
|
500
|
-
const lfIndex = chunk.indexOf("\n");
|
|
501
|
-
const lineEnd = crIndex === -1 ? lfIndex : lfIndex === -1 ? crIndex : crIndex < lfIndex ? crIndex : lfIndex;
|
|
502
|
-
if (lineEnd === -1) {
|
|
503
|
-
return "";
|
|
504
|
-
}
|
|
505
|
-
if (lineEnd === chunk.length - 1 && chunk.charCodeAt(lineEnd) === CR) {
|
|
506
|
-
skippingLine = false;
|
|
507
|
-
skipNextLineFeed = true;
|
|
508
|
-
return "";
|
|
509
|
-
}
|
|
510
|
-
skippingLine = false;
|
|
511
|
-
return chunk.slice(lineEnd + (chunk.charCodeAt(lineEnd) === CR && chunk.charCodeAt(lineEnd + 1) === LF ? 2 : 1));
|
|
512
|
-
}
|
|
513
|
-
function storeTrailing(trailing) {
|
|
514
|
-
if (!trailing)
|
|
515
|
-
return;
|
|
516
|
-
if (trailing.charCodeAt(trailing.length - 1) === CR) {
|
|
517
|
-
parseLine(trailing, 0, trailing.length - 1);
|
|
518
|
-
skipNextLineFeed = true;
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
if (shouldBufferTrailing(trailing)) {
|
|
522
|
-
pendingFragments.push(trailing);
|
|
523
|
-
pendingFragmentsLength = trailing.length;
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
skippingLine = true;
|
|
527
|
-
}
|
|
528
|
-
function shouldBufferTrailing(trailing) {
|
|
529
|
-
const firstCharCode = trailing.charCodeAt(0);
|
|
530
|
-
return firstCharCode === 58 && !!onComment || firstCharCode === 100 && isPotentialField(trailing, "data") || firstCharCode === 101 && isPotentialField(trailing, "event") || firstCharCode === 105 && isPotentialField(trailing, "id") || firstCharCode === 114 && isPotentialField(trailing, "retry");
|
|
531
|
-
}
|
|
532
|
-
function checkBufferSize() {
|
|
533
|
-
if (maxBufferSize === void 0)
|
|
534
|
-
return;
|
|
535
|
-
if (pendingFragmentsLength + data.length <= maxBufferSize)
|
|
536
|
-
return;
|
|
537
|
-
terminated = true;
|
|
538
|
-
pendingFragments.length = 0;
|
|
539
|
-
pendingFragmentsLength = 0;
|
|
540
|
-
id = void 0;
|
|
541
|
-
data = "";
|
|
542
|
-
dataLines = 0;
|
|
543
|
-
eventType = void 0;
|
|
544
|
-
skippingLine = false;
|
|
545
|
-
skipNextLineFeed = false;
|
|
546
|
-
onError(new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {
|
|
547
|
-
type: "max-buffer-size-exceeded"
|
|
548
|
-
}));
|
|
549
|
-
}
|
|
550
|
-
function processLines(chunk) {
|
|
551
|
-
let searchIndex = 0;
|
|
552
|
-
if (chunk.indexOf("\r") === -1) {
|
|
553
|
-
let lfIndex = chunk.indexOf("\n", searchIndex);
|
|
554
|
-
while (lfIndex !== -1) {
|
|
555
|
-
if (searchIndex === lfIndex) {
|
|
556
|
-
if (dataLines > 0) {
|
|
557
|
-
onEvent({ id, event: eventType, data });
|
|
558
|
-
}
|
|
559
|
-
id = void 0;
|
|
560
|
-
data = "";
|
|
561
|
-
dataLines = 0;
|
|
562
|
-
eventType = void 0;
|
|
563
|
-
searchIndex = lfIndex + 1;
|
|
564
|
-
lfIndex = chunk.indexOf("\n", searchIndex);
|
|
565
|
-
continue;
|
|
566
|
-
}
|
|
567
|
-
const firstCharCode = chunk.charCodeAt(searchIndex);
|
|
568
|
-
if (isDataPrefix(chunk, searchIndex, firstCharCode)) {
|
|
569
|
-
const valueStart = chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5;
|
|
570
|
-
const value = chunk.slice(valueStart, lfIndex);
|
|
571
|
-
if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {
|
|
572
|
-
onEvent({ id, event: eventType, data: value });
|
|
573
|
-
id = void 0;
|
|
574
|
-
data = "";
|
|
575
|
-
eventType = void 0;
|
|
576
|
-
searchIndex = lfIndex + 2;
|
|
577
|
-
lfIndex = chunk.indexOf("\n", searchIndex);
|
|
578
|
-
continue;
|
|
579
|
-
}
|
|
580
|
-
data = dataLines === 0 ? value : `${data}
|
|
581
|
-
${value}`;
|
|
582
|
-
dataLines++;
|
|
583
|
-
} else if (isEventPrefix(chunk, searchIndex, firstCharCode)) {
|
|
584
|
-
eventType = chunk.slice(chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6, lfIndex) || void 0;
|
|
585
|
-
} else {
|
|
586
|
-
parseLine(chunk, searchIndex, lfIndex);
|
|
587
|
-
}
|
|
588
|
-
searchIndex = lfIndex + 1;
|
|
589
|
-
lfIndex = chunk.indexOf("\n", searchIndex);
|
|
590
|
-
}
|
|
591
|
-
return chunk.slice(searchIndex);
|
|
592
|
-
}
|
|
593
|
-
while (searchIndex < chunk.length) {
|
|
594
|
-
const crIndex = chunk.indexOf("\r", searchIndex);
|
|
595
|
-
const lfIndex = chunk.indexOf("\n", searchIndex);
|
|
596
|
-
let lineEnd = -1;
|
|
597
|
-
if (crIndex !== -1 && lfIndex !== -1) {
|
|
598
|
-
lineEnd = crIndex < lfIndex ? crIndex : lfIndex;
|
|
599
|
-
} else if (crIndex !== -1) {
|
|
600
|
-
if (crIndex === chunk.length - 1) {
|
|
601
|
-
lineEnd = -1;
|
|
602
|
-
} else {
|
|
603
|
-
lineEnd = crIndex;
|
|
604
|
-
}
|
|
605
|
-
} else if (lfIndex !== -1) {
|
|
606
|
-
lineEnd = lfIndex;
|
|
607
|
-
}
|
|
608
|
-
if (lineEnd === -1) {
|
|
609
|
-
break;
|
|
610
|
-
}
|
|
611
|
-
parseLine(chunk, searchIndex, lineEnd);
|
|
612
|
-
searchIndex = lineEnd + 1;
|
|
613
|
-
if (chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF) {
|
|
614
|
-
searchIndex++;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
return chunk.slice(searchIndex);
|
|
618
|
-
}
|
|
619
|
-
function parseLine(chunk, start, end) {
|
|
620
|
-
if (start === end) {
|
|
621
|
-
dispatchEvent();
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
624
|
-
const firstCharCode = chunk.charCodeAt(start);
|
|
625
|
-
if (isDataPrefix(chunk, start, firstCharCode)) {
|
|
626
|
-
const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5;
|
|
627
|
-
const value2 = chunk.slice(valueStart, end);
|
|
628
|
-
data = dataLines === 0 ? value2 : `${data}
|
|
629
|
-
${value2}`;
|
|
630
|
-
dataLines++;
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
|
-
if (isEventPrefix(chunk, start, firstCharCode)) {
|
|
634
|
-
eventType = chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || void 0;
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
if (firstCharCode === 105 && chunk.charCodeAt(start + 1) === 100 && chunk.charCodeAt(start + 2) === 58) {
|
|
638
|
-
const value2 = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end);
|
|
639
|
-
if (!value2.includes("\0"))
|
|
640
|
-
id = value2;
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
if (firstCharCode === 58) {
|
|
644
|
-
if (onComment) {
|
|
645
|
-
const line2 = chunk.slice(start, end);
|
|
646
|
-
onComment(line2.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1));
|
|
647
|
-
}
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
const line = chunk.slice(start, end);
|
|
651
|
-
const fieldSeparatorIndex = line.indexOf(":");
|
|
652
|
-
if (fieldSeparatorIndex === -1) {
|
|
653
|
-
processField(line, "", line);
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
const field = line.slice(0, fieldSeparatorIndex);
|
|
657
|
-
const offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1;
|
|
658
|
-
const value = line.slice(fieldSeparatorIndex + offset);
|
|
659
|
-
processField(field, value, line);
|
|
660
|
-
}
|
|
661
|
-
function processField(field, value, line) {
|
|
662
|
-
switch (field) {
|
|
663
|
-
case "event":
|
|
664
|
-
eventType = value || void 0;
|
|
665
|
-
break;
|
|
666
|
-
case "data":
|
|
667
|
-
data = dataLines === 0 ? value : `${data}
|
|
668
|
-
${value}`;
|
|
669
|
-
dataLines++;
|
|
670
|
-
break;
|
|
671
|
-
case "id":
|
|
672
|
-
if (!value.includes("\0"))
|
|
673
|
-
id = value;
|
|
674
|
-
break;
|
|
675
|
-
case "retry":
|
|
676
|
-
if (/^\d+$/.test(value)) {
|
|
677
|
-
onRetry(parseInt(value, 10));
|
|
678
|
-
} else {
|
|
679
|
-
onError(new ParseError(`Invalid \`retry\` value: "${value}"`, {
|
|
680
|
-
type: "invalid-retry",
|
|
681
|
-
value,
|
|
682
|
-
line
|
|
683
|
-
}));
|
|
684
|
-
}
|
|
685
|
-
break;
|
|
686
|
-
default:
|
|
687
|
-
onError(new ParseError(`Unknown field "${field.length > 20 ? `${field.slice(0, 20)}\u2026` : field}"`, { type: "unknown-field", field, value, line }));
|
|
688
|
-
break;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
function dispatchEvent() {
|
|
692
|
-
if (dataLines > 0) {
|
|
693
|
-
onEvent({
|
|
694
|
-
id,
|
|
695
|
-
event: eventType,
|
|
696
|
-
data
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
id = void 0;
|
|
700
|
-
data = "";
|
|
701
|
-
dataLines = 0;
|
|
702
|
-
eventType = void 0;
|
|
703
|
-
}
|
|
704
|
-
function reset(options = {}) {
|
|
705
|
-
if (options.consume && pendingFragments.length > 0) {
|
|
706
|
-
const incompleteLine = pendingFragments.join("");
|
|
707
|
-
parseLine(incompleteLine, 0, incompleteLine.length);
|
|
708
|
-
}
|
|
709
|
-
isFirstChunk = true;
|
|
710
|
-
id = void 0;
|
|
711
|
-
data = "";
|
|
712
|
-
dataLines = 0;
|
|
713
|
-
eventType = void 0;
|
|
714
|
-
pendingFragments.length = 0;
|
|
715
|
-
pendingFragmentsLength = 0;
|
|
716
|
-
terminated = false;
|
|
717
|
-
skippingLine = false;
|
|
718
|
-
skipNextLineFeed = false;
|
|
719
|
-
}
|
|
720
|
-
return { feed, reset };
|
|
721
|
-
}
|
|
722
|
-
function isDataPrefix(chunk, i, firstCharCode) {
|
|
723
|
-
return firstCharCode === 100 && chunk.charCodeAt(i + 1) === 97 && chunk.charCodeAt(i + 2) === 116 && chunk.charCodeAt(i + 3) === 97 && chunk.charCodeAt(i + 4) === 58;
|
|
724
|
-
}
|
|
725
|
-
function isEventPrefix(chunk, i, firstCharCode) {
|
|
726
|
-
return firstCharCode === 101 && chunk.charCodeAt(i + 1) === 118 && chunk.charCodeAt(i + 2) === 101 && chunk.charCodeAt(i + 3) === 110 && chunk.charCodeAt(i + 4) === 116 && chunk.charCodeAt(i + 5) === 58;
|
|
727
|
-
}
|
|
728
|
-
function isPotentialField(line, field) {
|
|
729
|
-
let i = 1;
|
|
730
|
-
while (i < line.length && i < field.length) {
|
|
731
|
-
if (line.charCodeAt(i) !== field.charCodeAt(i)) {
|
|
732
|
-
return false;
|
|
733
|
-
}
|
|
734
|
-
i++;
|
|
735
|
-
}
|
|
736
|
-
return line.length <= field.length || line.charCodeAt(field.length) === 58;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
// src/services/ChatService.ts
|
|
740
|
-
var PRODUCTS_OPENING_FENCE = "```products";
|
|
741
|
-
var MARKDOWN_CLOSING_FENCE = "\n```";
|
|
742
|
-
function findProductsBlockStart(content, openingFenceIndex) {
|
|
743
|
-
let blockStart = openingFenceIndex + PRODUCTS_OPENING_FENCE.length;
|
|
744
|
-
while (blockStart < content.length && (content[blockStart] === " " || content[blockStart] === " " || content[blockStart] === "\r")) {
|
|
745
|
-
blockStart += 1;
|
|
746
|
-
}
|
|
747
|
-
return content[blockStart] === "\n" ? blockStart + 1 : null;
|
|
748
|
-
}
|
|
749
|
-
function isStandaloneClosingFence(content, fenceEnd) {
|
|
750
|
-
const lineEnd = content.indexOf("\n", fenceEnd);
|
|
751
|
-
const suffixEnd = lineEnd === -1 ? content.length : lineEnd;
|
|
752
|
-
return content.slice(fenceEnd, suffixEnd).trim() === "";
|
|
753
|
-
}
|
|
754
|
-
function findClosingFence(content, fromIndex) {
|
|
755
|
-
let candidate = content.indexOf(MARKDOWN_CLOSING_FENCE, fromIndex);
|
|
756
|
-
while (candidate !== -1) {
|
|
757
|
-
const fenceEnd = candidate + MARKDOWN_CLOSING_FENCE.length;
|
|
758
|
-
if (isStandaloneClosingFence(content, fenceEnd)) {
|
|
759
|
-
return candidate;
|
|
760
|
-
}
|
|
761
|
-
candidate = content.indexOf(MARKDOWN_CLOSING_FENCE, fenceEnd);
|
|
762
|
-
}
|
|
763
|
-
return -1;
|
|
764
|
-
}
|
|
765
|
-
function isRecord(value) {
|
|
766
|
-
return value !== null && typeof value === "object";
|
|
767
|
-
}
|
|
768
|
-
function getRenderProductListItems(event) {
|
|
769
|
-
if (event["type"] !== "render" || event["pattern"] !== "product_list") {
|
|
770
|
-
return [];
|
|
771
|
-
}
|
|
772
|
-
const data = event["data"];
|
|
773
|
-
if (!isRecord(data) || !Array.isArray(data["items"])) {
|
|
774
|
-
return [];
|
|
775
|
-
}
|
|
776
|
-
return data["items"].filter(isRecord);
|
|
777
|
-
}
|
|
778
|
-
function extractProductsFromMarkdown(content) {
|
|
779
|
-
const products = [];
|
|
780
|
-
const summaryParts = [];
|
|
781
|
-
let summaryCursor = 0;
|
|
782
|
-
let searchCursor = 0;
|
|
783
|
-
while (searchCursor < content.length) {
|
|
784
|
-
const start = content.indexOf(PRODUCTS_OPENING_FENCE, searchCursor);
|
|
785
|
-
if (start === -1) break;
|
|
786
|
-
const blockStart = findProductsBlockStart(content, start);
|
|
787
|
-
if (blockStart === null) {
|
|
788
|
-
searchCursor = start + PRODUCTS_OPENING_FENCE.length;
|
|
789
|
-
continue;
|
|
790
|
-
}
|
|
791
|
-
const nextOpeningFence = content.indexOf(
|
|
792
|
-
PRODUCTS_OPENING_FENCE,
|
|
793
|
-
blockStart
|
|
794
|
-
);
|
|
795
|
-
const end = findClosingFence(content, blockStart);
|
|
796
|
-
if (end === -1) break;
|
|
797
|
-
if (nextOpeningFence !== -1 && nextOpeningFence < end) {
|
|
798
|
-
searchCursor = nextOpeningFence;
|
|
799
|
-
continue;
|
|
800
|
-
}
|
|
801
|
-
try {
|
|
802
|
-
const parsed = JSON.parse(content.slice(blockStart, end));
|
|
803
|
-
if (Array.isArray(parsed)) {
|
|
804
|
-
products.push(...parsed);
|
|
805
|
-
}
|
|
806
|
-
} catch (e) {
|
|
807
|
-
console.error("Failed to parse products block:", e);
|
|
808
|
-
}
|
|
809
|
-
summaryParts.push(content.slice(summaryCursor, start));
|
|
810
|
-
summaryCursor = end + MARKDOWN_CLOSING_FENCE.length;
|
|
811
|
-
searchCursor = summaryCursor;
|
|
812
|
-
}
|
|
813
|
-
summaryParts.push(content.slice(summaryCursor));
|
|
814
|
-
return { summary: summaryParts.join("").trim(), products };
|
|
815
|
-
}
|
|
816
|
-
function createChatService(options) {
|
|
817
|
-
const {
|
|
818
|
-
conversationsUrl = DEFAULT_AI_CHAT_CONVERSATIONS_URL,
|
|
819
|
-
messagesUrl = DEFAULT_AI_CHAT_MESSAGES_URL,
|
|
820
|
-
salesAiAppKey,
|
|
821
|
-
salesAiParentOrigin,
|
|
822
|
-
market,
|
|
823
|
-
language,
|
|
824
|
-
fetchImpl: fetchImplOption
|
|
825
|
-
} = options;
|
|
826
|
-
let fetchImpl;
|
|
827
|
-
if (fetchImplOption !== void 0) {
|
|
828
|
-
fetchImpl = fetchImplOption;
|
|
829
|
-
} else if (typeof fetch === "function") {
|
|
830
|
-
fetchImpl = fetch.bind(globalThis);
|
|
831
|
-
} else {
|
|
832
|
-
fetchImpl = () => {
|
|
833
|
-
throw new Error("ChatService: no fetch implementation available");
|
|
834
|
-
};
|
|
835
|
-
}
|
|
836
|
-
function buildHeaders(extra, includeJsonContentType = true) {
|
|
837
|
-
const headers = {
|
|
838
|
-
...extra
|
|
839
|
-
};
|
|
840
|
-
if (includeJsonContentType) {
|
|
841
|
-
headers["Content-Type"] = "application/json";
|
|
842
|
-
}
|
|
843
|
-
const accessToken = typeof window !== "undefined" ? window.localStorage?.getItem("accessToken") : null;
|
|
844
|
-
if (accessToken) {
|
|
845
|
-
headers["Authorization"] = `Bearer ${accessToken}`;
|
|
846
|
-
}
|
|
847
|
-
if (salesAiAppKey) {
|
|
848
|
-
headers["X-Sales-AI-App-Key"] = salesAiAppKey;
|
|
849
|
-
}
|
|
850
|
-
if (salesAiParentOrigin) {
|
|
851
|
-
headers["X-Sales-AI-Parent-Origin"] = salesAiParentOrigin;
|
|
852
|
-
}
|
|
853
|
-
return headers;
|
|
854
|
-
}
|
|
855
|
-
return {
|
|
856
|
-
async createConversation() {
|
|
857
|
-
const res = await fetchImpl(conversationsUrl, {
|
|
858
|
-
method: "POST",
|
|
859
|
-
headers: buildHeaders(void 0, false)
|
|
860
|
-
});
|
|
861
|
-
if (!res.ok) {
|
|
862
|
-
throw new Error(
|
|
863
|
-
`ChatService.createConversation: ${res.status} ${res.statusText}`
|
|
864
|
-
);
|
|
865
|
-
}
|
|
866
|
-
const data = await res.json();
|
|
867
|
-
const id = data["id"] ?? data["conversation_id"];
|
|
868
|
-
if (typeof id !== "number") {
|
|
869
|
-
throw new TypeError(
|
|
870
|
-
"ChatService.createConversation: response missing numeric id"
|
|
871
|
-
);
|
|
872
|
-
}
|
|
873
|
-
return id;
|
|
874
|
-
},
|
|
875
|
-
async sendMessage(conversationId, content, onChunk) {
|
|
876
|
-
const requestBody = {
|
|
877
|
-
conversation_id: conversationId,
|
|
878
|
-
content
|
|
879
|
-
};
|
|
880
|
-
if (market || language) {
|
|
881
|
-
requestBody.scope_context = {
|
|
882
|
-
market: market ?? "US",
|
|
883
|
-
language: language ?? "en"
|
|
884
|
-
};
|
|
885
|
-
}
|
|
886
|
-
requestBody.client_capabilities = {
|
|
887
|
-
patterns: {
|
|
888
|
-
product_list: [1],
|
|
889
|
-
follow_up: [1]
|
|
890
|
-
}
|
|
891
|
-
};
|
|
892
|
-
const res = await fetchImpl(messagesUrl, {
|
|
893
|
-
method: "POST",
|
|
894
|
-
headers: buildHeaders({ Accept: "text/event-stream" }),
|
|
895
|
-
body: JSON.stringify(requestBody)
|
|
896
|
-
});
|
|
897
|
-
if (!res.ok) {
|
|
898
|
-
throw new Error(
|
|
899
|
-
`ChatService.sendMessage: ${res.status} ${res.statusText}`
|
|
900
|
-
);
|
|
901
|
-
}
|
|
902
|
-
let accumulated = "";
|
|
903
|
-
const renderedProducts = [];
|
|
904
|
-
const parser = createParser({
|
|
905
|
-
onEvent(event) {
|
|
906
|
-
const data = event.data;
|
|
907
|
-
if (!data || data === "[DONE]") return;
|
|
908
|
-
try {
|
|
909
|
-
const parsed = JSON.parse(data);
|
|
910
|
-
const parsedType = parsed["type"];
|
|
911
|
-
const type = typeof parsedType === "string" ? parsedType : "";
|
|
912
|
-
renderedProducts.push(
|
|
913
|
-
...getRenderProductListItems(parsed)
|
|
914
|
-
);
|
|
915
|
-
if (type === "content" && typeof parsed["text"] === "string") {
|
|
916
|
-
const text = parsed["text"];
|
|
917
|
-
accumulated += text;
|
|
918
|
-
onChunk?.(type, text);
|
|
919
|
-
} else if (type === "status" && typeof parsed["message"] === "string") {
|
|
920
|
-
const message = parsed["message"];
|
|
921
|
-
onChunk?.(type, message);
|
|
922
|
-
}
|
|
923
|
-
} catch {
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
});
|
|
927
|
-
const body = res.body;
|
|
928
|
-
if (body && typeof body.getReader === "function") {
|
|
929
|
-
const reader = body.getReader();
|
|
930
|
-
const decoder = new TextDecoder();
|
|
931
|
-
while (true) {
|
|
932
|
-
const { value, done } = await reader.read();
|
|
933
|
-
if (done) break;
|
|
934
|
-
parser.feed(decoder.decode(value, { stream: true }));
|
|
935
|
-
}
|
|
936
|
-
parser.feed(decoder.decode());
|
|
937
|
-
} else {
|
|
938
|
-
const rawBody = await res.text();
|
|
939
|
-
parser.feed(rawBody);
|
|
940
|
-
}
|
|
941
|
-
const { summary, products } = extractProductsFromMarkdown(accumulated);
|
|
942
|
-
const finalProducts = renderedProducts.length > 0 ? renderedProducts : products;
|
|
943
|
-
return {
|
|
944
|
-
summary,
|
|
945
|
-
products: finalProducts,
|
|
946
|
-
rawContent: accumulated
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
|
-
};
|
|
950
|
-
}
|
|
951
|
-
|
|
952
404
|
// src/utils/catalogHelpers.ts
|
|
953
405
|
var CART_DISABLED_PRODUCT_STATUSES = /* @__PURE__ */ new Set([
|
|
954
406
|
"discontinued",
|
|
@@ -1249,26 +701,28 @@ function useSearchSuggestions({
|
|
|
1249
701
|
service,
|
|
1250
702
|
enabled = true
|
|
1251
703
|
}) {
|
|
1252
|
-
const [query, setQuery] =
|
|
1253
|
-
const [suggestions, setSuggestions] =
|
|
1254
|
-
const [didYouMean, setDidYouMean] =
|
|
1255
|
-
const [loading, setLoading] =
|
|
1256
|
-
const [emptyFetched, setEmptyFetched] =
|
|
1257
|
-
const [suggestionFetchFailed, setSuggestionFetchFailed] =
|
|
1258
|
-
const [hasMoreResults, setHasMoreResults] =
|
|
1259
|
-
const [totalResultsCount, setTotalResultsCount] =
|
|
1260
|
-
const
|
|
1261
|
-
const
|
|
1262
|
-
const
|
|
704
|
+
const [query, setQuery] = react.useState("");
|
|
705
|
+
const [suggestions, setSuggestions] = react.useState([]);
|
|
706
|
+
const [didYouMean, setDidYouMean] = react.useState([]);
|
|
707
|
+
const [loading, setLoading] = react.useState(false);
|
|
708
|
+
const [emptyFetched, setEmptyFetched] = react.useState(false);
|
|
709
|
+
const [suggestionFetchFailed, setSuggestionFetchFailed] = react.useState(false);
|
|
710
|
+
const [hasMoreResults, setHasMoreResults] = react.useState(false);
|
|
711
|
+
const [totalResultsCount, setTotalResultsCount] = react.useState(0);
|
|
712
|
+
const [settledQuery, setSettledQuery] = react.useState("");
|
|
713
|
+
const fetchDebounced = react.useRef(null);
|
|
714
|
+
const latestRequestId = react.useRef(0);
|
|
715
|
+
const resetSuggestionsState = react.useCallback(() => {
|
|
1263
716
|
setSuggestions([]);
|
|
1264
717
|
setDidYouMean([]);
|
|
1265
718
|
setEmptyFetched(false);
|
|
1266
719
|
setSuggestionFetchFailed(false);
|
|
1267
720
|
setHasMoreResults(false);
|
|
1268
721
|
setTotalResultsCount(0);
|
|
722
|
+
setSettledQuery("");
|
|
1269
723
|
setLoading(false);
|
|
1270
724
|
}, []);
|
|
1271
|
-
const updateQuery =
|
|
725
|
+
const updateQuery = react.useCallback(
|
|
1272
726
|
(nextQuery) => {
|
|
1273
727
|
const hasNextQuery = Boolean(nextQuery.trim());
|
|
1274
728
|
setEmptyFetched(false);
|
|
@@ -1278,11 +732,11 @@ function useSearchSuggestions({
|
|
|
1278
732
|
},
|
|
1279
733
|
[enabled]
|
|
1280
734
|
);
|
|
1281
|
-
const clearQuery =
|
|
735
|
+
const clearQuery = react.useCallback(() => {
|
|
1282
736
|
resetSuggestionsState();
|
|
1283
737
|
setQuery("");
|
|
1284
738
|
}, [resetSuggestionsState]);
|
|
1285
|
-
const runFetch =
|
|
739
|
+
const runFetch = react.useCallback(
|
|
1286
740
|
async (nextQuery) => {
|
|
1287
741
|
const trimmedQuery = nextQuery.trim();
|
|
1288
742
|
const requestId = latestRequestId.current;
|
|
@@ -1316,6 +770,7 @@ function useSearchSuggestions({
|
|
|
1316
770
|
);
|
|
1317
771
|
setTotalResultsCount(effectiveTotal);
|
|
1318
772
|
setSuggestionFetchFailed(false);
|
|
773
|
+
setSettledQuery(trimmedQuery);
|
|
1319
774
|
setEmptyFetched(
|
|
1320
775
|
hasDidYouMean ? false : visibleSuggestions.length === 0 && suggestedTerms.length === 0
|
|
1321
776
|
);
|
|
@@ -1329,6 +784,7 @@ function useSearchSuggestions({
|
|
|
1329
784
|
setSuggestionFetchFailed(true);
|
|
1330
785
|
setHasMoreResults(false);
|
|
1331
786
|
setTotalResultsCount(0);
|
|
787
|
+
setSettledQuery(trimmedQuery);
|
|
1332
788
|
} finally {
|
|
1333
789
|
if (requestId === latestRequestId.current) {
|
|
1334
790
|
setLoading(false);
|
|
@@ -1337,7 +793,7 @@ function useSearchSuggestions({
|
|
|
1337
793
|
},
|
|
1338
794
|
[enabled, service, resetSuggestionsState]
|
|
1339
795
|
);
|
|
1340
|
-
|
|
796
|
+
react.useEffect(() => {
|
|
1341
797
|
if (!enabled) {
|
|
1342
798
|
resetSuggestionsState();
|
|
1343
799
|
return void 0;
|
|
@@ -1363,6 +819,7 @@ function useSearchSuggestions({
|
|
|
1363
819
|
didYouMean,
|
|
1364
820
|
loading,
|
|
1365
821
|
query,
|
|
822
|
+
settledQuery,
|
|
1366
823
|
setQuery,
|
|
1367
824
|
suggestionFetchFailed,
|
|
1368
825
|
suggestions,
|
|
@@ -1371,381 +828,25 @@ function useSearchSuggestions({
|
|
|
1371
828
|
};
|
|
1372
829
|
}
|
|
1373
830
|
|
|
1374
|
-
//
|
|
1375
|
-
var
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
if (!Object.is(nextState, state)) {
|
|
1381
|
-
const previousState = state;
|
|
1382
|
-
state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
|
|
1383
|
-
listeners.forEach((listener) => listener(state, previousState));
|
|
1384
|
-
}
|
|
1385
|
-
};
|
|
1386
|
-
const getState = () => state;
|
|
1387
|
-
const getInitialState = () => initialState;
|
|
1388
|
-
const subscribe = (listener) => {
|
|
1389
|
-
listeners.add(listener);
|
|
1390
|
-
return () => listeners.delete(listener);
|
|
1391
|
-
};
|
|
1392
|
-
const api = { setState, getState, getInitialState, subscribe };
|
|
1393
|
-
const initialState = state = createState(setState, getState, api);
|
|
1394
|
-
return api;
|
|
1395
|
-
};
|
|
1396
|
-
var createStore = (createState) => createState ? createStoreImpl(createState) : createStoreImpl;
|
|
1397
|
-
var identity = (arg) => arg;
|
|
1398
|
-
function useStore(api, selector = identity) {
|
|
1399
|
-
const slice = React__default.default.useSyncExternalStore(
|
|
1400
|
-
api.subscribe,
|
|
1401
|
-
() => selector(api.getState()),
|
|
1402
|
-
() => selector(api.getInitialState())
|
|
1403
|
-
);
|
|
1404
|
-
React__default.default.useDebugValue(slice);
|
|
1405
|
-
return slice;
|
|
1406
|
-
}
|
|
1407
|
-
var createImpl = (createState) => {
|
|
1408
|
-
const api = createStore(createState);
|
|
1409
|
-
const useBoundStore = (selector) => useStore(api, selector);
|
|
1410
|
-
Object.assign(useBoundStore, api);
|
|
1411
|
-
return useBoundStore;
|
|
831
|
+
// src/utils/gtmConstants.ts
|
|
832
|
+
var SEARCH_GTM_EVENTS = {
|
|
833
|
+
CHAT_ENTRY_POINT: "Begin_chat",
|
|
834
|
+
SEARCH_TYPE: "Search_Type",
|
|
835
|
+
PRODUCT_SEARCH: "ProductSearch",
|
|
836
|
+
ERROR_POPUP: "Error_popup"
|
|
1412
837
|
};
|
|
1413
|
-
var
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
function createJSONStorage(getStorage, options) {
|
|
1417
|
-
let storage;
|
|
1418
|
-
try {
|
|
1419
|
-
storage = getStorage();
|
|
1420
|
-
} catch (e) {
|
|
1421
|
-
return;
|
|
1422
|
-
}
|
|
1423
|
-
const persistStorage = {
|
|
1424
|
-
getItem: (name) => {
|
|
1425
|
-
var _a;
|
|
1426
|
-
const parse = (str2) => {
|
|
1427
|
-
if (str2 === null) {
|
|
1428
|
-
return null;
|
|
1429
|
-
}
|
|
1430
|
-
return JSON.parse(str2, void 0 );
|
|
1431
|
-
};
|
|
1432
|
-
const str = (_a = storage.getItem(name)) != null ? _a : null;
|
|
1433
|
-
if (str instanceof Promise) {
|
|
1434
|
-
return str.then(parse);
|
|
1435
|
-
}
|
|
1436
|
-
return parse(str);
|
|
1437
|
-
},
|
|
1438
|
-
setItem: (name, newValue) => storage.setItem(
|
|
1439
|
-
name,
|
|
1440
|
-
JSON.stringify(newValue, void 0 )
|
|
1441
|
-
),
|
|
1442
|
-
removeItem: (name) => storage.removeItem(name)
|
|
1443
|
-
};
|
|
1444
|
-
return persistStorage;
|
|
1445
|
-
}
|
|
1446
|
-
var toThenable = (fn) => (input) => {
|
|
1447
|
-
try {
|
|
1448
|
-
const result = fn(input);
|
|
1449
|
-
if (result instanceof Promise) {
|
|
1450
|
-
return result;
|
|
1451
|
-
}
|
|
1452
|
-
return {
|
|
1453
|
-
then(onFulfilled) {
|
|
1454
|
-
return toThenable(onFulfilled)(result);
|
|
1455
|
-
},
|
|
1456
|
-
catch(_onRejected) {
|
|
1457
|
-
return this;
|
|
1458
|
-
}
|
|
1459
|
-
};
|
|
1460
|
-
} catch (e) {
|
|
1461
|
-
return {
|
|
1462
|
-
then(_onFulfilled) {
|
|
1463
|
-
return this;
|
|
1464
|
-
},
|
|
1465
|
-
catch(onRejected) {
|
|
1466
|
-
return toThenable(onRejected)(e);
|
|
1467
|
-
}
|
|
1468
|
-
};
|
|
1469
|
-
}
|
|
838
|
+
var SEARCH_GTM_SEARCH_TYPES = {
|
|
839
|
+
SEMANTIC: "semantic search",
|
|
840
|
+
AI_AGENT: "AI agent"
|
|
1470
841
|
};
|
|
1471
|
-
var
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
partialize: (state) => state,
|
|
1475
|
-
version: 0,
|
|
1476
|
-
merge: (persistedState, currentState) => ({
|
|
1477
|
-
...currentState,
|
|
1478
|
-
...persistedState
|
|
1479
|
-
}),
|
|
1480
|
-
...baseOptions
|
|
1481
|
-
};
|
|
1482
|
-
let hasHydrated = false;
|
|
1483
|
-
const hydrationListeners = /* @__PURE__ */ new Set();
|
|
1484
|
-
const finishHydrationListeners = /* @__PURE__ */ new Set();
|
|
1485
|
-
let storage = options.storage;
|
|
1486
|
-
if (!storage) {
|
|
1487
|
-
return config(
|
|
1488
|
-
(...args) => {
|
|
1489
|
-
console.warn(
|
|
1490
|
-
`[zustand persist middleware] Unable to update item '${options.name}', the given storage is currently unavailable.`
|
|
1491
|
-
);
|
|
1492
|
-
set(...args);
|
|
1493
|
-
},
|
|
1494
|
-
get,
|
|
1495
|
-
api
|
|
1496
|
-
);
|
|
1497
|
-
}
|
|
1498
|
-
const setItem = () => {
|
|
1499
|
-
const state = options.partialize({ ...get() });
|
|
1500
|
-
return storage.setItem(options.name, {
|
|
1501
|
-
state,
|
|
1502
|
-
version: options.version
|
|
1503
|
-
});
|
|
1504
|
-
};
|
|
1505
|
-
const savedSetState = api.setState;
|
|
1506
|
-
api.setState = (state, replace) => {
|
|
1507
|
-
savedSetState(state, replace);
|
|
1508
|
-
void setItem();
|
|
1509
|
-
};
|
|
1510
|
-
const configResult = config(
|
|
1511
|
-
(...args) => {
|
|
1512
|
-
set(...args);
|
|
1513
|
-
void setItem();
|
|
1514
|
-
},
|
|
1515
|
-
get,
|
|
1516
|
-
api
|
|
1517
|
-
);
|
|
1518
|
-
api.getInitialState = () => configResult;
|
|
1519
|
-
let stateFromStorage;
|
|
1520
|
-
const hydrate = () => {
|
|
1521
|
-
var _a, _b;
|
|
1522
|
-
if (!storage) return;
|
|
1523
|
-
hasHydrated = false;
|
|
1524
|
-
hydrationListeners.forEach((cb) => {
|
|
1525
|
-
var _a2;
|
|
1526
|
-
return cb((_a2 = get()) != null ? _a2 : configResult);
|
|
1527
|
-
});
|
|
1528
|
-
const postRehydrationCallback = ((_b = options.onRehydrateStorage) == null ? void 0 : _b.call(options, (_a = get()) != null ? _a : configResult)) || void 0;
|
|
1529
|
-
return toThenable(storage.getItem.bind(storage))(options.name).then((deserializedStorageValue) => {
|
|
1530
|
-
if (deserializedStorageValue) {
|
|
1531
|
-
if (typeof deserializedStorageValue.version === "number" && deserializedStorageValue.version !== options.version) {
|
|
1532
|
-
if (options.migrate) {
|
|
1533
|
-
return [
|
|
1534
|
-
true,
|
|
1535
|
-
options.migrate(
|
|
1536
|
-
deserializedStorageValue.state,
|
|
1537
|
-
deserializedStorageValue.version
|
|
1538
|
-
)
|
|
1539
|
-
];
|
|
1540
|
-
}
|
|
1541
|
-
console.error(
|
|
1542
|
-
`State loaded from storage couldn't be migrated since no migrate function was provided`
|
|
1543
|
-
);
|
|
1544
|
-
} else {
|
|
1545
|
-
return [false, deserializedStorageValue.state];
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
return [false, void 0];
|
|
1549
|
-
}).then((migrationResult) => {
|
|
1550
|
-
var _a2;
|
|
1551
|
-
const [migrated, migratedState] = migrationResult;
|
|
1552
|
-
stateFromStorage = options.merge(
|
|
1553
|
-
migratedState,
|
|
1554
|
-
(_a2 = get()) != null ? _a2 : configResult
|
|
1555
|
-
);
|
|
1556
|
-
set(stateFromStorage, true);
|
|
1557
|
-
if (migrated) {
|
|
1558
|
-
return setItem();
|
|
1559
|
-
}
|
|
1560
|
-
}).then(() => {
|
|
1561
|
-
postRehydrationCallback == null ? void 0 : postRehydrationCallback(stateFromStorage, void 0);
|
|
1562
|
-
stateFromStorage = get();
|
|
1563
|
-
hasHydrated = true;
|
|
1564
|
-
finishHydrationListeners.forEach((cb) => cb(stateFromStorage));
|
|
1565
|
-
}).catch((e) => {
|
|
1566
|
-
postRehydrationCallback == null ? void 0 : postRehydrationCallback(void 0, e);
|
|
1567
|
-
});
|
|
1568
|
-
};
|
|
1569
|
-
api.persist = {
|
|
1570
|
-
setOptions: (newOptions) => {
|
|
1571
|
-
options = {
|
|
1572
|
-
...options,
|
|
1573
|
-
...newOptions
|
|
1574
|
-
};
|
|
1575
|
-
if (newOptions.storage) {
|
|
1576
|
-
storage = newOptions.storage;
|
|
1577
|
-
}
|
|
1578
|
-
},
|
|
1579
|
-
clearStorage: () => {
|
|
1580
|
-
storage == null ? void 0 : storage.removeItem(options.name);
|
|
1581
|
-
},
|
|
1582
|
-
getOptions: () => options,
|
|
1583
|
-
rehydrate: () => hydrate(),
|
|
1584
|
-
hasHydrated: () => hasHydrated,
|
|
1585
|
-
onHydrate: (cb) => {
|
|
1586
|
-
hydrationListeners.add(cb);
|
|
1587
|
-
return () => {
|
|
1588
|
-
hydrationListeners.delete(cb);
|
|
1589
|
-
};
|
|
1590
|
-
},
|
|
1591
|
-
onFinishHydration: (cb) => {
|
|
1592
|
-
finishHydrationListeners.add(cb);
|
|
1593
|
-
return () => {
|
|
1594
|
-
finishHydrationListeners.delete(cb);
|
|
1595
|
-
};
|
|
1596
|
-
}
|
|
1597
|
-
};
|
|
1598
|
-
if (!options.skipHydration) {
|
|
1599
|
-
hydrate();
|
|
1600
|
-
}
|
|
1601
|
-
return stateFromStorage || configResult;
|
|
842
|
+
var SEARCH_GTM_USER_STATUS = {
|
|
843
|
+
GUEST: "guest",
|
|
844
|
+
LOGGED_IN: "logged_in"
|
|
1602
845
|
};
|
|
1603
|
-
var
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
var
|
|
1607
|
-
var useSearchHistoryStore = create()(
|
|
1608
|
-
persist(
|
|
1609
|
-
(set) => ({
|
|
1610
|
-
entries: [],
|
|
1611
|
-
addEntry: (entry) => {
|
|
1612
|
-
const trimmed = entry.query.trim();
|
|
1613
|
-
if (!trimmed) return;
|
|
1614
|
-
set((state) => {
|
|
1615
|
-
const deduped = state.entries.filter(
|
|
1616
|
-
(e) => e.query.toLowerCase() !== trimmed.toLowerCase()
|
|
1617
|
-
);
|
|
1618
|
-
const newEntry = {
|
|
1619
|
-
...entry,
|
|
1620
|
-
query: trimmed,
|
|
1621
|
-
timestamp: Date.now()
|
|
1622
|
-
};
|
|
1623
|
-
return {
|
|
1624
|
-
entries: [newEntry, ...deduped].slice(0, MAX_HISTORY)
|
|
1625
|
-
};
|
|
1626
|
-
});
|
|
1627
|
-
},
|
|
1628
|
-
addQuery: (query) => {
|
|
1629
|
-
const trimmed = query.trim();
|
|
1630
|
-
if (!trimmed) return;
|
|
1631
|
-
set((state) => {
|
|
1632
|
-
const deduped = state.entries.filter(
|
|
1633
|
-
(e) => e.query.toLowerCase() !== trimmed.toLowerCase()
|
|
1634
|
-
);
|
|
1635
|
-
return {
|
|
1636
|
-
entries: [
|
|
1637
|
-
{
|
|
1638
|
-
query: trimmed,
|
|
1639
|
-
isAI: false,
|
|
1640
|
-
timestamp: Date.now()
|
|
1641
|
-
},
|
|
1642
|
-
...deduped
|
|
1643
|
-
].slice(0, MAX_HISTORY)
|
|
1644
|
-
};
|
|
1645
|
-
});
|
|
1646
|
-
},
|
|
1647
|
-
removeEntry: (query) => set((state) => ({
|
|
1648
|
-
entries: state.entries.filter((e) => e.query !== query)
|
|
1649
|
-
})),
|
|
1650
|
-
clearAll: () => set({ entries: [] })
|
|
1651
|
-
}),
|
|
1652
|
-
{
|
|
1653
|
-
name: "ai_search_recent_queries",
|
|
1654
|
-
version: 1,
|
|
1655
|
-
storage: createJSONStorage(
|
|
1656
|
-
() => typeof window !== "undefined" ? window.sessionStorage : {}
|
|
1657
|
-
),
|
|
1658
|
-
// Migrate from v0 (queries: string[]) to v1 (entries: SearchHistoryEntry[])
|
|
1659
|
-
migrate(persistedState, version) {
|
|
1660
|
-
if (version === 0) {
|
|
1661
|
-
const old = persistedState;
|
|
1662
|
-
if (Array.isArray(old?.queries)) {
|
|
1663
|
-
return {
|
|
1664
|
-
entries: old.queries.map((q) => ({
|
|
1665
|
-
query: q,
|
|
1666
|
-
isAI: false,
|
|
1667
|
-
timestamp: Date.now()
|
|
1668
|
-
}))
|
|
1669
|
-
};
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
return persistedState;
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1675
|
-
)
|
|
1676
|
-
);
|
|
1677
|
-
|
|
1678
|
-
// src/hooks/useSearchHistory.ts
|
|
1679
|
-
function useSearchHistory() {
|
|
1680
|
-
const entries = useSearchHistoryStore((s) => s.entries);
|
|
1681
|
-
const addEntry = useSearchHistoryStore((s) => s.addEntry);
|
|
1682
|
-
const addQuery = useSearchHistoryStore((s) => s.addQuery);
|
|
1683
|
-
const removeEntry = useSearchHistoryStore((s) => s.removeEntry);
|
|
1684
|
-
const clearAll = useSearchHistoryStore((s) => s.clearAll);
|
|
1685
|
-
return {
|
|
1686
|
-
entries,
|
|
1687
|
-
queries: entries.map((e) => e.query),
|
|
1688
|
-
addEntry,
|
|
1689
|
-
addQuery,
|
|
1690
|
-
removeEntry,
|
|
1691
|
-
clearAll
|
|
1692
|
-
};
|
|
1693
|
-
}
|
|
1694
|
-
function getSpeechRecognition() {
|
|
1695
|
-
if (typeof window === "undefined") return null;
|
|
1696
|
-
const w = window;
|
|
1697
|
-
return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
|
|
1698
|
-
}
|
|
1699
|
-
function useVoiceSearch({
|
|
1700
|
-
lang,
|
|
1701
|
-
onInterimResult,
|
|
1702
|
-
onResult,
|
|
1703
|
-
onError
|
|
1704
|
-
}) {
|
|
1705
|
-
const [listening, setListening] = React.useState(false);
|
|
1706
|
-
const recognitionRef = React.useRef(null);
|
|
1707
|
-
const supported = getSpeechRecognition() !== null;
|
|
1708
|
-
const stop = React.useCallback(() => {
|
|
1709
|
-
recognitionRef.current?.stop();
|
|
1710
|
-
recognitionRef.current = null;
|
|
1711
|
-
setListening(false);
|
|
1712
|
-
}, []);
|
|
1713
|
-
const start = React.useCallback(() => {
|
|
1714
|
-
const SR = getSpeechRecognition();
|
|
1715
|
-
if (!SR) return;
|
|
1716
|
-
stop();
|
|
1717
|
-
const rec = new SR();
|
|
1718
|
-
rec.lang = lang ?? navigator.language ?? "en-US";
|
|
1719
|
-
rec.interimResults = true;
|
|
1720
|
-
rec.maxAlternatives = 1;
|
|
1721
|
-
rec.onresult = (event) => {
|
|
1722
|
-
let finalTranscript = "";
|
|
1723
|
-
let interimTranscript = "";
|
|
1724
|
-
const startIndex = event.resultIndex ?? 0;
|
|
1725
|
-
for (let index = startIndex; index < event.results.length; index += 1) {
|
|
1726
|
-
const result = event.results[index];
|
|
1727
|
-
const transcript = result?.[0]?.transcript ?? "";
|
|
1728
|
-
if (!transcript) continue;
|
|
1729
|
-
if (result?.isFinal) {
|
|
1730
|
-
finalTranscript += transcript;
|
|
1731
|
-
} else {
|
|
1732
|
-
interimTranscript += transcript;
|
|
1733
|
-
}
|
|
1734
|
-
}
|
|
1735
|
-
if (interimTranscript) onInterimResult?.(interimTranscript);
|
|
1736
|
-
if (finalTranscript) onResult(finalTranscript);
|
|
1737
|
-
};
|
|
1738
|
-
rec.onerror = (event) => {
|
|
1739
|
-
onError?.(event);
|
|
1740
|
-
setListening(false);
|
|
1741
|
-
};
|
|
1742
|
-
rec.onend = () => setListening(false);
|
|
1743
|
-
recognitionRef.current = rec;
|
|
1744
|
-
rec.start();
|
|
1745
|
-
setListening(true);
|
|
1746
|
-
}, [lang, onInterimResult, onResult, onError, stop]);
|
|
1747
|
-
return { listening, supported, start, stop };
|
|
1748
|
-
}
|
|
846
|
+
var IS_AUTHENTICATED_KEY = "IsAuthenticated";
|
|
847
|
+
var ACCOUNT_TYPE_KEY = "accountType";
|
|
848
|
+
var NUSKIN_ACCOUNT_DATA_KEY = "nuskin.account.data";
|
|
849
|
+
var GTM_FALLBACK_NA = "NA";
|
|
1749
850
|
|
|
1750
851
|
// src/utils/commonUtils.ts
|
|
1751
852
|
var IS_BROWSER = typeof window !== "undefined";
|
|
@@ -1781,42 +882,8 @@ function getLocalStorage(key) {
|
|
|
1781
882
|
return void 0;
|
|
1782
883
|
}
|
|
1783
884
|
}
|
|
1784
|
-
function getSessionStorage2(key) {
|
|
1785
|
-
if (!IS_BROWSER) return void 0;
|
|
1786
|
-
try {
|
|
1787
|
-
const raw = window.sessionStorage.getItem(key);
|
|
1788
|
-
return raw === null ? void 0 : safeParse(raw);
|
|
1789
|
-
} catch {
|
|
1790
|
-
return void 0;
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
|
|
1794
|
-
// src/utils/gtmConstants.ts
|
|
1795
|
-
var SEARCH_GTM_EVENTS = {
|
|
1796
|
-
CHAT_ENTRY_POINT: "Begin_chat",
|
|
1797
|
-
SEARCH_TYPE: "Search_Type",
|
|
1798
|
-
PRODUCT_SEARCH: "ProductSearch",
|
|
1799
|
-
ERROR_POPUP: "Error_popup"
|
|
1800
|
-
};
|
|
1801
|
-
var SEARCH_GTM_SEARCH_TYPES = {
|
|
1802
|
-
SEMANTIC: "semantic search",
|
|
1803
|
-
AI_AGENT: "AI agent"
|
|
1804
|
-
};
|
|
1805
|
-
var SEARCH_GTM_USER_STATUS = {
|
|
1806
|
-
GUEST: "guest",
|
|
1807
|
-
LOGGED_IN: "logged_in"
|
|
1808
|
-
};
|
|
1809
|
-
var AI_CHAT_CONVERSATION_ID_KEY = "ai_chat_conversation_id";
|
|
1810
|
-
var IS_AUTHENTICATED_KEY = "IsAuthenticated";
|
|
1811
|
-
var ACCOUNT_TYPE_KEY = "accountType";
|
|
1812
|
-
var NUSKIN_ACCOUNT_DATA_KEY = "nuskin.account.data";
|
|
1813
|
-
var GTM_FALLBACK_NA = "NA";
|
|
1814
885
|
|
|
1815
886
|
// src/utils/gtmTracking.ts
|
|
1816
|
-
function readSessionChatId() {
|
|
1817
|
-
const raw = getSessionStorage2(AI_CHAT_CONVERSATION_ID_KEY);
|
|
1818
|
-
return typeof raw === "string" || typeof raw === "number" ? String(raw) : void 0;
|
|
1819
|
-
}
|
|
1820
887
|
function readUserStatusFromStorage() {
|
|
1821
888
|
const value = getLocalStorage(IS_AUTHENTICATED_KEY);
|
|
1822
889
|
if (value === void 0) return void 0;
|
|
@@ -1843,13 +910,12 @@ function resolveUserId(explicit) {
|
|
|
1843
910
|
}
|
|
1844
911
|
function baseEventPayload(ctx) {
|
|
1845
912
|
const urlLocale = getLocaleFromUrl();
|
|
1846
|
-
const chatId = ctx.chatId ?? readSessionChatId();
|
|
1847
913
|
const userStatus = ctx.userStatus ?? readUserStatusFromStorage();
|
|
1848
914
|
const accountType = ctx.accountType ?? readAccountTypeFromStorage();
|
|
1849
915
|
const resolvedUserStatus = userStatus ?? SEARCH_GTM_USER_STATUS.GUEST;
|
|
1850
916
|
const documentTitle = IS_BROWSER && typeof document?.title === "string" ? document.title : void 0;
|
|
1851
917
|
return {
|
|
1852
|
-
Chat_ID: toEventString(chatId, GTM_FALLBACK_NA),
|
|
918
|
+
Chat_ID: toEventString(ctx.chatId, GTM_FALLBACK_NA),
|
|
1853
919
|
page_type: toEventString(
|
|
1854
920
|
ctx.pageType ?? documentTitle,
|
|
1855
921
|
GTM_FALLBACK_NA
|
|
@@ -1929,15 +995,419 @@ function trackErrorPopup(input) {
|
|
|
1929
995
|
});
|
|
1930
996
|
}
|
|
1931
997
|
|
|
998
|
+
// src/utils/nuiSearchGate.ts
|
|
999
|
+
var NUI_SEARCH_FLAG = "enableNuiSearch";
|
|
1000
|
+
function parseFlagValue(raw) {
|
|
1001
|
+
const value = raw?.trim().toLowerCase();
|
|
1002
|
+
if (value === "true" || value === "1") return true;
|
|
1003
|
+
if (value === "false" || value === "0") return false;
|
|
1004
|
+
return void 0;
|
|
1005
|
+
}
|
|
1006
|
+
function readCookieFlag(cookie) {
|
|
1007
|
+
for (const part of cookie.split(";")) {
|
|
1008
|
+
const separator = part.indexOf("=");
|
|
1009
|
+
if (separator < 0) continue;
|
|
1010
|
+
if (part.slice(0, separator).trim() !== NUI_SEARCH_FLAG) continue;
|
|
1011
|
+
const rawValue = part.slice(separator + 1);
|
|
1012
|
+
try {
|
|
1013
|
+
return parseFlagValue(decodeURIComponent(rawValue));
|
|
1014
|
+
} catch {
|
|
1015
|
+
return void 0;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return void 0;
|
|
1019
|
+
}
|
|
1020
|
+
function resolveNuiSearchFlag(env = {}) {
|
|
1021
|
+
const inBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
1022
|
+
const search = env.search ?? (inBrowser ? window.location.search : "");
|
|
1023
|
+
const cookie = env.cookie ?? (inBrowser ? document.cookie : "");
|
|
1024
|
+
const setCookie = env.setCookie ?? (inBrowser ? (value) => {
|
|
1025
|
+
document.cookie = value;
|
|
1026
|
+
} : void 0);
|
|
1027
|
+
const fromQuery = parseFlagValue(
|
|
1028
|
+
new URLSearchParams(search).get(NUI_SEARCH_FLAG)
|
|
1029
|
+
);
|
|
1030
|
+
if (fromQuery !== void 0) {
|
|
1031
|
+
const secure = inBrowser && window.location.protocol === "https:" ? "; Secure" : "";
|
|
1032
|
+
setCookie?.(
|
|
1033
|
+
`${NUI_SEARCH_FLAG}=${fromQuery}; path=/; SameSite=Lax${secure}`
|
|
1034
|
+
);
|
|
1035
|
+
return fromQuery;
|
|
1036
|
+
}
|
|
1037
|
+
return readCookieFlag(cookie);
|
|
1038
|
+
}
|
|
1039
|
+
function isNuiSearchEnabled({
|
|
1040
|
+
isAuthenticated,
|
|
1041
|
+
flag
|
|
1042
|
+
}) {
|
|
1043
|
+
return isAuthenticated === true && flag === true;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// src/utils/searchRouting.ts
|
|
1047
|
+
function decideSearchRoute({
|
|
1048
|
+
enabled,
|
|
1049
|
+
snapshot
|
|
1050
|
+
}) {
|
|
1051
|
+
const term = snapshot.query.trim();
|
|
1052
|
+
if (!enabled || !term) return "search";
|
|
1053
|
+
if (snapshot.loading || snapshot.settledQuery !== term) return "wait";
|
|
1054
|
+
if (snapshot.suggestionFetchFailed) return "search";
|
|
1055
|
+
return snapshot.suggestions.length > 0 ? "search" : "handoff";
|
|
1056
|
+
}
|
|
1057
|
+
var HANDLE_POLL_MS = 50;
|
|
1058
|
+
var HANDLE_TIMEOUT_MS = 15e3;
|
|
1059
|
+
var SalesAIChatWidget = react.lazy(async () => {
|
|
1060
|
+
const [widget] = await Promise.all([
|
|
1061
|
+
import('@nuskin/react-nui-chat-sdk'),
|
|
1062
|
+
import('@nuskin/react-nui-chat-sdk/styles.css')
|
|
1063
|
+
]);
|
|
1064
|
+
return { default: widget.SalesAIChatWidget };
|
|
1065
|
+
});
|
|
1066
|
+
function getSalesAIHandle() {
|
|
1067
|
+
return window.SalesAI;
|
|
1068
|
+
}
|
|
1069
|
+
var AgentErrorBoundary = class extends react.Component {
|
|
1070
|
+
constructor() {
|
|
1071
|
+
super(...arguments);
|
|
1072
|
+
this.state = { failed: false };
|
|
1073
|
+
}
|
|
1074
|
+
static getDerivedStateFromError() {
|
|
1075
|
+
return { failed: true };
|
|
1076
|
+
}
|
|
1077
|
+
componentDidCatch(_error, _info) {
|
|
1078
|
+
this.props.onError();
|
|
1079
|
+
}
|
|
1080
|
+
render() {
|
|
1081
|
+
return this.state.failed ? null : this.props.children;
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
function useHandoff({
|
|
1085
|
+
query,
|
|
1086
|
+
handoffContext,
|
|
1087
|
+
agentContext,
|
|
1088
|
+
onUnavailable
|
|
1089
|
+
}) {
|
|
1090
|
+
react.useEffect(() => {
|
|
1091
|
+
let cancelled = false;
|
|
1092
|
+
let timer;
|
|
1093
|
+
const startedAt = Date.now();
|
|
1094
|
+
const tryOpen = () => {
|
|
1095
|
+
if (cancelled) return;
|
|
1096
|
+
const handle = getSalesAIHandle();
|
|
1097
|
+
if (handle) {
|
|
1098
|
+
handle.open({
|
|
1099
|
+
query,
|
|
1100
|
+
context: { ...agentContext, ...handoffContext }
|
|
1101
|
+
});
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
if (Date.now() - startedAt >= HANDLE_TIMEOUT_MS) {
|
|
1105
|
+
onUnavailable();
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
timer = setTimeout(tryOpen, HANDLE_POLL_MS);
|
|
1109
|
+
};
|
|
1110
|
+
tryOpen();
|
|
1111
|
+
return () => {
|
|
1112
|
+
cancelled = true;
|
|
1113
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
1114
|
+
};
|
|
1115
|
+
}, [query]);
|
|
1116
|
+
}
|
|
1117
|
+
function NuiAgentPanel({
|
|
1118
|
+
agent,
|
|
1119
|
+
query,
|
|
1120
|
+
handoffContext,
|
|
1121
|
+
onUnavailable
|
|
1122
|
+
}) {
|
|
1123
|
+
useHandoff({
|
|
1124
|
+
query,
|
|
1125
|
+
handoffContext,
|
|
1126
|
+
agentContext: agent.context,
|
|
1127
|
+
onUnavailable
|
|
1128
|
+
});
|
|
1129
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AgentErrorBoundary, { onError: onUnavailable, children: /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1130
|
+
SalesAIChatWidget,
|
|
1131
|
+
{
|
|
1132
|
+
appKey: agent.appKey,
|
|
1133
|
+
apiBaseUrl: agent.apiBaseUrl,
|
|
1134
|
+
theme: agent.theme ?? "nui-light",
|
|
1135
|
+
defaultOpen: true,
|
|
1136
|
+
hideFab: true,
|
|
1137
|
+
placement: "host",
|
|
1138
|
+
...agent.language === void 0 ? {} : { language: agent.language },
|
|
1139
|
+
...agent.renderProduct === void 0 ? {} : { renderProduct: agent.renderProduct }
|
|
1140
|
+
}
|
|
1141
|
+
) }) });
|
|
1142
|
+
}
|
|
1143
|
+
var SUBMIT_WAIT_TIMEOUT_MS = 2e3;
|
|
1144
|
+
function uniqueRecentSearches(queries) {
|
|
1145
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1146
|
+
return queries.filter((query) => {
|
|
1147
|
+
const key = query.trim().toLowerCase();
|
|
1148
|
+
if (!key || seen.has(key)) return false;
|
|
1149
|
+
seen.add(key);
|
|
1150
|
+
return true;
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
function ProductSearch({
|
|
1154
|
+
isOpen,
|
|
1155
|
+
onClose,
|
|
1156
|
+
service,
|
|
1157
|
+
navigate,
|
|
1158
|
+
isAuthenticated,
|
|
1159
|
+
agent,
|
|
1160
|
+
logo,
|
|
1161
|
+
cancelLabel,
|
|
1162
|
+
fallbackImageUrl,
|
|
1163
|
+
copy,
|
|
1164
|
+
gtmContext
|
|
1165
|
+
}) {
|
|
1166
|
+
const [flag, setFlag] = react.useState(void 0);
|
|
1167
|
+
const [handoff, setHandoff] = react.useState(null);
|
|
1168
|
+
const agentQuery = handoff?.query ?? null;
|
|
1169
|
+
const [pendingTerm, setPendingTerm] = react.useState(null);
|
|
1170
|
+
const [recentSearches, setRecentSearches] = react.useState([]);
|
|
1171
|
+
const search = useSearchSuggestions({
|
|
1172
|
+
service,
|
|
1173
|
+
enabled: isOpen && agentQuery === null
|
|
1174
|
+
});
|
|
1175
|
+
const {
|
|
1176
|
+
clearQuery,
|
|
1177
|
+
didYouMean,
|
|
1178
|
+
emptyFetched,
|
|
1179
|
+
hasMoreResults,
|
|
1180
|
+
loading,
|
|
1181
|
+
query,
|
|
1182
|
+
settledQuery,
|
|
1183
|
+
suggestionFetchFailed,
|
|
1184
|
+
suggestions,
|
|
1185
|
+
updateQuery
|
|
1186
|
+
} = search;
|
|
1187
|
+
const enabled = isNuiSearchEnabled({ isAuthenticated, flag });
|
|
1188
|
+
const items = getVisibleSuggestions({
|
|
1189
|
+
didYouMean,
|
|
1190
|
+
suggestions
|
|
1191
|
+
});
|
|
1192
|
+
const showDidYouMean = didYouMean.length > 0;
|
|
1193
|
+
react.useEffect(() => {
|
|
1194
|
+
if (isOpen) setFlag(resolveNuiSearchFlag());
|
|
1195
|
+
}, [isOpen]);
|
|
1196
|
+
react.useEffect(() => {
|
|
1197
|
+
if (isOpen) {
|
|
1198
|
+
setRecentSearches(
|
|
1199
|
+
uniqueRecentSearches(service.getRecentSearches())
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
}, [isOpen, service, agentQuery]);
|
|
1203
|
+
const close = react.useCallback(() => {
|
|
1204
|
+
setHandoff(null);
|
|
1205
|
+
setPendingTerm(null);
|
|
1206
|
+
clearQuery();
|
|
1207
|
+
onClose();
|
|
1208
|
+
}, [clearQuery, onClose]);
|
|
1209
|
+
react.useEffect(() => {
|
|
1210
|
+
if (!isOpen) {
|
|
1211
|
+
setHandoff(null);
|
|
1212
|
+
setPendingTerm(null);
|
|
1213
|
+
clearQuery();
|
|
1214
|
+
}
|
|
1215
|
+
}, [isOpen, clearQuery]);
|
|
1216
|
+
const navigateTo = react.useCallback(
|
|
1217
|
+
(path) => {
|
|
1218
|
+
if (!path || path === "action-handled" || path === "action-pending") {
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
navigate(path);
|
|
1222
|
+
close();
|
|
1223
|
+
},
|
|
1224
|
+
[navigate, close]
|
|
1225
|
+
);
|
|
1226
|
+
const trackSemantic = react.useCallback(
|
|
1227
|
+
(productResultsData) => {
|
|
1228
|
+
trackProductSearch({
|
|
1229
|
+
...gtmContext,
|
|
1230
|
+
searchType: SEARCH_GTM_SEARCH_TYPES.SEMANTIC,
|
|
1231
|
+
productResultsData
|
|
1232
|
+
});
|
|
1233
|
+
},
|
|
1234
|
+
[gtmContext]
|
|
1235
|
+
);
|
|
1236
|
+
const runSearch = react.useCallback(
|
|
1237
|
+
(term) => {
|
|
1238
|
+
trackSemantic(items.length > 0 ? items : { query: term });
|
|
1239
|
+
navigateTo(service.submitProductSearch(term));
|
|
1240
|
+
},
|
|
1241
|
+
[items, navigateTo, service, trackSemantic]
|
|
1242
|
+
);
|
|
1243
|
+
const decide = react.useCallback(
|
|
1244
|
+
(term) => decideSearchRoute({
|
|
1245
|
+
enabled,
|
|
1246
|
+
snapshot: {
|
|
1247
|
+
query: term,
|
|
1248
|
+
loading,
|
|
1249
|
+
settledQuery,
|
|
1250
|
+
suggestions,
|
|
1251
|
+
suggestionFetchFailed
|
|
1252
|
+
}
|
|
1253
|
+
}),
|
|
1254
|
+
[enabled, loading, settledQuery, suggestions, suggestionFetchFailed]
|
|
1255
|
+
);
|
|
1256
|
+
const act = react.useCallback(
|
|
1257
|
+
(term, route) => {
|
|
1258
|
+
setPendingTerm(null);
|
|
1259
|
+
if (route === "handoff") {
|
|
1260
|
+
trackChatEntryPoint(gtmContext ?? {});
|
|
1261
|
+
setHandoff({
|
|
1262
|
+
query: term,
|
|
1263
|
+
didYouMean: didYouMean.map((item) => item.value)
|
|
1264
|
+
});
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
runSearch(term);
|
|
1268
|
+
},
|
|
1269
|
+
[didYouMean, gtmContext, runSearch]
|
|
1270
|
+
);
|
|
1271
|
+
const handleSubmit = react.useCallback(
|
|
1272
|
+
(value) => {
|
|
1273
|
+
const term = value.trim();
|
|
1274
|
+
if (!term) return;
|
|
1275
|
+
const route = decide(term);
|
|
1276
|
+
if (route === "wait") {
|
|
1277
|
+
setPendingTerm(term);
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
act(term, route);
|
|
1281
|
+
},
|
|
1282
|
+
[act, decide]
|
|
1283
|
+
);
|
|
1284
|
+
const latest = react.useRef({ act, decide });
|
|
1285
|
+
latest.current = { act, decide };
|
|
1286
|
+
react.useEffect(() => {
|
|
1287
|
+
if (pendingTerm === null) return void 0;
|
|
1288
|
+
const route = latest.current.decide(pendingTerm);
|
|
1289
|
+
if (route !== "wait") {
|
|
1290
|
+
latest.current.act(pendingTerm, route);
|
|
1291
|
+
return void 0;
|
|
1292
|
+
}
|
|
1293
|
+
const timer = setTimeout(
|
|
1294
|
+
() => latest.current.act(pendingTerm, "search"),
|
|
1295
|
+
SUBMIT_WAIT_TIMEOUT_MS
|
|
1296
|
+
);
|
|
1297
|
+
return () => clearTimeout(timer);
|
|
1298
|
+
}, [
|
|
1299
|
+
pendingTerm,
|
|
1300
|
+
loading,
|
|
1301
|
+
settledQuery,
|
|
1302
|
+
suggestions,
|
|
1303
|
+
suggestionFetchFailed
|
|
1304
|
+
]);
|
|
1305
|
+
const handleSelect = react.useCallback(
|
|
1306
|
+
(option) => {
|
|
1307
|
+
if (isSuggestionDisabled(
|
|
1308
|
+
option
|
|
1309
|
+
)) {
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
const term = String(option["value"] ?? option["label"] ?? "");
|
|
1313
|
+
if (term.trim()) trackSemantic([option]);
|
|
1314
|
+
const suggestionOption = option;
|
|
1315
|
+
const href = service.getSuggestionHref(suggestionOption);
|
|
1316
|
+
navigateTo(
|
|
1317
|
+
href || service.submitProductSearch(term, suggestionOption)
|
|
1318
|
+
);
|
|
1319
|
+
},
|
|
1320
|
+
[navigateTo, service, trackSemantic]
|
|
1321
|
+
);
|
|
1322
|
+
const handleRunAction = react.useCallback(
|
|
1323
|
+
(option) => {
|
|
1324
|
+
if (isSuggestionDisabled(
|
|
1325
|
+
option
|
|
1326
|
+
)) {
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
const result = service.activateSuggestionAction(
|
|
1330
|
+
option
|
|
1331
|
+
);
|
|
1332
|
+
void Promise.resolve(result).then(navigateTo).catch(() => void 0);
|
|
1333
|
+
},
|
|
1334
|
+
[navigateTo, service]
|
|
1335
|
+
);
|
|
1336
|
+
const handleSeeMore = react.useCallback(() => {
|
|
1337
|
+
const term = query.trim();
|
|
1338
|
+
if (term) navigateTo(service.viewMoreResults({ query: term }));
|
|
1339
|
+
}, [navigateTo, query, service]);
|
|
1340
|
+
const handleRecentSelect = react.useCallback(
|
|
1341
|
+
(recentQuery) => {
|
|
1342
|
+
trackSemantic({ query: recentQuery });
|
|
1343
|
+
navigateTo(service.submitProductSearch(recentQuery));
|
|
1344
|
+
},
|
|
1345
|
+
[navigateTo, service, trackSemantic]
|
|
1346
|
+
);
|
|
1347
|
+
const handleClearRecent = react.useCallback(() => {
|
|
1348
|
+
service.clearRecentSearches();
|
|
1349
|
+
setRecentSearches([]);
|
|
1350
|
+
}, [service]);
|
|
1351
|
+
const handleAgentUnavailable = react.useCallback(() => {
|
|
1352
|
+
if (agentQuery !== null)
|
|
1353
|
+
navigateTo(service.submitProductSearch(agentQuery));
|
|
1354
|
+
}, [agentQuery, navigateTo, service]);
|
|
1355
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1356
|
+
reactSearchComponents.SearchModal,
|
|
1357
|
+
{
|
|
1358
|
+
isOpen,
|
|
1359
|
+
onClose: close,
|
|
1360
|
+
logo,
|
|
1361
|
+
...cancelLabel === void 0 ? {} : { cancelLabel },
|
|
1362
|
+
...fallbackImageUrl === void 0 ? {} : { fallbackImageUrl },
|
|
1363
|
+
...copy === void 0 ? {} : { copy },
|
|
1364
|
+
input: {
|
|
1365
|
+
value: query,
|
|
1366
|
+
onChange: updateQuery,
|
|
1367
|
+
onSubmit: handleSubmit
|
|
1368
|
+
},
|
|
1369
|
+
suggestions: {
|
|
1370
|
+
items,
|
|
1371
|
+
loading,
|
|
1372
|
+
showNoResults: !loading && emptyFetched && items.length === 0,
|
|
1373
|
+
showDidYouMean,
|
|
1374
|
+
hasMore: hasMoreResults,
|
|
1375
|
+
query,
|
|
1376
|
+
onSelect: handleSelect,
|
|
1377
|
+
onRunAction: handleRunAction,
|
|
1378
|
+
onSeeMore: handleSeeMore
|
|
1379
|
+
},
|
|
1380
|
+
recentSearches: {
|
|
1381
|
+
items: recentSearches,
|
|
1382
|
+
onSelect: handleRecentSelect,
|
|
1383
|
+
onClear: handleClearRecent
|
|
1384
|
+
},
|
|
1385
|
+
agentPanel: handoff === null ? void 0 : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1386
|
+
NuiAgentPanel,
|
|
1387
|
+
{
|
|
1388
|
+
agent,
|
|
1389
|
+
query: handoff.query,
|
|
1390
|
+
handoffContext: {
|
|
1391
|
+
source: "storefront_search",
|
|
1392
|
+
trigger: "zero_results",
|
|
1393
|
+
query: handoff.query,
|
|
1394
|
+
did_you_mean: handoff.didYouMean
|
|
1395
|
+
},
|
|
1396
|
+
onUnavailable: handleAgentUnavailable
|
|
1397
|
+
}
|
|
1398
|
+
)
|
|
1399
|
+
}
|
|
1400
|
+
);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1932
1403
|
exports.CATALOG_SUGGESTIONS_PATH = CATALOG_SUGGESTIONS_PATH;
|
|
1933
1404
|
exports.DEBOUNCE_MS = DEBOUNCE_MS;
|
|
1934
|
-
exports.DEFAULT_AI_CHAT_BASE_URL = DEFAULT_AI_CHAT_BASE_URL;
|
|
1935
|
-
exports.DEFAULT_AI_CHAT_CONVERSATIONS_URL = DEFAULT_AI_CHAT_CONVERSATIONS_URL;
|
|
1936
|
-
exports.DEFAULT_AI_CHAT_MESSAGES_URL = DEFAULT_AI_CHAT_MESSAGES_URL;
|
|
1937
1405
|
exports.DEFAULT_SEARCH_PATH = DEFAULT_SEARCH_PATH;
|
|
1938
1406
|
exports.DEFAULT_SUGGESTION_PAGE = DEFAULT_SUGGESTION_PAGE;
|
|
1939
1407
|
exports.DEFAULT_SUGGESTION_SIZE = DEFAULT_SUGGESTION_SIZE;
|
|
1940
1408
|
exports.MAX_VISIBLE_SUGGESTIONS = MAX_VISIBLE_SUGGESTIONS;
|
|
1409
|
+
exports.NUI_SEARCH_FLAG = NUI_SEARCH_FLAG;
|
|
1410
|
+
exports.ProductSearch = ProductSearch;
|
|
1941
1411
|
exports.RECENT_SEARCHES_DEFAULT_LIMIT = RECENT_SEARCHES_DEFAULT_LIMIT;
|
|
1942
1412
|
exports.RECENT_SEARCHES_PREFIX = RECENT_SEARCHES_PREFIX;
|
|
1943
1413
|
exports.SEARCH_GTM_EVENTS = SEARCH_GTM_EVENTS;
|
|
@@ -1945,29 +1415,29 @@ exports.SEARCH_GTM_SEARCH_TYPES = SEARCH_GTM_SEARCH_TYPES;
|
|
|
1945
1415
|
exports.SEARCH_GTM_USER_STATUS = SEARCH_GTM_USER_STATUS;
|
|
1946
1416
|
exports.SEARCH_PLACEHOLDER = SEARCH_PLACEHOLDER;
|
|
1947
1417
|
exports.STOREFRONT_SHOPPING_CONTEXT = STOREFRONT_SHOPPING_CONTEXT;
|
|
1418
|
+
exports.SUBMIT_WAIT_TIMEOUT_MS = SUBMIT_WAIT_TIMEOUT_MS;
|
|
1948
1419
|
exports.TRIGGER_PLACEHOLDER = TRIGGER_PLACEHOLDER;
|
|
1949
|
-
exports.createChatService = createChatService;
|
|
1950
1420
|
exports.createSearchService = createSearchService;
|
|
1421
|
+
exports.decideSearchRoute = decideSearchRoute;
|
|
1951
1422
|
exports.getImageFromProperty = getImageFromProperty;
|
|
1952
1423
|
exports.getIngredientTerms = getIngredientTerms;
|
|
1953
1424
|
exports.getLocaleFromUrl = getLocaleFromUrl;
|
|
1954
1425
|
exports.getSeeMoreResultsPayload = getSeeMoreResultsPayload;
|
|
1955
1426
|
exports.getVisibleSuggestions = getVisibleSuggestions;
|
|
1956
1427
|
exports.isCartActionDisabled = isCartActionDisabled;
|
|
1428
|
+
exports.isNuiSearchEnabled = isNuiSearchEnabled;
|
|
1957
1429
|
exports.isSellableProductStatus = isSellableProductStatus;
|
|
1958
1430
|
exports.isSuggestionDisabled = isSuggestionDisabled;
|
|
1959
1431
|
exports.mapCatalogProductToSuggestion = mapCatalogProductToSuggestion;
|
|
1960
1432
|
exports.normalizeDidYouMean = normalizeDidYouMean;
|
|
1961
1433
|
exports.pushGtmEvent = pushGtmEvent;
|
|
1962
1434
|
exports.resolveApiGatewayPath = resolveApiGatewayPath;
|
|
1435
|
+
exports.resolveNuiSearchFlag = resolveNuiSearchFlag;
|
|
1963
1436
|
exports.resolveSuggestionImageSrc = resolveSuggestionImageSrc;
|
|
1964
1437
|
exports.trackChatEntryPoint = trackChatEntryPoint;
|
|
1965
1438
|
exports.trackErrorPopup = trackErrorPopup;
|
|
1966
1439
|
exports.trackProductSearch = trackProductSearch;
|
|
1967
1440
|
exports.trackSearchType = trackSearchType;
|
|
1968
|
-
exports.useSearchHistory = useSearchHistory;
|
|
1969
|
-
exports.useSearchHistoryStore = useSearchHistoryStore;
|
|
1970
1441
|
exports.useSearchSuggestions = useSearchSuggestions;
|
|
1971
|
-
exports.useVoiceSearch = useVoiceSearch;
|
|
1972
1442
|
//# sourceMappingURL=index.js.map
|
|
1973
1443
|
//# sourceMappingURL=index.js.map
|