@nyris/nyris-webapp 0.3.88 → 0.3.90

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.
Files changed (70) hide show
  1. package/build/_headers +2 -0
  2. package/build/asset-manifest.json +6 -6
  3. package/build/index.html +1 -1
  4. package/build/js/settings.example.js +17 -0
  5. package/build/static/css/main.734b52e1.css +4 -0
  6. package/build/static/css/main.734b52e1.css.map +1 -0
  7. package/build/static/js/main.cede3ae1.js +3 -0
  8. package/build/static/js/{main.e861b336.js.map → main.cede3ae1.js.map} +1 -1
  9. package/package.json +3 -3
  10. package/public/_headers +2 -0
  11. package/public/index.html +1 -1
  12. package/public/js/settings.example.js +17 -0
  13. package/src/App.tsx +5 -3
  14. package/src/components/Cart.tsx +321 -0
  15. package/src/components/CustomCameraDrawer.tsx +4 -22
  16. package/src/components/DragDropFile.tsx +57 -38
  17. package/src/components/ExperienceVisualSearch/ExperienceVisualSearch.tsx +6 -1
  18. package/src/components/ExperienceVisualSearch/ExperienceVisualSearchTrigger.tsx +2 -2
  19. package/src/components/GroundingSpecs.tsx +47 -0
  20. package/src/components/Header.tsx +94 -93
  21. package/src/components/HitsPerPage.tsx +4 -2
  22. package/src/components/ImagePreview.tsx +64 -31
  23. package/src/components/ImageUpload.tsx +247 -0
  24. package/src/components/ItemSpecification.tsx +164 -0
  25. package/src/components/MatchNotificationBanner.tsx +165 -0
  26. package/src/components/PostFilter/PostFilter.tsx +22 -1
  27. package/src/components/PostFilter/PostFilterComponent.tsx +59 -26
  28. package/src/components/PostFilter/PostFilterFindApi.tsx +242 -0
  29. package/src/components/PoweredBy.tsx +16 -0
  30. package/src/components/PreFilter/PreFilter.tsx +77 -54
  31. package/src/components/Product/Product.tsx +186 -28
  32. package/src/components/Product/ProductAttribute.tsx +2 -2
  33. package/src/components/Product/ProductDetailView.tsx +123 -18
  34. package/src/components/Product/ProductDetailViewModal.tsx +3 -0
  35. package/src/components/Product/ProductList.tsx +78 -8
  36. package/src/components/SidePanel.tsx +212 -120
  37. package/src/components/TextSearch.tsx +82 -203
  38. package/src/components/Toaster.tsx +34 -15
  39. package/src/helpers/ToastHelper.ts +6 -2
  40. package/src/hooks/useCadSearch.ts +5 -0
  41. package/src/hooks/useImageSearch.ts +102 -13
  42. package/src/index.css +59 -0
  43. package/src/layouts/AppLayout.tsx +16 -14
  44. package/src/pages/Home.tsx +61 -13
  45. package/src/pages/Result.tsx +287 -295
  46. package/src/services/vizo.ts +161 -0
  47. package/src/stores/request/Misc/misc.initialstate.ts +1 -0
  48. package/src/stores/request/Misc/misc.slice.ts +1 -0
  49. package/src/stores/request/filter/filter.initialState.ts +3 -0
  50. package/src/stores/request/filter/filter.slice.ts +23 -0
  51. package/src/stores/result/prodcuts/products.initialState.ts +4 -0
  52. package/src/stores/result/prodcuts/products.slice.ts +15 -0
  53. package/src/stores/types.ts +27 -1
  54. package/src/stores/ui/loading/loading.initialState.ts +1 -0
  55. package/src/stores/ui/loading/loading.slice.ts +4 -0
  56. package/src/stores/ui/sidePanel/sidePanel.initialState.ts +5 -0
  57. package/src/stores/ui/sidePanel/sidePanel.slice.ts +11 -0
  58. package/src/stores/ui/uiStore.ts +4 -1
  59. package/src/styles/Cart.scss +210 -0
  60. package/src/styles/common.scss +10 -0
  61. package/src/translations.ts +4 -4
  62. package/src/types.ts +11 -3
  63. package/src/utils/prepareImageList.ts +6 -5
  64. package/src/utils/textSearchFilter.ts +203 -0
  65. package/tailwind.config.js +1 -0
  66. package/build/static/css/main.ba1c7479.css +0 -4
  67. package/build/static/css/main.ba1c7479.css.map +0 -1
  68. package/build/static/js/main.e861b336.js +0 -3
  69. package/src/components/Footer.tsx +0 -21
  70. /package/build/static/js/{main.e861b336.js.LICENSE.txt → main.cede3ae1.js.LICENSE.txt} +0 -0
