@nyris/nyris-webapp 0.3.92 → 0.3.94

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 (33) hide show
  1. package/build/asset-manifest.json +6 -6
  2. package/build/index.html +1 -1
  3. package/build/js/settings.example.js +1 -0
  4. package/build/static/css/main.5fff4ca0.css +4 -0
  5. package/build/static/css/main.5fff4ca0.css.map +1 -0
  6. package/build/static/js/{main.36b77705.js → main.09201b03.js} +3 -3
  7. package/build/static/js/{main.36b77705.js.map → main.09201b03.js.map} +1 -1
  8. package/package.json +3 -3
  9. package/public/js/settings.example.js +1 -0
  10. package/src/components/ChatAssistant/useChatAssistantLogic.ts +1 -1
  11. package/src/components/CustomCameraDrawer.tsx +5 -5
  12. package/src/components/DragDropFile.tsx +4 -4
  13. package/src/components/ExperienceVisualSearch/ExperienceVisualSearchModal.tsx +1 -1
  14. package/src/components/ExperienceVisualSearch/ExperienceVisualSearchTrigger.tsx +1 -1
  15. package/src/components/IdentifiedAttributes.tsx +159 -0
  16. package/src/components/ImagePreview.tsx +4 -4
  17. package/src/components/ImageUpload.tsx +5 -5
  18. package/src/components/MobileCameraCTA.tsx +1 -1
  19. package/src/components/Rfq/RfqBanner.tsx +3 -4
  20. package/src/components/SidePanel.tsx +55 -10
  21. package/src/components/SmartFilter.tsx +70 -42
  22. package/src/hooks/useGoodResultsChat.ts +1 -1
  23. package/src/hooks/useImageSearch.ts +8 -12
  24. package/src/hooks/useResultEvaluator.ts +5 -4
  25. package/src/pages/Home.tsx +2 -2
  26. package/src/pages/Result.tsx +58 -5
  27. package/src/services/vizo.ts +0 -6
  28. package/src/styles/common.scss +1 -0
  29. package/src/styles/experienceVisualSearch.scss +2 -0
  30. package/src/types.ts +2 -1
  31. package/build/static/css/main.5ea01690.css +0 -4
  32. package/build/static/css/main.5ea01690.css.map +0 -1
  33. /package/build/static/js/{main.36b77705.js.LICENSE.txt → main.09201b03.js.LICENSE.txt} +0 -0
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@nyris/nyris-webapp",
3
- "version": "0.3.92",
3
+ "version": "0.3.94",
4
4
  "dependencies": {
5
5
  "@auth0/auth0-react": "^2.2.4",
6
6
  "@emailjs/browser": "^4.3.3",
7
7
  "@google/genai": "^1.46.0",
8
- "@nyris/nyris-api": "^0.3.92",
9
- "@nyris/nyris-react-components": "^0.3.92",
8
+ "@nyris/nyris-api": "^0.3.94",
9
+ "@nyris/nyris-react-components": "^0.3.94",
10
10
  "@radix-ui/react-accordion": "^1.2.2",
11
11
  "@radix-ui/react-dialog": "^1.1.4",
12
12
  "@radix-ui/react-popover": "^1.1.4",
@@ -110,6 +110,7 @@ var settings = {
110
110
  geoLocation: false,
111
111
  geoLocationMessage: '',
112
112
  showImageDetails: false,
113
+ supportButtonLabel: 'Request a Quote',
113
114
  rfq: {
114
115
  enabled: '',
115
116
  emailTemplateId: '',
@@ -682,7 +682,7 @@ Each result includes a unique SKU in brackets. Use the result thumbnails and met
682
682
 
683
683
  const tableSuggestions = lookupRelatedParts(
684
684
  table,
685
- imageAnalysis.specification.product_category,
685
+ imageAnalysis?.specification?.product_category,
686
686
  );
687
687
 
688
688
  if (tableSuggestions && tableSuggestions.length > 0) {
@@ -95,7 +95,7 @@ function CustomCamera(props: Props) {
95
95
  showFeedback: true,
96
96
  }).then(singleImageResp => {
97
97
  const specificationPrefilter =
98
- singleImageResp.image_analysis?.specification?.prefilter_value || null;
98
+ singleImageResp?.image_analysis?.specification?.prefilter_value || null;
99
99
 
100
100
  const hasPrefilter = preFilterList.filter((filter: any) =>
101
101
  filter.values.includes(specificationPrefilter),
@@ -105,15 +105,15 @@ function CustomCamera(props: Props) {
105
105
  setShowNotMatchedError(false);
106
106
  if (hasPrefilter.length) {
107
107
  setSpecifications(
108
- clone(singleImageResp.image_analysis.specification),
108
+ clone(singleImageResp?.image_analysis?.specification),
109
109
  );
110
110
  setNameplateImage(image);
111
111
  setPreFilter({
112
- [singleImageResp.image_analysis?.specification?.prefilter_value]:
112
+ [singleImageResp?.image_analysis?.specification?.prefilter_value]:
113
113
  true,
114
114
  });
115
115
  setAlgoliaFilter(
116
- `${settings.alogoliaFilterField}:'${singleImageResp.image_analysis?.specification?.prefilter_value}'`,
116
+ `${settings.alogoliaFilterField}:'${singleImageResp?.image_analysis?.specification?.prefilter_value}'`,
117
117
  );
118
118
 
119
119
  setShowLoading(false);
@@ -133,7 +133,7 @@ function CustomCamera(props: Props) {
133
133
  if (!hasPrefilter.length && window.settings.preFilterOption) {
134
134
  setSpecifications(
135
135
  clone({
136
- ...singleImageResp.image_analysis.specification,
136
+ ...singleImageResp?.image_analysis?.specification,
137
137
  specificationPrefilter,
138
138
  }),
139
139
  );
@@ -70,15 +70,15 @@ function DragDropFile(props: Props) {
70
70
  setShowNotMatchedError(false);
71
71
  if (hasPrefilter.length) {
72
72
  setSpecifications(
73
- clone(singleImageResp.image_analysis.specification),
73
+ clone(singleImageResp?.image_analysis?.specification),
74
74
  );
75
75
  setNameplateImage(file);
76
76
  setPreFilter({
77
- [singleImageResp.image_analysis?.specification?.prefilter_value]:
77
+ [singleImageResp?.image_analysis?.specification?.prefilter_value]:
78
78
  true,
79
79
  });
80
80
  setAlgoliaFilter(
81
- `${window.settings.alogoliaFilterField}:'${singleImageResp.image_analysis?.specification?.prefilter_value}'`,
81
+ `${window.settings.alogoliaFilterField}:'${singleImageResp?.image_analysis?.specification?.prefilter_value}'`,
82
82
  );
83
83
 
84
84
  setShowLoading(false);
@@ -100,7 +100,7 @@ function DragDropFile(props: Props) {
100
100
  if (!hasPrefilter.length && window.settings.preFilterOption) {
101
101
  setSpecifications(
102
102
  clone({
103
- ...singleImageResp.image_analysis.specification,
103
+ ...singleImageResp?.image_analysis?.specification,
104
104
  specificationPrefilter,
105
105
  }),
106
106
  );
@@ -23,7 +23,7 @@ function ExperienceVisualSearchModal(props: Props) {
23
23
  return (
24
24
  <Dialog open={openModal} onOpenChange={handleClose}>
25
25
  <DialogContent
26
- className="bg-white p-0 max-w-[1500px] w-fit max-h-[95vh] h-fit m-auto overflow-y-hidden rounded-lg"
26
+ className="bg-white p-0 max-w-[1500px] w-fit max-h-[95vh] m-auto overflow-y-hidden rounded-lg"
27
27
  closeButton={false}
28
28
  >
29
29
  <DialogTitle className="h-0 w-0 hidden">
@@ -46,7 +46,7 @@ function ExperienceVisualSearchTrigger({
46
46
 
47
47
  return (
48
48
  <>
49
- <div className="flex flex-col items-center">
49
+ <div className={`flex flex-col items-center ${window.settings.experienceVisualSearch ? 'mb-[80px]' : ''}`}>
50
50
  <div
51
51
  className={`group bg-[#3E36DC] w-10 hover:w-[215px] hover:gap-2 ${
52
52
  expand ? 'w-[215px] gap-2' : ''
@@ -0,0 +1,159 @@
1
+ import { useRefinementList } from 'react-instantsearch';
2
+ import Tooltip from './Tooltip/TooltipComponent';
3
+ import { twMerge } from 'tailwind-merge';
4
+ import useRequestStore from '../stores/request/requestStore';
5
+ import useResultStore from '../stores/result/resultStore';
6
+ import { Icon } from '@nyris/nyris-react-components';
7
+ import { useMediaQuery } from 'react-responsive';
8
+
9
+ const IdentifiedAttributes = ({
10
+ attr,
11
+ value,
12
+ specificationFilter,
13
+ imageAnalysis,
14
+ }: any) => {
15
+ const refinement = window.settings.refinements.filter((ref: any) => {
16
+ return ref.header?.toLocaleLowerCase() === attr?.toLocaleLowerCase();
17
+ });
18
+ const attribute = refinement?.length ? refinement[0].attribute : 'none';
19
+ const isMobile = useMediaQuery({ query: '(max-width: 776px)' });
20
+ const { refine } = useRefinementList({
21
+ attribute,
22
+ operator: 'or',
23
+ showMore: false,
24
+ limit: 1,
25
+ });
26
+
27
+ return isMobile ? (
28
+ <>
29
+ <div className="inline-flex justify-center items-start gap-2 " key={attr}>
30
+ <div className="pl-1 inline-flex justify-center items-center gap-2.5">
31
+ <div className="justify-start text-[#3B3E5F] text-sm font-semibold">
32
+ {attr}
33
+ </div>
34
+ </div>
35
+ <div
36
+ className={twMerge(
37
+ `p-1 bg-[#e4e3ff] rounded-lg inline-flex justify-center items-center gap-1.5`,
38
+ 'text-[#3e36dc]',
39
+ specificationFilter[attr]
40
+ ? 'border-[#3E36DC] bg-[#3E36DC] text-white'
41
+ : '',
42
+ )}
43
+ onClick={() => {
44
+ if (!value) {
45
+ return;
46
+ }
47
+ if (attribute !== 'none') {
48
+ refine(value);
49
+ } else {
50
+ const setSpecificationFilter =
51
+ useRequestStore.getState().setSpecificationFilter;
52
+
53
+ const setSpecificationFilteredProducts =
54
+ useResultStore.getState().setSpecificationFilteredProducts;
55
+
56
+ if (specificationFilter[attr]) {
57
+ setSpecificationFilter({});
58
+ setSpecificationFilteredProducts([]);
59
+ // setProducts(results);
60
+ } else {
61
+ setSpecificationFilter({
62
+ [attr]: value,
63
+ });
64
+ }
65
+ }
66
+ }}
67
+ >
68
+ <div className="justify-start items-center text-sm font-medium leading-none flex gap-2">
69
+ {imageAnalysis?.specification[attr] || 'N/A'}
70
+ <Icon
71
+ name="close"
72
+ className={twMerge(
73
+ 'w-3 h-3 text-white',
74
+ specificationFilter[attr] ? 'block' : 'hidden',
75
+ )}
76
+ />
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </>
81
+ ) : (
82
+ <div key={attr} className="flex justify-between w-full gap-2 items-center">
83
+ <div className="self-stretch inline-flex justify-start items-center gap-1.5">
84
+ <div className="justify-start text-[#3B3E5F] text-xs font-semibold">
85
+ {attr}:
86
+ </div>
87
+ <Tooltip
88
+ content={
89
+ specificationFilter[attr]
90
+ ? 'Filter applied. Clear to choose a different value.'
91
+ : 'Click to apply as a search filter.'
92
+ }
93
+ delayDuration={1000}
94
+ disabled={!value}
95
+ >
96
+ <div
97
+ className={twMerge(
98
+ `px-1 py-1 bg-[#e4e3ff] rounded-[1px] flex justify-center items-center gap-1.5`,
99
+ 'border border-solid border-transparent hover:border-[#3E36DC]',
100
+ 'cursor-pointer',
101
+ specificationFilter[attr] ? 'border-[#3E36DC] bg-[#3E36DC] ' : '',
102
+ )}
103
+ onClick={() => {
104
+ if (!value) {
105
+ return;
106
+ }
107
+ if (attribute !== 'none') {
108
+ refine(value);
109
+ } else {
110
+ const setSpecificationFilter =
111
+ useRequestStore.getState().setSpecificationFilter;
112
+
113
+ const setSpecificationFilteredProducts =
114
+ useResultStore.getState().setSpecificationFilteredProducts;
115
+
116
+ if (specificationFilter[attr]) {
117
+ setSpecificationFilter({});
118
+ setSpecificationFilteredProducts([]);
119
+ // setProducts(results);
120
+ } else {
121
+ setSpecificationFilter({
122
+ [attr]: value,
123
+ });
124
+ }
125
+ }
126
+ }}
127
+ >
128
+ <div
129
+ className={twMerge(
130
+ 'justify-start text-[#3e36dc] text-[10px] leading-none px-0.5',
131
+ 'font-normal hover:font-bold hover:px-0',
132
+ specificationFilter[attr]
133
+ ? 'font-bold text-white hover:px-0.5'
134
+ : '',
135
+ 'max-line-1',
136
+ )}
137
+ >
138
+ {imageAnalysis?.specification[attr] || 'N/A'}
139
+ </div>
140
+ </div>
141
+ </Tooltip>
142
+ </div>
143
+ <div
144
+ onClick={() => {
145
+ navigator.clipboard.writeText(
146
+ imageAnalysis?.specification[attr] || '',
147
+ );
148
+ }}
149
+ >
150
+ <Icon
151
+ name="copy"
152
+ className="text-[#AAABB5] w-[12px] h-[12px] hover:text-[#3E36DC] cursor-pointer"
153
+ />
154
+ </div>
155
+ </div>
156
+ );
157
+ };
158
+
159
+ export default IdentifiedAttributes;
@@ -116,13 +116,13 @@ function ImagePreviewComponent({
116
116
  setRequestImages([]);
117
117
  setShowNotMatchedError(false);
118
118
  if (hasPrefilter.length) {
119
- setSpecifications(clone(res.image_analysis.specification));
119
+ setSpecifications(clone(res?.image_analysis?.specification));
120
120
  setNameplateImage(image);
121
121
  setPreFilter({
122
- [res.image_analysis?.specification?.prefilter_value]: true,
122
+ [res?.image_analysis?.specification?.prefilter_value]: true,
123
123
  });
124
124
  setAlgoliaFilter(
125
- `${settings.alogoliaFilterField}:'${res.image_analysis?.specification?.prefilter_value}'`,
125
+ `${settings.alogoliaFilterField}:'${res?.image_analysis?.specification?.prefilter_value}'`,
126
126
  );
127
127
 
128
128
  setShowLoading(false);
@@ -144,7 +144,7 @@ function ImagePreviewComponent({
144
144
  if (!hasPrefilter.length && window.settings.preFilterOption) {
145
145
  setSpecifications(
146
146
  clone({
147
- ...res.image_analysis.specification,
147
+ ...res?.image_analysis?.specification,
148
148
  specificationPrefilter,
149
149
  }),
150
150
  );
@@ -103,7 +103,7 @@ function ImageUpload({
103
103
  clearPostFilter: true,
104
104
  }).then(singleImageResp => {
105
105
  const specificationPrefilter =
106
- singleImageResp.image_analysis?.specification?.prefilter_value || null;
106
+ singleImageResp?.image_analysis?.specification?.prefilter_value || null;
107
107
  const hasPrefilter = preFilterList.filter((filter: any) =>
108
108
  filter.values.includes(specificationPrefilter),
109
109
  );
@@ -112,15 +112,15 @@ function ImageUpload({
112
112
  setShowNotMatchedError(false);
113
113
  if (hasPrefilter.length) {
114
114
  setSpecifications(
115
- clone(singleImageResp.image_analysis.specification),
115
+ clone(singleImageResp?.image_analysis?.specification),
116
116
  );
117
117
  setNameplateImage(files[0]);
118
118
  setPreFilter({
119
- [singleImageResp.image_analysis?.specification?.prefilter_value]:
119
+ [singleImageResp?.image_analysis?.specification?.prefilter_value]:
120
120
  true,
121
121
  });
122
122
  setAlgoliaFilter(
123
- `${settings.alogoliaFilterField}:'${singleImageResp.image_analysis?.specification?.prefilter_value}'`,
123
+ `${settings.alogoliaFilterField}:'${singleImageResp?.image_analysis?.specification?.prefilter_value}'`,
124
124
  );
125
125
 
126
126
  setShowLoading(false);
@@ -142,7 +142,7 @@ function ImageUpload({
142
142
  if (!hasPrefilter.length && showPreFilter) {
143
143
  setSpecifications(
144
144
  clone({
145
- ...singleImageResp.image_analysis.specification,
145
+ ...singleImageResp?.image_analysis?.specification,
146
146
  specificationPrefilter,
147
147
  }),
148
148
  );
@@ -4,7 +4,7 @@ import '../styles/mobileCameraCTA.scss';
4
4
 
5
5
  function MobileCameraCTA({ setOpenModalCamera }: { setOpenModalCamera: any }) {
6
6
  return (
7
- <div className="take-photo">
7
+ <div className={`take-photo ${window.settings.experienceVisualSearch ? 'mt-[180px]' : ''}`}>
8
8
  <div
9
9
  className="take-photo-wrapper"
10
10
  >
@@ -29,7 +29,6 @@ function RfqBanner({
29
29
  <div
30
30
  style={{
31
31
  padding: '0px 16px 0px 16px',
32
- backgroundColor: '#F6F3F1',
33
32
  width: '100%',
34
33
  alignSelf: 'end',
35
34
  height: '248px',
@@ -72,9 +71,9 @@ function RfqBanner({
72
71
  fontWeight: 'bold',
73
72
  }}
74
73
  >
75
- <p className="desktop:hidden">{`${t('No matches found')}?`}</p>
74
+ <p className="desktop:hidden">{`${t('No matches found')}`}?</p>
76
75
  <p className="hidden desktop:block">
77
- `${t('No matches found for your request')}?`
76
+ {`${t('No matches found for your request')}`}?
78
77
  </p>
79
78
  </div>
80
79
  <div
@@ -112,7 +111,7 @@ function RfqBanner({
112
111
  setIsRfqModalOpen(true);
113
112
  }}
114
113
  >
115
- Request a Quote
114
+ {window.settings.supportButtonLabel}
116
115
  </button>
117
116
  </div>
118
117
  </div>
@@ -8,6 +8,7 @@ import useUiStore from 'stores/ui/uiStore';
8
8
  import GroundingSpecs from './GroundingSpecs';
9
9
  import SmartFilter from './SmartFilter';
10
10
  import { useEffectiveGroundingResults } from 'hooks/useEffectiveGroundingResults';
11
+ import IdentifiedAttributes from './IdentifiedAttributes';
11
12
 
12
13
  export default function SidePanel({ className }: { className?: string }) {
13
14
  const settings = window.settings;
@@ -23,7 +24,6 @@ export default function SidePanel({ className }: { className?: string }) {
23
24
  const showSidePanel = useUiStore(state => state.showSidePanel);
24
25
 
25
26
  const imageAnalysis = useResultStore(state => state.imageAnalysis);
26
- const smartFilters = useResultStore(state => state.smartFilters);
27
27
 
28
28
  const showPostFilter = window.settings?.postFilterOption;
29
29
 
@@ -104,18 +104,63 @@ export default function SidePanel({ className }: { className?: string }) {
104
104
  {requestImages[0] && <ImagePreview />}
105
105
  </div>
106
106
  <div className="pb-4 flex flex-col gap-6">
107
- {isSmartFiltersEnabled &&
108
- window.settings.showImageDetails &&
107
+ {window.settings.showImageDetails &&
109
108
  (imageAnalysis?.imageDescription ||
110
- Object.keys(imageAnalysis?.specification || {}).length > 0 ||
111
- smartFilters.status !== 'idle') && (
112
- <SmartFilter
113
- imageAnalysis={imageAnalysis}
114
- specificationFilter={specificationFilter}
115
- variant="sidepanel"
116
- />
109
+ Object.keys(imageAnalysis?.specification || {}).length > 0) && (
110
+ <div className="self-stretch p-3.5 bg-[#F3F4F8] rounded-3xl inline-flex flex-col justify-start items-start gap-1.5 mt-4 mx-4 ">
111
+ {imageAnalysis?.imageDescription !==
112
+ 'No description available' && (
113
+ <div className="self-stretch flex flex-col justify-start items-start">
114
+ <div className="justify-start text-[#3B3E5F] text-base font-semibold">
115
+ Image description
116
+ </div>
117
+ <div className="self-stretch justify-start text-[#3B3E5F] text-xs font-normal">
118
+ {imageAnalysis?.imageDescription || ''}
119
+ </div>
120
+ </div>
121
+ )}
122
+
123
+ {Object.keys(imageAnalysis?.specification || {})
124
+ .filter(
125
+ key =>
126
+ key !== 'is_nameplate' && key !== 'prefilter_value',
127
+ )
128
+ .some(key => !!imageAnalysis?.specification[key]) && (
129
+ <div className="justify-start text-[#3B3E5F] text-base font-semibold mt-2">
130
+ Identified Attributes
131
+ </div>
132
+ )}
133
+
134
+ {Object.keys(imageAnalysis?.specification || {})
135
+ .filter(
136
+ key =>
137
+ key !== 'is_nameplate' && key !== 'prefilter_value',
138
+ )
139
+ .map(key => {
140
+ const value = imageAnalysis?.specification[key];
141
+ if (!value) {
142
+ return null;
143
+ }
144
+ return (
145
+ <IdentifiedAttributes
146
+ attr={key}
147
+ value={value}
148
+ specificationFilter={specificationFilter}
149
+ imageAnalysis={imageAnalysis}
150
+ />
151
+ );
152
+ })}
153
+ </div>
117
154
  )}
118
155
 
156
+ {isSmartFiltersEnabled && (
157
+ <SmartFilter
158
+ imageAnalysis={imageAnalysis}
159
+ specificationFilter={specificationFilter}
160
+ variant="sidepanel"
161
+ />
162
+ )}
163
+
119
164
  {(isGoogleGroundingLoading ||
120
165
  googleGroundingResponse ||
121
166
  groundingError) && (