@nuskin/react-search-sdk 1.0.0-comx-2233.1 → 1.0.0-comx-2233.3

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 CHANGED
@@ -237,9 +237,9 @@ interface SearchService {
237
237
  * SearchApiService — Phase 0 lift-and-shift of
238
238
  * `header-mfe/src/services/HeaderSearchService.js`.
239
239
  *
240
- * Behaviour is intentionally preserved verbatim: same URL formats, same
241
- * `sessionStorage` key format, same fetch parameter shape, same fallback
242
- * values. See the ported source for context.
240
+ * Behaviour follows the header search contract for URL formats,
241
+ * `sessionStorage` key format, fetch parameter shape, and fallback values.
242
+ * Navigation side effects are left to the consuming router.
243
243
  *
244
244
  * Two responsibilities were intentionally moved OUT of the SDK to preserve
245
245
  * the SDK-narrow boundary:
@@ -292,10 +292,14 @@ interface ChatServiceOptions {
292
292
  conversationsUrl?: string;
293
293
  /** POST endpoint for sending a message (SSE response). */
294
294
  messagesUrl?: string;
295
- /** X-Tenant-Id header value (used for conversations call). */
296
- tenantId?: string;
297
- /** Returns the current Bearer token, or null if unauthenticated. */
298
- getAuthToken?: () => string | null;
295
+ /** X-Sales-AI-App-Key header value (from CS configurations). */
296
+ salesAiAppKey?: string;
297
+ /** X-Sales-AI-Parent-Origin header value (from CS configurations). */
298
+ salesAiParentOrigin?: string;
299
+ /** Market code (e.g., "US", "JP") for scope_context in message requests. */
300
+ market?: string;
301
+ /** Language code (e.g., "en", "ja") for scope_context in message requests. */
302
+ language?: string;
299
303
  /** Override fetch for testing. */
300
304
  fetchImpl?: typeof fetch | null;
301
305
  }
@@ -385,6 +389,7 @@ interface SpeechRecognitionErrorEventLike {
385
389
  interface UseVoiceSearchOptions {
386
390
  lang?: string;
387
391
  onResult: (transcript: string) => void;
392
+ onInterimResult?: (transcript: string) => void;
388
393
  onError?: (error: SpeechRecognitionErrorEventLike) => void;
389
394
  }
390
395
  interface UseVoiceSearchResult {
@@ -393,7 +398,7 @@ interface UseVoiceSearchResult {
393
398
  start: () => void;
394
399
  stop: () => void;
395
400
  }
396
- declare function useVoiceSearch({ lang, onResult, onError, }: UseVoiceSearchOptions): UseVoiceSearchResult;
401
+ declare function useVoiceSearch({ lang, onInterimResult, onResult, onError, }: UseVoiceSearchOptions): UseVoiceSearchResult;
397
402
 
398
403
  /**
399
404
  * Catalog-product normalisation helpers — Phase 0 lift-and-shift of the
@@ -502,12 +507,12 @@ declare const TRIGGER_PLACEHOLDER = "Product Search";
502
507
  /**
503
508
  * Full path segment for the AI commerce chat SSE endpoint.
504
509
  * Appended to the consumer-provided `chatBaseUrl`.
505
- * Source: https://admin.tst.nuskin.ai/api/analytics/v1/commerce/chat
510
+ * Source: https://admin.tst.nuskin.ai/api/analytics/v1/embed
506
511
  */
507
- declare const DEFAULT_AI_CHAT_BASE_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/chat";
512
+ declare const DEFAULT_AI_CHAT_BASE_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/embed";
508
513
  /** Default POST endpoint for creating an AI chat conversation. */
509
- declare const DEFAULT_AI_CHAT_CONVERSATIONS_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/chat/conversations";
514
+ declare const DEFAULT_AI_CHAT_CONVERSATIONS_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/embed/conversations";
510
515
  /** Default POST endpoint for sending AI chat messages. */
511
- declare const DEFAULT_AI_CHAT_MESSAGES_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/chat/messages";
516
+ declare const DEFAULT_AI_CHAT_MESSAGES_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/embed/messages";
512
517
 
513
518
  export { CATALOG_SUGGESTIONS_PATH, type CatalogSuggestionItem, type ChatMessageResult, type ChatProduct, type ChatService, type ChatServiceOptions, DEBOUNCE_MS, DEFAULT_AI_CHAT_BASE_URL, DEFAULT_AI_CHAT_CONVERSATIONS_URL, DEFAULT_AI_CHAT_MESSAGES_URL, DEFAULT_SEARCH_PATH, DEFAULT_SUGGESTION_PAGE, DEFAULT_SUGGESTION_SIZE, type DidYouMeanItem, type GetMarketConfig, type LocaleParts, type LocationLike, MAX_VISIBLE_SUGGESTIONS, type MarketConfig, type ProductType, RECENT_SEARCHES_DEFAULT_LIMIT, RECENT_SEARCHES_PREFIX, SEARCH_PLACEHOLDER, STOREFRONT_SHOPPING_CONTEXT, type SearchChromeCopy, type SearchHistoryEntry, type SearchHistoryState, type SearchHistoryEntry as SearchHistoryStoreEntry, type SearchService, type SearchServiceOptions, type SuggestionOption, type SuggestionsResponse, TRIGGER_PLACEHOLDER, type UseSearchHistoryResult, type UseSearchSuggestionsOptions, type UseSearchSuggestionsResult, type UseVoiceSearchOptions, type UseVoiceSearchResult, createChatService, createSearchService, getImageFromProperty, getIngredientTerms, getSeeMoreResultsPayload, getVisibleSuggestions, isCartActionDisabled, isSellableProductStatus, isSuggestionDisabled, mapCatalogProductToSuggestion, normalizeDidYouMean, resolveApiGatewayPath, resolveSuggestionImageSrc, useSearchHistory, useSearchHistoryStore, useSearchSuggestions, useVoiceSearch };
package/dist/index.d.ts CHANGED
@@ -237,9 +237,9 @@ interface SearchService {
237
237
  * SearchApiService — Phase 0 lift-and-shift of
238
238
  * `header-mfe/src/services/HeaderSearchService.js`.
239
239
  *
240
- * Behaviour is intentionally preserved verbatim: same URL formats, same
241
- * `sessionStorage` key format, same fetch parameter shape, same fallback
242
- * values. See the ported source for context.
240
+ * Behaviour follows the header search contract for URL formats,
241
+ * `sessionStorage` key format, fetch parameter shape, and fallback values.
242
+ * Navigation side effects are left to the consuming router.
243
243
  *
244
244
  * Two responsibilities were intentionally moved OUT of the SDK to preserve
245
245
  * the SDK-narrow boundary:
@@ -292,10 +292,14 @@ interface ChatServiceOptions {
292
292
  conversationsUrl?: string;
293
293
  /** POST endpoint for sending a message (SSE response). */
294
294
  messagesUrl?: string;
295
- /** X-Tenant-Id header value (used for conversations call). */
296
- tenantId?: string;
297
- /** Returns the current Bearer token, or null if unauthenticated. */
298
- getAuthToken?: () => string | null;
295
+ /** X-Sales-AI-App-Key header value (from CS configurations). */
296
+ salesAiAppKey?: string;
297
+ /** X-Sales-AI-Parent-Origin header value (from CS configurations). */
298
+ salesAiParentOrigin?: string;
299
+ /** Market code (e.g., "US", "JP") for scope_context in message requests. */
300
+ market?: string;
301
+ /** Language code (e.g., "en", "ja") for scope_context in message requests. */
302
+ language?: string;
299
303
  /** Override fetch for testing. */
300
304
  fetchImpl?: typeof fetch | null;
301
305
  }
@@ -385,6 +389,7 @@ interface SpeechRecognitionErrorEventLike {
385
389
  interface UseVoiceSearchOptions {
386
390
  lang?: string;
387
391
  onResult: (transcript: string) => void;
392
+ onInterimResult?: (transcript: string) => void;
388
393
  onError?: (error: SpeechRecognitionErrorEventLike) => void;
389
394
  }
390
395
  interface UseVoiceSearchResult {
@@ -393,7 +398,7 @@ interface UseVoiceSearchResult {
393
398
  start: () => void;
394
399
  stop: () => void;
395
400
  }
396
- declare function useVoiceSearch({ lang, onResult, onError, }: UseVoiceSearchOptions): UseVoiceSearchResult;
401
+ declare function useVoiceSearch({ lang, onInterimResult, onResult, onError, }: UseVoiceSearchOptions): UseVoiceSearchResult;
397
402
 
398
403
  /**
399
404
  * Catalog-product normalisation helpers — Phase 0 lift-and-shift of the
@@ -502,12 +507,12 @@ declare const TRIGGER_PLACEHOLDER = "Product Search";
502
507
  /**
503
508
  * Full path segment for the AI commerce chat SSE endpoint.
504
509
  * Appended to the consumer-provided `chatBaseUrl`.
505
- * Source: https://admin.tst.nuskin.ai/api/analytics/v1/commerce/chat
510
+ * Source: https://admin.tst.nuskin.ai/api/analytics/v1/embed
506
511
  */
507
- declare const DEFAULT_AI_CHAT_BASE_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/chat";
512
+ declare const DEFAULT_AI_CHAT_BASE_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/embed";
508
513
  /** Default POST endpoint for creating an AI chat conversation. */
509
- declare const DEFAULT_AI_CHAT_CONVERSATIONS_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/chat/conversations";
514
+ declare const DEFAULT_AI_CHAT_CONVERSATIONS_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/embed/conversations";
510
515
  /** Default POST endpoint for sending AI chat messages. */
511
- declare const DEFAULT_AI_CHAT_MESSAGES_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/chat/messages";
516
+ declare const DEFAULT_AI_CHAT_MESSAGES_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/embed/messages";
512
517
 
513
518
  export { CATALOG_SUGGESTIONS_PATH, type CatalogSuggestionItem, type ChatMessageResult, type ChatProduct, type ChatService, type ChatServiceOptions, DEBOUNCE_MS, DEFAULT_AI_CHAT_BASE_URL, DEFAULT_AI_CHAT_CONVERSATIONS_URL, DEFAULT_AI_CHAT_MESSAGES_URL, DEFAULT_SEARCH_PATH, DEFAULT_SUGGESTION_PAGE, DEFAULT_SUGGESTION_SIZE, type DidYouMeanItem, type GetMarketConfig, type LocaleParts, type LocationLike, MAX_VISIBLE_SUGGESTIONS, type MarketConfig, type ProductType, RECENT_SEARCHES_DEFAULT_LIMIT, RECENT_SEARCHES_PREFIX, SEARCH_PLACEHOLDER, STOREFRONT_SHOPPING_CONTEXT, type SearchChromeCopy, type SearchHistoryEntry, type SearchHistoryState, type SearchHistoryEntry as SearchHistoryStoreEntry, type SearchService, type SearchServiceOptions, type SuggestionOption, type SuggestionsResponse, TRIGGER_PLACEHOLDER, type UseSearchHistoryResult, type UseSearchSuggestionsOptions, type UseSearchSuggestionsResult, type UseVoiceSearchOptions, type UseVoiceSearchResult, createChatService, createSearchService, getImageFromProperty, getIngredientTerms, getSeeMoreResultsPayload, getVisibleSuggestions, isCartActionDisabled, isSellableProductStatus, isSuggestionDisabled, mapCatalogProductToSuggestion, normalizeDidYouMean, resolveApiGatewayPath, resolveSuggestionImageSrc, useSearchHistory, useSearchHistoryStore, useSearchSuggestions, useVoiceSearch };
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ var DEBOUNCE_MS = 220;
18
18
  var MAX_VISIBLE_SUGGESTIONS = 6;
19
19
  var SEARCH_PLACEHOLDER = "Search by product name, ingredient, benefits, or SKU";
20
20
  var TRIGGER_PLACEHOLDER = "Product Search";
21
- var DEFAULT_AI_CHAT_BASE_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/chat";
21
+ var DEFAULT_AI_CHAT_BASE_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/embed";
22
22
  var DEFAULT_AI_CHAT_CONVERSATIONS_URL = `${DEFAULT_AI_CHAT_BASE_URL}/conversations`;
23
23
  var DEFAULT_AI_CHAT_MESSAGES_URL = `${DEFAULT_AI_CHAT_BASE_URL}/messages`;
24
24
 
@@ -343,17 +343,13 @@ function createSearchService(options = {}) {
343
343
  if (!trimmedQuery) return void 0;
344
344
  addRecentHeaderSearch(localeParts, trimmedQuery, storage);
345
345
  const optionHref = resolveOptionHref(option, localeParts);
346
- if (optionHref && locationLike && typeof locationLike.assign === "function") {
347
- locationLike.assign(optionHref);
346
+ if (optionHref) {
348
347
  return stripLocalePrefix(optionHref, localeParts);
349
348
  }
350
349
  const targetPath = buildSearchResultsPath(
351
350
  trimmedQuery,
352
351
  localeParts
353
352
  );
354
- if (locationLike && typeof locationLike.assign === "function") {
355
- locationLike.assign(targetPath);
356
- }
357
353
  return stripLocalePrefix(targetPath, localeParts);
358
354
  },
359
355
  activateSuggestionAction(option) {
@@ -366,9 +362,7 @@ function createSearchService(options = {}) {
366
362
  option,
367
363
  localeParts
368
364
  );
369
- if (optionHref2 && locationLike && typeof locationLike.assign === "function") {
370
- locationLike.assign(optionHref2);
371
- } else {
365
+ if (!optionHref2) {
372
366
  const optionQuery2 = option?.value ?? option?.label ?? option?.identifier ?? "";
373
367
  service.submitProductSearch(
374
368
  String(optionQuery2)
@@ -387,9 +381,8 @@ function createSearchService(options = {}) {
387
381
  }
388
382
  }
389
383
  const optionHref = resolveOptionHref(option, localeParts);
390
- if (optionHref && locationLike && typeof locationLike.assign === "function") {
391
- locationLike.assign(optionHref);
392
- return optionHref;
384
+ if (optionHref) {
385
+ return stripLocalePrefix(optionHref, localeParts);
393
386
  }
394
387
  const optionQuery = option?.value ?? option?.label ?? option?.identifier ?? "";
395
388
  return service.submitProductSearch(String(optionQuery));
@@ -769,6 +762,19 @@ function findClosingFence(content, fromIndex) {
769
762
  }
770
763
  return -1;
771
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
+ }
772
778
  function extractProductsFromMarkdown(content) {
773
779
  const products = [];
774
780
  const summaryParts = [];
@@ -797,7 +803,8 @@ function extractProductsFromMarkdown(content) {
797
803
  if (Array.isArray(parsed)) {
798
804
  products.push(...parsed);
799
805
  }
800
- } catch {
806
+ } catch (e) {
807
+ console.error("Failed to parse products block:", e);
801
808
  }
802
809
  summaryParts.push(content.slice(summaryCursor, start));
803
810
  summaryCursor = end + MARKDOWN_CLOSING_FENCE.length;
@@ -810,8 +817,10 @@ function createChatService(options) {
810
817
  const {
811
818
  conversationsUrl = DEFAULT_AI_CHAT_CONVERSATIONS_URL,
812
819
  messagesUrl = DEFAULT_AI_CHAT_MESSAGES_URL,
813
- tenantId,
814
- getAuthToken,
820
+ salesAiAppKey,
821
+ salesAiParentOrigin,
822
+ market,
823
+ language,
815
824
  fetchImpl: fetchImplOption
816
825
  } = options;
817
826
  let fetchImpl;
@@ -824,22 +833,30 @@ function createChatService(options) {
824
833
  throw new Error("ChatService: no fetch implementation available");
825
834
  };
826
835
  }
827
- function buildHeaders(extra) {
836
+ function buildHeaders(extra, includeJsonContentType = true) {
828
837
  const headers = {
829
- "Content-Type": "application/json",
830
838
  ...extra
831
839
  };
832
- if (tenantId) headers["X-Tenant-Id"] = tenantId;
833
- const token = getAuthToken?.();
834
- if (token) headers["Authorization"] = `Bearer ${token}`;
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
+ }
835
853
  return headers;
836
854
  }
837
855
  return {
838
- async createConversation(title = "Search Session") {
856
+ async createConversation() {
839
857
  const res = await fetchImpl(conversationsUrl, {
840
858
  method: "POST",
841
- headers: buildHeaders(),
842
- body: JSON.stringify({ title })
859
+ headers: buildHeaders(void 0, false)
843
860
  });
844
861
  if (!res.ok) {
845
862
  throw new Error(
@@ -856,14 +873,26 @@ function createChatService(options) {
856
873
  return id;
857
874
  },
858
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
+ };
859
892
  const res = await fetchImpl(messagesUrl, {
860
893
  method: "POST",
861
- headers: buildHeaders({ Accept: "application/json" }),
862
- body: JSON.stringify({
863
- conversation_id: conversationId,
864
- content,
865
- compact: false
866
- })
894
+ headers: buildHeaders({ Accept: "text/event-stream" }),
895
+ body: JSON.stringify(requestBody)
867
896
  });
868
897
  if (!res.ok) {
869
898
  throw new Error(
@@ -871,6 +900,7 @@ function createChatService(options) {
871
900
  );
872
901
  }
873
902
  let accumulated = "";
903
+ const renderedProducts = [];
874
904
  const parser = createParser({
875
905
  onEvent(event) {
876
906
  const data = event.data;
@@ -879,6 +909,9 @@ function createChatService(options) {
879
909
  const parsed = JSON.parse(data);
880
910
  const parsedType = parsed["type"];
881
911
  const type = typeof parsedType === "string" ? parsedType : "";
912
+ renderedProducts.push(
913
+ ...getRenderProductListItems(parsed)
914
+ );
882
915
  if (type === "content" && typeof parsed["text"] === "string") {
883
916
  const text = parsed["text"];
884
917
  accumulated += text;
@@ -906,7 +939,12 @@ function createChatService(options) {
906
939
  parser.feed(rawBody);
907
940
  }
908
941
  const { summary, products } = extractProductsFromMarkdown(accumulated);
909
- return { summary, products, rawContent: accumulated };
942
+ const finalProducts = renderedProducts.length > 0 ? renderedProducts : products;
943
+ return {
944
+ summary,
945
+ products: finalProducts,
946
+ rawContent: accumulated
947
+ };
910
948
  }
911
949
  };
912
950
  }
@@ -1653,6 +1691,7 @@ function getSpeechRecognition() {
1653
1691
  }
1654
1692
  function useVoiceSearch({
1655
1693
  lang,
1694
+ onInterimResult,
1656
1695
  onResult,
1657
1696
  onError
1658
1697
  }) {
@@ -1670,11 +1709,24 @@ function useVoiceSearch({
1670
1709
  stop();
1671
1710
  const rec = new SR();
1672
1711
  rec.lang = lang ?? navigator.language ?? "en-US";
1673
- rec.interimResults = false;
1712
+ rec.interimResults = true;
1674
1713
  rec.maxAlternatives = 1;
1675
1714
  rec.onresult = (event) => {
1676
- const transcript = event.results[0]?.[0]?.transcript ?? "";
1677
- if (transcript) onResult(transcript);
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);
1678
1730
  };
1679
1731
  rec.onerror = (event) => {
1680
1732
  onError?.(event);
@@ -1684,7 +1736,7 @@ function useVoiceSearch({
1684
1736
  recognitionRef.current = rec;
1685
1737
  rec.start();
1686
1738
  setListening(true);
1687
- }, [lang, onResult, onError, stop]);
1739
+ }, [lang, onInterimResult, onResult, onError, stop]);
1688
1740
  return { listening, supported, start, stop };
1689
1741
  }
1690
1742