@nyris/nyris-webapp 0.3.98 → 0.4.0
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 +42 -0
- package/build/static/css/main.a7ff7a7f.css +4 -0
- package/build/static/css/main.a7ff7a7f.css.map +1 -0
- package/build/static/js/main.916c157c.js +3 -0
- package/build/static/js/{main.93fdbd6e.js.map → main.916c157c.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 +42 -0
- package/src/common/assets/images/plate_info.png +0 -0
- package/src/components/Cart.tsx +41 -0
- package/src/components/ChatAssistant/ChatAssistant.test.tsx +213 -0
- package/src/components/ChatAssistant/ChatAssistant.tsx +18 -1
- package/src/components/ChatAssistant/MobileChatAssistant.tsx +18 -1
- package/src/components/ChatAssistant/OptionChip.tsx +23 -1
- package/src/components/ChatAssistant/useChatAssistantLogic.ts +35 -14
- 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 +12 -1
- package/src/components/Inquiry/InquiryModal.tsx +61 -26
- package/src/components/Pagination.tsx +33 -6
- package/src/components/PostFilter/PostFilter.tsx +8 -1
- 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 -18
- 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 +33 -0
- package/src/components/TextSearch.tsx +77 -11
- package/src/components/Tooltip/TooltipComponent.tsx +12 -4
- package/src/components/TypePlateUpload.tsx +132 -0
- package/src/hooks/chatProductMetadata.test.ts +307 -0
- package/src/hooks/useBadResultsRecovery.ts +26 -49
- package/src/hooks/useEffectiveGroundingResults.ts +8 -6
- package/src/hooks/useGoodResultsChat.ts +28 -39
- package/src/hooks/useImageSearch.ts +290 -12
- package/src/hooks/useMachinePartsListingPagination.ts +95 -0
- package/src/hooks/useResultEvaluator.ts +63 -73
- package/src/index.css +1 -1
- package/src/layouts/AppLayout.tsx +43 -3
- package/src/pages/Home.tsx +60 -14
- package/src/pages/Result.tsx +194 -14
- package/src/services/Feedback.ts +225 -34
- package/src/services/feedbackSubtypes.ts +63 -0
- package/src/services/filter.ts +1 -1
- 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 +71 -3
- package/src/services/vizo.ts +4 -3
- package/src/stores/chat/conversationStore.test.ts +133 -0
- package/src/stores/chat/conversationStore.ts +55 -14
- 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 +41 -0
- package/src/utils/chatProductMetadata.test.ts +130 -0
- package/src/utils/chatProductMetadata.ts +93 -0
- package/src/utils/compressImage.ts +3 -3
- package/src/utils/fetchProductImage.ts +1 -0
- package/src/utils/normalizeChatAssistantResponse.ts +227 -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.81216c5d.css +0 -4
- package/build/static/css/main.81216c5d.css.map +0 -1
- package/build/static/js/main.93fdbd6e.js +0 -3
- /package/build/static/js/{main.93fdbd6e.js.LICENSE.txt → main.916c157c.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import useConversationStore from './conversationStore';
|
|
2
|
+
|
|
3
|
+
beforeEach(() => {
|
|
4
|
+
window.settings = {
|
|
5
|
+
vizo: { requestSupport: true },
|
|
6
|
+
support: { emailInquiry: true, emailTemplateId: 'support-template' },
|
|
7
|
+
} as typeof window.settings;
|
|
8
|
+
useConversationStore.getState().reset();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const sendUserMessages = (count: number) => {
|
|
12
|
+
for (let index = 0; index < count; index += 1) {
|
|
13
|
+
useConversationStore.getState().addMessage({
|
|
14
|
+
role: 'user',
|
|
15
|
+
content: `Still searching ${index}`,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const addApiResponse = () => {
|
|
21
|
+
useConversationStore.getState().addMessage(
|
|
22
|
+
{
|
|
23
|
+
role: 'assistant',
|
|
24
|
+
content: 'Let us keep looking.',
|
|
25
|
+
options: [{ label: 'Continue search', action_type: 'filter' }],
|
|
26
|
+
},
|
|
27
|
+
{ isApiResponse: true },
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
test('offers support within the third API reply, once per session', () => {
|
|
32
|
+
sendUserMessages(2);
|
|
33
|
+
addApiResponse();
|
|
34
|
+
expect(useConversationStore.getState().supportPromptShown).toBe(false);
|
|
35
|
+
|
|
36
|
+
sendUserMessages(1);
|
|
37
|
+
expect(useConversationStore.getState().supportPromptShown).toBe(false);
|
|
38
|
+
addApiResponse();
|
|
39
|
+
const state = useConversationStore.getState();
|
|
40
|
+
expect(state.userMessageCount).toBe(3);
|
|
41
|
+
expect(state.messages).toHaveLength(5);
|
|
42
|
+
expect(state.messages[state.messages.length - 1]).toMatchObject({
|
|
43
|
+
role: 'assistant',
|
|
44
|
+
content: 'Let us keep looking.',
|
|
45
|
+
options: [
|
|
46
|
+
{ label: 'Continue search', action_type: 'filter' },
|
|
47
|
+
{ label: 'Request support', action_type: 'request_support' },
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
sendUserMessages(2);
|
|
52
|
+
addApiResponse();
|
|
53
|
+
expect(
|
|
54
|
+
useConversationStore
|
|
55
|
+
.getState()
|
|
56
|
+
.messages.filter(message =>
|
|
57
|
+
message.options?.some(
|
|
58
|
+
option => option.action_type === 'request_support',
|
|
59
|
+
),
|
|
60
|
+
),
|
|
61
|
+
).toHaveLength(1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('waits for a response and excludes assistant and empty user messages', () => {
|
|
65
|
+
const store = useConversationStore.getState();
|
|
66
|
+
store.addMessage({ role: 'assistant', content: 'How can I help?' });
|
|
67
|
+
store.addMessage({ role: 'user', content: ' ' });
|
|
68
|
+
expect(useConversationStore.getState().userMessageCount).toBe(0);
|
|
69
|
+
sendUserMessages(3);
|
|
70
|
+
store.setIsLoading(true);
|
|
71
|
+
expect(useConversationStore.getState().supportPromptShown).toBe(false);
|
|
72
|
+
store.setIsLoading(false);
|
|
73
|
+
expect(useConversationStore.getState().supportPromptShown).toBe(false);
|
|
74
|
+
store.addMessage({ role: 'assistant', content: 'Something went wrong.' });
|
|
75
|
+
expect(useConversationStore.getState().supportPromptShown).toBe(false);
|
|
76
|
+
addApiResponse();
|
|
77
|
+
expect(useConversationStore.getState().supportPromptShown).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('does not offer support after a successful outcome', () => {
|
|
81
|
+
sendUserMessages(3);
|
|
82
|
+
useConversationStore.getState().setSearchResolved(true);
|
|
83
|
+
addApiResponse();
|
|
84
|
+
expect(useConversationStore.getState().supportPromptShown).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('offers support on the third reply when recovery exits without another API call', () => {
|
|
88
|
+
sendUserMessages(3);
|
|
89
|
+
useConversationStore.getState().addMessage({
|
|
90
|
+
role: 'assistant',
|
|
91
|
+
content: 'I was not able to find a match.',
|
|
92
|
+
action: { type: 'exit' },
|
|
93
|
+
});
|
|
94
|
+
const state = useConversationStore.getState();
|
|
95
|
+
expect(state.supportPromptShown).toBe(true);
|
|
96
|
+
expect(state.messages[state.messages.length - 1].options).toContainEqual({
|
|
97
|
+
label: 'Request support',
|
|
98
|
+
action_type: 'request_support',
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('a new session resets message counting and allows another support prompt', () => {
|
|
103
|
+
sendUserMessages(3);
|
|
104
|
+
addApiResponse();
|
|
105
|
+
useConversationStore.getState().startNewSession();
|
|
106
|
+
expect(useConversationStore.getState()).toMatchObject({
|
|
107
|
+
userMessageCount: 0,
|
|
108
|
+
supportPromptShown: false,
|
|
109
|
+
searchResolved: false,
|
|
110
|
+
});
|
|
111
|
+
sendUserMessages(3);
|
|
112
|
+
addApiResponse();
|
|
113
|
+
expect(useConversationStore.getState().supportPromptShown).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('does not offer support when vizo.requestSupport is disabled', () => {
|
|
117
|
+
window.settings.vizo!.requestSupport = false;
|
|
118
|
+
sendUserMessages(3);
|
|
119
|
+
addApiResponse();
|
|
120
|
+
const state = useConversationStore.getState();
|
|
121
|
+
expect(state.supportPromptShown).toBe(false);
|
|
122
|
+
expect(state.messages[state.messages.length - 1].options).not.toContainEqual(
|
|
123
|
+
expect.objectContaining({ action_type: 'request_support' }),
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('offers support even when the inquiry banner and email template are disabled', () => {
|
|
128
|
+
window.settings.support.emailInquiry = false;
|
|
129
|
+
window.settings.support.emailTemplateId = '';
|
|
130
|
+
sendUserMessages(3);
|
|
131
|
+
addApiResponse();
|
|
132
|
+
expect(useConversationStore.getState().supportPromptShown).toBe(true);
|
|
133
|
+
});
|
|
@@ -39,7 +39,8 @@ export interface QuickReplyOption {
|
|
|
39
39
|
| 'restore_previous'
|
|
40
40
|
| 'related_part'
|
|
41
41
|
| 'find_related_parts'
|
|
42
|
-
| 'restore_original'
|
|
42
|
+
| 'restore_original'
|
|
43
|
+
| 'request_support';
|
|
43
44
|
value?: string;
|
|
44
45
|
key?: string;
|
|
45
46
|
}
|
|
@@ -72,7 +73,7 @@ export interface RelatedPartSuggestion {
|
|
|
72
73
|
interface ConversationState {
|
|
73
74
|
// Session state
|
|
74
75
|
sessionId: string;
|
|
75
|
-
isEvaluated: boolean;
|
|
76
|
+
isEvaluated: boolean; // P2 has fired
|
|
76
77
|
// Routing
|
|
77
78
|
route: ConversationRoute;
|
|
78
79
|
quality: ResultQuality;
|
|
@@ -84,6 +85,9 @@ interface ConversationState {
|
|
|
84
85
|
// Conversation
|
|
85
86
|
messages: ChatMessage[];
|
|
86
87
|
activeFilters: ActiveFilter[];
|
|
88
|
+
userMessageCount: number;
|
|
89
|
+
supportPromptShown: boolean;
|
|
90
|
+
searchResolved: boolean;
|
|
87
91
|
|
|
88
92
|
// UI state
|
|
89
93
|
isOpen: boolean;
|
|
@@ -126,8 +130,12 @@ interface ConversationActions {
|
|
|
126
130
|
) => void;
|
|
127
131
|
|
|
128
132
|
// Messages
|
|
129
|
-
addMessage: (
|
|
133
|
+
addMessage: (
|
|
134
|
+
message: Omit<ChatMessage, 'id' | 'timestamp'>,
|
|
135
|
+
options?: { isApiResponse?: boolean },
|
|
136
|
+
) => void;
|
|
130
137
|
clearMessages: () => void;
|
|
138
|
+
setSearchResolved: (resolved: boolean) => void;
|
|
131
139
|
|
|
132
140
|
// Filters (for P3)
|
|
133
141
|
addFilter: (key: string, value: string) => void;
|
|
@@ -172,6 +180,9 @@ const initialState: ConversationState = {
|
|
|
172
180
|
attemptCounter: 0,
|
|
173
181
|
messages: [],
|
|
174
182
|
activeFilters: [],
|
|
183
|
+
userMessageCount: 0,
|
|
184
|
+
supportPromptShown: false,
|
|
185
|
+
searchResolved: false,
|
|
175
186
|
isOpen: false, // Start closed so left panel is open by default
|
|
176
187
|
hasNotification: false,
|
|
177
188
|
isLoading: false,
|
|
@@ -240,20 +251,50 @@ const useConversationStore = create<ConversationState & ConversationActions>()(
|
|
|
240
251
|
}));
|
|
241
252
|
},
|
|
242
253
|
|
|
243
|
-
addMessage: message =>
|
|
244
|
-
set(state =>
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
+
addMessage: (message, { isApiResponse = false } = {}) =>
|
|
255
|
+
set(state => {
|
|
256
|
+
const shouldOfferSupport =
|
|
257
|
+
window.settings?.vizo?.requestSupport &&
|
|
258
|
+
(isApiResponse || message.action?.type === 'exit') &&
|
|
259
|
+
message.role === 'assistant' &&
|
|
260
|
+
state.userMessageCount > 2 &&
|
|
261
|
+
!state.supportPromptShown &&
|
|
262
|
+
!state.searchResolved;
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
userMessageCount:
|
|
266
|
+
state.userMessageCount +
|
|
267
|
+
(message.role === 'user' &&
|
|
268
|
+
(message.content.trim() || message.imageUrl)
|
|
269
|
+
? 1
|
|
270
|
+
: 0),
|
|
271
|
+
supportPromptShown: state.supportPromptShown || shouldOfferSupport,
|
|
272
|
+
messages: [
|
|
273
|
+
...state.messages,
|
|
274
|
+
{
|
|
275
|
+
...message,
|
|
276
|
+
id: generateId(),
|
|
277
|
+
timestamp: Date.now(),
|
|
278
|
+
...(shouldOfferSupport
|
|
279
|
+
? {
|
|
280
|
+
options: [
|
|
281
|
+
...(message.options || []),
|
|
282
|
+
{
|
|
283
|
+
label: 'Request support',
|
|
284
|
+
action_type: 'request_support' as const,
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
}
|
|
288
|
+
: {}),
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
};
|
|
292
|
+
}),
|
|
254
293
|
|
|
255
294
|
clearMessages: () => set({ messages: [] }),
|
|
256
295
|
|
|
296
|
+
setSearchResolved: searchResolved => set({ searchResolved }),
|
|
297
|
+
|
|
257
298
|
addFilter: (key, value) =>
|
|
258
299
|
set(state => ({
|
|
259
300
|
activeFilters: [
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_MACHINE_PARTS_PAGE_SIZE,
|
|
4
|
+
machinePartsPageText,
|
|
5
|
+
} from 'services/machinePartsPage';
|
|
6
|
+
import useScopeStore from 'stores/scope/scopeStore';
|
|
7
|
+
|
|
8
|
+
interface MachinePartsListingState {
|
|
9
|
+
pageSize: number;
|
|
10
|
+
pageIndex: number;
|
|
11
|
+
setPageSize: (pageSize: number) => void;
|
|
12
|
+
setPageIndex: (pageIndex: number) => void;
|
|
13
|
+
resetPage: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const useMachinePartsListingStore = create<MachinePartsListingState>()(set => ({
|
|
17
|
+
pageSize: DEFAULT_MACHINE_PARTS_PAGE_SIZE,
|
|
18
|
+
pageIndex: 0,
|
|
19
|
+
setPageSize: pageSize => set({ pageSize, pageIndex: 0 }),
|
|
20
|
+
setPageIndex: pageIndex => set({ pageIndex }),
|
|
21
|
+
resetPage: () => set({ pageIndex: 0 }),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
/** Scope-only listing restarts at page 1 and keeps the selected page size. */
|
|
25
|
+
export function firstMachinePartsListingText(): string | undefined {
|
|
26
|
+
useMachinePartsListingStore.getState().resetPage();
|
|
27
|
+
const { pageSize } = useMachinePartsListingStore.getState();
|
|
28
|
+
return machinePartsPageText(
|
|
29
|
+
useScopeStore.getState().itemKeys || [],
|
|
30
|
+
0,
|
|
31
|
+
pageSize,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default useMachinePartsListingStore;
|
|
@@ -13,6 +13,8 @@ const filterSlice: StateCreator<FilterState & FilterAction> = set => ({
|
|
|
13
13
|
setPreFilterList: filter => set(() => ({ preFilterList: filter })),
|
|
14
14
|
setPreFilterLoading: isLoading =>
|
|
15
15
|
set(() => ({ preFilterLoading: isLoading })),
|
|
16
|
+
setTypePlatePreFilterValues: values =>
|
|
17
|
+
set(() => ({ typePlatePreFilterValues: values })),
|
|
16
18
|
setPostFilterSelections: filters =>
|
|
17
19
|
set(() => ({ postFilterSelections: filters })),
|
|
18
20
|
togglePostFilterSelection: (attribute, value) =>
|
|
@@ -61,6 +61,10 @@ const productsSlice: StateCreator<ProductsState & ProductsAction> = set => ({
|
|
|
61
61
|
set(() => ({
|
|
62
62
|
smartFilters,
|
|
63
63
|
})),
|
|
64
|
+
setRequestQuality: requestQuality =>
|
|
65
|
+
set(() => ({
|
|
66
|
+
requestQuality,
|
|
67
|
+
})),
|
|
64
68
|
setPostFilterFacets: facets =>
|
|
65
69
|
set(() => ({
|
|
66
70
|
postFilterFacets: facets,
|
|
@@ -1,35 +1,98 @@
|
|
|
1
1
|
import { create } from 'zustand';
|
|
2
2
|
import { Filter } from '@nyris/nyris-api';
|
|
3
3
|
|
|
4
|
+
export type ScopeSource = 'manual' | 'image';
|
|
5
|
+
|
|
6
|
+
export type AppliedScopeInput =
|
|
7
|
+
| { source: 'manual'; value: string }
|
|
8
|
+
| { source: 'image'; fileKey: string };
|
|
9
|
+
|
|
4
10
|
interface ScopeState {
|
|
5
11
|
itemKeys: string[];
|
|
6
12
|
filterAttributes: Filter[];
|
|
13
|
+
scopeId: string | null;
|
|
7
14
|
isLoading: boolean;
|
|
8
15
|
hasFetched: boolean;
|
|
16
|
+
formResetKey: number;
|
|
17
|
+
manualScopeId: string;
|
|
18
|
+
pendingFile: File | null;
|
|
19
|
+
previewUrl: string | null;
|
|
20
|
+
activeSource: ScopeSource | null;
|
|
21
|
+
lastAppliedInput: AppliedScopeInput | null;
|
|
22
|
+
lastExtractedFromImage: string;
|
|
9
23
|
}
|
|
10
24
|
|
|
11
25
|
interface ScopeAction {
|
|
12
|
-
setScope: (
|
|
26
|
+
setScope: (
|
|
27
|
+
itemKeys: string[],
|
|
28
|
+
filterAttributes: Filter[],
|
|
29
|
+
scopeId?: string | null,
|
|
30
|
+
) => void;
|
|
13
31
|
setScopeLoading: (isLoading: boolean) => void;
|
|
14
32
|
setScopeFetched: (hasFetched: boolean) => void;
|
|
33
|
+
setManualScopeId: (manualScopeId: string) => void;
|
|
34
|
+
setPendingFile: (pendingFile: File | null) => void;
|
|
35
|
+
setPreviewUrl: (previewUrl: string | null) => void;
|
|
36
|
+
setActiveSource: (activeSource: ScopeSource | null) => void;
|
|
37
|
+
setLastAppliedInput: (lastAppliedInput: AppliedScopeInput | null) => void;
|
|
38
|
+
setLastExtractedFromImage: (lastExtractedFromImage: string) => void;
|
|
15
39
|
resetScope: () => void;
|
|
16
40
|
}
|
|
17
41
|
|
|
18
42
|
const initialState: ScopeState = {
|
|
19
43
|
itemKeys: [],
|
|
20
44
|
filterAttributes: [],
|
|
45
|
+
scopeId: null,
|
|
21
46
|
isLoading: false,
|
|
22
47
|
hasFetched: false,
|
|
48
|
+
formResetKey: 0,
|
|
49
|
+
manualScopeId: '',
|
|
50
|
+
pendingFile: null,
|
|
51
|
+
previewUrl: null,
|
|
52
|
+
activeSource: null,
|
|
53
|
+
lastAppliedInput: null,
|
|
54
|
+
lastExtractedFromImage: '',
|
|
23
55
|
};
|
|
24
56
|
|
|
25
57
|
const useScopeStore = create<ScopeState & ScopeAction>()(set => ({
|
|
26
58
|
...initialState,
|
|
27
59
|
|
|
28
|
-
setScope: (itemKeys, filterAttributes) =>
|
|
29
|
-
set({ itemKeys, filterAttributes, hasFetched: true }),
|
|
60
|
+
setScope: (itemKeys, filterAttributes, scopeId = null) =>
|
|
61
|
+
set({ itemKeys, filterAttributes, scopeId, hasFetched: true }),
|
|
30
62
|
setScopeLoading: isLoading => set({ isLoading }),
|
|
31
63
|
setScopeFetched: hasFetched => set({ hasFetched }),
|
|
32
|
-
|
|
64
|
+
setManualScopeId: manualScopeId => set({ manualScopeId }),
|
|
65
|
+
setPendingFile: pendingFile => set({ pendingFile }),
|
|
66
|
+
setPreviewUrl: previewUrl => set({ previewUrl }),
|
|
67
|
+
setActiveSource: activeSource => set({ activeSource }),
|
|
68
|
+
setLastAppliedInput: lastAppliedInput => set({ lastAppliedInput }),
|
|
69
|
+
setLastExtractedFromImage: lastExtractedFromImage =>
|
|
70
|
+
set({ lastExtractedFromImage }),
|
|
71
|
+
resetScope: () =>
|
|
72
|
+
set(state => {
|
|
73
|
+
if (state.previewUrl) {
|
|
74
|
+
try {
|
|
75
|
+
URL.revokeObjectURL(state.previewUrl);
|
|
76
|
+
} catch {}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
...initialState,
|
|
80
|
+
formResetKey: state.formResetKey + 1,
|
|
81
|
+
};
|
|
82
|
+
}),
|
|
33
83
|
}));
|
|
34
84
|
|
|
85
|
+
/** Machine scope that should constrain the results currently on screen. */
|
|
86
|
+
export function hasActiveMachineScope(): boolean {
|
|
87
|
+
if (!window.settings?.scopes?.scopeID) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const { scopeId, itemKeys } = useScopeStore.getState();
|
|
91
|
+
return (
|
|
92
|
+
typeof scopeId === 'string' &&
|
|
93
|
+
scopeId.trim().length > 0 &&
|
|
94
|
+
itemKeys.length > 0
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
35
98
|
export default useScopeStore;
|
package/src/stores/types.ts
CHANGED
|
@@ -71,20 +71,21 @@ export interface ProductsState {
|
|
|
71
71
|
groundingError: string | null;
|
|
72
72
|
firstAlgoliaProducts: any[];
|
|
73
73
|
fullResultPool: any[];
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
74
|
+
smartFilters: {
|
|
75
|
+
status: 'idle' | 'loading' | 'success' | 'error';
|
|
76
|
+
filters: Array<{
|
|
77
|
+
key: string;
|
|
78
|
+
label: string;
|
|
79
|
+
value: string;
|
|
80
|
+
selected?: boolean;
|
|
81
|
+
}>;
|
|
82
|
+
selectedFilterKey: string | null;
|
|
83
|
+
selectedFilterValue: string | null;
|
|
84
|
+
rerankedSkus: string[];
|
|
85
|
+
rerankStatus: 'idle' | 'loading' | 'success' | 'error';
|
|
86
|
+
errorMessage: string | null;
|
|
87
|
+
};
|
|
88
|
+
requestQuality: string | null;
|
|
88
89
|
postFilterFacets: any[];
|
|
89
90
|
initialFindSearchSnapshot: {
|
|
90
91
|
results: any[];
|
|
@@ -110,6 +111,7 @@ export interface ProductsAction {
|
|
|
110
111
|
setFullResultPool: (products: any[]) => void;
|
|
111
112
|
resetGrounding: () => void;
|
|
112
113
|
setSmartFilters: (smartFilters: ProductsState['smartFilters']) => void;
|
|
114
|
+
setRequestQuality: (requestQuality: string | null) => void;
|
|
113
115
|
setPostFilterFacets: (facets: any[]) => void;
|
|
114
116
|
setInitialFindSearchSnapshot: (
|
|
115
117
|
snapshot: ProductsState['initialFindSearchSnapshot'],
|
|
@@ -132,6 +134,8 @@ export interface FilterState {
|
|
|
132
134
|
specificationFilter: Record<string, string>;
|
|
133
135
|
postFilterSelections: Record<string, string[]>;
|
|
134
136
|
preFilterLoading: boolean;
|
|
137
|
+
// Values applied from type-plate upload; cleared independently of manual picks.
|
|
138
|
+
typePlatePreFilterValues: string[];
|
|
135
139
|
}
|
|
136
140
|
|
|
137
141
|
export interface FilterAction {
|
|
@@ -144,6 +148,7 @@ export interface FilterAction {
|
|
|
144
148
|
togglePostFilterSelection: (attribute: string, value: string) => void;
|
|
145
149
|
clearPostFilterSelections: () => void;
|
|
146
150
|
setPreFilterLoading: (isLoading: boolean) => void;
|
|
151
|
+
setTypePlatePreFilterValues: (values: string[]) => void;
|
|
147
152
|
}
|
|
148
153
|
|
|
149
154
|
export interface MiscState {
|