@blotoutio/providers-evo-search-sdk 1.56.1 → 1.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/core.cjs.js CHANGED
@@ -689,6 +689,10 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
689
689
  logger.info('EvoSearch API: Fetching trending searches');
690
690
  return getJSON('/trending', 'Trending', undefined, signal);
691
691
  };
692
+ const config = (signal) => {
693
+ logger.info('EvoSearch API: Fetching ask config');
694
+ return getJSON('/config', 'Config', undefined, signal);
695
+ };
692
696
  const recents = (signal) => {
693
697
  logger.info('EvoSearch API: Fetching recent searches');
694
698
  return getJSON('/user/recents', 'Recents', undefined, signal);
@@ -723,6 +727,58 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
723
727
  logger.info(`EvoSearch API: Search success - ${data.results.length} products`);
724
728
  return data;
725
729
  };
730
+ // SSE consumer for Ask AI. Decodes `event: <type>\ndata: <json>` frames
731
+ // from the worker stream and yields typed events as they arrive. Caller
732
+ // aborts via the AbortSignal — fetch teardown closes the underlying
733
+ // reader, the for-await loop drops out, and the iterator returns.
734
+ async function* askStream(query, signal) {
735
+ const url = getURL('/ask');
736
+ const response = await fetchImpl(url, {
737
+ method: 'POST',
738
+ headers: (() => {
739
+ const h = getHeaders();
740
+ h.set('Content-Type', 'application/json');
741
+ return h;
742
+ })(),
743
+ body: JSON.stringify({ query }),
744
+ credentials: 'omit',
745
+ signal,
746
+ });
747
+ if (!response.ok || !response.body) {
748
+ throw new Error(`Ask failed - ${response.status}`);
749
+ }
750
+ const reader = response.body
751
+ .pipeThrough(new TextDecoderStream())
752
+ .getReader();
753
+ let buffer = '';
754
+ try {
755
+ while (true) {
756
+ const { done, value } = await reader.read();
757
+ if (done) {
758
+ break;
759
+ }
760
+ buffer += value;
761
+ let boundary = buffer.indexOf('\n\n');
762
+ while (boundary !== -1) {
763
+ const frame = buffer.slice(0, boundary);
764
+ buffer = buffer.slice(boundary + 2);
765
+ const event = parseSseFrame(frame);
766
+ if (event) {
767
+ yield event;
768
+ }
769
+ boundary = buffer.indexOf('\n\n');
770
+ }
771
+ }
772
+ }
773
+ finally {
774
+ try {
775
+ reader.releaseLock();
776
+ }
777
+ catch {
778
+ // ignore
779
+ }
780
+ }
781
+ }
726
782
  const sendEvent = (action, currency, actionData, clickData, beacon) => {
727
783
  // Send event to backend /user/event endpoint for storage
728
784
  fetchImpl(getURL('/user/event'), {
@@ -766,15 +822,40 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
766
822
  return {
767
823
  autocomplete,
768
824
  trending,
825
+ config,
769
826
  user: {
770
827
  recents,
771
828
  deleteRecent,
772
829
  clearRecents,
773
830
  },
774
831
  search,
832
+ askStream,
775
833
  sendEvent,
776
834
  };
777
835
  };
836
+ const parseSseFrame = (frame) => {
837
+ let eventType = '';
838
+ const dataLines = [];
839
+ for (const line of frame.split('\n')) {
840
+ if (line.startsWith('event:')) {
841
+ eventType = line.slice(6).trim();
842
+ }
843
+ else if (line.startsWith('data:')) {
844
+ dataLines.push(line.slice(5).trim());
845
+ }
846
+ }
847
+ if (!eventType) {
848
+ return null;
849
+ }
850
+ try {
851
+ const data = dataLines.join('\n');
852
+ const parsed = data ? JSON.parse(data) : {};
853
+ return { type: eventType, ...parsed };
854
+ }
855
+ catch {
856
+ return null;
857
+ }
858
+ };
778
859
 
779
860
  const error = (message) => console.error(message);
780
861
  const init = (params) => {
@@ -794,8 +875,9 @@ const init = (params) => {
794
875
  error('EvoSearch SDK: Failed to set previewEvoSearch sessionStorage');
795
876
  }
796
877
  }
797
- const { enabled, uiPreferences } = (_c = (_b = params.manifest) === null || _b === void 0 ? void 0 : _b.variables) !== null && _c !== void 0 ? _c : {};
878
+ const { enabled, uiPreferences, askAi } = (_c = (_b = params.manifest) === null || _b === void 0 ? void 0 : _b.variables) !== null && _c !== void 0 ? _c : {};
798
879
  const mode = uiPreferences === null || uiPreferences === void 0 ? void 0 : uiPreferences.mode;
880
+ const askAiEnabled = (askAi === null || askAi === void 0 ? void 0 : askAi.enabled) === true;
799
881
  const hasPreview = hasPreviewKey();
800
882
  const shouldEnable = enabled || hasPreview;
801
883
  if (!shouldEnable) {
@@ -840,6 +922,7 @@ const init = (params) => {
840
922
  handpickedProducts: [],
841
923
  api: evoSearchAPI,
842
924
  uiPreferences,
925
+ askAiEnabled,
843
926
  });
844
927
  params.sendTag({
845
928
  eventId: crypto.randomUUID(),
package/core.js CHANGED
@@ -690,6 +690,10 @@ var ProvidersEvoSearchSdk = (function () {
690
690
  logger.info('EvoSearch API: Fetching trending searches');
691
691
  return getJSON('/trending', 'Trending', undefined, signal);
692
692
  };
693
+ const config = (signal) => {
694
+ logger.info('EvoSearch API: Fetching ask config');
695
+ return getJSON('/config', 'Config', undefined, signal);
696
+ };
693
697
  const recents = (signal) => {
694
698
  logger.info('EvoSearch API: Fetching recent searches');
695
699
  return getJSON('/user/recents', 'Recents', undefined, signal);
@@ -724,6 +728,58 @@ var ProvidersEvoSearchSdk = (function () {
724
728
  logger.info(`EvoSearch API: Search success - ${data.results.length} products`);
725
729
  return data;
726
730
  };
731
+ // SSE consumer for Ask AI. Decodes `event: <type>\ndata: <json>` frames
732
+ // from the worker stream and yields typed events as they arrive. Caller
733
+ // aborts via the AbortSignal — fetch teardown closes the underlying
734
+ // reader, the for-await loop drops out, and the iterator returns.
735
+ async function* askStream(query, signal) {
736
+ const url = getURL('/ask');
737
+ const response = await fetchImpl(url, {
738
+ method: 'POST',
739
+ headers: (() => {
740
+ const h = getHeaders();
741
+ h.set('Content-Type', 'application/json');
742
+ return h;
743
+ })(),
744
+ body: JSON.stringify({ query }),
745
+ credentials: 'omit',
746
+ signal,
747
+ });
748
+ if (!response.ok || !response.body) {
749
+ throw new Error(`Ask failed - ${response.status}`);
750
+ }
751
+ const reader = response.body
752
+ .pipeThrough(new TextDecoderStream())
753
+ .getReader();
754
+ let buffer = '';
755
+ try {
756
+ while (true) {
757
+ const { done, value } = await reader.read();
758
+ if (done) {
759
+ break;
760
+ }
761
+ buffer += value;
762
+ let boundary = buffer.indexOf('\n\n');
763
+ while (boundary !== -1) {
764
+ const frame = buffer.slice(0, boundary);
765
+ buffer = buffer.slice(boundary + 2);
766
+ const event = parseSseFrame(frame);
767
+ if (event) {
768
+ yield event;
769
+ }
770
+ boundary = buffer.indexOf('\n\n');
771
+ }
772
+ }
773
+ }
774
+ finally {
775
+ try {
776
+ reader.releaseLock();
777
+ }
778
+ catch {
779
+ // ignore
780
+ }
781
+ }
782
+ }
727
783
  const sendEvent = (action, currency, actionData, clickData, beacon) => {
728
784
  // Send event to backend /user/event endpoint for storage
729
785
  fetchImpl(getURL('/user/event'), {
@@ -767,15 +823,40 @@ var ProvidersEvoSearchSdk = (function () {
767
823
  return {
768
824
  autocomplete,
769
825
  trending,
826
+ config,
770
827
  user: {
771
828
  recents,
772
829
  deleteRecent,
773
830
  clearRecents,
774
831
  },
775
832
  search,
833
+ askStream,
776
834
  sendEvent,
777
835
  };
778
836
  };
837
+ const parseSseFrame = (frame) => {
838
+ let eventType = '';
839
+ const dataLines = [];
840
+ for (const line of frame.split('\n')) {
841
+ if (line.startsWith('event:')) {
842
+ eventType = line.slice(6).trim();
843
+ }
844
+ else if (line.startsWith('data:')) {
845
+ dataLines.push(line.slice(5).trim());
846
+ }
847
+ }
848
+ if (!eventType) {
849
+ return null;
850
+ }
851
+ try {
852
+ const data = dataLines.join('\n');
853
+ const parsed = data ? JSON.parse(data) : {};
854
+ return { type: eventType, ...parsed };
855
+ }
856
+ catch {
857
+ return null;
858
+ }
859
+ };
779
860
 
780
861
  const error = (message) => console.error(message);
781
862
  const init = (params) => {
@@ -795,8 +876,9 @@ var ProvidersEvoSearchSdk = (function () {
795
876
  error('EvoSearch SDK: Failed to set previewEvoSearch sessionStorage');
796
877
  }
797
878
  }
798
- const { enabled, uiPreferences } = (_c = (_b = params.manifest) === null || _b === void 0 ? void 0 : _b.variables) !== null && _c !== void 0 ? _c : {};
879
+ const { enabled, uiPreferences, askAi } = (_c = (_b = params.manifest) === null || _b === void 0 ? void 0 : _b.variables) !== null && _c !== void 0 ? _c : {};
799
880
  const mode = uiPreferences === null || uiPreferences === void 0 ? void 0 : uiPreferences.mode;
881
+ const askAiEnabled = (askAi === null || askAi === void 0 ? void 0 : askAi.enabled) === true;
800
882
  const hasPreview = hasPreviewKey();
801
883
  const shouldEnable = enabled || hasPreview;
802
884
  if (!shouldEnable) {
@@ -841,6 +923,7 @@ var ProvidersEvoSearchSdk = (function () {
841
923
  handpickedProducts: [],
842
924
  api: evoSearchAPI,
843
925
  uiPreferences,
926
+ askAiEnabled,
844
927
  });
845
928
  params.sendTag({
846
929
  eventId: crypto.randomUUID(),
package/core.mjs CHANGED
@@ -687,6 +687,10 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
687
687
  logger.info('EvoSearch API: Fetching trending searches');
688
688
  return getJSON('/trending', 'Trending', undefined, signal);
689
689
  };
690
+ const config = (signal) => {
691
+ logger.info('EvoSearch API: Fetching ask config');
692
+ return getJSON('/config', 'Config', undefined, signal);
693
+ };
690
694
  const recents = (signal) => {
691
695
  logger.info('EvoSearch API: Fetching recent searches');
692
696
  return getJSON('/user/recents', 'Recents', undefined, signal);
@@ -721,6 +725,58 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
721
725
  logger.info(`EvoSearch API: Search success - ${data.results.length} products`);
722
726
  return data;
723
727
  };
728
+ // SSE consumer for Ask AI. Decodes `event: <type>\ndata: <json>` frames
729
+ // from the worker stream and yields typed events as they arrive. Caller
730
+ // aborts via the AbortSignal — fetch teardown closes the underlying
731
+ // reader, the for-await loop drops out, and the iterator returns.
732
+ async function* askStream(query, signal) {
733
+ const url = getURL('/ask');
734
+ const response = await fetchImpl(url, {
735
+ method: 'POST',
736
+ headers: (() => {
737
+ const h = getHeaders();
738
+ h.set('Content-Type', 'application/json');
739
+ return h;
740
+ })(),
741
+ body: JSON.stringify({ query }),
742
+ credentials: 'omit',
743
+ signal,
744
+ });
745
+ if (!response.ok || !response.body) {
746
+ throw new Error(`Ask failed - ${response.status}`);
747
+ }
748
+ const reader = response.body
749
+ .pipeThrough(new TextDecoderStream())
750
+ .getReader();
751
+ let buffer = '';
752
+ try {
753
+ while (true) {
754
+ const { done, value } = await reader.read();
755
+ if (done) {
756
+ break;
757
+ }
758
+ buffer += value;
759
+ let boundary = buffer.indexOf('\n\n');
760
+ while (boundary !== -1) {
761
+ const frame = buffer.slice(0, boundary);
762
+ buffer = buffer.slice(boundary + 2);
763
+ const event = parseSseFrame(frame);
764
+ if (event) {
765
+ yield event;
766
+ }
767
+ boundary = buffer.indexOf('\n\n');
768
+ }
769
+ }
770
+ }
771
+ finally {
772
+ try {
773
+ reader.releaseLock();
774
+ }
775
+ catch {
776
+ // ignore
777
+ }
778
+ }
779
+ }
724
780
  const sendEvent = (action, currency, actionData, clickData, beacon) => {
725
781
  // Send event to backend /user/event endpoint for storage
726
782
  fetchImpl(getURL('/user/event'), {
@@ -764,15 +820,40 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
764
820
  return {
765
821
  autocomplete,
766
822
  trending,
823
+ config,
767
824
  user: {
768
825
  recents,
769
826
  deleteRecent,
770
827
  clearRecents,
771
828
  },
772
829
  search,
830
+ askStream,
773
831
  sendEvent,
774
832
  };
775
833
  };
834
+ const parseSseFrame = (frame) => {
835
+ let eventType = '';
836
+ const dataLines = [];
837
+ for (const line of frame.split('\n')) {
838
+ if (line.startsWith('event:')) {
839
+ eventType = line.slice(6).trim();
840
+ }
841
+ else if (line.startsWith('data:')) {
842
+ dataLines.push(line.slice(5).trim());
843
+ }
844
+ }
845
+ if (!eventType) {
846
+ return null;
847
+ }
848
+ try {
849
+ const data = dataLines.join('\n');
850
+ const parsed = data ? JSON.parse(data) : {};
851
+ return { type: eventType, ...parsed };
852
+ }
853
+ catch {
854
+ return null;
855
+ }
856
+ };
776
857
 
777
858
  const error = (message) => console.error(message);
778
859
  const init = (params) => {
@@ -792,8 +873,9 @@ const init = (params) => {
792
873
  error('EvoSearch SDK: Failed to set previewEvoSearch sessionStorage');
793
874
  }
794
875
  }
795
- const { enabled, uiPreferences } = (_c = (_b = params.manifest) === null || _b === void 0 ? void 0 : _b.variables) !== null && _c !== void 0 ? _c : {};
876
+ const { enabled, uiPreferences, askAi } = (_c = (_b = params.manifest) === null || _b === void 0 ? void 0 : _b.variables) !== null && _c !== void 0 ? _c : {};
796
877
  const mode = uiPreferences === null || uiPreferences === void 0 ? void 0 : uiPreferences.mode;
878
+ const askAiEnabled = (askAi === null || askAi === void 0 ? void 0 : askAi.enabled) === true;
797
879
  const hasPreview = hasPreviewKey();
798
880
  const shouldEnable = enabled || hasPreview;
799
881
  if (!shouldEnable) {
@@ -838,6 +920,7 @@ const init = (params) => {
838
920
  handpickedProducts: [],
839
921
  api: evoSearchAPI,
840
922
  uiPreferences,
923
+ askAiEnabled,
841
924
  });
842
925
  params.sendTag({
843
926
  eventId: crypto.randomUUID(),
package/hooks.cjs.js CHANGED
@@ -645,6 +645,7 @@ const mapAPIProductToSDKProduct = (apiProduct) => {
645
645
  handle,
646
646
  description: apiProduct.description,
647
647
  url: safeUrl,
648
+ inStock: apiProduct.inStock,
648
649
  image: {
649
650
  url: safeImg,
650
651
  alt: apiProduct.name,
package/hooks.d.ts CHANGED
@@ -70,12 +70,65 @@ declare module '@blotoutio/providers-evo-search-sdk/hooks' {
70
70
  recents: string[]
71
71
  }
72
72
 
73
+ export type AskConfigResponse = {
74
+ askAiEnabled: boolean
75
+ defaultView?: 'search' | 'ask'
76
+ askStarters?: string[]
77
+ }
78
+
73
79
  export type EvoSearchUserAPI = {
74
80
  recents: (signal?: AbortSignal) => Promise<RecentsAPIResponse>
75
81
  deleteRecent: (value: string) => Promise<void>
76
82
  clearRecents: () => Promise<void>
77
83
  }
78
84
 
85
+ export type AskEvidenceProduct = {
86
+ id: string
87
+ title: string
88
+ vendor?: string
89
+ price?: number
90
+ compareAtPrice?: number
91
+ inStock?: boolean
92
+ imageUrl?: string
93
+ topTags?: string[]
94
+ }
95
+
96
+ export type AskEvidenceCollection = {
97
+ id: string
98
+ title: string
99
+ productCount?: number
100
+ }
101
+
102
+ export type AskEvidence = {
103
+ products: AskEvidenceProduct[]
104
+ collections: AskEvidenceCollection[]
105
+ }
106
+
107
+ export type AskCitations = {
108
+ productIds: string[]
109
+ collectionIds: string[]
110
+ resolved: Array<{
111
+ type: 'product' | 'collection'
112
+ id: string
113
+ title: string
114
+ }>
115
+ }
116
+
117
+ export type AskTimings = {
118
+ evidenceMs?: number
119
+ firstTokenMs?: number
120
+ totalMs?: number
121
+ }
122
+
123
+ export type AskEvent =
124
+ | { type: 'mode'; degraded?: string; redirect?: string }
125
+ | { type: 'evidence'; evidence: AskEvidence }
126
+ | { type: 'answer-delta'; token: string }
127
+ | { type: 'citations'; citations: AskCitations }
128
+ | { type: 'follow-ups'; chips: string[] }
129
+ | { type: 'done'; timings: AskTimings }
130
+ | { type: 'error'; message: string }
131
+
79
132
  export type EvoSearchAPI = {
80
133
  autocomplete: (
81
134
  query: string,
@@ -91,7 +144,9 @@ declare module '@blotoutio/providers-evo-search-sdk/hooks' {
91
144
  trending: string[]
92
145
  recommended_products: AutocompleteAPIProduct[]
93
146
  }>
147
+ config: (signal?: AbortSignal) => Promise<AskConfigResponse>
94
148
  user: EvoSearchUserAPI
149
+ askStream: (query: string, signal?: AbortSignal) => AsyncIterable<AskEvent>
95
150
  sendEvent: (
96
151
  action:
97
152
  | 'evoSearchProductRecommendationClicked'
package/hooks.mjs CHANGED
@@ -643,6 +643,7 @@ const mapAPIProductToSDKProduct = (apiProduct) => {
643
643
  handle,
644
644
  description: apiProduct.description,
645
645
  url: safeUrl,
646
+ inStock: apiProduct.inStock,
646
647
  image: {
647
648
  url: safeImg,
648
649
  alt: apiProduct.name,