@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
|
+
import { invalidatePostFilterRequests } from 'components/PostFilter/PostFilterFindApi';
|
|
2
3
|
import { RectCoords } from '@nyris/nyris-api';
|
|
3
4
|
import { isEmpty, isUndefined } from 'lodash';
|
|
4
5
|
|
|
@@ -17,16 +18,131 @@ import { isHEIC } from 'utils/misc';
|
|
|
17
18
|
import useResultEvaluator from './useResultEvaluator';
|
|
18
19
|
import useConversationStore from 'stores/chat/conversationStore';
|
|
19
20
|
import { useGroundedSearch } from './useGroundedSearch';
|
|
20
|
-
import { generateSmartFilters } from '../services/vizo';
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
21
|
+
import { chatAnalysis, generateSmartFilters } from '../services/vizo';
|
|
22
|
+
import { parseScopeIdChatResponse } from 'components/ScopeSelect/parseScopeIdResponse';
|
|
23
|
+
import { lookupScopeById } from 'services/scopeLookup';
|
|
24
|
+
import { canvasToBase64, cropImage } from 'utils/cropImageToBase64';
|
|
25
|
+
import useScopeStore, { hasActiveMachineScope } from 'stores/scope/scopeStore';
|
|
26
|
+
import { derivePostFilterFacetsFromResults } from 'utils/derivePostFilterFacets';
|
|
23
27
|
import { useAuth0 } from '@auth0/auth0-react';
|
|
28
|
+
import { isNyrisApiKeyReady } from 'utils/nyrisApiKey';
|
|
29
|
+
import { trackRegion } from 'services/Feedback';
|
|
30
|
+
import { REGION_SUBTYPE } from 'services/feedbackSubtypes';
|
|
31
|
+
|
|
32
|
+
export type TypePlateScopeResult =
|
|
33
|
+
| { status: 'applied'; itemKeys: string[] }
|
|
34
|
+
| { status: 'empty' }
|
|
35
|
+
| { status: 'not_found' }
|
|
36
|
+
| { status: 'unavailable' };
|
|
37
|
+
|
|
38
|
+
export async function applyScopeById(
|
|
39
|
+
scopeId: string,
|
|
40
|
+
): Promise<TypePlateScopeResult> {
|
|
41
|
+
const lookup = await lookupScopeById(scopeId);
|
|
42
|
+
if (lookup.status !== 'found') {
|
|
43
|
+
return { status: lookup.status };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
useScopeStore
|
|
47
|
+
.getState()
|
|
48
|
+
.setScope(lookup.itemKeys, lookup.filterAttributes, scopeId);
|
|
49
|
+
return { status: 'applied', itemKeys: lookup.itemKeys };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Extract scopeId from a type-plate image. Does not call GET /v1/scopes.
|
|
54
|
+
*/
|
|
55
|
+
export async function extractScopeIdFromTypePlate(file: File): Promise<{
|
|
56
|
+
extractedScopeId: string;
|
|
57
|
+
status: 'extracted' | 'not_found' | 'unavailable';
|
|
58
|
+
}> {
|
|
59
|
+
const settings = window.settings;
|
|
60
|
+
if (!settings.scopes?.scopeID) {
|
|
61
|
+
return { extractedScopeId: '', status: 'unavailable' };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!isNyrisApiKeyReady()) {
|
|
65
|
+
return { extractedScopeId: '', status: 'unavailable' };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const requestImage = await createImage(file);
|
|
69
|
+
const croppedCanvas = cropImage(requestImage);
|
|
70
|
+
const base64Image = canvasToBase64(croppedCanvas, 'image/jpeg', 1);
|
|
71
|
+
const parts: {
|
|
72
|
+
text?: string;
|
|
73
|
+
inlineData?: { mimeType: string; data: string };
|
|
74
|
+
}[] = [
|
|
75
|
+
{
|
|
76
|
+
text: 'request_image',
|
|
77
|
+
inlineData: { mimeType: 'image/jpeg', data: base64Image },
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const response = await chatAnalysis({
|
|
82
|
+
promptKey: 'scope_id',
|
|
83
|
+
multimodalInputs: parts.map(p =>
|
|
84
|
+
p.inlineData
|
|
85
|
+
? {
|
|
86
|
+
metadata: p.text || '',
|
|
87
|
+
mimeType: p.inlineData.mimeType,
|
|
88
|
+
image: p.inlineData.data,
|
|
89
|
+
}
|
|
90
|
+
: {
|
|
91
|
+
metadata: p.text || '',
|
|
92
|
+
},
|
|
93
|
+
),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const parsed = parseScopeIdChatResponse(response);
|
|
97
|
+
if (parsed.status !== 'extracted') {
|
|
98
|
+
return { extractedScopeId: '', status: 'not_found' };
|
|
99
|
+
}
|
|
100
|
+
return { extractedScopeId: parsed.serialNumber, status: 'extracted' };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Nameplate upload only resolves scope context (itemKeys).
|
|
105
|
+
* Does not run /find or navigate — normal searches happen afterwards.
|
|
106
|
+
*/
|
|
107
|
+
export async function resolveTypePlateScope(
|
|
108
|
+
file: File,
|
|
109
|
+
): Promise<TypePlateScopeResult & { extractedScopeId: string }> {
|
|
110
|
+
const extract = await extractScopeIdFromTypePlate(file);
|
|
111
|
+
if (extract.status !== 'extracted') {
|
|
112
|
+
return {
|
|
113
|
+
status: extract.status === 'unavailable' ? 'unavailable' : 'not_found',
|
|
114
|
+
extractedScopeId: extract.extractedScopeId,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const result = await applyScopeById(extract.extractedScopeId);
|
|
118
|
+
return { ...result, extractedScopeId: extract.extractedScopeId };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const RECT_TOLERANCE = 1e-3;
|
|
122
|
+
|
|
123
|
+
const isSameRect = (a: RectCoords, b: RectCoords) =>
|
|
124
|
+
Math.abs(a.x1 - b.x1) < RECT_TOLERANCE &&
|
|
125
|
+
Math.abs(a.y1 - b.y1) < RECT_TOLERANCE &&
|
|
126
|
+
Math.abs(a.x2 - b.x2) < RECT_TOLERANCE &&
|
|
127
|
+
Math.abs(a.y2 - b.y2) < RECT_TOLERANCE;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* A user-selected region that matches one of the detected object regions
|
|
131
|
+
* came from clicking a detection dot; anything else is a manual crop.
|
|
132
|
+
*/
|
|
133
|
+
const getRegionSubtype = (region: RectCoords) => {
|
|
134
|
+
const detected = useResultStore.getState().detectedRegions?.[0] || [];
|
|
135
|
+
return detected.some(entry => isSameRect(entry.normalizedRect, region))
|
|
136
|
+
? REGION_SUBTYPE.OBJECT_DETECTION_CLICK
|
|
137
|
+
: REGION_SUBTYPE.CROP;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
let findSearchGeneration = 0;
|
|
24
141
|
|
|
25
142
|
export const useImageSearch = () => {
|
|
26
143
|
const { user } = useAuth0();
|
|
27
144
|
|
|
28
145
|
const setRegions = useRequestStore(state => state.setRegions);
|
|
29
|
-
const query = useRequestStore(state => state.query);
|
|
30
146
|
|
|
31
147
|
const setRequestImages = useRequestStore(state => state.setRequestImages);
|
|
32
148
|
const setSpecificationFilter = useRequestStore(
|
|
@@ -72,6 +188,7 @@ export const useImageSearch = () => {
|
|
|
72
188
|
state => state.setGroundingFilterResult,
|
|
73
189
|
);
|
|
74
190
|
const setSmartFilters = useResultStore(state => state.setSmartFilters);
|
|
191
|
+
const setRequestQuality = useResultStore(state => state.setRequestQuality);
|
|
75
192
|
const setPostFilterFacets = useResultStore(
|
|
76
193
|
state => state.setPostFilterFacets,
|
|
77
194
|
);
|
|
@@ -97,6 +214,7 @@ export const useImageSearch = () => {
|
|
|
97
214
|
preFilterParams,
|
|
98
215
|
clearPostFilter,
|
|
99
216
|
text,
|
|
217
|
+
regionSelectedByUser = false,
|
|
100
218
|
}: {
|
|
101
219
|
image?: any;
|
|
102
220
|
settings: AppSettings;
|
|
@@ -107,8 +225,13 @@ export const useImageSearch = () => {
|
|
|
107
225
|
preFilterParams?: Record<string, boolean>;
|
|
108
226
|
clearPostFilter?: boolean;
|
|
109
227
|
text?: string;
|
|
228
|
+
/** True when the user just chose `imageRegion` (crop / detection dot). */
|
|
229
|
+
regionSelectedByUser?: boolean;
|
|
110
230
|
}) => {
|
|
231
|
+
const searchGeneration = ++findSearchGeneration;
|
|
232
|
+
const isCurrentSearch = () => searchGeneration === findSearchGeneration;
|
|
111
233
|
const isSmartFiltersEnabled = !!settings.useSmartFilters;
|
|
234
|
+
const isRequestQualityEnabled = !!settings.requestQuality;
|
|
112
235
|
setIsFindApiLoading(true);
|
|
113
236
|
// setAlgoliaProducts([]);
|
|
114
237
|
useRequestStore.getState().setGroundingQuery('');
|
|
@@ -133,6 +256,7 @@ export const useImageSearch = () => {
|
|
|
133
256
|
errorMessage: null,
|
|
134
257
|
});
|
|
135
258
|
}
|
|
259
|
+
setRequestQuality(null);
|
|
136
260
|
useRequestStore.getState().setGroundingQuery('');
|
|
137
261
|
useResultStore.getState().setShowingGroundingFilterResult(false);
|
|
138
262
|
startNewSession();
|
|
@@ -181,6 +305,7 @@ export const useImageSearch = () => {
|
|
|
181
305
|
canvasImage = await createImage(compressedBase64 || blob);
|
|
182
306
|
|
|
183
307
|
requestImage = await createImage(blob);
|
|
308
|
+
if (!isCurrentSearch()) return;
|
|
184
309
|
|
|
185
310
|
if (!imageRegion) {
|
|
186
311
|
setRequestImages([canvasImage]);
|
|
@@ -190,10 +315,12 @@ export const useImageSearch = () => {
|
|
|
190
315
|
if (!imageRegion) {
|
|
191
316
|
try {
|
|
192
317
|
let res = await findRegions(requestImage, settings);
|
|
318
|
+
if (!isCurrentSearch()) return;
|
|
193
319
|
setDetectedRegions(res.regions, 0);
|
|
194
320
|
region = res.selectedRegion;
|
|
195
321
|
setRegions([region]);
|
|
196
322
|
} catch (error) {}
|
|
323
|
+
if (!isCurrentSearch()) return;
|
|
197
324
|
}
|
|
198
325
|
}
|
|
199
326
|
|
|
@@ -213,6 +340,7 @@ export const useImageSearch = () => {
|
|
|
213
340
|
];
|
|
214
341
|
|
|
215
342
|
let smartFilterPromise: Promise<void> | null = null;
|
|
343
|
+
let requestQualityPromise: Promise<string | null> | null = null;
|
|
216
344
|
|
|
217
345
|
if (isSmartFiltersEnabled && requestImage && (newSearch || imageRegion)) {
|
|
218
346
|
setSmartFilters({
|
|
@@ -228,6 +356,7 @@ export const useImageSearch = () => {
|
|
|
228
356
|
const smartFilterImage = cropImage(requestImage, region);
|
|
229
357
|
smartFilterPromise = generateSmartFilters({ image: smartFilterImage })
|
|
230
358
|
.then(response => {
|
|
359
|
+
if (!isCurrentSearch()) return;
|
|
231
360
|
const rawFilters = response?.filters;
|
|
232
361
|
if (!rawFilters || typeof rawFilters !== 'object') {
|
|
233
362
|
setSmartFilters({
|
|
@@ -276,6 +405,7 @@ export const useImageSearch = () => {
|
|
|
276
405
|
});
|
|
277
406
|
})
|
|
278
407
|
.catch(error => {
|
|
408
|
+
if (!isCurrentSearch()) return;
|
|
279
409
|
const timeout = String(error?.message || '').includes(
|
|
280
410
|
'SMART_FILTER_TIMEOUT',
|
|
281
411
|
);
|
|
@@ -292,23 +422,82 @@ export const useImageSearch = () => {
|
|
|
292
422
|
});
|
|
293
423
|
});
|
|
294
424
|
}
|
|
425
|
+
if (
|
|
426
|
+
isRequestQualityEnabled &&
|
|
427
|
+
requestImage &&
|
|
428
|
+
(newSearch || imageRegion)
|
|
429
|
+
) {
|
|
430
|
+
const requestQualityImage = cropImage(requestImage, region);
|
|
431
|
+
const requestQualityBase64 = canvasToBase64(
|
|
432
|
+
requestQualityImage,
|
|
433
|
+
'image/jpeg',
|
|
434
|
+
1,
|
|
435
|
+
);
|
|
436
|
+
const parts: Array<{
|
|
437
|
+
text?: string;
|
|
438
|
+
inlineData?: { mimeType: string; data: string };
|
|
439
|
+
}> = [
|
|
440
|
+
{
|
|
441
|
+
text: 'data',
|
|
442
|
+
inlineData: {
|
|
443
|
+
mimeType: 'image/jpeg',
|
|
444
|
+
data: requestQualityBase64,
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
];
|
|
448
|
+
|
|
449
|
+
requestQualityPromise = chatAnalysis({
|
|
450
|
+
promptKey: 'request_quality',
|
|
451
|
+
multimodalInputs: parts.map(p =>
|
|
452
|
+
p.inlineData
|
|
453
|
+
? {
|
|
454
|
+
metadata: p.text || '',
|
|
455
|
+
mimeType: p.inlineData.mimeType,
|
|
456
|
+
image: p.inlineData.data,
|
|
457
|
+
}
|
|
458
|
+
: {
|
|
459
|
+
metadata: p.text || '',
|
|
460
|
+
},
|
|
461
|
+
),
|
|
462
|
+
})
|
|
463
|
+
.then(response => {
|
|
464
|
+
const description =
|
|
465
|
+
typeof response === 'string' ? response.trim() : '';
|
|
466
|
+
return description.length > 0 ? description : null;
|
|
467
|
+
})
|
|
468
|
+
.catch(() => null);
|
|
469
|
+
}
|
|
470
|
+
if (!requestImage) {
|
|
471
|
+
setRequestQuality(null);
|
|
472
|
+
}
|
|
295
473
|
|
|
474
|
+
const usesFindApiPostFilters =
|
|
475
|
+
!settings.algolia?.enabled && settings.postFilterOption;
|
|
476
|
+
// Crop/region finds replace results without sending postFilterSelections;
|
|
477
|
+
// clear stale selections so checkboxes match the new unfiltered grid.
|
|
296
478
|
const shouldResetPostFilters =
|
|
297
479
|
clearPostFilter ||
|
|
298
|
-
(
|
|
299
|
-
|
|
300
|
-
settings.postFilterOption);
|
|
480
|
+
(Boolean(imageRegion) && usesFindApiPostFilters) ||
|
|
481
|
+
(!requestImage && settings.postFilterOption);
|
|
301
482
|
|
|
302
483
|
if (shouldResetPostFilters) {
|
|
303
484
|
invalidatePostFilterRequests();
|
|
304
485
|
refine();
|
|
305
486
|
clearPostFilterSelections();
|
|
306
|
-
if (clearPostFilter) {
|
|
487
|
+
if (clearPostFilter || !requestImage) {
|
|
307
488
|
setPostFilterFacets([]);
|
|
308
489
|
setInitialFindSearchSnapshot(null);
|
|
309
490
|
}
|
|
310
491
|
}
|
|
311
492
|
|
|
493
|
+
const requestedText = isUndefined(text)
|
|
494
|
+
? useRequestStore.getState().query
|
|
495
|
+
: text;
|
|
496
|
+
const keyword =
|
|
497
|
+
typeof requestedText === 'string' && requestedText.length > 0
|
|
498
|
+
? requestedText
|
|
499
|
+
: undefined;
|
|
500
|
+
|
|
312
501
|
try {
|
|
313
502
|
res = await find({
|
|
314
503
|
image: requestImage,
|
|
@@ -325,26 +514,56 @@ export const useImageSearch = () => {
|
|
|
325
514
|
: undefined,
|
|
326
515
|
region,
|
|
327
516
|
text:
|
|
328
|
-
!
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
: text
|
|
517
|
+
!requestImage &&
|
|
518
|
+
(hasActiveMachineScope() || !window.settings.algolia.enabled)
|
|
519
|
+
? keyword
|
|
332
520
|
: undefined,
|
|
333
521
|
scopeIds: window.settings?.scopeFilter ? user?.email : undefined,
|
|
334
522
|
});
|
|
523
|
+
if (!isCurrentSearch()) return;
|
|
335
524
|
useResultStore.getState().resetGrounding();
|
|
336
525
|
|
|
337
526
|
setFindApiProducts(res?.results);
|
|
338
|
-
|
|
339
|
-
|
|
527
|
+
|
|
528
|
+
// Image: API facets if present, else derive from result.filters.
|
|
529
|
+
// Text: API facets only.
|
|
530
|
+
// Empty results with no facet payload may keep prior facet UI for unselect,
|
|
531
|
+
// but the search snapshot always updates to this response.
|
|
532
|
+
const isImageSearch = Boolean(requestImage);
|
|
533
|
+
const results = res?.results || [];
|
|
534
|
+
const apiFacets = Array.isArray(res?.facets) ? res.facets : [];
|
|
535
|
+
const nextFacets = isImageSearch
|
|
536
|
+
? apiFacets.length > 0
|
|
537
|
+
? apiFacets
|
|
538
|
+
: derivePostFilterFacetsFromResults(results)
|
|
539
|
+
: apiFacets;
|
|
540
|
+
const previousFacets = useResultStore.getState().postFilterFacets;
|
|
541
|
+
const shouldPreserveFacets =
|
|
542
|
+
results.length === 0 &&
|
|
543
|
+
nextFacets.length === 0 &&
|
|
544
|
+
Array.isArray(previousFacets) &&
|
|
545
|
+
previousFacets.length > 0;
|
|
546
|
+
|
|
547
|
+
// Facet UI may stay for recovery; snapshot always matches this search.
|
|
548
|
+
if (!shouldPreserveFacets) {
|
|
549
|
+
setPostFilterFacets(nextFacets);
|
|
550
|
+
}
|
|
340
551
|
setInitialFindSearchSnapshot({
|
|
341
|
-
results
|
|
342
|
-
postFilterFacets:
|
|
552
|
+
results,
|
|
553
|
+
postFilterFacets: nextFacets,
|
|
343
554
|
});
|
|
555
|
+
|
|
344
556
|
setImageAnalysis(res?.image_analysis);
|
|
345
557
|
setSessionId(res?.session);
|
|
346
558
|
setRequestId(res?.id);
|
|
347
559
|
|
|
560
|
+
if (regionSelectedByUser && imageRegion && res?.id) {
|
|
561
|
+
trackRegion(imageRegion, getRegionSubtype(imageRegion), {
|
|
562
|
+
requestId: res.id,
|
|
563
|
+
sessionId: res.session,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
348
567
|
const specification = res?.image_analysis?.specification || {};
|
|
349
568
|
const hasOcr = Object.keys(specification).length > 0;
|
|
350
569
|
|
|
@@ -363,6 +582,7 @@ export const useImageSearch = () => {
|
|
|
363
582
|
evaluateResults({
|
|
364
583
|
activeProducts: res?.results || [],
|
|
365
584
|
imageAnalysis: res?.image_analysis,
|
|
585
|
+
query: !requestImage ? keyword || '' : '',
|
|
366
586
|
});
|
|
367
587
|
}
|
|
368
588
|
const nonEmptyFilter: any[] = ['sku:DOES_NOT_EXIST<score=1> '];
|
|
@@ -374,7 +594,9 @@ export const useImageSearch = () => {
|
|
|
374
594
|
: '';
|
|
375
595
|
const filterSkusString = [...nonEmptyFilter, ...filterSkus].join('OR ');
|
|
376
596
|
|
|
377
|
-
|
|
597
|
+
if (!hasActiveMachineScope()) {
|
|
598
|
+
setAlgoliaFilter(filterSkusString);
|
|
599
|
+
}
|
|
378
600
|
setIsFindApiLoading(false);
|
|
379
601
|
|
|
380
602
|
if (showFeedback) {
|
|
@@ -390,12 +612,21 @@ export const useImageSearch = () => {
|
|
|
390
612
|
setFirstRequestImageAnalysis(res?.image_analysis);
|
|
391
613
|
}
|
|
392
614
|
} catch (error) {
|
|
393
|
-
|
|
615
|
+
if (isCurrentSearch()) {
|
|
616
|
+
setIsFindApiLoading(false);
|
|
617
|
+
}
|
|
394
618
|
}
|
|
395
619
|
|
|
396
620
|
if (smartFilterPromise) {
|
|
397
621
|
void smartFilterPromise;
|
|
398
622
|
}
|
|
623
|
+
if (requestQualityPromise) {
|
|
624
|
+
void requestQualityPromise.then(quality => {
|
|
625
|
+
if (isCurrentSearch()) {
|
|
626
|
+
setRequestQuality(quality);
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
}
|
|
399
630
|
|
|
400
631
|
return res;
|
|
401
632
|
},
|
|
@@ -409,7 +640,6 @@ export const useImageSearch = () => {
|
|
|
409
640
|
setDetectedRegions,
|
|
410
641
|
setRegions,
|
|
411
642
|
metaFilter,
|
|
412
|
-
query,
|
|
413
643
|
setFindApiProducts,
|
|
414
644
|
setImageAnalysis,
|
|
415
645
|
setSessionId,
|
|
@@ -426,11 +656,86 @@ export const useImageSearch = () => {
|
|
|
426
656
|
setFirstSearchPreFilter,
|
|
427
657
|
setFirstRequestImageAnalysis,
|
|
428
658
|
setSmartFilters,
|
|
659
|
+
setRequestQuality,
|
|
429
660
|
setPostFilterFacets,
|
|
430
661
|
setInitialFindSearchSnapshot,
|
|
431
662
|
user,
|
|
432
663
|
],
|
|
433
664
|
);
|
|
434
665
|
|
|
435
|
-
|
|
666
|
+
/**
|
|
667
|
+
* Scope-only itemKeys page. Reuses find(), but does not start a search:
|
|
668
|
+
* no Vizo session, no evaluateResults, and no first-search snapshot.
|
|
669
|
+
*/
|
|
670
|
+
const searchMachinePartsListing = useCallback(
|
|
671
|
+
async ({
|
|
672
|
+
text,
|
|
673
|
+
showFeedback = false,
|
|
674
|
+
clearPostFilters = false,
|
|
675
|
+
}: {
|
|
676
|
+
text: string;
|
|
677
|
+
showFeedback?: boolean;
|
|
678
|
+
clearPostFilters?: boolean;
|
|
679
|
+
}) => {
|
|
680
|
+
if (!text) {
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
const searchGeneration = ++findSearchGeneration;
|
|
684
|
+
const isCurrentSearch = () => searchGeneration === findSearchGeneration;
|
|
685
|
+
setIsFindApiLoading(true);
|
|
686
|
+
|
|
687
|
+
if (clearPostFilters) {
|
|
688
|
+
invalidatePostFilterRequests();
|
|
689
|
+
refine();
|
|
690
|
+
clearPostFilterSelections();
|
|
691
|
+
setPostFilterFacets([]);
|
|
692
|
+
setInitialFindSearchSnapshot(null);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
try {
|
|
696
|
+
const res: any = await find({
|
|
697
|
+
settings: window.settings,
|
|
698
|
+
text,
|
|
699
|
+
});
|
|
700
|
+
if (!isCurrentSearch()) {
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
const results = res?.results || [];
|
|
704
|
+
const facets = Array.isArray(res?.facets) ? res.facets : [];
|
|
705
|
+
setFindApiProducts(results);
|
|
706
|
+
setImageAnalysis(res?.image_analysis);
|
|
707
|
+
setSessionId(res?.session);
|
|
708
|
+
setRequestId(res?.id);
|
|
709
|
+
setPostFilterFacets(facets);
|
|
710
|
+
setInitialFindSearchSnapshot({
|
|
711
|
+
results,
|
|
712
|
+
postFilterFacets: facets,
|
|
713
|
+
});
|
|
714
|
+
useResultStore.getState().setShowingGroundingFilterResult(false);
|
|
715
|
+
setIsFindApiLoading(false);
|
|
716
|
+
if (showFeedback) {
|
|
717
|
+
setShowFeedback(true);
|
|
718
|
+
}
|
|
719
|
+
return res;
|
|
720
|
+
} catch {
|
|
721
|
+
if (isCurrentSearch()) {
|
|
722
|
+
setIsFindApiLoading(false);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
[
|
|
727
|
+
clearPostFilterSelections,
|
|
728
|
+
refine,
|
|
729
|
+
setFindApiProducts,
|
|
730
|
+
setImageAnalysis,
|
|
731
|
+
setInitialFindSearchSnapshot,
|
|
732
|
+
setIsFindApiLoading,
|
|
733
|
+
setPostFilterFacets,
|
|
734
|
+
setRequestId,
|
|
735
|
+
setSessionId,
|
|
736
|
+
setShowFeedback,
|
|
737
|
+
],
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
return { singleImageSearch, searchMachinePartsListing };
|
|
436
741
|
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'react';
|
|
2
|
+
import { useImageSearch } from 'hooks/useImageSearch';
|
|
3
|
+
import {
|
|
4
|
+
hasActivePostFilters,
|
|
5
|
+
isScopeOnlyMachinePartsListing,
|
|
6
|
+
machinePartsPageCount,
|
|
7
|
+
machinePartsPageText,
|
|
8
|
+
} from 'services/machinePartsPage';
|
|
9
|
+
import useMachinePartsListingStore from 'stores/machinePartsListing/machinePartsListingStore';
|
|
10
|
+
import useRequestStore from 'stores/request/requestStore';
|
|
11
|
+
import useScopeStore from 'stores/scope/scopeStore';
|
|
12
|
+
|
|
13
|
+
export function useMachinePartsListingPagination() {
|
|
14
|
+
const pageSize = useMachinePartsListingStore(state => state.pageSize);
|
|
15
|
+
const pageIndex = useMachinePartsListingStore(state => state.pageIndex);
|
|
16
|
+
const setPageSize = useMachinePartsListingStore(state => state.setPageSize);
|
|
17
|
+
const setPageIndex = useMachinePartsListingStore(state => state.setPageIndex);
|
|
18
|
+
const scopeId = useScopeStore(state => state.scopeId);
|
|
19
|
+
const itemKeys = useScopeStore(state => state.itemKeys);
|
|
20
|
+
const query = useRequestStore(state => state.query);
|
|
21
|
+
const requestImages = useRequestStore(state => state.requestImages);
|
|
22
|
+
const postFilterSelections = useRequestStore(
|
|
23
|
+
state => state.postFilterSelections,
|
|
24
|
+
);
|
|
25
|
+
const { searchMachinePartsListing } = useImageSearch();
|
|
26
|
+
|
|
27
|
+
const isListing = useMemo(
|
|
28
|
+
() =>
|
|
29
|
+
isScopeOnlyMachinePartsListing({
|
|
30
|
+
scopeEnabled: Boolean(window.settings.scopes?.scopeID),
|
|
31
|
+
scopeId,
|
|
32
|
+
itemKeyCount: itemKeys.length,
|
|
33
|
+
keyword: query || '',
|
|
34
|
+
hasSearchImage: requestImages.length > 0,
|
|
35
|
+
hasPostFilters: hasActivePostFilters(postFilterSelections),
|
|
36
|
+
}),
|
|
37
|
+
[itemKeys.length, postFilterSelections, query, requestImages.length, scopeId],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const pageCount = isListing
|
|
41
|
+
? machinePartsPageCount(itemKeys.length, pageSize)
|
|
42
|
+
: 0;
|
|
43
|
+
|
|
44
|
+
const searchPage = useCallback(
|
|
45
|
+
(nextPageIndex: number, nextPageSize: number) => {
|
|
46
|
+
const text = machinePartsPageText(
|
|
47
|
+
useScopeStore.getState().itemKeys || [],
|
|
48
|
+
nextPageIndex,
|
|
49
|
+
nextPageSize,
|
|
50
|
+
);
|
|
51
|
+
if (!text) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
void searchMachinePartsListing({ text });
|
|
55
|
+
},
|
|
56
|
+
[searchMachinePartsListing],
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const changePage = useCallback(
|
|
60
|
+
(nextPageIndex: number) => {
|
|
61
|
+
if (!isListing || nextPageIndex === pageIndex) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (nextPageIndex < 0 || nextPageIndex >= pageCount) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
setPageIndex(nextPageIndex);
|
|
68
|
+
searchPage(nextPageIndex, pageSize);
|
|
69
|
+
},
|
|
70
|
+
[isListing, pageCount, pageIndex, pageSize, searchPage, setPageIndex],
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const changePageSize = useCallback(
|
|
74
|
+
(nextPageSize: number) => {
|
|
75
|
+
if (nextPageSize === pageSize && pageIndex === 0) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
setPageSize(nextPageSize);
|
|
79
|
+
if (!isListing) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
searchPage(0, nextPageSize);
|
|
83
|
+
},
|
|
84
|
+
[isListing, pageIndex, pageSize, searchPage, setPageSize],
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
isListing,
|
|
89
|
+
pageCount,
|
|
90
|
+
pageIndex,
|
|
91
|
+
pageSize,
|
|
92
|
+
changePage,
|
|
93
|
+
changePageSize,
|
|
94
|
+
};
|
|
95
|
+
}
|