@nyris/nyris-webapp 0.3.90 → 0.3.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/build/asset-manifest.json +6 -6
  2. package/build/data/related-parts.json +83 -0
  3. package/build/index.html +1 -1
  4. package/build/js/settings.example.js +3 -0
  5. package/build/static/css/main.5ea01690.css +4 -0
  6. package/build/static/css/main.5ea01690.css.map +1 -0
  7. package/build/static/js/main.36b77705.js +3 -0
  8. package/build/static/js/{main.cede3ae1.js.map → main.36b77705.js.map} +1 -1
  9. package/package.json +4 -3
  10. package/public/data/related-parts.json +83 -0
  11. package/public/js/settings.example.js +3 -0
  12. package/src/App.test.tsx +0 -1
  13. package/src/App.tsx +0 -1
  14. package/src/assets/arrow_down_expanded.svg +3 -0
  15. package/src/assets/arrow_enter.svg +3 -0
  16. package/src/assets/camera.svg +3 -0
  17. package/src/assets/close.svg +3 -0
  18. package/src/assets/enter.svg +3 -0
  19. package/src/assets/refresh.svg +3 -0
  20. package/src/assets/vizo_avatar.svg +16 -0
  21. package/src/components/Cadenas/CadenasWebViewer.tsx +1 -1
  22. package/src/components/Cart.tsx +48 -36
  23. package/src/components/ChatAssistant/ChatAssistant.tsx +289 -0
  24. package/src/components/ChatAssistant/MobileChatAssistant.tsx +291 -0
  25. package/src/components/ChatAssistant/OptionChip.tsx +78 -0
  26. package/src/components/ChatAssistant/index.ts +3 -0
  27. package/src/components/ChatAssistant/useChatAssistantLogic.ts +745 -0
  28. package/src/components/CurrentRefinements.tsx +2 -2
  29. package/src/components/CustomCameraDrawer.tsx +56 -13
  30. package/src/components/DragDropFile.tsx +5 -5
  31. package/src/components/ExperienceVisualSearch/ExperienceVisualSearch.tsx +1 -1
  32. package/src/components/Header.tsx +116 -96
  33. package/src/components/Hint.tsx +1 -2
  34. package/src/components/HitsPerPage.tsx +9 -3
  35. package/src/components/ImagePreview.tsx +32 -17
  36. package/src/components/ImageUpload.tsx +16 -8
  37. package/src/components/Inquiry/InquiryBanner.tsx +1 -1
  38. package/src/components/Inquiry/InquiryModal.tsx +35 -29
  39. package/src/components/ItemSpecification.tsx +58 -126
  40. package/src/components/LocationInfoPopup.tsx +33 -33
  41. package/src/components/MatchNotificationBanner.tsx +90 -36
  42. package/src/components/PostFilter/PostFilter.tsx +1 -1
  43. package/src/components/PostFilter/PostFilterComponent.tsx +0 -1
  44. package/src/components/PostFilter/PostFilterFindApi.tsx +0 -1
  45. package/src/components/PoweredBy.tsx +1 -1
  46. package/src/components/PreFilter/PreFilter.tsx +14 -3
  47. package/src/components/PreFilter/PreFilterModal.tsx +0 -1
  48. package/src/components/Product/Product.tsx +15 -11
  49. package/src/components/Product/ProductAttribute.tsx +4 -5
  50. package/src/components/Product/ProductDetailViewModal.tsx +2 -4
  51. package/src/components/Product/ProductList.tsx +26 -13
  52. package/src/components/Rfq/RfqModal.tsx +1 -1
  53. package/src/components/SidePanel.tsx +124 -91
  54. package/src/components/SmartFilter.tsx +320 -0
  55. package/src/components/TextSearch.tsx +134 -70
  56. package/src/components/UploadDisclaimer.tsx +1 -1
  57. package/src/hooks/useBadResultsRecovery.ts +407 -0
  58. package/src/hooks/useEffectiveGroundingResults.ts +54 -0
  59. package/src/hooks/useGoodResultsChat.ts +651 -0
  60. package/src/hooks/useGroundedSearch.ts +88 -0
  61. package/src/hooks/useImageSearch.ts +139 -187
  62. package/src/hooks/useResultEvaluator.ts +417 -0
  63. package/src/index.css +1 -1
  64. package/src/index.tsx +0 -1
  65. package/src/layouts/AppLayout.tsx +53 -2
  66. package/src/pages/Home.tsx +11 -52
  67. package/src/pages/Login.tsx +1 -2
  68. package/src/pages/Logout.tsx +1 -1
  69. package/src/pages/Result.tsx +198 -200
  70. package/src/providers/AuthProvider.tsx +0 -1
  71. package/src/services/Feedback.ts +1 -1
  72. package/src/services/visualSearch.ts +0 -21
  73. package/src/services/vizo.ts +192 -4
  74. package/src/stores/chat/chatStore.ts +150 -0
  75. package/src/stores/chat/conversationStore.ts +300 -0
  76. package/src/stores/request/Misc/misc.slice.ts +2 -2
  77. package/src/stores/request/filter/filter.slice.ts +8 -8
  78. package/src/stores/request/query/query.slice.ts +2 -2
  79. package/src/stores/request/requestImage/requestImage.slice.ts +6 -6
  80. package/src/stores/request/specifications/specifications.slice.ts +10 -7
  81. package/src/stores/result/detectedRegions/detectedRegions.slice.ts +1 -1
  82. package/src/stores/result/prodcuts/products.initialState.ts +12 -0
  83. package/src/stores/result/prodcuts/products.slice.ts +28 -8
  84. package/src/stores/result/session/session.slice.ts +2 -2
  85. package/src/stores/smartFilters/smartFiltersStore.ts +270 -0
  86. package/src/stores/types.ts +41 -0
  87. package/src/stores/ui/ai/ai.initialState.ts +5 -0
  88. package/src/stores/ui/ai/ai.slice.ts +15 -0
  89. package/src/stores/ui/banner/banner.initialState.ts +6 -0
  90. package/src/stores/ui/banner/banner.slice.ts +14 -0
  91. package/src/stores/ui/feedback/feedback.slice.ts +1 -1
  92. package/src/stores/ui/loading/loading.slice.ts +4 -4
  93. package/src/stores/ui/uiStore.ts +7 -1
  94. package/src/styles/product.scss +0 -2
  95. package/src/types.ts +3 -7
  96. package/src/utils/cropImageToBase64.ts +32 -0
  97. package/src/utils/fetchProductImage.ts +109 -0
  98. package/src/utils/imageConverters.ts +124 -0
  99. package/src/utils/relatedParts.ts +35 -0
  100. package/src/utils/specificationFilter.ts +1 -5
  101. package/tailwind.config.js +3 -2
  102. package/build/static/css/main.734b52e1.css +0 -4
  103. package/build/static/css/main.734b52e1.css.map +0 -1
  104. package/build/static/js/main.cede3ae1.js +0 -3
  105. package/src/utils/addAssets.ts +0 -40
  106. /package/build/static/js/{main.cede3ae1.js.LICENSE.txt → main.36b77705.js.LICENSE.txt} +0 -0