@@ -19,6 +19,11 @@ import { GlobalWorkerOptions } from 'pdfjs-dist';
19
19
 
20
20
  // @ts-ignore
21
21
  import * as pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.mjs';
22
+ import {
23
+ groundedSearch,
24
+ groundedSearchFilter,
25
+ groundingSearchQuery,
26
+ } from 'services/vizo';
22
27
 
23
28
  GlobalWorkerOptions.workerSrc = new URL(
24
29
  pdfjsWorker,
@@ -33,6 +38,9 @@ export const useImageSearch = () => {
33
38
  const setSpecificationFilter = useRequestStore(
34
39
  state => state.setSpecificationFilter,
35
40
  );
41
+ const clearPostFilterSelections = useRequestStore(
42
+ state => state.clearPostFilterSelections,
43
+ );
36
44
 
37
45
  const setAlgoliaFilter = useRequestStore(state => state.setAlgoliaFilter);
38
46
  const preFilter = useRequestStore(state => state.preFilter);
@@ -62,6 +70,17 @@ export const useImageSearch = () => {
62
70
  state => state.setFirstSearchResults,
63
71
  );
64
72
 
73
+ const setGoogleGroundingResponse = useResultStore(
74
+ state => state.setGoogleGroundingResponse,
75
+ );
76
+ const setIsGoogleGroundingLoading = useUiStore(
77
+ state => state.setIsGoogleGroundingLoading,
78
+ );
79
+
80
+ const setGroundingFilterResult = useResultStore(
81
+ state => state.setGroundingFilterResult,
82
+ );
83
+
65
84
  const { refine } = useClearRefinements();
66
85
 
67
86
  const tiffToJpg = async (file: File): Promise<Blob> => {
@@ -189,6 +208,7 @@ export const useImageSearch = () => {
189
208
  preFilterParams,
190
209
  clearPostFilter,
191
210
  text,
211
+ grounding,
192
212
  }: {
193
213
  image?: any;
194
214
  settings: AppSettings;
@@ -199,10 +219,18 @@ export const useImageSearch = () => {
199
219
  preFilterParams?: Record<string, boolean>;
200
220
  clearPostFilter?: boolean;
201
221
  text?: string;
222
+ grounding?: boolean;
202
223
  }) => {
203
224
  setIsFindApiLoading(true);
204
225
  // setAlgoliaProducts([]);
205
226
 
227
+ if (newSearch) {
228
+ setGroundingFilterResult([]);
229
+ setGoogleGroundingResponse(null);
230
+ useRequestStore.getState().setGroundingQuery('');
231
+ useResultStore.getState().setShowingGroundingFilterResult(false);
232
+ }
233
+
206
234
  let region: RectCoords | undefined = imageRegion;
207
235
  let res: any;
208
236
  let compressedBase64;
@@ -276,23 +304,25 @@ export const useImageSearch = () => {
276
304
  filters: !isEmpty(preFilterParams || preFilter)
277
305
  ? preFilterValues
278
306
  : metaFilter
279
- ? [
280
- {
281
- key: settings.visualSearchFilterKey,
282
- values: [metaFilter],
283
- },
284
- ]
285
- : undefined,
307
+ ? [
308
+ {
309
+ key: settings.visualSearchFilterKey,
310
+ values: [metaFilter],
311
+ },
312
+ ]
313
+ : undefined,
286
314
  region,
287
- text: !window.settings.algolia.enabled
288
- ? isUndefined(text)
289
- ? query
290
- : text
291
- : undefined,
315
+ text:
316
+ !window.settings.algolia.enabled && !requestImage
317
+ ? isUndefined(text)
318
+ ? query
319
+ : text
320
+ : undefined,
292
321
  });
293
322
 
294
323
  if (clearPostFilter) {
295
324
  refine();
325
+ clearPostFilterSelections();
296
326
  }
297
327
 
298
328
  setFindApiProducts(res?.results);
@@ -300,6 +330,61 @@ export const useImageSearch = () => {
300
330
  setSessionId(res?.session);
301
331
  setRequestId(res?.id);
302
332
 
333
+ const specification = res?.image_analysis?.specification || {};
334
+ const hasOcr = Object.keys(specification).length > 0;
335
+
336
+ if (hasOcr && newSearch && window.settings.vizo?.groundingEnabled) {
337
+ // call grounding API with ocr text
338
+ setIsGoogleGroundingLoading(true);
339
+
340
+ groundedSearch(
341
+ requestImage || canvasImage,
342
+ window.settings.aiApiKey || '',
343
+ )
344
+ .then(groundingRes => {
345
+ const groundingSpecs = {
346
+ product_name: groundingRes.product_name,
347
+ brand: groundingRes.brand,
348
+ part_number: groundingRes.part_number,
349
+ key_specs: groundingRes.key_specs,
350
+ };
351
+
352
+ groundedSearchFilter({
353
+ products: res?.results || [],
354
+ groundingSummary: groundingRes?.grounding_summary || '',
355
+ apiKey: window.settings.aiApiKey || '',
356
+ })
357
+ .then((filterRes: any) => {
358
+ setGroundingFilterResult(filterRes?.matches || []);
359
+
360
+ if (
361
+ filterRes?.matches?.length === 0 &&
362
+ window.settings.vizo?.fallbackToElasticSearch
363
+ ) {
364
+ groundingSearchQuery({
365
+ groundingSummary: JSON.stringify(groundingRes),
366
+ apiKey: window.settings.aiApiKey || '',
367
+ }).then((queryRes: any) => {
368
+ useRequestStore
369
+ .getState()
370
+ .setGroundingQuery(queryRes?.query);
371
+ });
372
+ }
373
+ })
374
+ .catch(error => {
375
+ console.log('Grounding Filter API error:', error);
376
+ });
377
+
378
+ setGoogleGroundingResponse(groundingSpecs);
379
+ })
380
+ .catch(error => {
381
+ console.log('Grounding API error:', error);
382
+ })
383
+ .finally(() => {
384
+ setIsGoogleGroundingLoading(false);
385
+ });
386
+ }
387
+
303
388
  const nonEmptyFilter: any[] = ['sku:DOES_NOT_EXIST<score=1> '];
304
389
  const filterSkus: any = res?.results
305
390
  ? res?.results
@@ -336,6 +421,7 @@ export const useImageSearch = () => {
336
421
  setDetectedRegions,
337
422
  setRegions,
338
423
  metaFilter,
424
+ query,
339
425
  setFindApiProducts,
340
426
  setImageAnalysis,
341
427
  setSessionId,
@@ -343,12 +429,15 @@ export const useImageSearch = () => {
343
429
  setAlgoliaFilter,
344
430
  firstSearchResults.length,
345
431
  refine,
432
+ clearPostFilterSelections,
433
+ setIsGoogleGroundingLoading,
434
+ setGoogleGroundingResponse,
435
+ setGroundingFilterResult,
346
436
  setShowFeedback,
347
437
  setFirstSearchResults,
348
438
  setFirstSearchImage,
349
439
  setFirstSearchPreFilter,
350
440
  setFirstRequestImageAnalysis,
351
- query,
352
441
  ],
353
442
  );
354
443
 
package/src/index.css CHANGED
@@ -55,6 +55,65 @@ body {
55
55
  font-family: 'Source Sans 3', sans-serif !important;
56
56
  }
57
57
 
58
+ /* Side panel scrollbar: visible on hover only (Facebook-like). */
59
+ .sidepanel-scroll {
60
+ scrollbar-width: thin;
61
+ scrollbar-color: transparent transparent;
62
+ scrollbar-gutter: stable;
63
+ position: relative;
64
+ clip-path: inset(0 round 1px);
65
+ }
66
+
67
+ .sidepanel-scroll:hover {
68
+ scrollbar-color: #c3c6cf transparent;
69
+ }
70
+
71
+ .sidepanel-scroll::-webkit-scrollbar {
72
+ width: 8px;
73
+ }
74
+
75
+ .sidepanel-scroll:hover::-webkit-scrollbar {
76
+ width: 8px;
77
+ }
78
+
79
+ .sidepanel-scroll::-webkit-scrollbar-track {
80
+ background: transparent;
81
+ margin: 16px 0;
82
+ }
83
+
84
+ .sidepanel-scroll::-webkit-scrollbar-thumb {
85
+ background: transparent;
86
+ border-radius: 999px;
87
+ border: 2px solid transparent;
88
+ background-clip: padding-box;
89
+ }
90
+
91
+ .sidepanel-scroll:hover::-webkit-scrollbar-thumb {
92
+ background: #c3c6cf;
93
+ }
94
+
95
+ .sidepanel-scroll::before,
96
+ .sidepanel-scroll::after {
97
+ content: "";
98
+ position: absolute;
99
+ right: 0;
100
+ width: 8px;
101
+ height: 16px;
102
+ background: #fff;
103
+ pointer-events: none;
104
+ z-index: 1;
105
+ }
106
+
107
+ .sidepanel-scroll::before {
108
+ top: 0;
109
+ border-top-right-radius: 32px;
110
+ }
111
+
112
+ .sidepanel-scroll::after {
113
+ bottom: 0;
114
+ border-bottom-right-radius: 32px;
115
+ }
116
+
58
117
  a,
59
118
  abbr,
60
119
  acronym,
@@ -8,7 +8,7 @@ import { initReactI18next } from 'react-i18next';
8
8
 
9
9
  import '../styles/common.scss';
10
10
 
11
- import Footer from 'components/Footer';
11
+ import PoweredBy from 'components/PoweredBy';
12
12
  import Header from 'components/Header';
13
13
  import { translations } from 'translations';
14
14
 
@@ -97,26 +97,26 @@ function AppLayout(): JSX.Element {
97
97
  }
98
98
 
99
99
  return (
100
- <div className="full-height flex flex-col">
100
+ <div className="full-height flex flex-col ">
101
101
  <div>
102
102
  <Toaster />
103
103
  </div>
104
104
  <Header />
105
105
  {window.settings?.algolia.enabled && (
106
106
  <Configure
107
- query={!query && specifications && !requestImages.length ? '*' : query}
107
+ query={query}
108
108
  filters={
109
109
  specifications && !requestImages.length
110
- ? specifications.prefilter_value ? `${alogoliaFilterField}:'${specifications.prefilter_value}'` : ''
110
+ ? `${alogoliaFilterField}:'${specifications.prefilter_value}'`
111
111
  : !query && !algoliaFilter.includes('score=1')
112
- ? undefined
113
- : `${algoliaFilter}${
114
- metaFilter
115
- ? `${
116
- algoliaFilter ? 'AND ' : ''
117
- }${alogoliaFilterField}:'${metaFilter}'`
118
- : ''
119
- }`
112
+ ? undefined
113
+ : `${algoliaFilter}${
114
+ metaFilter
115
+ ? `${
116
+ algoliaFilter ? 'AND ' : ''
117
+ }${alogoliaFilterField}:'${metaFilter}'`
118
+ : ''
119
+ }`
120
120
  }
121
121
  // facets={['brand', 'keyword_0']}
122
122
  hitsPerPage={20}
@@ -127,9 +127,11 @@ function AppLayout(): JSX.Element {
127
127
  <Outlet />
128
128
  </div>
129
129
  {showPoweredByNyris && (
130
- <Footer
130
+ <PoweredBy
131
131
  className={
132
- location.pathname === '/result' ? 'hidden desktop:flex' : 'flex'
132
+ location.pathname === '/result'
133
+ ? 'hidden'
134
+ : 'flex w-full justify-center items-center mb-2'
133
135
  }
134
136
  />
135
137
  )}
@@ -1,5 +1,5 @@
1
- import React, { useEffect, useState } from 'react';
2
-
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { useAuth0 } from '@auth0/auth0-react';
3
3
  import { twMerge } from 'tailwind-merge';
4
4
 
5
5
  import DragDropFile from 'components/DragDropFile';
@@ -8,19 +8,50 @@ import TextSearch from 'components/TextSearch';
8
8
  import CustomCamera from 'components/CustomCameraDrawer';
9
9
  import ExperienceVisualSearchTrigger from 'components/ExperienceVisualSearch/ExperienceVisualSearchTrigger';
10
10
  import { useNavigate } from 'react-router';
11
- import LocationInfoPopup from "../components/LocationInfoPopup";
11
+ import LocationInfoPopup from '../components/LocationInfoPopup';
12
12
  import Hint from '../components/Hint';
13
13
  import Loading from '../components/Loading';
14
14
  import useRequestStore from '../stores/request/requestStore';
15
+ import { getFilters } from '../services/filter';
16
+
15
17
  function Home() {
16
18
  const settings = window.settings;
17
- const { experienceVisualSearch, experienceVisualSearchImages, geoLocation } = settings;
19
+ const user = useAuth0().user;
20
+ const { experienceVisualSearch, experienceVisualSearchImages, geoLocation } =
21
+ settings;
18
22
  const navigate = useNavigate();
19
23
 
20
24
  const [experienceVisualSearchBlobs, setExperienceVisualSearchBlobs] =
21
25
  useState<Blob[]>([]);
22
26
  const [isOpenModalCamera, setOpenModalCamera] = useState<boolean>(false);
23
27
  const showLoading = useRequestStore(store => store.showLoading);
28
+ const setPreFilterList = useRequestStore(state => state.setPreFilterList);
29
+ const setPreFilterLoading = useRequestStore(
30
+ state => state.setPreFilterLoading,
31
+ );
32
+ const setMetaFilter = useRequestStore(state => state.setMetaFilter);
33
+
34
+ const showPreFilter = useMemo(() => {
35
+ if (settings.shouldUseUserMetadata && user) {
36
+ if (user['/user_metadata'].value) {
37
+ setMetaFilter(user['/user_metadata'].value);
38
+ }
39
+ }
40
+
41
+ if (settings.shouldUseUserMetadata && user) {
42
+ if (settings.preFilterOption && !user['/user_metadata'].value) {
43
+ return true;
44
+ }
45
+ return false;
46
+ }
47
+
48
+ return settings.preFilterOption;
49
+ }, [
50
+ setMetaFilter,
51
+ settings.preFilterOption,
52
+ settings.shouldUseUserMetadata,
53
+ user,
54
+ ]);
24
55
 
25
56
  const fetchImage = async (url: string) => {
26
57
  const response = await fetch(url, { cache: 'force-cache' });
@@ -67,6 +98,24 @@ function Home() {
67
98
  // eslint-disable-next-line react-hooks/exhaustive-deps
68
99
  }, []);
69
100
 
101
+ const getPreFilters = useCallback(async () => {
102
+ setPreFilterLoading(true);
103
+ try {
104
+ const res = await getFilters(1000, settings);
105
+ setPreFilterList(res);
106
+ } catch (e: any) {
107
+ console.log('err getDataFilterDesktop', e);
108
+ } finally {
109
+ setPreFilterLoading(false);
110
+ }
111
+ }, [settings, setPreFilterList, setPreFilterLoading]);
112
+
113
+ useEffect(() => {
114
+ if (showPreFilter) {
115
+ getPreFilters();
116
+ }
117
+ }, [getPreFilters, showPreFilter]);
118
+
70
119
  return (
71
120
  <>
72
121
  {showLoading && (
@@ -82,12 +131,11 @@ function Home() {
82
131
  'items-center',
83
132
  'justify-center',
84
133
  'h-auto',
85
- 'mt-[calc(50vh-75px)]',
86
- 'bg-[#fafafa]',
134
+ 'mt-[calc(50vh-110px)]',
87
135
  ])}
88
136
  >
89
137
  {geoLocation && <LocationInfoPopup />}
90
- <div className="relative flex flex-col items-center justify-center w-full">
138
+ <div className="relative flex items-center justify-center w-full gap-2">
91
139
  {settings.headerText && (
92
140
  <div
93
141
  className={twMerge([
@@ -101,9 +149,8 @@ function Home() {
101
149
  </div>
102
150
  )}
103
151
 
104
- <div className="w-[427px]">
105
- <TextSearch />
106
- </div>
152
+ <TextSearch />
153
+ {/* <ImageUpload /> */}
107
154
  </div>
108
155
  <div className="max-w-[532px] relative w-full">
109
156
  <DragDropFile />
@@ -115,7 +162,7 @@ function Home() {
115
162
  </div>
116
163
  </div>
117
164
 
118
- <div className="flex flex-col desktop:hidden justify-center items-center w-full h-full bg-white">
165
+ <div className="flex flex-col desktop:hidden justify-center items-center w-full h-full ">
119
166
  <MobileCameraCTA setOpenModalCamera={setOpenModalCamera} />
120
167
  <div className="box-screenshot-camera">
121
168
  <CustomCamera
@@ -141,8 +188,9 @@ function Home() {
141
188
  />
142
189
  )}
143
190
 
144
- <div className="flex desktop:hidden w-full">
145
- <TextSearch className="flex md:hidden fixed bottom-12 w-full px-2 gap-2" />
191
+ <div className="flex desktop:hidden w-full fixed bottom-12 px-2 gap-2">
192
+ <TextSearch className="flex md:hidden w-full " />
193
+ {/* <ImageUpload /> */}
146
194
  </div>
147
195
  </div>
148
196
  </>