@nyris/nyris-webapp 0.3.97 → 0.3.99
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 +7 -6
- package/build/index.html +1 -1
- package/build/js/settings.example.js +41 -0
- package/build/static/css/main.5cb72c09.css +4 -0
- package/build/static/css/main.5cb72c09.css.map +1 -0
- package/build/static/js/main.2339271b.js +3 -0
- package/build/static/js/{main.9e7fb38a.js.map → main.2339271b.js.map} +1 -1
- package/build/static/media/plate_info.6beaea36d35053be3a45.png +0 -0
- package/package.json +3 -3
- package/public/js/settings.example.js +41 -0
- package/src/common/assets/images/plate_info.png +0 -0
- package/src/components/AuthApiKeySync.tsx +34 -0
- package/src/components/Cart.tsx +41 -0
- package/src/components/ChatAssistant/useChatAssistantLogic.ts +3 -2
- package/src/components/GoBackButton.tsx +29 -2
- package/src/components/Header.tsx +3 -0
- package/src/components/HitsPerPage.tsx +55 -18
- package/src/components/ImagePreview.tsx +31 -1
- package/src/components/ImageUpload.tsx +14 -3
- package/src/components/Inquiry/InquiryModal.tsx +3 -0
- package/src/components/LogoutModal.tsx +2 -0
- package/src/components/Pagination.tsx +33 -6
- package/src/components/PostFilter/PostFilter.tsx +8 -1
- package/src/components/PostFilter/PostFilterComponent.tsx +8 -22
- package/src/components/PostFilter/PostFilterFindApi.test.tsx +249 -0
- package/src/components/PostFilter/PostFilterFindApi.tsx +128 -52
- package/src/components/PreFilter/PreFilter.tsx +10 -2
- package/src/components/Product/Product.tsx +60 -10
- package/src/components/Product/ProductDetailView.tsx +23 -1
- package/src/components/Product/ProductDetailViewModal.tsx +5 -1
- package/src/components/Product/ProductList.tsx +51 -16
- package/src/components/Rfq/RfqModal.tsx +3 -0
- package/src/components/ScopeSelect/ScopeSelectForm.tsx +514 -0
- package/src/components/ScopeSelect/ScopeSelectImageActions.tsx +62 -0
- package/src/components/ScopeSelect/ScopeSelectImagePreview.tsx +141 -0
- package/src/components/ScopeSelect/ScopeSelectInfo.tsx +68 -0
- package/src/components/ScopeSelect/ScopeSelectInput.tsx +123 -0
- package/src/components/ScopeSelect/ScopeSelectModal.tsx +310 -0
- package/src/components/ScopeSelect/identifyTypePlateImage.test.ts +129 -0
- package/src/components/ScopeSelect/identifyTypePlateImage.ts +83 -0
- package/src/components/ScopeSelect/parseScopeIdResponse.test.ts +103 -0
- package/src/components/ScopeSelect/parseScopeIdResponse.ts +72 -0
- package/src/components/ScopeSelect/scopeSelectHelpers.ts +54 -0
- package/src/components/SidePanel.tsx +43 -0
- package/src/components/TextSearch.tsx +80 -14
- package/src/components/Tooltip/TooltipComponent.tsx +12 -4
- package/src/components/TypePlateUpload.tsx +132 -0
- package/src/hooks/useEffectiveGroundingResults.ts +8 -6
- package/src/hooks/useGoodResultsChat.ts +8 -22
- package/src/hooks/useImageSearch.ts +325 -20
- package/src/hooks/useMachinePartsListingPagination.ts +95 -0
- package/src/hooks/useResultEvaluator.ts +43 -19
- package/src/index.css +1 -1
- package/src/index.tsx +4 -0
- package/src/layouts/AppLayout.tsx +95 -9
- package/src/pages/Home.tsx +60 -14
- package/src/pages/Logout.tsx +2 -0
- package/src/pages/Result.tsx +206 -20
- package/src/providers/AuthProvider.tsx +5 -0
- package/src/services/Feedback.ts +224 -35
- package/src/services/feedbackSubtypes.ts +63 -0
- package/src/services/filter.ts +4 -3
- package/src/services/initialMachinePartsText.payload.test.ts +61 -0
- package/src/services/initialMachinePartsText.test.ts +33 -0
- package/src/services/initialMachinePartsText.ts +11 -0
- package/src/services/machinePartsPage.test.ts +74 -0
- package/src/services/machinePartsPage.ts +120 -0
- package/src/services/normalizeChatResponse.ts +26 -0
- package/src/services/scopeLookup.test.ts +67 -0
- package/src/services/scopeLookup.ts +39 -0
- package/src/services/visualSearch.listingOptions.test.ts +79 -0
- package/src/services/visualSearch.ts +76 -7
- package/src/services/vizo.ts +17 -5
- package/src/stores/machinePartsListing/machinePartsListingStore.ts +35 -0
- package/src/stores/request/filter/filter.initialState.ts +1 -0
- package/src/stores/request/filter/filter.slice.ts +2 -0
- package/src/stores/result/prodcuts/products.initialState.ts +1 -0
- package/src/stores/result/prodcuts/products.slice.ts +4 -0
- package/src/stores/scope/scopeStore.ts +67 -4
- package/src/stores/types.ts +19 -14
- package/src/translations.ts +213 -0
- package/src/types.ts +40 -0
- package/src/utils/derivePostFilterFacets.ts +68 -0
- package/src/utils/fetchProductImage.ts +1 -0
- package/src/utils/normalizeChatAssistantResponse.ts +227 -0
- package/src/utils/nyrisApiKey.ts +115 -0
- package/src/utils/productCardImageUrl.test.ts +41 -0
- package/src/utils/productCardImageUrl.ts +49 -0
- package/src/utils/textSearchFilter.test.ts +41 -0
- package/src/utils/textSearchFilter.ts +13 -6
- package/build/static/css/main.7c7f5447.css +0 -4
- package/build/static/css/main.7c7f5447.css.map +0 -1
- package/build/static/js/main.9e7fb38a.js +0 -3
- /package/build/static/js/{main.9e7fb38a.js.LICENSE.txt → main.2339271b.js.LICENSE.txt} +0 -0
|
@@ -8,11 +8,20 @@ import { Icon } from '@nyris/nyris-react-components';
|
|
|
8
8
|
import { useImageSearch } from 'hooks/useImageSearch';
|
|
9
9
|
import PreFilterModal from './PreFilter/PreFilterModal';
|
|
10
10
|
import useRequestStore from 'stores/request/requestStore';
|
|
11
|
+
import { hasActiveMachineScope } from 'stores/scope/scopeStore';
|
|
12
|
+
import { firstMachinePartsListingText } from 'stores/machinePartsListing/machinePartsListingStore';
|
|
11
13
|
import Tooltip from './Tooltip/TooltipComponent';
|
|
12
14
|
import { useMediaQuery } from 'react-responsive';
|
|
13
15
|
import ImageUpload from './ImageUpload';
|
|
16
|
+
import { TypePlateUpload } from './TypePlateUpload';
|
|
14
17
|
import useUiStore from 'stores/ui/uiStore';
|
|
15
18
|
import useConversationStore from 'stores/chat/conversationStore';
|
|
19
|
+
import { trackClick } from 'services/Feedback';
|
|
20
|
+
import { CLICK_SUBTYPE } from 'services/feedbackSubtypes';
|
|
21
|
+
|
|
22
|
+
function machinePartsListingText() {
|
|
23
|
+
return firstMachinePartsListingText();
|
|
24
|
+
}
|
|
16
25
|
|
|
17
26
|
function TextSearch({
|
|
18
27
|
className,
|
|
@@ -63,15 +72,19 @@ function TextSearch({
|
|
|
63
72
|
useState<boolean>(false);
|
|
64
73
|
const [isInputFocused, setIsInputFocused] = useState(false);
|
|
65
74
|
|
|
75
|
+
const isScopeMode = !!settings.scopes?.scopeID;
|
|
66
76
|
const showPreFilter = useMemo(() => {
|
|
77
|
+
if (settings.scopes?.scopeID) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
67
80
|
if (settings.shouldUseUserMetadata && user) {
|
|
68
|
-
if (user['/user_metadata']
|
|
81
|
+
if (user['/user_metadata']?.value) {
|
|
69
82
|
setMetaFilter(user['/user_metadata'].value);
|
|
70
83
|
}
|
|
71
84
|
}
|
|
72
85
|
|
|
73
86
|
if (settings.shouldUseUserMetadata && user) {
|
|
74
|
-
if (settings.preFilterOption && !user['/user_metadata']
|
|
87
|
+
if (settings.preFilterOption && !user['/user_metadata']?.value) {
|
|
75
88
|
return true;
|
|
76
89
|
}
|
|
77
90
|
return false;
|
|
@@ -81,29 +94,44 @@ function TextSearch({
|
|
|
81
94
|
}, [
|
|
82
95
|
setMetaFilter,
|
|
83
96
|
settings.preFilterOption,
|
|
97
|
+
settings.scopes?.scopeID,
|
|
84
98
|
settings.shouldUseUserMetadata,
|
|
85
99
|
user,
|
|
86
100
|
]);
|
|
87
101
|
|
|
88
102
|
const visualSearch = useMemo(() => requestImages.length > 0, [requestImages]);
|
|
89
|
-
const { singleImageSearch } = useImageSearch();
|
|
103
|
+
const { singleImageSearch, searchMachinePartsListing } = useImageSearch();
|
|
90
104
|
|
|
91
105
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
92
106
|
const searchOrRedirect = useCallback(
|
|
93
107
|
debounce((value: any) => {
|
|
94
108
|
setQuery(value);
|
|
95
|
-
if (requestImages.length === 0 && value === ''
|
|
96
|
-
|
|
97
|
-
|
|
109
|
+
if (requestImages.length === 0 && value === '') {
|
|
110
|
+
if (hasActiveMachineScope()) {
|
|
111
|
+
searchMachinePartsListing({
|
|
112
|
+
text: machinePartsListingText() || '',
|
|
113
|
+
showFeedback: true,
|
|
114
|
+
clearPostFilters: true,
|
|
115
|
+
});
|
|
116
|
+
navigate('/result');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (!specifications) {
|
|
120
|
+
navigate('/');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
98
123
|
}
|
|
99
124
|
|
|
100
|
-
if (
|
|
125
|
+
if (
|
|
126
|
+
requestImages.length === 0 &&
|
|
127
|
+
(hasActiveMachineScope() || !window.settings?.algolia.enabled)
|
|
128
|
+
) {
|
|
101
129
|
singleImageSearch({
|
|
102
130
|
image: requestImages[0],
|
|
103
131
|
imageRegion: regions[0],
|
|
104
132
|
text: value,
|
|
105
133
|
settings,
|
|
106
|
-
showFeedback:
|
|
134
|
+
showFeedback: true,
|
|
107
135
|
newSearch: false,
|
|
108
136
|
});
|
|
109
137
|
}
|
|
@@ -114,10 +142,28 @@ function TextSearch({
|
|
|
114
142
|
);
|
|
115
143
|
|
|
116
144
|
const onChangeText = (event: any) => {
|
|
117
|
-
|
|
118
|
-
|
|
145
|
+
const nextValue = event.currentTarget.value;
|
|
146
|
+
setValueInput(nextValue);
|
|
147
|
+
|
|
148
|
+
if (
|
|
149
|
+
nextValue === '' &&
|
|
150
|
+
requestImages.length === 0 &&
|
|
151
|
+
hasActiveMachineScope()
|
|
152
|
+
) {
|
|
153
|
+
searchOrRedirect.cancel();
|
|
154
|
+
setQuery('');
|
|
155
|
+
searchMachinePartsListing({
|
|
156
|
+
text: machinePartsListingText() || '',
|
|
157
|
+
showFeedback: true,
|
|
158
|
+
clearPostFilters: true,
|
|
159
|
+
});
|
|
160
|
+
navigate('/result');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
119
163
|
|
|
120
|
-
|
|
164
|
+
searchOrRedirect(nextValue);
|
|
165
|
+
|
|
166
|
+
if (nextValue === '' && !specifications) {
|
|
121
167
|
setValueInput('');
|
|
122
168
|
setQuery('');
|
|
123
169
|
}
|
|
@@ -179,6 +225,11 @@ function TextSearch({
|
|
|
179
225
|
showPreFilter ? setToggleModalFilterDesktop(true) : false
|
|
180
226
|
}
|
|
181
227
|
>
|
|
228
|
+
{isScopeMode && (
|
|
229
|
+
<TypePlateUpload
|
|
230
|
+
iconOnly={location.pathname === '/result'}
|
|
231
|
+
/>
|
|
232
|
+
)}
|
|
182
233
|
{showPreFilter && (
|
|
183
234
|
<Tooltip
|
|
184
235
|
content={
|
|
@@ -342,7 +393,7 @@ function TextSearch({
|
|
|
342
393
|
{nameplateNotificationText}
|
|
343
394
|
</div>
|
|
344
395
|
)}
|
|
345
|
-
{!showPreFilter && (
|
|
396
|
+
{!isScopeMode && !showPreFilter && (
|
|
346
397
|
<div className="p-2 hidden desktop:block">
|
|
347
398
|
<Icon name="search" width={16} height={16} />
|
|
348
399
|
</div>
|
|
@@ -390,7 +441,7 @@ function TextSearch({
|
|
|
390
441
|
location.pathname === '/result' &&
|
|
391
442
|
'desktop:bg-[#E7E8F1] focus:bg-[#FFFFFF]',
|
|
392
443
|
)}
|
|
393
|
-
placeholder={t('Search')}
|
|
444
|
+
placeholder={t('Search part names, SKU, or description...')}
|
|
394
445
|
value={valueInput}
|
|
395
446
|
onChange={onChangeText}
|
|
396
447
|
onFocus={() => setIsInputFocused(true)}
|
|
@@ -419,6 +470,7 @@ function TextSearch({
|
|
|
419
470
|
event.preventDefault();
|
|
420
471
|
}}
|
|
421
472
|
onClick={() => {
|
|
473
|
+
searchOrRedirect.cancel();
|
|
422
474
|
if (visualSearch) {
|
|
423
475
|
navigate('/result');
|
|
424
476
|
setValueInput('');
|
|
@@ -438,6 +490,17 @@ function TextSearch({
|
|
|
438
490
|
}
|
|
439
491
|
return;
|
|
440
492
|
}
|
|
493
|
+
if (hasActiveMachineScope()) {
|
|
494
|
+
setQuery('');
|
|
495
|
+
setValueInput('');
|
|
496
|
+
searchMachinePartsListing({
|
|
497
|
+
text: machinePartsListingText() || '',
|
|
498
|
+
showFeedback: false,
|
|
499
|
+
clearPostFilters: true,
|
|
500
|
+
});
|
|
501
|
+
navigate('/result');
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
441
504
|
setSpecifications(null);
|
|
442
505
|
setQuery('');
|
|
443
506
|
setValueInput('');
|
|
@@ -451,7 +514,7 @@ function TextSearch({
|
|
|
451
514
|
{showImageUpload && <ImageUpload onCameraClick={onCameraClick} />}
|
|
452
515
|
{aiMode &&
|
|
453
516
|
window.settings.vizo?.chat &&
|
|
454
|
-
requestImages.length !== 0 &&
|
|
517
|
+
(requestImages.length !== 0 || valueInput) &&
|
|
455
518
|
!(isMobile && isInputFocused) && (
|
|
456
519
|
<div className="relative">
|
|
457
520
|
<button
|
|
@@ -467,6 +530,9 @@ function TextSearch({
|
|
|
467
530
|
)}
|
|
468
531
|
onClick={() => {
|
|
469
532
|
const nextIsAiModeOpen = !isAiModeOpen;
|
|
533
|
+
if (nextIsAiModeOpen) {
|
|
534
|
+
trackClick(CLICK_SUBTYPE.CHAT_WITH_VIZO);
|
|
535
|
+
}
|
|
470
536
|
toggleAiMode();
|
|
471
537
|
setIsChatOpen(nextIsAiModeOpen);
|
|
472
538
|
}}
|
|
@@ -13,20 +13,28 @@ function Tooltip({
|
|
|
13
13
|
disabled,
|
|
14
14
|
sideOffset,
|
|
15
15
|
delayDuration = 80,
|
|
16
|
+
open,
|
|
17
|
+
side = 'top',
|
|
16
18
|
}: {
|
|
17
|
-
content:
|
|
19
|
+
content: React.ReactNode;
|
|
18
20
|
children: React.ReactNode;
|
|
19
21
|
disabled?: boolean;
|
|
20
22
|
sideOffset?: number;
|
|
21
23
|
delayDuration?: number;
|
|
24
|
+
open?: boolean;
|
|
25
|
+
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
22
26
|
}) {
|
|
23
27
|
return (
|
|
24
28
|
<TooltipProvider delayDuration={delayDuration}>
|
|
25
|
-
<RadixTooltip open={disabled ? false :
|
|
29
|
+
<RadixTooltip open={disabled ? false : open}>
|
|
26
30
|
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
|
27
|
-
<TooltipContent sideOffset={sideOffset}>
|
|
31
|
+
<TooltipContent side={side} sideOffset={sideOffset}>
|
|
28
32
|
<TooltipArrow />
|
|
29
|
-
|
|
33
|
+
{typeof content === 'string' ? (
|
|
34
|
+
<p className="text-white max-w-[350px]">{content}</p>
|
|
35
|
+
) : (
|
|
36
|
+
content
|
|
37
|
+
)}
|
|
30
38
|
</TooltipContent>
|
|
31
39
|
</RadixTooltip>
|
|
32
40
|
</TooltipProvider>
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Icon } from '@nyris/nyris-react-components';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { twMerge } from 'tailwind-merge';
|
|
5
|
+
import useScopeStore from 'stores/scope/scopeStore';
|
|
6
|
+
import Tooltip from './Tooltip/TooltipComponent';
|
|
7
|
+
import { ScopeSelectModal } from './ScopeSelect/ScopeSelectModal';
|
|
8
|
+
|
|
9
|
+
export interface TypePlateUploadProps {
|
|
10
|
+
className?: string;
|
|
11
|
+
disabledTooltip?: boolean;
|
|
12
|
+
alternativeView?: boolean;
|
|
13
|
+
/** When false, the button only opens a modal owned by the parent. */
|
|
14
|
+
showModal?: boolean;
|
|
15
|
+
open?: boolean;
|
|
16
|
+
onOpenChange?: (open: boolean) => void;
|
|
17
|
+
/** Results-page text search: icon, tooltip, and active-scope marker. */
|
|
18
|
+
iconOnly?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function TypePlateUpload({
|
|
22
|
+
className,
|
|
23
|
+
alternativeView = false,
|
|
24
|
+
showModal = true,
|
|
25
|
+
open,
|
|
26
|
+
onOpenChange,
|
|
27
|
+
iconOnly = false,
|
|
28
|
+
}: TypePlateUploadProps = {}) {
|
|
29
|
+
const { t } = useTranslation();
|
|
30
|
+
const scopeId = useScopeStore(state => state.scopeId);
|
|
31
|
+
const scopeItemKeys = useScopeStore(state => state.itemKeys);
|
|
32
|
+
const hasActiveScope = Boolean(scopeId) && scopeItemKeys.length > 0;
|
|
33
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
34
|
+
const isControlled = open !== undefined;
|
|
35
|
+
const isOpen = isControlled ? open : internalOpen;
|
|
36
|
+
const setIsOpen = (next: boolean) => {
|
|
37
|
+
if (!isControlled) {
|
|
38
|
+
setInternalOpen(next);
|
|
39
|
+
}
|
|
40
|
+
onOpenChange?.(next);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
if (!window.settings.scopes?.scopeID) return null;
|
|
44
|
+
|
|
45
|
+
const buttonLabel =
|
|
46
|
+
window.settings.scopes?.typePlateButtonLabel ||
|
|
47
|
+
window.settings.scopes?.typePlateButtonText ||
|
|
48
|
+
'Scan type plate';
|
|
49
|
+
const tooltipText =
|
|
50
|
+
iconOnly && hasActiveScope
|
|
51
|
+
? t(
|
|
52
|
+
'Searching within parts for {{scopeId}}. You may scan another type plate.',
|
|
53
|
+
{ scopeId },
|
|
54
|
+
)
|
|
55
|
+
: t('Scan Type plate');
|
|
56
|
+
const accessibleName = iconOnly ? tooltipText : t(buttonLabel);
|
|
57
|
+
|
|
58
|
+
const button = (
|
|
59
|
+
<button
|
|
60
|
+
type="button"
|
|
61
|
+
className={twMerge(
|
|
62
|
+
'p-2 desktop:p-3 flex items-center gap-2 rounded-full border border-solid cursor-pointer',
|
|
63
|
+
alternativeView
|
|
64
|
+
? 'bg-theme-primary border-theme-primary'
|
|
65
|
+
: 'bg-[#f3f3f5] border-[#2B2C46] desktop:bg-[#FAFAFA]',
|
|
66
|
+
className,
|
|
67
|
+
)}
|
|
68
|
+
aria-label={accessibleName}
|
|
69
|
+
onClick={() => setIsOpen(true)}
|
|
70
|
+
>
|
|
71
|
+
<Icon
|
|
72
|
+
name="typeplate"
|
|
73
|
+
width={16}
|
|
74
|
+
height={16}
|
|
75
|
+
fill={alternativeView ? '#FFFFFF' : '#2B2C46'}
|
|
76
|
+
/>
|
|
77
|
+
{!iconOnly && (
|
|
78
|
+
<span
|
|
79
|
+
className={twMerge(
|
|
80
|
+
'whitespace-nowrap pr-1 text-[14px] leading-none',
|
|
81
|
+
alternativeView ? 'text-white' : 'text-[#2B2C46]',
|
|
82
|
+
)}
|
|
83
|
+
>
|
|
84
|
+
{t(buttonLabel)}
|
|
85
|
+
</span>
|
|
86
|
+
)}
|
|
87
|
+
</button>
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<>
|
|
92
|
+
{iconOnly ? (
|
|
93
|
+
<div className="relative">
|
|
94
|
+
<Tooltip content={tooltipText}>{button}</Tooltip>
|
|
95
|
+
{hasActiveScope && (
|
|
96
|
+
<div
|
|
97
|
+
className="pointer-events-none top-2 left-8 desktop:top-[1px] desktop:left-[26px]"
|
|
98
|
+
style={{
|
|
99
|
+
position: 'absolute',
|
|
100
|
+
display: 'flex',
|
|
101
|
+
justifyContent: 'center',
|
|
102
|
+
alignItems: 'center',
|
|
103
|
+
background: 'white',
|
|
104
|
+
width: '12px',
|
|
105
|
+
height: '12px',
|
|
106
|
+
borderRadius: '100%',
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
<div
|
|
110
|
+
style={{
|
|
111
|
+
width: '8px',
|
|
112
|
+
height: '8px',
|
|
113
|
+
background: window.settings.theme?.primaryColor,
|
|
114
|
+
borderRadius: '100%',
|
|
115
|
+
strokeWidth: '2px',
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
) : (
|
|
122
|
+
button
|
|
123
|
+
)}
|
|
124
|
+
{showModal && (
|
|
125
|
+
<ScopeSelectModal
|
|
126
|
+
openModal={isOpen}
|
|
127
|
+
handleClose={() => setIsOpen(false)}
|
|
128
|
+
/>
|
|
129
|
+
)}
|
|
130
|
+
</>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
@@ -26,12 +26,14 @@ export function useEffectiveGroundingResults() {
|
|
|
26
26
|
const postEntries = Object.entries(postFilterSelections || {}).filter(
|
|
27
27
|
([, values]) => values?.length,
|
|
28
28
|
);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
// Text-only find-API post-filters re-query; skip client-side narrowing.
|
|
30
|
+
// Image search uses the old client-side post-filter behavior.
|
|
31
|
+
const isTextOnlyFindApiPostFilter =
|
|
32
|
+
!window.settings.algolia?.enabled &&
|
|
33
|
+
window.settings.postFilterOption &&
|
|
34
|
+
!(requestImages?.length > 0);
|
|
35
|
+
|
|
36
|
+
if (postEntries.length > 0 && !isTextOnlyFindApiPostFilter) {
|
|
35
37
|
filteredProducts = filteredProducts.filter((product: any) => {
|
|
36
38
|
const productFilters = product?.filters || {};
|
|
37
39
|
return postEntries.every(([attribute, selectedValues]) => {
|
|
@@ -7,10 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { useCallback, useRef } from 'react';
|
|
10
|
-
import useConversationStore
|
|
11
|
-
QuickReplyOption,
|
|
12
|
-
ActionType,
|
|
13
|
-
} from 'stores/chat/conversationStore';
|
|
10
|
+
import useConversationStore from 'stores/chat/conversationStore';
|
|
14
11
|
import useSmartFiltersStore from 'stores/smartFilters/smartFiltersStore';
|
|
15
12
|
import useResultStore from 'stores/result/resultStore';
|
|
16
13
|
import useRequestStore from 'stores/request/requestStore';
|
|
@@ -19,6 +16,10 @@ import {
|
|
|
19
16
|
fetchProductImageBase64,
|
|
20
17
|
getProductImageUrl,
|
|
21
18
|
} from 'utils/fetchProductImage';
|
|
19
|
+
import {
|
|
20
|
+
normalizeChatAssistantResponse,
|
|
21
|
+
P3Response,
|
|
22
|
+
} from 'utils/normalizeChatAssistantResponse';
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Extracts search keywords from a user's natural language description.
|
|
@@ -138,18 +139,6 @@ function extractSearchKeywords(description: string): string {
|
|
|
138
139
|
);
|
|
139
140
|
}
|
|
140
141
|
|
|
141
|
-
interface P3Response {
|
|
142
|
-
message: string;
|
|
143
|
-
action: {
|
|
144
|
-
type: ActionType;
|
|
145
|
-
key?: string;
|
|
146
|
-
value?: string;
|
|
147
|
-
matched_ids?: string[];
|
|
148
|
-
matched_indices?: number[]; // Legacy fallback
|
|
149
|
-
};
|
|
150
|
-
options: QuickReplyOption[];
|
|
151
|
-
}
|
|
152
|
-
|
|
153
142
|
export function useGoodResultsChat() {
|
|
154
143
|
const {
|
|
155
144
|
messages,
|
|
@@ -374,7 +363,7 @@ USER MESSAGE: ${userMessage}
|
|
|
374
363
|
Respond to the user's message with an appropriate action and follow-up options.`,
|
|
375
364
|
});
|
|
376
365
|
|
|
377
|
-
const
|
|
366
|
+
const rawResponse = await chatAnalysis({
|
|
378
367
|
promptKey: 'chat_assistant_prompt',
|
|
379
368
|
multimodalInputs: userParts.map(part => ({
|
|
380
369
|
metadata: part.text,
|
|
@@ -382,11 +371,8 @@ Respond to the user's message with an appropriate action and follow-up options.`
|
|
|
382
371
|
mimeType: part.inlineData?.mimeType,
|
|
383
372
|
})),
|
|
384
373
|
});
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
// Parse the JSON response
|
|
389
|
-
const p3Response: P3Response = responseText;
|
|
374
|
+
const p3Response: P3Response =
|
|
375
|
+
normalizeChatAssistantResponse(rawResponse);
|
|
390
376
|
|
|
391
377
|
// Silent mode: only suggest filters, never apply actions
|
|
392
378
|
if (options?.silent && p3Response.action) {
|