@nuskin/react-search-sdk 1.0.0-comx-2233.3 → 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 +261 -127
- package/dist/index.d.ts +261 -127
- package/dist/index.js +589 -921
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +561 -897
- 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,37 +701,42 @@ 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 [
|
|
1258
|
-
const [
|
|
1259
|
-
const
|
|
1260
|
-
const
|
|
1261
|
-
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(() => {
|
|
1262
716
|
setSuggestions([]);
|
|
1263
717
|
setDidYouMean([]);
|
|
1264
718
|
setEmptyFetched(false);
|
|
719
|
+
setSuggestionFetchFailed(false);
|
|
1265
720
|
setHasMoreResults(false);
|
|
1266
721
|
setTotalResultsCount(0);
|
|
722
|
+
setSettledQuery("");
|
|
1267
723
|
setLoading(false);
|
|
1268
724
|
}, []);
|
|
1269
|
-
const updateQuery =
|
|
725
|
+
const updateQuery = react.useCallback(
|
|
1270
726
|
(nextQuery) => {
|
|
1271
727
|
const hasNextQuery = Boolean(nextQuery.trim());
|
|
1272
728
|
setEmptyFetched(false);
|
|
729
|
+
setSuggestionFetchFailed(false);
|
|
1273
730
|
setLoading(enabled && hasNextQuery);
|
|
1274
731
|
setQuery(nextQuery);
|
|
1275
732
|
},
|
|
1276
733
|
[enabled]
|
|
1277
734
|
);
|
|
1278
|
-
const clearQuery =
|
|
735
|
+
const clearQuery = react.useCallback(() => {
|
|
1279
736
|
resetSuggestionsState();
|
|
1280
737
|
setQuery("");
|
|
1281
738
|
}, [resetSuggestionsState]);
|
|
1282
|
-
const runFetch =
|
|
739
|
+
const runFetch = react.useCallback(
|
|
1283
740
|
async (nextQuery) => {
|
|
1284
741
|
const trimmedQuery = nextQuery.trim();
|
|
1285
742
|
const requestId = latestRequestId.current;
|
|
@@ -1289,6 +746,7 @@ function useSearchSuggestions({
|
|
|
1289
746
|
}
|
|
1290
747
|
setLoading(true);
|
|
1291
748
|
setEmptyFetched(false);
|
|
749
|
+
setSuggestionFetchFailed(false);
|
|
1292
750
|
try {
|
|
1293
751
|
const fetchedSuggestions = await service.fetchSuggestions(trimmedQuery);
|
|
1294
752
|
if (requestId !== latestRequestId.current) {
|
|
@@ -1311,6 +769,8 @@ function useSearchSuggestions({
|
|
|
1311
769
|
!hasDidYouMean && effectiveTotal > MAX_VISIBLE_SUGGESTIONS
|
|
1312
770
|
);
|
|
1313
771
|
setTotalResultsCount(effectiveTotal);
|
|
772
|
+
setSuggestionFetchFailed(false);
|
|
773
|
+
setSettledQuery(trimmedQuery);
|
|
1314
774
|
setEmptyFetched(
|
|
1315
775
|
hasDidYouMean ? false : visibleSuggestions.length === 0 && suggestedTerms.length === 0
|
|
1316
776
|
);
|
|
@@ -1321,8 +781,10 @@ function useSearchSuggestions({
|
|
|
1321
781
|
setSuggestions([]);
|
|
1322
782
|
setDidYouMean([]);
|
|
1323
783
|
setEmptyFetched(true);
|
|
784
|
+
setSuggestionFetchFailed(true);
|
|
1324
785
|
setHasMoreResults(false);
|
|
1325
786
|
setTotalResultsCount(0);
|
|
787
|
+
setSettledQuery(trimmedQuery);
|
|
1326
788
|
} finally {
|
|
1327
789
|
if (requestId === latestRequestId.current) {
|
|
1328
790
|
setLoading(false);
|
|
@@ -1331,7 +793,7 @@ function useSearchSuggestions({
|
|
|
1331
793
|
},
|
|
1332
794
|
[enabled, service, resetSuggestionsState]
|
|
1333
795
|
);
|
|
1334
|
-
|
|
796
|
+
react.useEffect(() => {
|
|
1335
797
|
if (!enabled) {
|
|
1336
798
|
resetSuggestionsState();
|
|
1337
799
|
return void 0;
|
|
@@ -1357,419 +819,625 @@ function useSearchSuggestions({
|
|
|
1357
819
|
didYouMean,
|
|
1358
820
|
loading,
|
|
1359
821
|
query,
|
|
822
|
+
settledQuery,
|
|
1360
823
|
setQuery,
|
|
824
|
+
suggestionFetchFailed,
|
|
1361
825
|
suggestions,
|
|
1362
826
|
totalResultsCount,
|
|
1363
827
|
updateQuery
|
|
1364
828
|
};
|
|
1365
829
|
}
|
|
1366
830
|
|
|
1367
|
-
//
|
|
1368
|
-
var
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
if (!Object.is(nextState, state)) {
|
|
1374
|
-
const previousState = state;
|
|
1375
|
-
state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
|
|
1376
|
-
listeners.forEach((listener) => listener(state, previousState));
|
|
1377
|
-
}
|
|
1378
|
-
};
|
|
1379
|
-
const getState = () => state;
|
|
1380
|
-
const getInitialState = () => initialState;
|
|
1381
|
-
const subscribe = (listener) => {
|
|
1382
|
-
listeners.add(listener);
|
|
1383
|
-
return () => listeners.delete(listener);
|
|
1384
|
-
};
|
|
1385
|
-
const api = { setState, getState, getInitialState, subscribe };
|
|
1386
|
-
const initialState = state = createState(setState, getState, api);
|
|
1387
|
-
return api;
|
|
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"
|
|
1388
837
|
};
|
|
1389
|
-
var
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
);
|
|
1397
|
-
React__default.default.useDebugValue(slice);
|
|
1398
|
-
return slice;
|
|
1399
|
-
}
|
|
1400
|
-
var createImpl = (createState) => {
|
|
1401
|
-
const api = createStore(createState);
|
|
1402
|
-
const useBoundStore = (selector) => useStore(api, selector);
|
|
1403
|
-
Object.assign(useBoundStore, api);
|
|
1404
|
-
return useBoundStore;
|
|
838
|
+
var SEARCH_GTM_SEARCH_TYPES = {
|
|
839
|
+
SEMANTIC: "semantic search",
|
|
840
|
+
AI_AGENT: "AI agent"
|
|
841
|
+
};
|
|
842
|
+
var SEARCH_GTM_USER_STATUS = {
|
|
843
|
+
GUEST: "guest",
|
|
844
|
+
LOGGED_IN: "logged_in"
|
|
1405
845
|
};
|
|
1406
|
-
var
|
|
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";
|
|
1407
850
|
|
|
1408
|
-
//
|
|
1409
|
-
|
|
1410
|
-
|
|
851
|
+
// src/utils/commonUtils.ts
|
|
852
|
+
var IS_BROWSER = typeof window !== "undefined";
|
|
853
|
+
function toEventString(value, fallback = " ") {
|
|
854
|
+
if (value === void 0 || value === null) return fallback;
|
|
855
|
+
const str = String(value).trim();
|
|
856
|
+
return str.length > 0 ? str : fallback;
|
|
857
|
+
}
|
|
858
|
+
function getLocaleFromUrl(pathname) {
|
|
859
|
+
const path = pathname ?? (IS_BROWSER ? window.location.pathname : "");
|
|
860
|
+
const [country, language] = path.split("/").filter(Boolean);
|
|
861
|
+
if (!/^[a-z]{2}$/i.test(country ?? "") || !/^[a-z]{2}$/i.test(language ?? "")) {
|
|
862
|
+
return {};
|
|
863
|
+
}
|
|
864
|
+
return {
|
|
865
|
+
countryCode: country.toUpperCase(),
|
|
866
|
+
language: language.toLowerCase()
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
function safeParse(raw) {
|
|
1411
870
|
try {
|
|
1412
|
-
|
|
1413
|
-
} catch
|
|
1414
|
-
return;
|
|
871
|
+
return JSON.parse(raw);
|
|
872
|
+
} catch {
|
|
873
|
+
return raw;
|
|
1415
874
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
875
|
+
}
|
|
876
|
+
function getLocalStorage(key) {
|
|
877
|
+
if (!IS_BROWSER) return void 0;
|
|
878
|
+
try {
|
|
879
|
+
const raw = window.localStorage.getItem(key);
|
|
880
|
+
return raw === null ? void 0 : safeParse(raw);
|
|
881
|
+
} catch {
|
|
882
|
+
return void 0;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// src/utils/gtmTracking.ts
|
|
887
|
+
function readUserStatusFromStorage() {
|
|
888
|
+
const value = getLocalStorage(IS_AUTHENTICATED_KEY);
|
|
889
|
+
if (value === void 0) return void 0;
|
|
890
|
+
if (value === SEARCH_GTM_USER_STATUS.GUEST || value === SEARCH_GTM_USER_STATUS.LOGGED_IN) {
|
|
891
|
+
return value;
|
|
892
|
+
}
|
|
893
|
+
const isAuthenticated = value === true || typeof value === "string" && ["true", "1", "yes", "y"].includes(value.toLowerCase().trim());
|
|
894
|
+
return isAuthenticated ? SEARCH_GTM_USER_STATUS.LOGGED_IN : SEARCH_GTM_USER_STATUS.GUEST;
|
|
895
|
+
}
|
|
896
|
+
function readAccountTypeFromStorage() {
|
|
897
|
+
const value = getLocalStorage(ACCOUNT_TYPE_KEY);
|
|
898
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
899
|
+
}
|
|
900
|
+
function readUserIdFromStorage() {
|
|
901
|
+
const raw = getLocalStorage(NUSKIN_ACCOUNT_DATA_KEY);
|
|
902
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
903
|
+
const user = raw.user;
|
|
904
|
+
if (!user || typeof user !== "object") return void 0;
|
|
905
|
+
const id = user.id;
|
|
906
|
+
return typeof id === "string" && id.trim().length > 0 ? id : void 0;
|
|
907
|
+
}
|
|
908
|
+
function resolveUserId(explicit) {
|
|
909
|
+
return toEventString(explicit ?? readUserIdFromStorage(), GTM_FALLBACK_NA);
|
|
910
|
+
}
|
|
911
|
+
function baseEventPayload(ctx) {
|
|
912
|
+
const urlLocale = getLocaleFromUrl();
|
|
913
|
+
const userStatus = ctx.userStatus ?? readUserStatusFromStorage();
|
|
914
|
+
const accountType = ctx.accountType ?? readAccountTypeFromStorage();
|
|
915
|
+
const resolvedUserStatus = userStatus ?? SEARCH_GTM_USER_STATUS.GUEST;
|
|
916
|
+
const documentTitle = IS_BROWSER && typeof document?.title === "string" ? document.title : void 0;
|
|
917
|
+
return {
|
|
918
|
+
Chat_ID: toEventString(ctx.chatId, GTM_FALLBACK_NA),
|
|
919
|
+
page_type: toEventString(
|
|
920
|
+
ctx.pageType ?? documentTitle,
|
|
921
|
+
GTM_FALLBACK_NA
|
|
922
|
+
),
|
|
923
|
+
country_code: toEventString(
|
|
924
|
+
ctx.countryCode ?? urlLocale.countryCode,
|
|
925
|
+
GTM_FALLBACK_NA
|
|
926
|
+
),
|
|
927
|
+
language: toEventString(
|
|
928
|
+
ctx.language ?? urlLocale.language,
|
|
929
|
+
GTM_FALLBACK_NA
|
|
930
|
+
),
|
|
931
|
+
user_status: toEventString(
|
|
932
|
+
resolvedUserStatus,
|
|
933
|
+
SEARCH_GTM_USER_STATUS.GUEST
|
|
1434
934
|
),
|
|
1435
|
-
|
|
935
|
+
account_type: toEventString(accountType, GTM_FALLBACK_NA)
|
|
1436
936
|
};
|
|
1437
|
-
return persistStorage;
|
|
1438
937
|
}
|
|
1439
|
-
|
|
938
|
+
function serializeProductResults(productResultsData, productResults) {
|
|
939
|
+
if (productResultsData !== void 0) {
|
|
940
|
+
try {
|
|
941
|
+
return JSON.stringify(productResultsData);
|
|
942
|
+
} catch {
|
|
943
|
+
return GTM_FALLBACK_NA;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
if (productResults === void 0 || productResults === null) {
|
|
947
|
+
return GTM_FALLBACK_NA;
|
|
948
|
+
}
|
|
949
|
+
return String(productResults);
|
|
950
|
+
}
|
|
951
|
+
function pushGtmEvent(event) {
|
|
952
|
+
if (!IS_BROWSER) return;
|
|
1440
953
|
try {
|
|
1441
|
-
const
|
|
1442
|
-
|
|
1443
|
-
|
|
954
|
+
const layer = window.dataLayer ?? (window.dataLayer = []);
|
|
955
|
+
layer.push(event);
|
|
956
|
+
} catch (err) {
|
|
957
|
+
if (typeof console !== "undefined") {
|
|
958
|
+
console.warn("[react-search-sdk] pushGtmEvent failed:", err);
|
|
1444
959
|
}
|
|
1445
|
-
return {
|
|
1446
|
-
then(onFulfilled) {
|
|
1447
|
-
return toThenable(onFulfilled)(result);
|
|
1448
|
-
},
|
|
1449
|
-
catch(_onRejected) {
|
|
1450
|
-
return this;
|
|
1451
|
-
}
|
|
1452
|
-
};
|
|
1453
|
-
} catch (e) {
|
|
1454
|
-
return {
|
|
1455
|
-
then(_onFulfilled) {
|
|
1456
|
-
return this;
|
|
1457
|
-
},
|
|
1458
|
-
catch(onRejected) {
|
|
1459
|
-
return toThenable(onRejected)(e);
|
|
1460
|
-
}
|
|
1461
|
-
};
|
|
1462
960
|
}
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
...
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
961
|
+
}
|
|
962
|
+
function trackChatEntryPoint(ctx) {
|
|
963
|
+
pushGtmEvent({
|
|
964
|
+
event: SEARCH_GTM_EVENTS.CHAT_ENTRY_POINT,
|
|
965
|
+
...baseEventPayload(ctx)
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
function trackSearchType(input) {
|
|
969
|
+
pushGtmEvent({
|
|
970
|
+
event: SEARCH_GTM_EVENTS.SEARCH_TYPE,
|
|
971
|
+
...baseEventPayload(input),
|
|
972
|
+
id: resolveUserId(input.userId),
|
|
973
|
+
type: input.searchType ?? SEARCH_GTM_SEARCH_TYPES.SEMANTIC,
|
|
974
|
+
search_response: toEventString(input.searchResponse, GTM_FALLBACK_NA)
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
function trackProductSearch(input) {
|
|
978
|
+
pushGtmEvent({
|
|
979
|
+
event: SEARCH_GTM_EVENTS.PRODUCT_SEARCH,
|
|
980
|
+
...baseEventPayload(input),
|
|
981
|
+
id: resolveUserId(input.userId),
|
|
982
|
+
search_type: input.searchType ?? SEARCH_GTM_SEARCH_TYPES.SEMANTIC,
|
|
983
|
+
Product_results: serializeProductResults(
|
|
984
|
+
input.productResultsData,
|
|
985
|
+
input.productResults
|
|
986
|
+
)
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
function trackErrorPopup(input) {
|
|
990
|
+
pushGtmEvent({
|
|
991
|
+
event: SEARCH_GTM_EVENTS.ERROR_POPUP,
|
|
992
|
+
...baseEventPayload(input),
|
|
993
|
+
id: resolveUserId(input.userId),
|
|
994
|
+
search_type: input.searchType ?? SEARCH_GTM_SEARCH_TYPES.SEMANTIC
|
|
995
|
+
});
|
|
996
|
+
}
|
|
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}`
|
|
1489
1034
|
);
|
|
1035
|
+
return fromQuery;
|
|
1490
1036
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
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();
|
|
1507
1223
|
},
|
|
1508
|
-
|
|
1509
|
-
api
|
|
1224
|
+
[navigate, close]
|
|
1510
1225
|
);
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
} else {
|
|
1538
|
-
return [false, deserializedStorageValue.state];
|
|
1539
|
-
}
|
|
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
|
|
1540
1252
|
}
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
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;
|
|
1552
1266
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
...options,
|
|
1566
|
-
...newOptions
|
|
1567
|
-
};
|
|
1568
|
-
if (newOptions.storage) {
|
|
1569
|
-
storage = newOptions.storage;
|
|
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;
|
|
1570
1279
|
}
|
|
1280
|
+
act(term, route);
|
|
1571
1281
|
},
|
|
1572
|
-
|
|
1573
|
-
|
|
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
|
+
);
|
|
1574
1319
|
},
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
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);
|
|
1583
1333
|
},
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
(
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
};
|
|
1619
|
-
});
|
|
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
|
|
1620
1368
|
},
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
query: trimmed,
|
|
1632
|
-
isAI: false,
|
|
1633
|
-
timestamp: Date.now()
|
|
1634
|
-
},
|
|
1635
|
-
...deduped
|
|
1636
|
-
].slice(0, MAX_HISTORY)
|
|
1637
|
-
};
|
|
1638
|
-
});
|
|
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
|
|
1639
1379
|
},
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
entries: old.queries.map((q) => ({
|
|
1658
|
-
query: q,
|
|
1659
|
-
isAI: false,
|
|
1660
|
-
timestamp: Date.now()
|
|
1661
|
-
}))
|
|
1662
|
-
};
|
|
1663
|
-
}
|
|
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
|
|
1664
1397
|
}
|
|
1665
|
-
|
|
1666
|
-
}
|
|
1398
|
+
)
|
|
1667
1399
|
}
|
|
1668
|
-
)
|
|
1669
|
-
);
|
|
1670
|
-
|
|
1671
|
-
// src/hooks/useSearchHistory.ts
|
|
1672
|
-
function useSearchHistory() {
|
|
1673
|
-
const entries = useSearchHistoryStore((s) => s.entries);
|
|
1674
|
-
const addEntry = useSearchHistoryStore((s) => s.addEntry);
|
|
1675
|
-
const addQuery = useSearchHistoryStore((s) => s.addQuery);
|
|
1676
|
-
const removeEntry = useSearchHistoryStore((s) => s.removeEntry);
|
|
1677
|
-
const clearAll = useSearchHistoryStore((s) => s.clearAll);
|
|
1678
|
-
return {
|
|
1679
|
-
entries,
|
|
1680
|
-
queries: entries.map((e) => e.query),
|
|
1681
|
-
addEntry,
|
|
1682
|
-
addQuery,
|
|
1683
|
-
removeEntry,
|
|
1684
|
-
clearAll
|
|
1685
|
-
};
|
|
1686
|
-
}
|
|
1687
|
-
function getSpeechRecognition() {
|
|
1688
|
-
if (typeof window === "undefined") return null;
|
|
1689
|
-
const w = window;
|
|
1690
|
-
return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
|
|
1691
|
-
}
|
|
1692
|
-
function useVoiceSearch({
|
|
1693
|
-
lang,
|
|
1694
|
-
onInterimResult,
|
|
1695
|
-
onResult,
|
|
1696
|
-
onError
|
|
1697
|
-
}) {
|
|
1698
|
-
const [listening, setListening] = React.useState(false);
|
|
1699
|
-
const recognitionRef = React.useRef(null);
|
|
1700
|
-
const supported = getSpeechRecognition() !== null;
|
|
1701
|
-
const stop = React.useCallback(() => {
|
|
1702
|
-
recognitionRef.current?.stop();
|
|
1703
|
-
recognitionRef.current = null;
|
|
1704
|
-
setListening(false);
|
|
1705
|
-
}, []);
|
|
1706
|
-
const start = React.useCallback(() => {
|
|
1707
|
-
const SR = getSpeechRecognition();
|
|
1708
|
-
if (!SR) return;
|
|
1709
|
-
stop();
|
|
1710
|
-
const rec = new SR();
|
|
1711
|
-
rec.lang = lang ?? navigator.language ?? "en-US";
|
|
1712
|
-
rec.interimResults = true;
|
|
1713
|
-
rec.maxAlternatives = 1;
|
|
1714
|
-
rec.onresult = (event) => {
|
|
1715
|
-
let finalTranscript = "";
|
|
1716
|
-
let interimTranscript = "";
|
|
1717
|
-
const startIndex = event.resultIndex ?? 0;
|
|
1718
|
-
for (let index = startIndex; index < event.results.length; index += 1) {
|
|
1719
|
-
const result = event.results[index];
|
|
1720
|
-
const transcript = result?.[0]?.transcript ?? "";
|
|
1721
|
-
if (!transcript) continue;
|
|
1722
|
-
if (result?.isFinal) {
|
|
1723
|
-
finalTranscript += transcript;
|
|
1724
|
-
} else {
|
|
1725
|
-
interimTranscript += transcript;
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
if (interimTranscript) onInterimResult?.(interimTranscript);
|
|
1729
|
-
if (finalTranscript) onResult(finalTranscript);
|
|
1730
|
-
};
|
|
1731
|
-
rec.onerror = (event) => {
|
|
1732
|
-
onError?.(event);
|
|
1733
|
-
setListening(false);
|
|
1734
|
-
};
|
|
1735
|
-
rec.onend = () => setListening(false);
|
|
1736
|
-
recognitionRef.current = rec;
|
|
1737
|
-
rec.start();
|
|
1738
|
-
setListening(true);
|
|
1739
|
-
}, [lang, onInterimResult, onResult, onError, stop]);
|
|
1740
|
-
return { listening, supported, start, stop };
|
|
1400
|
+
);
|
|
1741
1401
|
}
|
|
1742
1402
|
|
|
1743
1403
|
exports.CATALOG_SUGGESTIONS_PATH = CATALOG_SUGGESTIONS_PATH;
|
|
1744
1404
|
exports.DEBOUNCE_MS = DEBOUNCE_MS;
|
|
1745
|
-
exports.DEFAULT_AI_CHAT_BASE_URL = DEFAULT_AI_CHAT_BASE_URL;
|
|
1746
|
-
exports.DEFAULT_AI_CHAT_CONVERSATIONS_URL = DEFAULT_AI_CHAT_CONVERSATIONS_URL;
|
|
1747
|
-
exports.DEFAULT_AI_CHAT_MESSAGES_URL = DEFAULT_AI_CHAT_MESSAGES_URL;
|
|
1748
1405
|
exports.DEFAULT_SEARCH_PATH = DEFAULT_SEARCH_PATH;
|
|
1749
1406
|
exports.DEFAULT_SUGGESTION_PAGE = DEFAULT_SUGGESTION_PAGE;
|
|
1750
1407
|
exports.DEFAULT_SUGGESTION_SIZE = DEFAULT_SUGGESTION_SIZE;
|
|
1751
1408
|
exports.MAX_VISIBLE_SUGGESTIONS = MAX_VISIBLE_SUGGESTIONS;
|
|
1409
|
+
exports.NUI_SEARCH_FLAG = NUI_SEARCH_FLAG;
|
|
1410
|
+
exports.ProductSearch = ProductSearch;
|
|
1752
1411
|
exports.RECENT_SEARCHES_DEFAULT_LIMIT = RECENT_SEARCHES_DEFAULT_LIMIT;
|
|
1753
1412
|
exports.RECENT_SEARCHES_PREFIX = RECENT_SEARCHES_PREFIX;
|
|
1413
|
+
exports.SEARCH_GTM_EVENTS = SEARCH_GTM_EVENTS;
|
|
1414
|
+
exports.SEARCH_GTM_SEARCH_TYPES = SEARCH_GTM_SEARCH_TYPES;
|
|
1415
|
+
exports.SEARCH_GTM_USER_STATUS = SEARCH_GTM_USER_STATUS;
|
|
1754
1416
|
exports.SEARCH_PLACEHOLDER = SEARCH_PLACEHOLDER;
|
|
1755
1417
|
exports.STOREFRONT_SHOPPING_CONTEXT = STOREFRONT_SHOPPING_CONTEXT;
|
|
1418
|
+
exports.SUBMIT_WAIT_TIMEOUT_MS = SUBMIT_WAIT_TIMEOUT_MS;
|
|
1756
1419
|
exports.TRIGGER_PLACEHOLDER = TRIGGER_PLACEHOLDER;
|
|
1757
|
-
exports.createChatService = createChatService;
|
|
1758
1420
|
exports.createSearchService = createSearchService;
|
|
1421
|
+
exports.decideSearchRoute = decideSearchRoute;
|
|
1759
1422
|
exports.getImageFromProperty = getImageFromProperty;
|
|
1760
1423
|
exports.getIngredientTerms = getIngredientTerms;
|
|
1424
|
+
exports.getLocaleFromUrl = getLocaleFromUrl;
|
|
1761
1425
|
exports.getSeeMoreResultsPayload = getSeeMoreResultsPayload;
|
|
1762
1426
|
exports.getVisibleSuggestions = getVisibleSuggestions;
|
|
1763
1427
|
exports.isCartActionDisabled = isCartActionDisabled;
|
|
1428
|
+
exports.isNuiSearchEnabled = isNuiSearchEnabled;
|
|
1764
1429
|
exports.isSellableProductStatus = isSellableProductStatus;
|
|
1765
1430
|
exports.isSuggestionDisabled = isSuggestionDisabled;
|
|
1766
1431
|
exports.mapCatalogProductToSuggestion = mapCatalogProductToSuggestion;
|
|
1767
1432
|
exports.normalizeDidYouMean = normalizeDidYouMean;
|
|
1433
|
+
exports.pushGtmEvent = pushGtmEvent;
|
|
1768
1434
|
exports.resolveApiGatewayPath = resolveApiGatewayPath;
|
|
1435
|
+
exports.resolveNuiSearchFlag = resolveNuiSearchFlag;
|
|
1769
1436
|
exports.resolveSuggestionImageSrc = resolveSuggestionImageSrc;
|
|
1770
|
-
exports.
|
|
1771
|
-
exports.
|
|
1437
|
+
exports.trackChatEntryPoint = trackChatEntryPoint;
|
|
1438
|
+
exports.trackErrorPopup = trackErrorPopup;
|
|
1439
|
+
exports.trackProductSearch = trackProductSearch;
|
|
1440
|
+
exports.trackSearchType = trackSearchType;
|
|
1772
1441
|
exports.useSearchSuggestions = useSearchSuggestions;
|
|
1773
|
-
exports.useVoiceSearch = useVoiceSearch;
|
|
1774
1442
|
//# sourceMappingURL=index.js.map
|
|
1775
1443
|
//# sourceMappingURL=index.js.map
|