@@ -4,17 +4,17 @@ import { initialState } from './filter.initialState';
4
4
 
5
5
  const filterSlice: StateCreator<FilterState & FilterAction> = set => ({
6
6
  ...initialState,
7
- setAlgoliaFilter: filter => set(state => ({ algoliaFilter: filter })),
8
- setPreFilter: filter => set(state => ({ preFilter: filter })),
7
+ setAlgoliaFilter: filter => set(() => ({ algoliaFilter: filter })),
8
+ setPreFilter: filter => set(() => ({ preFilter: filter })),
9
9
  setFirstSearchPreFilter: filter =>
10
- set(state => ({ firstSearchPreFilter: filter })),
10
+ set(() => ({ firstSearchPreFilter: filter })),
11
11
  setSpecificationFilter: filter =>
12
- set(state => ({ specificationFilter: filter })),
13
- setPreFilterList: filter => set(state => ({ preFilterList: filter })),
12
+ set(() => ({ specificationFilter: filter })),
13
+ setPreFilterList: filter => set(() => ({ preFilterList: filter })),
14
14
  setPreFilterLoading: isLoading =>
15
- set(state => ({ preFilterLoading: isLoading })),
15
+ set(() => ({ preFilterLoading: isLoading })),
16
16
  setPostFilterSelections: filters =>
17
- set(state => ({ postFilterSelections: filters })),
17
+ set(() => ({ postFilterSelections: filters })),
18
18
  togglePostFilterSelection: (attribute, value) =>
19
19
  set(state => {
20
20
  const current = state.postFilterSelections?.[attribute] || [];
@@ -32,7 +32,7 @@ const filterSlice: StateCreator<FilterState & FilterAction> = set => ({
32
32
 
33
33
  return { postFilterSelections: nextSelections };
34
34
  }),
35
- clearPostFilterSelections: () => set(state => ({ postFilterSelections: {} })),
35
+ clearPostFilterSelections: () => set(() => ({ postFilterSelections: {} })),
36
36
  });
37
37
 
38
38
  export default filterSlice;
@@ -4,8 +4,8 @@ import { initialState } from './query.initialState';
4
4
 
5
5
  const querySlice: StateCreator<QueryState & QueryAction> = set => ({
6
6
  ...initialState,
7
- setQuery: query => set(state => ({ query: query })),
8
- setValueInput: value => set(state => ({ valueInput: value })),
7
+ setQuery: query => set(() => ({ query: query })),
8
+ setValueInput: value => set(() => ({ valueInput: value })),
9
9
  });
10
10
 
11
11
  export default querySlice;
@@ -10,9 +10,9 @@ const requestImageSlice: StateCreator<
10
10
  addRequestImage: image =>
11
11
  set(state => ({ requestImages: [...state.requestImages, image] })),
12
12
 
13
- setRequestImages: images => set(state => ({ requestImages: images })),
13
+ setRequestImages: images => set(() => ({ requestImages: images })),
14
14
 
15
- setFirstSearchImage: image => set(state => ({ firstSearchImage: image })),
15
+ setFirstSearchImage: image => set(() => ({ firstSearchImage: image })),
16
16
 
17
17
  removeImage: index => {
18
18
  const images = get().requestImages;
@@ -22,7 +22,7 @@ const requestImageSlice: StateCreator<
22
22
  return;
23
23
  }
24
24
  updatedImages.splice(index, 1);
25
- set(state => ({ requestImages: updatedImages }));
25
+ set(() => ({ requestImages: updatedImages }));
26
26
  },
27
27
 
28
28
  updateRegion: (region, index) => {
@@ -30,12 +30,12 @@ const requestImageSlice: StateCreator<
30
30
  let updatedRegions = [...regions];
31
31
  updatedRegions[index] = region;
32
32
 
33
- set(state => ({ regions: updatedRegions }));
33
+ set(() => ({ regions: updatedRegions }));
34
34
  },
35
35
 
36
- setRegions: regions => set(state => ({ regions: regions })),
36
+ setRegions: regions => set(() => ({ regions: regions })),
37
37
  resetRegions: () =>
38
- set(state => ({
38
+ set(() => ({
39
39
  regions: [Array(3)].map(() => {
40
40
  return { x1: 0, x2: 1, y1: 0, y2: 1 };
41
41
  }),
@@ -2,14 +2,17 @@ import { SpecificationAction, SpecificationState } from 'stores/types';
2
2
  import { StateCreator } from 'zustand';
3
3
  import { initialState } from './specifications.initialState';
4
4
 
5
- const filterSlice: StateCreator<SpecificationState & SpecificationAction> = set => ({
5
+ const filterSlice: StateCreator<
6
+ SpecificationState & SpecificationAction
7
+ > = set => ({
6
8
  ...initialState,
7
- setSpecifications: specifications => set(state => ({ specifications })),
8
- setNameplateNotificationText: nameplateNotificationText => set(state => ({ nameplateNotificationText })),
9
- setShowLoading: showLoading => set(state => ({ showLoading })),
10
- setNameplateImage: nameplateImage => set(state => ({ nameplateImage })),
11
- setShowNotMatchedError: showNotMatchedError => set(state => ({ showNotMatchedError })),
12
-
9
+ setSpecifications: specifications => set(() => ({ specifications })),
10
+ setNameplateNotificationText: nameplateNotificationText =>
11
+ set(() => ({ nameplateNotificationText })),
12
+ setShowLoading: showLoading => set(() => ({ showLoading })),
13
+ setNameplateImage: nameplateImage => set(() => ({ nameplateImage })),
14
+ setShowNotMatchedError: showNotMatchedError =>
15
+ set(() => ({ showNotMatchedError })),
13
16
  });
14
17
 
15
18
  export default filterSlice;
@@ -10,7 +10,7 @@ const detectedRegionsSlice: StateCreator<
10
10
  const detectedObject = get().detectedRegions;
11
11
  let updatedDetectedObject = { ...detectedObject };
12
12
  updatedDetectedObject[index] = region;
13
- set(state => ({ detectedRegions: updatedDetectedObject }));
13
+ set(() => ({ detectedRegions: updatedDetectedObject }));
14
14
  },
15
15
  });
16
16
 
@@ -19,4 +19,16 @@ export const initialState: ProductsState = {
19
19
  googleGroundingResponse: null,
20
20
  groundingFilterResult: [],
21
21
  showingGroundingFilterResult: false,
22
+ groundingError: null,
23
+ firstAlgoliaProducts: [],
24
+ fullResultPool: [],
25
+ smartFilters: {
26
+ status: 'idle',
27
+ filters: [],
28
+ selectedFilterKey: null,
29
+ selectedFilterValue: null,
30
+ rerankedSkus: [],
31
+ rerankStatus: 'idle',
32
+ errorMessage: null,
33
+ },
22
34
  };
@@ -5,34 +5,34 @@ import { ProductsAction, ProductsState } from 'stores/types';
5
5
  const productsSlice: StateCreator<ProductsState & ProductsAction> = set => ({
6
6
  ...initialState,
7
7
  setAlgoliaProducts: products =>
8
- set(state => ({ productsFromAlgolia: products })),
8
+ set(() => ({ productsFromAlgolia: products })),
9
9
  setFindApiProducts: products =>
10
- set(state => ({ productsFromFindApi: products })),
10
+ set(() => ({ productsFromFindApi: products })),
11
11
  setFirstSearchResults: products =>
12
- set(state => ({ firstSearchResults: products })),
12
+ set(() => ({ firstSearchResults: products })),
13
13
  setImageAnalysis: analysis =>
14
- set(state => ({
14
+ set(() => ({
15
15
  imageAnalysis: {
16
16
  ...analysis,
17
17
  },
18
18
  })),
19
19
  setSpecificationFilteredProducts: products =>
20
- set(state => ({
20
+ set(() => ({
21
21
  specificationFilteredProducts: products,
22
22
  })),
23
23
  setFirstRequestImageAnalysis: analysis =>
24
- set(state => ({
24
+ set(() => ({
25
25
  firstRequestImageAnalysis: {
26
26
  ...analysis,
27
27
  },
28
28
  })),
29
29
  setGoogleGroundingResponse: result =>
30
- set(state => ({
30
+ set(() => ({
31
31
  googleGroundingResponse: result,
32
32
  })),
33
33
 
34
34
  setGroundingFilterResult: result =>
35
- set(state => ({
35
+ set(() => ({
36
36
  groundingFilterResult: result,
37
37
  })),
38
38
  setShowingGroundingFilterResult: (val?: boolean) =>
@@ -41,6 +41,26 @@ const productsSlice: StateCreator<ProductsState & ProductsAction> = set => ({
41
41
  ? { showingGroundingFilterResult: !state.showingGroundingFilterResult }
42
42
  : { showingGroundingFilterResult: val },
43
43
  ),
44
+ setGroundingError: error =>
45
+ set(() => ({
46
+ groundingError: error,
47
+ })),
48
+
49
+ setFullResultPool: products =>
50
+ set(() => ({
51
+ fullResultPool: products,
52
+ })),
53
+ resetGrounding: () =>
54
+ set(() => ({
55
+ groundingError: null,
56
+ groundingFilterResult: [],
57
+ googleGroundingResponse: null,
58
+ showingGroundingFilterResult: false,
59
+ })),
60
+ setSmartFilters: smartFilters =>
61
+ set(() => ({
62
+ smartFilters,
63
+ })),
44
64
  });
45
65
 
46
66
  export default productsSlice;
@@ -4,8 +4,8 @@ import { SessionAction, SessionState } from 'stores/types';
4
4
 
5
5
  const sessionSlice: StateCreator<SessionState & SessionAction> = set => ({
6
6
  ...initialState,
7
- setSessionId: (sessionId: string) => set(state => ({ sessionId })),
8
- setRequestId: (requestId: string) => set(state => ({ requestId })),
7
+ setSessionId: (sessionId: string) => set(() => ({ sessionId })),
8
+ setRequestId: (requestId: string) => set(() => ({ requestId })),
9
9
  });
10
10
 
11
11
  export default sessionSlice;
@@ -0,0 +1,270 @@
1
+ import { create } from 'zustand';
2
+ import { persist } from 'zustand/middleware';
3
+
4
+ export interface SmartFilter {
5
+ id: string;
6
+ label: string;
7
+ value: string;
8
+ type: 'ai-generated';
9
+ }
10
+
11
+ export interface RerankedResult {
12
+ sku: string;
13
+ score: number;
14
+ reasoning: string;
15
+ matchedFilters: string[];
16
+ originalRank: number;
17
+ currentRank: number;
18
+ }
19
+
20
+ interface SmartFiltersState {
21
+ // API Keys (persisted in localStorage for admin convenience)
22
+ nyrisApiKeyOverride: string; // Override for nyris API key (empty = use settings.js)
23
+
24
+ // Settings
25
+ includeReasoning: boolean; // Toggle for AI reasoning (slower but more informative)
26
+ backgroundRemovalEnabled: boolean; // Toggle for rembg background removal
27
+ backgroundRemovalModel: string; // rembg model to use
28
+ sam3Enabled: boolean; // Toggle for SAM 3 segmentation
29
+ sam3ServerUrl: string; // SAM 3 server URL (ngrok/remote)
30
+ sam3SegmentOnClickOnly: boolean; // When true: search on upload, segment only when user clicks
31
+ chatWithDbEnabled: boolean; // P6: extract filters from product description, query full index / MongoDB
32
+ chatWithDbApiUrl: string; // Backend API URL for Chat with DB (MongoDB etc.)
33
+ googleGroundingEnabled: boolean; // P7: validate bad results via Google Search
34
+ visualRerankEnabled: boolean; // Visual reranking: 1:1 image comparison
35
+ showFilterChips: boolean; // Toggle visibility of Smart Filter chips (independent of Chat Assistant)
36
+
37
+ // Filters
38
+ filters: SmartFilter[];
39
+ selectedFilters: SmartFilter[];
40
+
41
+ // Reranking (Smart Filters)
42
+ rerankedResults: RerankedResult[];
43
+ originalRankings: Record<string, number>;
44
+ isReranked: boolean;
45
+
46
+ // Visual Reranking state
47
+ isVisualReranked: boolean;
48
+ isVisualReranking: boolean;
49
+ visualRerankRankings: Record<
50
+ string,
51
+ { originalRank: number; newRank: number; score: number; reasoning: string }
52
+ >;
53
+
54
+ // Loading states
55
+ isGeneratingFilters: boolean;
56
+ isReranking: boolean;
57
+
58
+ // Error
59
+ error: string | null;
60
+ }
61
+
62
+ interface SmartFiltersActions {
63
+ // API Key actions
64
+ setNyrisApiKeyOverride: (key: string) => void;
65
+
66
+ // Settings
67
+ setIncludeReasoning: (include: boolean) => void;
68
+ setBackgroundRemovalEnabled: (enabled: boolean) => void;
69
+ setBackgroundRemovalModel: (model: string) => void;
70
+ setSam3Enabled: (enabled: boolean) => void;
71
+ setSam3ServerUrl: (url: string) => void;
72
+ setSam3SegmentOnClickOnly: (onClickOnly: boolean) => void;
73
+ setChatWithDbEnabled: (enabled: boolean) => void;
74
+ setChatWithDbApiUrl: (url: string) => void;
75
+ setGoogleGroundingEnabled: (enabled: boolean) => void;
76
+ setVisualRerankEnabled: (enabled: boolean) => void;
77
+ setShowFilterChips: (show: boolean) => void;
78
+
79
+ // Filter actions
80
+ setFilters: (filters: SmartFilter[]) => void;
81
+ toggleFilter: (filter: SmartFilter) => void;
82
+ clearSelectedFilters: () => void;
83
+
84
+ // Reranking actions (Smart Filters)
85
+ setRerankedResults: (results: RerankedResult[]) => void;
86
+ setOriginalRankings: (rankings: Record<string, number>) => void;
87
+ setIsReranked: (isReranked: boolean) => void;
88
+ clearReranking: () => void;
89
+
90
+ // Visual Reranking actions
91
+ setIsVisualReranked: (isReranked: boolean) => void;
92
+ setIsVisualReranking: (loading: boolean) => void;
93
+ setVisualRerankRankings: (
94
+ rankings: Record<
95
+ string,
96
+ {
97
+ originalRank: number;
98
+ newRank: number;
99
+ score: number;
100
+ reasoning: string;
101
+ }
102
+ >,
103
+ ) => void;
104
+ clearVisualReranking: () => void;
105
+
106
+ // Loading states
107
+ setIsGeneratingFilters: (loading: boolean) => void;
108
+ setIsReranking: (loading: boolean) => void;
109
+
110
+ // Error
111
+ setError: (error: string | null) => void;
112
+
113
+ // Reset
114
+ reset: () => void;
115
+ }
116
+
117
+ const initialState: SmartFiltersState = {
118
+ nyrisApiKeyOverride: '',
119
+ includeReasoning: false, // Default: off for faster filtering
120
+ backgroundRemovalEnabled: false, // Default: off (needs rembg server)
121
+ backgroundRemovalModel: 'isnet-general-use', // Default model
122
+ sam3Enabled: false, // Default: off (needs server setup)
123
+ sam3ServerUrl: '', // No default - user must configure
124
+ sam3SegmentOnClickOnly: true, // Search first, segment only on click
125
+ chatWithDbEnabled: true, // P6 enabled by default
126
+ chatWithDbApiUrl: '', // No default - use Algolia fallback until backend is configured
127
+ googleGroundingEnabled: false, // P7 off by default
128
+ visualRerankEnabled: false, // Visual reranking off by default
129
+ showFilterChips: false, // Smart Filter chips hidden by default
130
+ filters: [],
131
+ selectedFilters: [],
132
+ rerankedResults: [],
133
+ originalRankings: {},
134
+ isReranked: false,
135
+ isVisualReranked: false,
136
+ isVisualReranking: false,
137
+ visualRerankRankings: {},
138
+ isGeneratingFilters: false,
139
+ isReranking: false,
140
+ error: null,
141
+ };
142
+
143
+ const useSmartFiltersStore = create<SmartFiltersState & SmartFiltersActions>()(
144
+ persist(
145
+ (set, _get) => ({
146
+ ...initialState,
147
+
148
+ setNyrisApiKeyOverride: key => set({ nyrisApiKeyOverride: key }),
149
+ setIncludeReasoning: include => set({ includeReasoning: include }),
150
+ setBackgroundRemovalEnabled: enabled =>
151
+ set({ backgroundRemovalEnabled: enabled }),
152
+ setBackgroundRemovalModel: model =>
153
+ set({ backgroundRemovalModel: model }),
154
+ setSam3Enabled: enabled => set({ sam3Enabled: enabled }),
155
+ setSam3ServerUrl: url => set({ sam3ServerUrl: url }),
156
+ setSam3SegmentOnClickOnly: onClickOnly =>
157
+ set({ sam3SegmentOnClickOnly: onClickOnly }),
158
+ setChatWithDbEnabled: enabled => set({ chatWithDbEnabled: enabled }),
159
+ setChatWithDbApiUrl: url => set({ chatWithDbApiUrl: url }),
160
+ setGoogleGroundingEnabled: enabled =>
161
+ set({ googleGroundingEnabled: enabled }),
162
+ setVisualRerankEnabled: enabled => set({ visualRerankEnabled: enabled }),
163
+ setShowFilterChips: show => set({ showFilterChips: show }),
164
+
165
+ setFilters: filters => set({ filters }),
166
+
167
+ toggleFilter: filter =>
168
+ set(state => {
169
+ const isSelected = state.selectedFilters.some(
170
+ f => f.id === filter.id,
171
+ );
172
+ if (isSelected) {
173
+ return {
174
+ selectedFilters: state.selectedFilters.filter(
175
+ f => f.id !== filter.id,
176
+ ),
177
+ };
178
+ } else {
179
+ return { selectedFilters: [...state.selectedFilters, filter] };
180
+ }
181
+ }),
182
+
183
+ clearSelectedFilters: () =>
184
+ set({
185
+ selectedFilters: [],
186
+ isReranked: false,
187
+ rerankedResults: [],
188
+ }),
189
+
190
+ setRerankedResults: results => set({ rerankedResults: results }),
191
+ setOriginalRankings: rankings => set({ originalRankings: rankings }),
192
+ setIsReranked: isReranked => set({ isReranked }),
193
+
194
+ clearReranking: () =>
195
+ set({
196
+ rerankedResults: [],
197
+ isReranked: false,
198
+ }),
199
+
200
+ setIsVisualReranked: isReranked => set({ isVisualReranked: isReranked }),
201
+ setIsVisualReranking: loading => set({ isVisualReranking: loading }),
202
+ setVisualRerankRankings: rankings =>
203
+ set({ visualRerankRankings: rankings }),
204
+ clearVisualReranking: () =>
205
+ set({
206
+ isVisualReranked: false,
207
+ isVisualReranking: false,
208
+ visualRerankRankings: {},
209
+ }),
210
+
211
+ setIsGeneratingFilters: loading => set({ isGeneratingFilters: loading }),
212
+ setIsReranking: loading => set({ isReranking: loading }),
213
+
214
+ setError: error => set({ error }),
215
+
216
+ reset: () =>
217
+ set({
218
+ filters: [],
219
+ selectedFilters: [],
220
+ rerankedResults: [],
221
+ originalRankings: {},
222
+ isReranked: false,
223
+ isVisualReranked: false,
224
+ isVisualReranking: false,
225
+ visualRerankRankings: {},
226
+ isGeneratingFilters: false,
227
+ isReranking: false,
228
+ error: null,
229
+ }),
230
+ }),
231
+ {
232
+ name: 'smart-filters-storage',
233
+ version: 3.2,
234
+ migrate: (persisted: unknown, version: number) => {
235
+ const state = persisted as Partial<SmartFiltersState>;
236
+ if (version < 2) {
237
+ state.backgroundRemovalEnabled = false;
238
+ state.googleGroundingEnabled = false;
239
+ state.visualRerankEnabled = false;
240
+ state.showFilterChips = false;
241
+ state.sam3Enabled = false;
242
+ }
243
+ // v3.1: removed geminiApiKey and geminiModel
244
+ delete (state as any).geminiApiKey;
245
+ delete (state as any).geminiModel;
246
+ // v3.2: removed prompt customization and admin modal state
247
+ delete (state as any).filterPrompt;
248
+ delete (state as any).rerankPrompt;
249
+ delete (state as any).showAdminSettings;
250
+ return state;
251
+ },
252
+ partialize: state => ({
253
+ nyrisApiKeyOverride: state.nyrisApiKeyOverride,
254
+ includeReasoning: state.includeReasoning,
255
+ backgroundRemovalEnabled: state.backgroundRemovalEnabled,
256
+ backgroundRemovalModel: state.backgroundRemovalModel,
257
+ sam3Enabled: state.sam3Enabled,
258
+ sam3ServerUrl: state.sam3ServerUrl,
259
+ sam3SegmentOnClickOnly: state.sam3SegmentOnClickOnly,
260
+ chatWithDbEnabled: state.chatWithDbEnabled,
261
+ chatWithDbApiUrl: state.chatWithDbApiUrl,
262
+ googleGroundingEnabled: state.googleGroundingEnabled,
263
+ visualRerankEnabled: state.visualRerankEnabled,
264
+ showFilterChips: state.showFilterChips,
265
+ }),
266
+ },
267
+ ),
268
+ );
269
+
270
+ export default useSmartFiltersStore;
@@ -33,6 +33,15 @@ export interface LoadingState {
33
33
  isGoogleGroundingLoading: boolean;
34
34
  }
35
35
 
36
+ export interface AiUiState {
37
+ isAiModeOpen: boolean;
38
+ }
39
+
40
+ export interface AiUiAction {
41
+ setIsAiModeOpen: (isOpen: boolean) => void;
42
+ toggleAiMode: () => void;
43
+ }
44
+
36
45
  export interface LoadingAction {
37
46
  setIsFindApiLoading: (isLoading: boolean) => void;
38
47
  setIsAlgoliaLoading: (isLoading: boolean) => void;
@@ -59,6 +68,23 @@ export interface ProductsState {
59
68
  googleGroundingResponse: Record<string, any> | null;
60
69
  groundingFilterResult: any[];
61
70
  showingGroundingFilterResult: boolean;
71
+ groundingError: string | null;
72
+ firstAlgoliaProducts: any[];
73
+ fullResultPool: any[];
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
+ };
62
88
  }
63
89
 
64
90
  export interface ProductsAction {
@@ -75,6 +101,10 @@ export interface ProductsAction {
75
101
  setGoogleGroundingResponse: (result: any | null) => void;
76
102
  setGroundingFilterResult: (result: any[]) => void;
77
103
  setShowingGroundingFilterResult: (val?: boolean) => void;
104
+ setGroundingError: (error: string | null) => void;
105
+ setFullResultPool: (products: any[]) => void;
106
+ resetGrounding: () => void;
107
+ setSmartFilters: (smartFilters: ProductsState['smartFilters']) => void;
78
108
  }
79
109
 
80
110
  export interface DetectedRegionsState {
@@ -125,6 +155,17 @@ export interface FeedbackAction {
125
155
  setShowFeedback: (show: boolean) => void;
126
156
  }
127
157
 
158
+ export interface BannerState {
159
+ bannerGoBack: boolean;
160
+ bannerTextSearch: 'loading' | 'resolved' | 'pending';
161
+ }
162
+
163
+ export interface BannerAction {
164
+ setBannerGoBack: (goBack: boolean) => void;
165
+ setBannerTextSearch: (textSearch: 'loading' | 'resolved' | 'pending') => void;
166
+ resetBanner: () => void;
167
+ }
168
+
128
169
  export interface SidePanelState {
129
170
  showSidePanel: boolean;
130
171
  }
@@ -0,0 +1,5 @@
1
+ import { AiUiState } from 'stores/types';
2
+
3
+ export const initialState: AiUiState = {
4
+ isAiModeOpen: false,
5
+ };
@@ -0,0 +1,15 @@
1
+ import { StateCreator } from 'zustand';
2
+ import { initialState } from './ai.initialState';
3
+ import { AiUiAction, AiUiState } from 'stores/types';
4
+
5
+ const aiSlice: StateCreator<AiUiState & AiUiAction> = set => ({
6
+ ...initialState,
7
+ setIsAiModeOpen: (isOpen: boolean) => {
8
+ set(() => ({ isAiModeOpen: isOpen }));
9
+ },
10
+ toggleAiMode: () => {
11
+ set(state => ({ isAiModeOpen: !state.isAiModeOpen }));
12
+ },
13
+ });
14
+
15
+ export default aiSlice;
@@ -0,0 +1,6 @@
1
+ import { BannerState } from 'stores/types';
2
+
3
+ export const initialState: BannerState = {
4
+ bannerGoBack: false,
5
+ bannerTextSearch: 'pending',
6
+ };
@@ -0,0 +1,14 @@
1
+ import { StateCreator } from 'zustand';
2
+ import { BannerAction, BannerState } from 'stores/types';
3
+
4
+ import { initialState } from './banner.initialState';
5
+
6
+ const bannerSlice: StateCreator<BannerState & BannerAction> = set => ({
7
+ ...initialState,
8
+ setBannerGoBack: (bannerGoBack: boolean) => set({ bannerGoBack }),
9
+ setBannerTextSearch: (bannerTextSearch: 'loading' | 'resolved' | 'pending') =>
10
+ set({ bannerTextSearch }),
11
+ resetBanner: () => set({ ...initialState }),
12
+ });
13
+
14
+ export default bannerSlice;
@@ -5,7 +5,7 @@ import { initialState } from './feedback.initialState';
5
5
 
6
6
  const feedbackSlice: StateCreator<FeedbackState & FeedbackAction> = set => ({
7
7
  ...initialState,
8
- setShowFeedback: (showFeedback: boolean) => set(state => ({ showFeedback })),
8
+ setShowFeedback: (showFeedback: boolean) => set(() => ({ showFeedback })),
9
9
  });
10
10
 
11
11
  export default feedbackSlice;
@@ -5,13 +5,13 @@ import { LoadingAction, LoadingState } from 'stores/types';
5
5
  const loadingSlice: StateCreator<LoadingState & LoadingAction> = set => ({
6
6
  ...initialState,
7
7
  setIsAlgoliaLoading: (isLoading: boolean) =>
8
- set(state => ({ isAlgoliaLoading: isLoading })),
8
+ set(() => ({ isAlgoliaLoading: isLoading })),
9
9
  setIsFindApiLoading: (isLoading: boolean) =>
10
- set(state => ({ isFindApiLoading: isLoading })),
10
+ set(() => ({ isFindApiLoading: isLoading })),
11
11
  setIsCadenasLoaded: (isLoading: boolean) =>
12
- set(state => ({ isCadenasLoaded: isLoading })),
12
+ set(() => ({ isCadenasLoaded: isLoading })),
13
13
  setIsGoogleGroundingLoading: isLoading =>
14
- set(state => ({
14
+ set(() => ({
15
15
  isGoogleGroundingLoading: isLoading,
16
16
  })),
17
17
  });
@@ -2,14 +2,20 @@ import { create } from 'zustand';
2
2
  import loadingSlice from './loading/loading.slice';
3
3
  import feedbackSlice from './feedback/feedback.slice';
4
4
  import sidePanelSlice from './sidePanel/sidePanel.slice';
5
+ import aiSlice from './ai/ai.slice';
6
+ import bannerSlice from './banner/banner.slice';
5
7
 
6
8
  type UiStore = ReturnType<typeof loadingSlice> &
7
9
  ReturnType<typeof feedbackSlice> &
8
- ReturnType<typeof sidePanelSlice>;
10
+ ReturnType<typeof sidePanelSlice> &
11
+ ReturnType<typeof aiSlice> &
12
+ ReturnType<typeof bannerSlice>;
9
13
 
10
14
  const useUiStore = create<UiStore>()((...a) => ({
11
15
  ...loadingSlice(...a),
12
16
  ...feedbackSlice(...a),
13
17
  ...sidePanelSlice(...a),
18
+ ...aiSlice(...a),
19
+ ...bannerSlice(...a),
14
20
  }));
15
21
  export default useUiStore;
@@ -1,9 +1,7 @@
1
1
  .wrap-main-item-result {
2
2
  display: flex;
3
3
  flex-direction: column;
4
- border-radius: 12px;
5
4
  overflow: hidden;
6
- box-shadow: 0px 0px 16px rgba(224, 224, 224, 0.8);
7
5
 
8
6
  @media screen and (max-width: 776px) {
9
7
  .wrap-main-item-result {