@nyris/nyris-webapp 0.3.93 → 0.3.95
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/build/asset-manifest.json +6 -6
- package/build/index.html +1 -1
- package/build/js/settings.example.js +1 -0
- package/build/static/css/main.5fff4ca0.css +4 -0
- package/build/static/css/main.5fff4ca0.css.map +1 -0
- package/build/static/js/{main.c9bbbcb9.js → main.b80e9533.js} +3 -3
- package/build/static/js/{main.c9bbbcb9.js.map → main.b80e9533.js.map} +1 -1
- package/package.json +3 -3
- package/public/js/settings.example.js +1 -0
- package/src/components/ChatAssistant/useChatAssistantLogic.ts +1 -1
- package/src/components/CustomCameraDrawer.tsx +5 -5
- package/src/components/DragDropFile.tsx +4 -4
- package/src/components/ExperienceVisualSearch/ExperienceVisualSearchModal.tsx +1 -1
- package/src/components/ExperienceVisualSearch/ExperienceVisualSearchTrigger.tsx +1 -1
- package/src/components/ImagePreview.tsx +4 -4
- package/src/components/ImageUpload.tsx +6 -110
- package/src/components/MobileCameraCTA.tsx +1 -1
- package/src/components/Rfq/RfqBanner.tsx +3 -4
- package/src/components/SidePanel.tsx +8 -12
- package/src/components/SmartFilter.tsx +70 -42
- package/src/components/Toaster.tsx +3 -2
- package/src/hooks/useGoodResultsChat.ts +1 -1
- package/src/hooks/useImageSearch.ts +4 -8
- package/src/hooks/useResultEvaluator.ts +5 -4
- package/src/hooks/useUploadSearch.ts +170 -0
- package/src/layouts/AppLayout.tsx +70 -0
- package/src/pages/Home.tsx +2 -2
- package/src/pages/Result.tsx +9 -11
- package/src/services/vizo.ts +0 -6
- package/src/styles/common.scss +1 -0
- package/src/styles/experienceVisualSearch.scss +2 -0
- package/src/translations.ts +33 -18
- package/src/types.ts +1 -0
- package/build/static/css/main.fed38059.css +0 -4
- package/build/static/css/main.fed38059.css.map +0 -1
- /package/build/static/js/{main.c9bbbcb9.js.LICENSE.txt → main.b80e9533.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.
|
|
3
|
+
"version": "0.3.95",
|
|
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.
|
|
9
|
-
"@nyris/nyris-react-components": "^0.3.
|
|
8
|
+
"@nyris/nyris-api": "^0.3.95",
|
|
9
|
+
"@nyris/nyris-react-components": "^0.3.95",
|
|
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",
|
|
@@ -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
|
|
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
|
|
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
|
|
108
|
+
clone(singleImageResp?.image_analysis?.specification),
|
|
109
109
|
);
|
|
110
110
|
setNameplateImage(image);
|
|
111
111
|
setPreFilter({
|
|
112
|
-
[singleImageResp
|
|
112
|
+
[singleImageResp?.image_analysis?.specification?.prefilter_value]:
|
|
113
113
|
true,
|
|
114
114
|
});
|
|
115
115
|
setAlgoliaFilter(
|
|
116
|
-
`${settings.alogoliaFilterField}:'${singleImageResp
|
|
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
|
|
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
|
|
73
|
+
clone(singleImageResp?.image_analysis?.specification),
|
|
74
74
|
);
|
|
75
75
|
setNameplateImage(file);
|
|
76
76
|
setPreFilter({
|
|
77
|
-
[singleImageResp
|
|
77
|
+
[singleImageResp?.image_analysis?.specification?.prefilter_value]:
|
|
78
78
|
true,
|
|
79
79
|
});
|
|
80
80
|
setAlgoliaFilter(
|
|
81
|
-
`${window.settings.alogoliaFilterField}:'${singleImageResp
|
|
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
|
|
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]
|
|
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=
|
|
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' : ''
|
|
@@ -116,13 +116,13 @@ function ImagePreviewComponent({
|
|
|
116
116
|
setRequestImages([]);
|
|
117
117
|
setShowNotMatchedError(false);
|
|
118
118
|
if (hasPrefilter.length) {
|
|
119
|
-
setSpecifications(clone(res
|
|
119
|
+
setSpecifications(clone(res?.image_analysis?.specification));
|
|
120
120
|
setNameplateImage(image);
|
|
121
121
|
setPreFilter({
|
|
122
|
-
[res
|
|
122
|
+
[res?.image_analysis?.specification?.prefilter_value]: true,
|
|
123
123
|
});
|
|
124
124
|
setAlgoliaFilter(
|
|
125
|
-
`${settings.alogoliaFilterField}:'${res
|
|
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
|
|
147
|
+
...res?.image_analysis?.specification,
|
|
148
148
|
specificationPrefilter,
|
|
149
149
|
}),
|
|
150
150
|
);
|
|
@@ -4,13 +4,10 @@ import { twMerge } from 'tailwind-merge';
|
|
|
4
4
|
import Tooltip from './Tooltip/TooltipComponent';
|
|
5
5
|
import { useAuth0 } from '@auth0/auth0-react';
|
|
6
6
|
import { useMemo, useState } from 'react';
|
|
7
|
-
import { useLocation
|
|
7
|
+
import { useLocation } from 'react-router';
|
|
8
8
|
import useRequestStore from 'stores/request/requestStore';
|
|
9
|
-
import { useImageSearch } from 'hooks/useImageSearch';
|
|
10
|
-
import { clone } from 'lodash';
|
|
11
|
-
import { useCadSearch } from 'hooks/useCadSearch';
|
|
12
|
-
import { isCadFile } from '@nyris/nyris-api';
|
|
13
9
|
import UploadDisclaimer from './UploadDisclaimer';
|
|
10
|
+
import { useUploadSearch } from 'hooks/useUploadSearch';
|
|
14
11
|
|
|
15
12
|
function ImageUpload({
|
|
16
13
|
onCameraClick,
|
|
@@ -25,27 +22,10 @@ function ImageUpload({
|
|
|
25
22
|
const { t } = useTranslation();
|
|
26
23
|
|
|
27
24
|
const location = useLocation();
|
|
28
|
-
const
|
|
25
|
+
const { uploadFile } = useUploadSearch();
|
|
29
26
|
|
|
30
|
-
const preFilterList = useRequestStore(state => state.preFilterList);
|
|
31
27
|
const requestImages = useRequestStore(state => state.requestImages);
|
|
32
|
-
const setQuery = useRequestStore(state => state.setQuery);
|
|
33
|
-
const setValueInput = useRequestStore(state => state.setValueInput);
|
|
34
28
|
const setMetaFilter = useRequestStore(state => state.setMetaFilter);
|
|
35
|
-
const specifications = useRequestStore(state => state.specifications);
|
|
36
|
-
|
|
37
|
-
const setRequestImages = useRequestStore(state => state.setRequestImages);
|
|
38
|
-
const setSpecifications = useRequestStore(state => state.setSpecifications);
|
|
39
|
-
const setShowLoading = useRequestStore(state => state.setShowLoading);
|
|
40
|
-
const setNameplateNotificationText = useRequestStore(
|
|
41
|
-
state => state.setNameplateNotificationText,
|
|
42
|
-
);
|
|
43
|
-
const setShowNotMatchedError = useRequestStore(
|
|
44
|
-
state => state.setShowNotMatchedError,
|
|
45
|
-
);
|
|
46
|
-
const setAlgoliaFilter = useRequestStore(state => state.setAlgoliaFilter);
|
|
47
|
-
const setPreFilter = useRequestStore(state => state.setPreFilter);
|
|
48
|
-
const setNameplateImage = useRequestStore(state => state.setNameplateImage);
|
|
49
29
|
|
|
50
30
|
const [showDisclaimer, setShowDisclaimer] = useState(false);
|
|
51
31
|
|
|
@@ -80,97 +60,13 @@ function ImageUpload({
|
|
|
80
60
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
81
61
|
}, [showDisclaimer, requestImages, disableDisclaimer]);
|
|
82
62
|
|
|
83
|
-
const { singleImageSearch } = useImageSearch();
|
|
84
|
-
const { cadSearch } = useCadSearch();
|
|
85
|
-
|
|
86
63
|
const handleUpload = (files: File[]) => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (isCadFile(files[0])) {
|
|
91
|
-
cadSearch({ file: files[0], settings, newSearch: true }).then(() => {});
|
|
92
|
-
|
|
64
|
+
const file = files[0];
|
|
65
|
+
if (!file) {
|
|
93
66
|
return;
|
|
94
67
|
}
|
|
95
68
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
singleImageSearch({
|
|
99
|
-
image: files[0],
|
|
100
|
-
settings: window.settings,
|
|
101
|
-
showFeedback: true,
|
|
102
|
-
newSearch: true,
|
|
103
|
-
clearPostFilter: true,
|
|
104
|
-
}).then(singleImageResp => {
|
|
105
|
-
const specificationPrefilter =
|
|
106
|
-
singleImageResp.image_analysis?.specification?.prefilter_value || null;
|
|
107
|
-
const hasPrefilter = preFilterList.filter((filter: any) =>
|
|
108
|
-
filter.values.includes(specificationPrefilter),
|
|
109
|
-
);
|
|
110
|
-
if (specificationPrefilter) {
|
|
111
|
-
setRequestImages([]);
|
|
112
|
-
setShowNotMatchedError(false);
|
|
113
|
-
if (hasPrefilter.length) {
|
|
114
|
-
setSpecifications(
|
|
115
|
-
clone(singleImageResp.image_analysis.specification),
|
|
116
|
-
);
|
|
117
|
-
setNameplateImage(files[0]);
|
|
118
|
-
setPreFilter({
|
|
119
|
-
[singleImageResp.image_analysis?.specification?.prefilter_value]:
|
|
120
|
-
true,
|
|
121
|
-
});
|
|
122
|
-
setAlgoliaFilter(
|
|
123
|
-
`${settings.alogoliaFilterField}:'${singleImageResp.image_analysis?.specification?.prefilter_value}'`,
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
setShowLoading(false);
|
|
127
|
-
navigate('/result');
|
|
128
|
-
|
|
129
|
-
setTimeout(() => {
|
|
130
|
-
setNameplateNotificationText(
|
|
131
|
-
t('We have successfully defined the search criteria', {
|
|
132
|
-
prefilter_value: specificationPrefilter,
|
|
133
|
-
preFilterTitle:
|
|
134
|
-
window.settings.preFilterTitle?.toLocaleLowerCase(),
|
|
135
|
-
}),
|
|
136
|
-
);
|
|
137
|
-
}, 1000);
|
|
138
|
-
setTimeout(() => {
|
|
139
|
-
setNameplateNotificationText('');
|
|
140
|
-
}, 10000);
|
|
141
|
-
}
|
|
142
|
-
if (!hasPrefilter.length && showPreFilter) {
|
|
143
|
-
setSpecifications(
|
|
144
|
-
clone({
|
|
145
|
-
...singleImageResp.image_analysis.specification,
|
|
146
|
-
specificationPrefilter,
|
|
147
|
-
}),
|
|
148
|
-
);
|
|
149
|
-
navigate('/result');
|
|
150
|
-
setPreFilter({});
|
|
151
|
-
setAlgoliaFilter('');
|
|
152
|
-
setShowLoading(false);
|
|
153
|
-
setShowNotMatchedError(true);
|
|
154
|
-
setTimeout(() => {
|
|
155
|
-
setNameplateNotificationText(
|
|
156
|
-
t('Extracted details from the nameplate could not be matched', {
|
|
157
|
-
preFilterTitle:
|
|
158
|
-
window.settings.preFilterTitle?.toLocaleLowerCase(),
|
|
159
|
-
}),
|
|
160
|
-
);
|
|
161
|
-
}, 1000);
|
|
162
|
-
setTimeout(() => {
|
|
163
|
-
setNameplateNotificationText('');
|
|
164
|
-
}, 15000);
|
|
165
|
-
}
|
|
166
|
-
} else {
|
|
167
|
-
if (!specifications?.is_nameplate) {
|
|
168
|
-
setSpecifications({ ...specifications, is_nameplate: false });
|
|
169
|
-
}
|
|
170
|
-
setShowLoading(false);
|
|
171
|
-
navigate('/result');
|
|
172
|
-
}
|
|
173
|
-
});
|
|
69
|
+
uploadFile(file, { showPreFilter });
|
|
174
70
|
};
|
|
175
71
|
|
|
176
72
|
return (
|
|
@@ -4,7 +4,7 @@ import '../styles/mobileCameraCTA.scss';
|
|
|
4
4
|
|
|
5
5
|
function MobileCameraCTA({ setOpenModalCamera }: { setOpenModalCamera: any }) {
|
|
6
6
|
return (
|
|
7
|
-
<div className=
|
|
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')}
|
|
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
|
-
|
|
114
|
+
{window.settings.supportButtonLabel}
|
|
116
115
|
</button>
|
|
117
116
|
</div>
|
|
118
117
|
</div>
|
|
@@ -24,7 +24,6 @@ export default function SidePanel({ className }: { className?: string }) {
|
|
|
24
24
|
const showSidePanel = useUiStore(state => state.showSidePanel);
|
|
25
25
|
|
|
26
26
|
const imageAnalysis = useResultStore(state => state.imageAnalysis);
|
|
27
|
-
const smartFilters = useResultStore(state => state.smartFilters);
|
|
28
27
|
|
|
29
28
|
const showPostFilter = window.settings?.postFilterOption;
|
|
30
29
|
|
|
@@ -105,17 +104,6 @@ export default function SidePanel({ className }: { className?: string }) {
|
|
|
105
104
|
{requestImages[0] && <ImagePreview />}
|
|
106
105
|
</div>
|
|
107
106
|
<div className="pb-4 flex flex-col gap-6">
|
|
108
|
-
{isSmartFiltersEnabled &&
|
|
109
|
-
(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
|
-
/>
|
|
117
|
-
)}
|
|
118
|
-
|
|
119
107
|
{window.settings.showImageDetails &&
|
|
120
108
|
(imageAnalysis?.imageDescription ||
|
|
121
109
|
Object.keys(imageAnalysis?.specification || {}).length > 0) && (
|
|
@@ -165,6 +153,14 @@ export default function SidePanel({ className }: { className?: string }) {
|
|
|
165
153
|
</div>
|
|
166
154
|
)}
|
|
167
155
|
|
|
156
|
+
{isSmartFiltersEnabled && (
|
|
157
|
+
<SmartFilter
|
|
158
|
+
imageAnalysis={imageAnalysis}
|
|
159
|
+
specificationFilter={specificationFilter}
|
|
160
|
+
variant="sidepanel"
|
|
161
|
+
/>
|
|
162
|
+
)}
|
|
163
|
+
|
|
168
164
|
{(isGoogleGroundingLoading ||
|
|
169
165
|
googleGroundingResponse ||
|
|
170
166
|
groundingError) && (
|
|
@@ -3,6 +3,7 @@ import ItemSpecification from './ItemSpecification';
|
|
|
3
3
|
import { Icon } from '@nyris/nyris-react-components';
|
|
4
4
|
import useRequestStore from 'stores/request/requestStore';
|
|
5
5
|
import useResultStore from 'stores/result/resultStore';
|
|
6
|
+
import useUiStore from 'stores/ui/uiStore';
|
|
6
7
|
import { rerankSmartFilters, SmartFilterRerankItem } from 'services/vizo';
|
|
7
8
|
|
|
8
9
|
function SmartFilter({
|
|
@@ -25,6 +26,7 @@ function SmartFilter({
|
|
|
25
26
|
const setAlgoliaProducts = useResultStore(state => state.setAlgoliaProducts);
|
|
26
27
|
const requestImages = useRequestStore(state => state.requestImages);
|
|
27
28
|
const setAlgoliaFilter = useRequestStore(state => state.setAlgoliaFilter);
|
|
29
|
+
const setIsFindApiLoading = useUiStore(state => state.setIsFindApiLoading);
|
|
28
30
|
const isAlgoliaEnabled = !!window.settings?.algolia?.enabled;
|
|
29
31
|
|
|
30
32
|
const activeProducts =
|
|
@@ -32,10 +34,20 @@ function SmartFilter({
|
|
|
32
34
|
? productsFromAlgolia
|
|
33
35
|
: productsFromFindApi;
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
// Always rerank against the original, unfiltered result set. Reranking
|
|
38
|
+
// mutates `productsFromFindApi`/`productsFromAlgolia`, so deriving items
|
|
39
|
+
// from those would shrink the payload on every subsequent rerank.
|
|
40
|
+
const originalResults = useMemo(
|
|
41
|
+
() =>
|
|
42
|
+
firstSearchResults && firstSearchResults.length > 0
|
|
43
|
+
? firstSearchResults
|
|
44
|
+
: activeProducts || [],
|
|
45
|
+
[firstSearchResults, activeProducts],
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const rerankItems: any[] = useMemo(() => {
|
|
49
|
+
return originalResults
|
|
50
|
+
.map((product: any): any | null => {
|
|
39
51
|
const sku = product?.sku || product?.objectID;
|
|
40
52
|
const imageUrl =
|
|
41
53
|
product?.matches?.[0]?.url ||
|
|
@@ -49,31 +61,39 @@ function SmartFilter({
|
|
|
49
61
|
}
|
|
50
62
|
|
|
51
63
|
return {
|
|
52
|
-
|
|
64
|
+
...product,
|
|
53
65
|
imageUrl: String(imageUrl),
|
|
54
|
-
metadata: {
|
|
55
|
-
title: product?.title || product?.name || product?.Bezeichnung || '',
|
|
56
|
-
description:
|
|
57
|
-
product?.description ||
|
|
58
|
-
product?.VK_Text_Englisch ||
|
|
59
|
-
product?.VK_Text_Deutsch ||
|
|
60
|
-
'',
|
|
61
|
-
language: window.settings.language || 'en',
|
|
62
|
-
sku: String(sku),
|
|
63
|
-
score:
|
|
64
|
-
typeof (product?.score ?? product?.similarity) === 'number'
|
|
65
|
-
? Number(product?.score ?? product?.similarity)
|
|
66
|
-
: null,
|
|
67
|
-
},
|
|
68
66
|
};
|
|
69
67
|
})
|
|
70
68
|
.filter((item): item is SmartFilterRerankItem => item !== null);
|
|
71
|
-
}, [
|
|
69
|
+
}, [originalResults]);
|
|
70
|
+
|
|
71
|
+
const buildAlgoliaSkuFilter = (skus: string[]) => {
|
|
72
|
+
const nonEmptyFilter: string[] = ['sku:DOES_NOT_EXIST<score=1> '];
|
|
73
|
+
const rankedFilters = skus
|
|
74
|
+
.slice()
|
|
75
|
+
.reverse()
|
|
76
|
+
.map((sku, i) => `sku:'${sku}'<score=${i}> `);
|
|
77
|
+
return [...nonEmptyFilter, ...rankedFilters].join('OR ');
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const restoreOriginalResults = () => {
|
|
81
|
+
if (originalResults.length === 0) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
setFindApiProducts(originalResults);
|
|
85
|
+
if (isAlgoliaEnabled) {
|
|
86
|
+
setAlgoliaProducts(originalResults);
|
|
87
|
+
const skus = originalResults
|
|
88
|
+
.map((p: any) => String(p?.sku || p?.objectID || '').trim())
|
|
89
|
+
.filter(Boolean);
|
|
90
|
+
setAlgoliaFilter(buildAlgoliaSkuFilter(skus));
|
|
91
|
+
}
|
|
92
|
+
};
|
|
72
93
|
|
|
73
94
|
const applySkuOrderingToCurrentResults = (orderedSkus: string[]) => {
|
|
74
|
-
const sourceResults = activeProducts || [];
|
|
75
95
|
const map = new Map<string, any>(
|
|
76
|
-
|
|
96
|
+
originalResults.map((item: any) => [
|
|
77
97
|
String(item?.sku || item?.objectID || '').trim(),
|
|
78
98
|
item,
|
|
79
99
|
]),
|
|
@@ -90,12 +110,7 @@ function SmartFilter({
|
|
|
90
110
|
setFindApiProducts(reordered);
|
|
91
111
|
if (isAlgoliaEnabled) {
|
|
92
112
|
setAlgoliaProducts(reordered);
|
|
93
|
-
|
|
94
|
-
const rankedFilters = orderedSkus
|
|
95
|
-
.slice()
|
|
96
|
-
.reverse()
|
|
97
|
-
.map((sku, i) => `sku:'${sku}'<score=${i}> `);
|
|
98
|
-
setAlgoliaFilter([...nonEmptyFilter, ...rankedFilters].join('OR '));
|
|
113
|
+
setAlgoliaFilter(buildAlgoliaSkuFilter(orderedSkus));
|
|
99
114
|
}
|
|
100
115
|
return true;
|
|
101
116
|
};
|
|
@@ -112,12 +127,11 @@ function SmartFilter({
|
|
|
112
127
|
...smartFilters,
|
|
113
128
|
selectedFilterKey: null,
|
|
114
129
|
selectedFilterValue: null,
|
|
130
|
+
rerankedSkus: [],
|
|
115
131
|
rerankStatus: 'idle',
|
|
116
132
|
errorMessage: null,
|
|
117
133
|
});
|
|
118
|
-
|
|
119
|
-
setFindApiProducts(firstSearchResults);
|
|
120
|
-
}
|
|
134
|
+
restoreOriginalResults();
|
|
121
135
|
return;
|
|
122
136
|
}
|
|
123
137
|
|
|
@@ -139,6 +153,7 @@ function SmartFilter({
|
|
|
139
153
|
rerankStatus: 'loading',
|
|
140
154
|
errorMessage: null,
|
|
141
155
|
});
|
|
156
|
+
setIsFindApiLoading(true);
|
|
142
157
|
|
|
143
158
|
try {
|
|
144
159
|
const rerankResponse = await rerankSmartFilters({
|
|
@@ -209,6 +224,8 @@ function SmartFilter({
|
|
|
209
224
|
? 'Smart filters are unavailable right now.'
|
|
210
225
|
: 'Could not rerank results.',
|
|
211
226
|
});
|
|
227
|
+
} finally {
|
|
228
|
+
setIsFindApiLoading(false);
|
|
212
229
|
}
|
|
213
230
|
};
|
|
214
231
|
|
|
@@ -224,7 +241,7 @@ function SmartFilter({
|
|
|
224
241
|
</div>
|
|
225
242
|
<Icon name="smart_filter_indication" className="mt-1" />
|
|
226
243
|
</div>
|
|
227
|
-
<div className=
|
|
244
|
+
<div className={`flex flex-wrap mx-4 gap-2 ${imageAnalysis?.specification ? 'mt-4' : ''}`}>
|
|
228
245
|
{Object.keys(imageAnalysis?.specification || {})
|
|
229
246
|
.filter(key => key !== 'is_nameplate' && key !== 'prefilter_value')
|
|
230
247
|
.map(key => {
|
|
@@ -244,23 +261,20 @@ function SmartFilter({
|
|
|
244
261
|
})}
|
|
245
262
|
</div>
|
|
246
263
|
{smartFilters.status === 'loading' && (
|
|
247
|
-
<div className="
|
|
264
|
+
<div className="ml-[30px] mt-3 text-xs text-[#3E36DC]">
|
|
248
265
|
Loading smart filters...
|
|
249
266
|
</div>
|
|
250
267
|
)}
|
|
251
268
|
{smartFilters.status === 'error' && (
|
|
252
|
-
<div className="
|
|
269
|
+
<div className="ml-[30px] mt-3 text-xs text-[#D14343]">
|
|
253
270
|
{smartFilters.errorMessage || 'Could not generate smart filters.'}
|
|
254
271
|
</div>
|
|
255
272
|
)}
|
|
256
273
|
{smartFilters.status === 'success' && smartFilters.filters.length === 0 && (
|
|
257
|
-
<div className="
|
|
274
|
+
<div className="ml-[30px] mt-3 text-xs text-[#767A9F]">
|
|
258
275
|
No smart filters available for this image.
|
|
259
276
|
</div>
|
|
260
277
|
)}
|
|
261
|
-
{smartFilters.rerankStatus === 'loading' && (
|
|
262
|
-
<div className="mx-4 mt-1 text-xs text-[#3E36DC]">Applying rerank...</div>
|
|
263
|
-
)}
|
|
264
278
|
<div
|
|
265
279
|
className={
|
|
266
280
|
variant === 'results'
|
|
@@ -290,24 +304,38 @@ function SmartFilter({
|
|
|
290
304
|
const isSelected =
|
|
291
305
|
smartFilters.selectedFilterKey === filter.key &&
|
|
292
306
|
smartFilters.selectedFilterValue === filter.value;
|
|
307
|
+
const hasSelection =
|
|
308
|
+
!!smartFilters.selectedFilterKey &&
|
|
309
|
+
!!smartFilters.selectedFilterValue;
|
|
310
|
+
const isRerankLoading = smartFilters.rerankStatus === 'loading';
|
|
311
|
+
const isDisabled = !isSelected && (hasSelection || isRerankLoading);
|
|
293
312
|
return (
|
|
294
313
|
<button
|
|
295
314
|
key={`${filter.key}-${filter.value}-${index}`}
|
|
315
|
+
type="button"
|
|
316
|
+
disabled={isDisabled}
|
|
317
|
+
aria-pressed={isSelected}
|
|
296
318
|
style={variant === 'results' ? { flex: '0 0 auto' } : undefined}
|
|
297
|
-
className={`px-3.5 py-2 rounded-lg border text-sm ${
|
|
319
|
+
className={`px-3.5 py-2 rounded-lg border text-sm inline-flex items-center gap-1.5 ${
|
|
298
320
|
isSelected
|
|
299
321
|
? 'bg-[#545987] text-white border-[#545987]'
|
|
300
322
|
: 'bg-[#E7E8F1] text-[#767A9F] border-transparent'
|
|
301
|
-
}`}
|
|
323
|
+
} ${isDisabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}`}
|
|
302
324
|
onClick={() => {
|
|
303
|
-
if (
|
|
325
|
+
if (isRerankLoading) return;
|
|
304
326
|
void onFilterClick({
|
|
305
327
|
key: filter.key,
|
|
306
328
|
value: filter.value,
|
|
307
329
|
});
|
|
308
330
|
}}
|
|
309
331
|
>
|
|
310
|
-
{filter.
|
|
332
|
+
<span>{filter.value}</span>
|
|
333
|
+
{isSelected && (
|
|
334
|
+
<Icon
|
|
335
|
+
name="close"
|
|
336
|
+
className="w-3 h-3 text-white"
|
|
337
|
+
/>
|
|
338
|
+
)}
|
|
311
339
|
</button>
|
|
312
340
|
);
|
|
313
341
|
})}
|
|
@@ -15,8 +15,9 @@ export const Toaster = () => {
|
|
|
15
15
|
<ReactHotToaster
|
|
16
16
|
position={isMobile ? 'bottom-center' : 'top-right'}
|
|
17
17
|
containerStyle={{
|
|
18
|
-
top:
|
|
19
|
-
bottom: isMobile ?
|
|
18
|
+
top: 100,
|
|
19
|
+
bottom: isMobile ? 90 : 'auto',
|
|
20
|
+
right: isMobile ? 15 : 25,
|
|
20
21
|
}}
|
|
21
22
|
>
|
|
22
23
|
{t => (
|
|
@@ -592,7 +592,7 @@ Respond to the user's message with an appropriate action and follow-up options.`
|
|
|
592
592
|
finalOptions = [
|
|
593
593
|
...finalOptions,
|
|
594
594
|
{
|
|
595
|
-
label: `Find parts related to ${imageAnalysis
|
|
595
|
+
label: `Find parts related to ${imageAnalysis?.specification?.product_category || 'request image'}`,
|
|
596
596
|
action_type: 'find_related_parts' as const,
|
|
597
597
|
key: 'find_related',
|
|
598
598
|
value: 'find_related_parts',
|
|
@@ -18,9 +18,7 @@ import useResultEvaluator from './useResultEvaluator';
|
|
|
18
18
|
import useConversationStore from 'stores/chat/conversationStore';
|
|
19
19
|
import { useGroundedSearch } from './useGroundedSearch';
|
|
20
20
|
import { generateSmartFilters } from '../services/vizo';
|
|
21
|
-
|
|
22
|
-
const DEFAULT_SMART_FILTER_PROMPT =
|
|
23
|
-
'Extract at most 4 relevant product intent filters from the request image. Return strict JSON only.';
|
|
21
|
+
import { cropImage } from 'utils/cropImageToBase64';
|
|
24
22
|
|
|
25
23
|
export const useImageSearch = () => {
|
|
26
24
|
const setRegions = useRequestStore(state => state.setRegions);
|
|
@@ -195,7 +193,7 @@ export const useImageSearch = () => {
|
|
|
195
193
|
];
|
|
196
194
|
|
|
197
195
|
let smartFilterPromise: Promise<void> | null = null;
|
|
198
|
-
if (isSmartFiltersEnabled && requestImage && newSearch) {
|
|
196
|
+
if (isSmartFiltersEnabled && requestImage && (newSearch || imageRegion)) {
|
|
199
197
|
setSmartFilters({
|
|
200
198
|
status: 'loading',
|
|
201
199
|
filters: [],
|
|
@@ -206,10 +204,8 @@ export const useImageSearch = () => {
|
|
|
206
204
|
errorMessage: null,
|
|
207
205
|
});
|
|
208
206
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
prompt: DEFAULT_SMART_FILTER_PROMPT,
|
|
212
|
-
})
|
|
207
|
+
const smartFilterImage = cropImage(requestImage, region);
|
|
208
|
+
smartFilterPromise = generateSmartFilters({ image: smartFilterImage })
|
|
213
209
|
.then(response => {
|
|
214
210
|
const rawFilters = response?.filters;
|
|
215
211
|
if (!rawFilters || typeof rawFilters !== 'object') {
|