@nyris/nyris-webapp 0.3.89 → 0.3.91

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.f2255597.js +3 -0
  8. package/build/static/js/{main.ca8b95bc.js.map → main.f2255597.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.ca8b95bc.js +0 -3
  69. package/src/components/Footer.tsx +0 -21
  70. /package/build/static/js/{main.ca8b95bc.js.LICENSE.txt → main.f2255597.js.LICENSE.txt} +0 -0
@@ -21,7 +21,12 @@ function ExperienceVisualSearch({
21
21
  const { singleImageSearch } = useImageSearch();
22
22
 
23
23
  const initiateVisualSearch = async (blob: string) => {
24
- singleImageSearch({ image: blob, settings }).then(() => {});
24
+ singleImageSearch({
25
+ image: blob,
26
+ settings,
27
+ clearPostFilter: true,
28
+ newSearch: true,
29
+ }).then(() => {});
25
30
  navigate('/result');
26
31
  };
27
32
 
@@ -48,8 +48,8 @@ function ExperienceVisualSearchTrigger({
48
48
  <>
49
49
  <div className="flex flex-col items-center">
50
50
  <div
51
- className={`group bg-[#3E36DC] w-[215px] desktop:w-10 hover:w-[215px] hover:gap-2 ${
52
- expand ? 'desktop:w-[215px] gap-2' : ''
51
+ className={`group bg-[#3E36DC] w-10 hover:w-[215px] hover:gap-2 ${
52
+ expand ? 'w-[215px] gap-2' : ''
53
53
  } h-10 flex flex-row justify-center items-center rounded-full cursor-pointer my-8 transition-all duration-300`}
54
54
  onClick={() => modalToggle(true)}
55
55
  >
@@ -0,0 +1,47 @@
1
+ import React, { useEffect } from 'react';
2
+
3
+ interface GroundingSpec {
4
+ label: string;
5
+ value: string;
6
+ }
7
+
8
+ const GroundingSpecs = ({ data }: { data: any }) => {
9
+ // Mapping the complex JSON object to the flat GroundingSpec interface
10
+ const [specs, setSpecs] = React.useState<GroundingSpec[]>([]);
11
+
12
+ useEffect(() => {
13
+ const newSpecs: GroundingSpec[] = [];
14
+ if (data.product_name) {
15
+ newSpecs.push({ label: 'Product Name', value: data.product_name });
16
+ }
17
+ if (data.brand) {
18
+ newSpecs.push({ label: 'Brand', value: data.brand });
19
+ }
20
+ if (data.category) {
21
+ newSpecs.push({ label: 'Category', value: data.category });
22
+ }
23
+ // Dynamically mapping the nested key_specs
24
+ Object.entries(data.key_specs || {}).forEach(([key, val]) => {
25
+ newSpecs.push({ label: key, value: String(val) });
26
+ });
27
+ setSpecs(newSpecs);
28
+ }, [data]);
29
+
30
+ return (
31
+ <div className="flex flex-col text-[10px] leading-4">
32
+ {specs.map((spec, index) => (
33
+ <div
34
+ key={index}
35
+ className={`flex items-center gap-2 py-[3px] pl-2 rounded-sm ${
36
+ index % 2 === 0 ? 'bg-[#F3F4F8]' : 'bg-white'
37
+ }`}
38
+ >
39
+ <div className="w-1/3 text-[#3B3E5F] font-semibold">{spec.label}</div>
40
+ <div className="w-2/3 text-[#3B3E5F]">{spec.value}</div>
41
+ </div>
42
+ ))}
43
+ </div>
44
+ );
45
+ };
46
+
47
+ export default GroundingSpecs;
@@ -11,6 +11,7 @@ import { useState } from 'react';
11
11
  import LogoutModal from './LogoutModal';
12
12
  import { Popover, PopoverContent, PopoverTrigger } from './popover';
13
13
  import useResultStore from 'stores/result/resultStore';
14
+ import Cart from './Cart';
14
15
 
15
16
  function Header() {
16
17
  const { theme, auth0 } = window.settings;
@@ -42,109 +43,109 @@ function Header() {
42
43
  style={{ background: theme?.headerColor }}
43
44
  >
44
45
  <div
45
- className={twMerge([
46
- 'w-full',
47
- 'flex',
48
- 'justify-center',
49
- 'items-center',
50
- 'relative',
51
- 'h-full',
52
- ])}
46
+ className={twMerge(['w-full', 'flex', 'items-center', 'h-full'])}
47
+ style={{ display: 'flex', alignItems: 'center', height: '100%' }}
53
48
  >
54
- <NavLink
55
- to="/"
56
- style={{ lineHeight: 0, position: 'absolute', left: 0 }}
57
- onClick={() => {
58
- reset();
59
- resetResultStore();
60
- setSpecifications(null);
61
- }}
49
+ {/* Left: Logo */}
50
+ <div
51
+ className="flex items-center justify-start min-w-0"
52
+ style={{ flex: '0 0 auto' }}
62
53
  >
63
- <img
64
- src={theme?.appBarLogoUrl}
65
- alt="logo"
66
- style={{
67
- aspectRatio: 1,
68
- width: theme?.logoWidth,
69
- height: theme?.logoHeight,
54
+ <NavLink
55
+ to="/"
56
+ style={{ lineHeight: 0 }}
57
+ onClick={() => {
58
+ reset();
59
+ resetResultStore();
60
+ setSpecifications(null);
70
61
  }}
71
- />
72
- </NavLink>
73
-
74
- <div>
62
+ >
63
+ <img
64
+ src={theme?.appBarLogoUrl}
65
+ alt="logo"
66
+ style={{
67
+ aspectRatio: 1,
68
+ width: theme?.logoWidth,
69
+ height: theme?.logoHeight,
70
+ }}
71
+ />
72
+ </NavLink>
73
+ </div>
74
+ {/* Center: Search bar */}
75
+ <div className="flex-1 flex justify-center items-center min-w-0">
75
76
  <div
76
77
  className={twMerge(['hidden', showSearchBar && 'desktop:block'])}
77
- style={{
78
- // position: 'relative',
79
- // left: '50%',
80
- // top: '50%',
81
- // transform: 'translate(-50%, -50%)',
82
- }}
83
78
  >
84
79
  <TextSearch />
85
80
  </div>
86
81
  </div>
82
+ {/* Right: Cart and user actions */}
83
+ <div
84
+ className="flex items-center justify-end min-w-0 gap-4"
85
+ style={{ flex: '0 0 auto' }}
86
+ >
87
+ {window.settings.cart && <Cart />}
87
88
 
88
- {auth0.enabled && isAuthenticated && (
89
- <>
90
- <div
91
- className="hidden desktop:block"
92
- style={{ position: 'relative' }}
93
- >
94
- <Popover>
95
- <PopoverTrigger>
96
- <div
97
- style={{
98
- display: 'flex',
99
- columnGap: '16px',
100
- alignItems: 'center',
101
- cursor: 'pointer',
102
- }}
103
- >
104
- <p style={{ color: '#2B2C46' }}>{user?.email}</p>
105
- <Icon name="avatar" />
106
- </div>
107
- </PopoverTrigger>
108
- <PopoverContent className="w-[152px] bg-white p-2 shadow-outer">
109
- <div
110
- className={twMerge([
111
- 'flex',
112
- 'w-[75px]',
113
- 'h-[24px]',
114
- 'px-2',
115
- 'items-center',
116
- 'bg-[#2B2C46]',
117
- 'text-white',
118
- 'text-[10px]',
119
- 'cursor-pointer',
120
- ])}
121
- onClick={() => {
122
- logout({
123
- logoutParams: { returnTo: window.location.origin },
124
- });
125
- }}
126
- >
127
- Sign out
128
- </div>
129
- </PopoverContent>
130
- </Popover>
131
- </div>
132
-
133
- <div
134
- className="block desktop:hidden cursor-pointer"
135
- onClick={() => {
136
- setShowLogoutModal(true);
137
- }}
138
- >
139
- <Icon
140
- name="logout"
141
- className="text-[#AAABB5]"
142
- width={24}
143
- height={24}
144
- />
145
- </div>
146
- </>
147
- )}
89
+ {auth0.enabled && isAuthenticated && (
90
+ <>
91
+ <div
92
+ className="hidden desktop:block"
93
+ style={{ position: 'relative' }}
94
+ >
95
+ <Popover>
96
+ <PopoverTrigger>
97
+ <div
98
+ style={{
99
+ display: 'flex',
100
+ columnGap: '16px',
101
+ alignItems: 'center',
102
+ cursor: 'pointer',
103
+ }}
104
+ >
105
+ <p style={{ color: '#2B2C46' }}>{user?.email}</p>
106
+ <Icon name="avatar" />
107
+ </div>
108
+ </PopoverTrigger>
109
+ <PopoverContent className="w-[152px] bg-white p-2 shadow-outer">
110
+ <div
111
+ className={twMerge([
112
+ 'flex',
113
+ 'w-[75px]',
114
+ 'h-[24px]',
115
+ 'px-2',
116
+ 'items-center',
117
+ 'bg-[#2B2C46]',
118
+ 'text-white',
119
+ 'text-[10px]',
120
+ 'cursor-pointer',
121
+ ])}
122
+ onClick={() => {
123
+ logout({
124
+ logoutParams: { returnTo: window.location.origin },
125
+ });
126
+ }}
127
+ >
128
+ Sign out
129
+ </div>
130
+ </PopoverContent>
131
+ </Popover>
132
+ </div>
133
+ <div
134
+ className="block desktop:hidden cursor-pointer"
135
+ onClick={() => {
136
+ setShowLogoutModal(true);
137
+ }}
138
+ >
139
+ <Icon
140
+ name="logout"
141
+ className="text-[#AAABB5]"
142
+ width={24}
143
+ height={24}
144
+ />
145
+ </div>
146
+ </>
147
+ )}
148
+ </div>
148
149
  </div>
149
150
  <LogoutModal
150
151
  setShowLogoutModal={setShowLogoutModal}
@@ -1,5 +1,6 @@
1
1
  import { useTranslation } from 'react-i18next';
2
2
  import { useHitsPerPage, UseHitsPerPageProps } from 'react-instantsearch';
3
+ import PoweredBy from './PoweredBy';
3
4
 
4
5
  export const HitsPerPage = (props: UseHitsPerPageProps) => {
5
6
  const { items, refine } = useHitsPerPage(props);
@@ -10,8 +11,8 @@ export const HitsPerPage = (props: UseHitsPerPageProps) => {
10
11
 
11
12
  return (
12
13
  <>
13
- <div className="w-full min-h-12 bg-[#f3f3f5] border-t border-solid border-[#e0e0e0] justify-start items-center hidden desktop:inline-flex">
14
- <div className="self-stretch pl-4 pr-6 border-r border-solid border-[#e0e0e0] justify-center items-center gap-1 flex">
14
+ <div className="w-full px-4 h-10 bg-white border border-solid border-[#DDDEE7] items-center hidden desktop:inline-flex justify-between">
15
+ <div className="self-stretch pr-6 border-r border-solid border-[#DDDEE7] justify-center items-center gap-1 flex">
15
16
  <div className="text-[#2b2c46] text-[13px] font-normal font-['Source Sans 3'] leading-none tracking-tight">
16
17
  {t('Items per page')}:
17
18
  </div>
@@ -29,6 +30,7 @@ export const HitsPerPage = (props: UseHitsPerPageProps) => {
29
30
  ))}
30
31
  </select>
31
32
  </div>
33
+ {window.settings.showPoweredByNyris && <PoweredBy />}
32
34
  </div>
33
35
  </>
34
36
  );
@@ -1,6 +1,6 @@
1
- import React, {useCallback, useEffect, useRef, useState} from 'react';
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
2
 
3
- import {clone, debounce} from 'lodash';
3
+ import { clone, debounce } from 'lodash';
4
4
  import { twMerge } from 'tailwind-merge';
5
5
  import { useNavigate } from 'react-router';
6
6
  import { useTranslation } from 'react-i18next';
@@ -12,7 +12,6 @@ import { DEFAULT_REGION } from '../constants';
12
12
  import { useImageSearch } from 'hooks/useImageSearch';
13
13
  import useRequestStore from 'stores/request/requestStore';
14
14
  import useResultStore from 'stores/result/resultStore';
15
- import { getFilters } from '../services/filter';
16
15
 
17
16
  function ImagePreviewComponent({
18
17
  showAdjustInfo = false,
@@ -24,13 +23,13 @@ function ImagePreviewComponent({
24
23
  }) {
25
24
  const [showAdjustInfoBasedOnConfidence, setShowAdjustInfoBasedOnConfidence] =
26
25
  useState(false);
27
- const [resultFilter, setResultFilter] = useState<any>([]);
28
26
 
29
27
  const { t } = useTranslation();
30
28
  const navigate = useNavigate();
31
29
  const settings = window.settings;
32
30
  const isMultiImageSearchEnabled = settings.multiImageSearch;
33
31
 
32
+ const preFilterList = useRequestStore(state => state.preFilterList);
34
33
  const requestImages = useRequestStore(state => state.requestImages);
35
34
  const specifications = useRequestStore(state => state.specifications);
36
35
  const resetRegions = useRequestStore(state => state.resetRegions);
@@ -40,11 +39,15 @@ function ImagePreviewComponent({
40
39
  const resetRequestStore = useRequestStore(state => state.reset);
41
40
  const setSpecifications = useRequestStore(state => state.setSpecifications);
42
41
  const setShowLoading = useRequestStore(state => state.setShowLoading);
43
- const setNameplateNotificationText = useRequestStore(state => state.setNameplateNotificationText);
42
+ const setNameplateNotificationText = useRequestStore(
43
+ state => state.setNameplateNotificationText,
44
+ );
44
45
  const setAlgoliaFilter = useRequestStore(state => state.setAlgoliaFilter);
45
46
  const setPreFilter = useRequestStore(state => state.setPreFilter);
46
47
  const setNameplateImage = useRequestStore(state => state.setNameplateImage);
47
- const setShowNotMatchedError = useRequestStore(state => state.setShowNotMatchedError);
48
+ const setShowNotMatchedError = useRequestStore(
49
+ state => state.setShowNotMatchedError,
50
+ );
48
51
 
49
52
  const detectedRegions = useResultStore(state => state.detectedRegions);
50
53
  const resetResultStore = useResultStore(state => state.reset);
@@ -55,21 +58,31 @@ function ImagePreviewComponent({
55
58
 
56
59
  const [isVisible] = useState(true);
57
60
  const [zIndex] = useState<number>(0);
61
+ const [previewWidth, setPreviewWidth] = useState(0);
58
62
 
59
63
  const previewWrapperRef = useRef<any>(null);
60
64
 
61
- const getPreFilters = async () => {
62
- getFilters(1000, settings)
63
- .then(res => {
64
- setResultFilter(res);
65
- })
66
- .catch((e: any) => {
67
- console.log('err getDataFilterDesktop', e);
68
- });
69
- };
70
-
71
65
  useEffect(() => {
72
- getPreFilters()
66
+ const container = previewWrapperRef.current as HTMLDivElement | null;
67
+ if (!container) {
68
+ return;
69
+ }
70
+
71
+ const updateWidth = () => {
72
+ const nextWidth = Math.round(container.getBoundingClientRect().width);
73
+ setPreviewWidth(prevWidth =>
74
+ prevWidth === nextWidth ? prevWidth : nextWidth,
75
+ );
76
+ };
77
+
78
+ updateWidth();
79
+
80
+ const resizeObserver = new ResizeObserver(updateWidth);
81
+ resizeObserver.observe(container);
82
+
83
+ return () => {
84
+ resizeObserver.disconnect();
85
+ };
73
86
  }, []);
74
87
 
75
88
  const onImageRemove = () => {
@@ -94,48 +107,66 @@ function ImagePreviewComponent({
94
107
  showFeedback: true,
95
108
  compress: false,
96
109
  }).then((res: any) => {
97
- const specificationPrefilter = res.image_analysis?.specification?.prefilter_value || null;
98
- const hasPrefilter = resultFilter.filter((filter: any) => filter.values.includes(specificationPrefilter));
110
+ const specificationPrefilter =
111
+ res.image_analysis?.specification?.prefilter_value || null;
112
+ const hasPrefilter = preFilterList.filter((filter: any) =>
113
+ filter.values.includes(specificationPrefilter),
114
+ );
99
115
  if (specificationPrefilter) {
100
116
  setRequestImages([]);
101
117
  setShowNotMatchedError(false);
102
118
  if (hasPrefilter.length) {
103
119
  setSpecifications(clone(res.image_analysis.specification));
104
120
  setNameplateImage(image);
105
- setPreFilter({[res.image_analysis?.specification?.prefilter_value]: true});
106
- setAlgoliaFilter(`${settings.alogoliaFilterField}:'${res.image_analysis?.specification?.prefilter_value}'`);
121
+ setPreFilter({
122
+ [res.image_analysis?.specification?.prefilter_value]: true,
123
+ });
124
+ setAlgoliaFilter(
125
+ `${settings.alogoliaFilterField}:'${res.image_analysis?.specification?.prefilter_value}'`,
126
+ );
107
127
 
108
128
  setShowLoading(false);
109
129
  navigate('/result');
110
130
 
111
131
  setTimeout(() => {
112
- setNameplateNotificationText(t('We have successfully defined the search criteria', {
113
- prefilter_value: specificationPrefilter,
114
- preFilterTitle: window.settings.preFilterTitle?.toLocaleLowerCase()
115
- }));
132
+ setNameplateNotificationText(
133
+ t('We have successfully defined the search criteria', {
134
+ prefilter_value: specificationPrefilter,
135
+ preFilterTitle:
136
+ window.settings.preFilterTitle?.toLocaleLowerCase(),
137
+ }),
138
+ );
116
139
  }, 1000);
117
140
  setTimeout(() => {
118
141
  setNameplateNotificationText('');
119
142
  }, 6000);
120
143
  }
121
144
  if (!hasPrefilter.length && window.settings.preFilterOption) {
122
- setSpecifications(clone({...res.image_analysis.specification, prefilter_value: '', specificationPrefilter}));
145
+ setSpecifications(
146
+ clone({
147
+ ...res.image_analysis.specification,
148
+ specificationPrefilter,
149
+ }),
150
+ );
123
151
  setPreFilter({});
124
152
  setAlgoliaFilter('');
125
153
  setShowLoading(false);
126
154
  setShowNotMatchedError(true);
127
155
  setTimeout(() => {
128
- setNameplateNotificationText(t('Extracted details from the nameplate could not be matched', { preFilterTitle: window.settings.preFilterTitle?.toLocaleLowerCase() }));
156
+ setNameplateNotificationText(
157
+ t('Extracted details from the nameplate could not be matched', {
158
+ preFilterTitle:
159
+ window.settings.preFilterTitle?.toLocaleLowerCase(),
160
+ }),
161
+ );
129
162
  }, 1000);
130
163
  setTimeout(() => {
131
164
  setNameplateNotificationText('');
132
165
  }, 6000);
133
166
  }
134
167
  } else {
135
- if (specifications?.is_nameplate) {
136
- setSpecifications({...specifications, prefilter_value: '', specificationPrefilter: ''});
137
- } else {
138
- setSpecifications({...specifications, is_nameplate: false});
168
+ if (!specifications?.is_nameplate) {
169
+ setSpecifications({ ...specifications, is_nameplate: false });
139
170
  }
140
171
  const highConfidence = res.results.find(
141
172
  (data: { score: number }) => data.score >= 0.65,
@@ -173,6 +204,7 @@ function ImagePreviewComponent({
173
204
  'justify-center',
174
205
  isVisible ? (!isMultiImageSearchEnabled ? 'py-5' : 'pt-6') : '',
175
206
  'px-7',
207
+ 'box-border',
176
208
  'w-full',
177
209
  'desktop:px-5',
178
210
  'relative',
@@ -202,6 +234,7 @@ function ImagePreviewComponent({
202
234
  ])}
203
235
  >
204
236
  <Preview
237
+ key={previewWidth}
205
238
  onSelectionChange={(r: RectCoords) => {
206
239
  debouncedOnImageSelectionChange(r, currentIndex);
207
240
  }}