@nyris/nyris-webapp 0.3.91 → 0.3.93

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 (107) 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.fed38059.css +4 -0
  6. package/build/static/css/main.fed38059.css.map +1 -0
  7. package/build/static/js/main.c9bbbcb9.js +3 -0
  8. package/build/static/js/{main.f2255597.js.map → main.c9bbbcb9.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/IdentifiedAttributes.tsx +159 -0
  36. package/src/components/ImagePreview.tsx +32 -17
  37. package/src/components/ImageUpload.tsx +16 -8
  38. package/src/components/Inquiry/InquiryBanner.tsx +1 -1
  39. package/src/components/Inquiry/InquiryModal.tsx +35 -29
  40. package/src/components/ItemSpecification.tsx +58 -126
  41. package/src/components/LocationInfoPopup.tsx +33 -33
  42. package/src/components/MatchNotificationBanner.tsx +90 -36
  43. package/src/components/PostFilter/PostFilter.tsx +1 -1
  44. package/src/components/PostFilter/PostFilterComponent.tsx +0 -1
  45. package/src/components/PostFilter/PostFilterFindApi.tsx +0 -1
  46. package/src/components/PoweredBy.tsx +1 -1
  47. package/src/components/PreFilter/PreFilter.tsx +14 -3
  48. package/src/components/PreFilter/PreFilterModal.tsx +0 -1
  49. package/src/components/Product/Product.tsx +15 -11
  50. package/src/components/Product/ProductAttribute.tsx +4 -5
  51. package/src/components/Product/ProductDetailViewModal.tsx +2 -4
  52. package/src/components/Product/ProductList.tsx +26 -13
  53. package/src/components/Rfq/RfqModal.tsx +1 -1
  54. package/src/components/SidePanel.tsx +128 -46
  55. package/src/components/SmartFilter.tsx +320 -0
  56. package/src/components/TextSearch.tsx +134 -70
  57. package/src/components/UploadDisclaimer.tsx +1 -1
  58. package/src/hooks/useBadResultsRecovery.ts +407 -0
  59. package/src/hooks/useEffectiveGroundingResults.ts +54 -0
  60. package/src/hooks/useGoodResultsChat.ts +651 -0
  61. package/src/hooks/useGroundedSearch.ts +88 -0
  62. package/src/hooks/useImageSearch.ts +139 -187
  63. package/src/hooks/useResultEvaluator.ts +417 -0
  64. package/src/index.css +1 -1
  65. package/src/index.tsx +0 -1
  66. package/src/layouts/AppLayout.tsx +53 -2
  67. package/src/pages/Home.tsx +11 -52
  68. package/src/pages/Login.tsx +1 -2
  69. package/src/pages/Logout.tsx +1 -1
  70. package/src/pages/Result.tsx +254 -200
  71. package/src/providers/AuthProvider.tsx +0 -1
  72. package/src/services/Feedback.ts +1 -1
  73. package/src/services/visualSearch.ts +0 -21
  74. package/src/services/vizo.ts +192 -4
  75. package/src/stores/chat/chatStore.ts +150 -0
  76. package/src/stores/chat/conversationStore.ts +300 -0
  77. package/src/stores/request/Misc/misc.slice.ts +2 -2
  78. package/src/stores/request/filter/filter.slice.ts +8 -8
  79. package/src/stores/request/query/query.slice.ts +2 -2
  80. package/src/stores/request/requestImage/requestImage.slice.ts +6 -6
  81. package/src/stores/request/specifications/specifications.slice.ts +10 -7
  82. package/src/stores/result/detectedRegions/detectedRegions.slice.ts +1 -1
  83. package/src/stores/result/prodcuts/products.initialState.ts +12 -0
  84. package/src/stores/result/prodcuts/products.slice.ts +28 -8
  85. package/src/stores/result/session/session.slice.ts +2 -2
  86. package/src/stores/smartFilters/smartFiltersStore.ts +270 -0
  87. package/src/stores/types.ts +41 -0
  88. package/src/stores/ui/ai/ai.initialState.ts +5 -0
  89. package/src/stores/ui/ai/ai.slice.ts +15 -0
  90. package/src/stores/ui/banner/banner.initialState.ts +6 -0
  91. package/src/stores/ui/banner/banner.slice.ts +14 -0
  92. package/src/stores/ui/feedback/feedback.slice.ts +1 -1
  93. package/src/stores/ui/loading/loading.slice.ts +4 -4
  94. package/src/stores/ui/uiStore.ts +7 -1
  95. package/src/styles/product.scss +0 -2
  96. package/src/types.ts +4 -8
  97. package/src/utils/cropImageToBase64.ts +32 -0
  98. package/src/utils/fetchProductImage.ts +109 -0
  99. package/src/utils/imageConverters.ts +124 -0
  100. package/src/utils/relatedParts.ts +35 -0
  101. package/src/utils/specificationFilter.ts +1 -5
  102. package/tailwind.config.js +3 -2
  103. package/build/static/css/main.734b52e1.css +0 -4
  104. package/build/static/css/main.734b52e1.css.map +0 -1
  105. package/build/static/js/main.f2255597.js +0 -3
  106. package/src/utils/addAssets.ts +0 -40
  107. /package/build/static/js/{main.f2255597.js.LICENSE.txt → main.c9bbbcb9.js.LICENSE.txt} +0 -0
@@ -4,20 +4,27 @@ import ImagePreview from './ImagePreview';
4
4
  import useRequestStore from 'stores/request/requestStore';
5
5
  import PostFilterComponent from './PostFilter/PostFilterComponent';
6
6
  import useResultStore from 'stores/result/resultStore';
7
- import ItemSpecification from './ItemSpecification';
8
7
  import useUiStore from 'stores/ui/uiStore';
9
8
  import GroundingSpecs from './GroundingSpecs';
9
+ import SmartFilter from './SmartFilter';
10
+ import { useEffectiveGroundingResults } from 'hooks/useEffectiveGroundingResults';
11
+ import IdentifiedAttributes from './IdentifiedAttributes';
10
12
 
11
13
  export default function SidePanel({ className }: { className?: string }) {
14
+ const settings = window.settings;
15
+ const isSmartFiltersEnabled = !!settings.useSmartFilters;
12
16
  const requestImages = useRequestStore(state => state.requestImages);
13
17
 
14
18
  const specificationFilter = useRequestStore(
15
19
  state => state.specificationFilter,
16
20
  );
17
21
 
22
+ const findApiProducts = useResultStore(state => state.productsFromFindApi);
23
+
18
24
  const showSidePanel = useUiStore(state => state.showSidePanel);
19
25
 
20
26
  const imageAnalysis = useResultStore(state => state.imageAnalysis);
27
+ const smartFilters = useResultStore(state => state.smartFilters);
21
28
 
22
29
  const showPostFilter = window.settings?.postFilterOption;
23
30
 
@@ -31,42 +38,54 @@ export default function SidePanel({ className }: { className?: string }) {
31
38
  const googleGroundingResponse = useResultStore(
32
39
  state => state.googleGroundingResponse,
33
40
  );
34
- const groundingFilterResult = useResultStore(
35
- state => state.groundingFilterResult,
36
- );
41
+ const groundingError = useResultStore(state => state.groundingError);
37
42
  const groundingProduct =
38
43
  googleGroundingResponse?.identified_product ||
39
44
  googleGroundingResponse ||
40
45
  null;
41
46
 
47
+ const effectiveGroundingResults = useEffectiveGroundingResults();
48
+
42
49
  if (!showPostFilter && requestImages.length === 0) {
43
50
  return <></>;
44
51
  }
45
52
 
53
+ if (
54
+ requestImages.length === 0 &&
55
+ findApiProducts.length === 0 &&
56
+ !window.settings.algolia.enabled
57
+ ) {
58
+ return <></>;
59
+ }
60
+
46
61
  return (
47
62
  <div
48
63
  className={twMerge(
49
64
  [
50
- 'max-w-[325px]',
51
65
  'w-full',
52
- 'shadow-[3px_-2px_3px_-3px_#d3d4d8]',
53
- 'overflow-x-hidden',
54
- 'overflow-y-auto',
66
+ 'overflow-hidden',
55
67
  'bg-white',
56
68
  'relative',
57
69
  'flex',
58
70
  'flex-col',
71
+ 'ml-4',
72
+ 'mb-4',
73
+ showSidePanel && 'border border-solid border-[#DDDEE7]',
74
+ showSidePanel ? 'rounded-[32px]' : 'rounded-2xl',
75
+ 'transition-all',
76
+ showSidePanel ? 'w-[334px] min-w-[334px]' : 'w-0 min-w-0',
77
+ 'shadow-ds-1',
59
78
  ],
60
79
  className,
61
80
  )}
62
81
  >
63
82
  <div
64
83
  className={twMerge(
65
- ' w-full h-full overflow-y-auto overflow-x-hidden',
84
+ ' w-full overflow-y-auto overflow-x-hidden',
66
85
  showSidePanel ? 'block' : 'hidden',
67
86
  )}
68
87
  >
69
- <div className="w-full h-full">
88
+ <div className="w-full">
70
89
  <div
71
90
  className={twMerge([
72
91
  'w-full',
@@ -76,7 +95,8 @@ export default function SidePanel({ className }: { className?: string }) {
76
95
  'flex',
77
96
  'justify-center',
78
97
  'items-center',
79
- 'min-w-[283px]',
98
+ 'w-[295px]',
99
+ 'm-4',
80
100
  // 'mx-4',
81
101
  // 'mt-4',
82
102
  // 'rounded',
@@ -84,7 +104,18 @@ export default function SidePanel({ className }: { className?: string }) {
84
104
  >
85
105
  {requestImages[0] && <ImagePreview />}
86
106
  </div>
87
- <div className="pb-4">
107
+ <div className="pb-4 flex flex-col gap-6">
108
+ {isSmartFiltersEnabled &&
109
+ (imageAnalysis?.imageDescription ||
110
+ Object.keys(imageAnalysis?.specification || {}).length > 0 ||
111
+ smartFilters.status !== 'idle') && (
112
+ <SmartFilter
113
+ imageAnalysis={imageAnalysis}
114
+ specificationFilter={specificationFilter}
115
+ variant="sidepanel"
116
+ />
117
+ )}
118
+
88
119
  {window.settings.showImageDetails &&
89
120
  (imageAnalysis?.imageDescription ||
90
121
  Object.keys(imageAnalysis?.specification || {}).length > 0) && (
@@ -123,7 +154,7 @@ export default function SidePanel({ className }: { className?: string }) {
123
154
  return null;
124
155
  }
125
156
  return (
126
- <ItemSpecification
157
+ <IdentifiedAttributes
127
158
  attr={key}
128
159
  value={value}
129
160
  specificationFilter={specificationFilter}
@@ -134,28 +165,71 @@ export default function SidePanel({ className }: { className?: string }) {
134
165
  </div>
135
166
  )}
136
167
 
137
- {(isGoogleGroundingLoading || googleGroundingResponse) && (
138
- <div className="mx-4 mb-4 mt-4 rounded-3xl bg-[#F3F4F8] border border-[#e4e3ff] p-3.5 text-[#3B3E5F] ">
168
+ {(isGoogleGroundingLoading ||
169
+ googleGroundingResponse ||
170
+ groundingError) && (
171
+ <div className="mx-4 rounded-3xl bg-[#F3F4F8] border border-[#e4e3ff] p-3.5 text-[#3B3E5F] ">
139
172
  <div className="flex items-center justify-between">
140
173
  {!isGoogleGroundingLoading && (
141
174
  <div className="flex items-center gap-2 text-base font-semibold">
142
175
  <span className="text-[#3B3E5F] font-bold text-sm leading-4">
143
176
  Smart Product Search
144
177
  </span>
145
- <svg
146
- width="18"
147
- height="18"
148
- viewBox="0 0 18 18"
149
- fill="none"
150
- xmlns="http://www.w3.org/2000/svg"
151
- >
152
- <path
153
- fill-rule="evenodd"
154
- clip-rule="evenodd"
155
- d="M7.92274 17.0815C8.14851 17.0509 9.50523 17.0531 9.68837 17.1069C10.0943 17.2269 10.0579 17.8935 9.66298 17.977C9.51665 18.0079 7.97566 18.0068 7.8202 17.977C7.37018 17.8902 7.31801 17.1641 7.92274 17.0815ZM6.43739 15.5718C6.93914 15.4849 10.5164 15.4852 11.0194 15.5718C11.5127 15.6567 11.5125 16.3802 11.0194 16.4673C10.5501 16.55 6.90241 16.5509 6.43739 16.4673C5.94907 16.3791 5.93794 15.6585 6.43739 15.5718ZM11.5575 14.1128C12.0542 14.2692 12.0136 14.9036 11.5057 15.0083H5.95106C5.44374 14.9029 5.40011 14.2659 5.90028 14.1128H11.5575ZM8.3827 0.00828993C14.342 -0.281826 17.6069 7.14091 13.0927 11.2456C12.8954 11.4249 12.3055 11.7912 12.2099 11.9751C11.9627 12.4507 12.3894 13.3278 11.6854 13.5493H5.77235C5.07649 13.3197 5.49332 12.4477 5.24794 11.9751C5.17094 11.827 4.07408 10.991 3.81434 10.6948C0.284508 6.66896 3.12542 0.264519 8.3827 0.00828993ZM8.40907 0.878407C4.05152 1.10169 1.53289 6.2906 4.22352 9.8247C4.76536 10.5363 6.01331 11.1906 6.16884 11.9751C6.19322 12.0982 6.1777 12.6441 6.25868 12.6792H11.1991C11.2727 12.6437 11.2775 12.0209 11.3143 11.8725C11.4808 11.2037 12.6134 10.5761 13.1054 9.97802C16.1901 6.22819 13.1583 0.635396 8.40907 0.878407ZM7.58973 5.5122C7.98646 5.46307 8.45858 5.63311 8.72841 5.93407C9.03408 6.2751 9.04196 6.64401 9.0995 7.07274C10.3076 7.27893 9.28147 6.27654 9.94423 6.04931C10.706 5.78845 10.8635 7.24438 10.2001 7.73876C9.82323 8.01942 9.48381 7.89542 9.06141 7.98192V12.0259C8.87496 12.4562 8.31043 12.4151 8.1913 11.9751V7.98192C7.81441 7.90152 7.50855 7.99688 7.15419 7.78954C6.23495 7.25134 6.49448 5.648 7.58973 5.5122ZM7.69227 6.38231C7.46368 6.42187 7.42105 6.76141 7.49989 6.93212C7.58326 7.11216 7.99291 7.06945 8.15223 7.07274C8.30758 6.75232 8.0614 6.3187 7.69227 6.38231ZM8.69032 1.72313C9.11035 1.61732 10.1708 1.90654 10.5839 2.08153C12.1761 2.75648 13.4423 4.46569 13.5155 6.21532C13.5407 6.81775 13.4838 7.13782 12.7597 6.99657C12.3736 6.92088 12.5135 6.31131 12.4657 6.01122C12.2302 4.53827 11.0312 3.15282 9.56044 2.82372C9.29861 2.76515 8.63971 2.79266 8.52333 2.58056C8.42292 2.39652 8.42838 1.78927 8.69032 1.72313ZM8.99794 3.61766C10.0217 3.49673 11.4103 4.65574 11.5956 5.65282C11.7437 6.4509 10.9592 6.46744 10.7763 6.062C10.5385 5.53507 10.6087 5.21626 10.0214 4.82079C9.55464 4.50653 9.25994 4.6364 8.87001 4.48778C8.51994 4.35422 8.58644 3.6664 8.99794 3.61766Z"
156
- fill="#3B3E5F"
157
- />
158
- </svg>
178
+ {groundingError && (
179
+ <svg
180
+ width="19"
181
+ height="18"
182
+ viewBox="0 0 19 18"
183
+ fill="none"
184
+ xmlns="http://www.w3.org/2000/svg"
185
+ >
186
+ <mask
187
+ id="mask0_15292_25467"
188
+ style={{ maskType: 'luminance' }}
189
+ maskUnits="userSpaceOnUse"
190
+ x="0"
191
+ y="0"
192
+ width="19"
193
+ height="18"
194
+ >
195
+ <path
196
+ fill-rule="evenodd"
197
+ clip-rule="evenodd"
198
+ d="M0.5 1.02344H18.5V16.9764H0.5V1.02344Z"
199
+ fill="white"
200
+ stroke="white"
201
+ />
202
+ </mask>
203
+ <g mask="url(#mask0_15292_25467)">
204
+ <path
205
+ d="M8.31042 1.71025L0.686092 14.916C0.157364 15.8317 0.818246 16.9764 1.87565 16.9764H17.1244C18.1818 16.9764 18.8426 15.8317 18.3139 14.916L10.6896 1.71025C10.1609 0.794501 8.83915 0.794501 8.31042 1.71025"
206
+ stroke="#3B3E5F"
207
+ />
208
+ </g>
209
+ <path
210
+ fill-rule="evenodd"
211
+ clip-rule="evenodd"
212
+ d="M8.54758 4.94824H10.4785L10.1523 11.0541H8.86071L8.54758 4.94824ZM8.45624 13.1547C8.45624 12.5937 8.89986 12.1501 9.51307 12.1501C10.1132 12.1501 10.5437 12.5937 10.5437 13.1547C10.5437 13.7157 10.1132 14.1723 9.51307 14.1723C8.89986 14.1723 8.45624 13.7157 8.45624 13.1547V13.1547Z"
213
+ fill="#3B3E5F"
214
+ />
215
+ </svg>
216
+ )}
217
+ {!groundingError && (
218
+ <svg
219
+ width="18"
220
+ height="18"
221
+ viewBox="0 0 18 18"
222
+ fill="none"
223
+ xmlns="http://www.w3.org/2000/svg"
224
+ >
225
+ <path
226
+ fill-rule="evenodd"
227
+ clip-rule="evenodd"
228
+ d="M7.92274 17.0815C8.14851 17.0509 9.50523 17.0531 9.68837 17.1069C10.0943 17.2269 10.0579 17.8935 9.66298 17.977C9.51665 18.0079 7.97566 18.0068 7.8202 17.977C7.37018 17.8902 7.31801 17.1641 7.92274 17.0815ZM6.43739 15.5718C6.93914 15.4849 10.5164 15.4852 11.0194 15.5718C11.5127 15.6567 11.5125 16.3802 11.0194 16.4673C10.5501 16.55 6.90241 16.5509 6.43739 16.4673C5.94907 16.3791 5.93794 15.6585 6.43739 15.5718ZM11.5575 14.1128C12.0542 14.2692 12.0136 14.9036 11.5057 15.0083H5.95106C5.44374 14.9029 5.40011 14.2659 5.90028 14.1128H11.5575ZM8.3827 0.00828993C14.342 -0.281826 17.6069 7.14091 13.0927 11.2456C12.8954 11.4249 12.3055 11.7912 12.2099 11.9751C11.9627 12.4507 12.3894 13.3278 11.6854 13.5493H5.77235C5.07649 13.3197 5.49332 12.4477 5.24794 11.9751C5.17094 11.827 4.07408 10.991 3.81434 10.6948C0.284508 6.66896 3.12542 0.264519 8.3827 0.00828993ZM8.40907 0.878407C4.05152 1.10169 1.53289 6.2906 4.22352 9.8247C4.76536 10.5363 6.01331 11.1906 6.16884 11.9751C6.19322 12.0982 6.1777 12.6441 6.25868 12.6792H11.1991C11.2727 12.6437 11.2775 12.0209 11.3143 11.8725C11.4808 11.2037 12.6134 10.5761 13.1054 9.97802C16.1901 6.22819 13.1583 0.635396 8.40907 0.878407ZM7.58973 5.5122C7.98646 5.46307 8.45858 5.63311 8.72841 5.93407C9.03408 6.2751 9.04196 6.64401 9.0995 7.07274C10.3076 7.27893 9.28147 6.27654 9.94423 6.04931C10.706 5.78845 10.8635 7.24438 10.2001 7.73876C9.82323 8.01942 9.48381 7.89542 9.06141 7.98192V12.0259C8.87496 12.4562 8.31043 12.4151 8.1913 11.9751V7.98192C7.81441 7.90152 7.50855 7.99688 7.15419 7.78954C6.23495 7.25134 6.49448 5.648 7.58973 5.5122ZM7.69227 6.38231C7.46368 6.42187 7.42105 6.76141 7.49989 6.93212C7.58326 7.11216 7.99291 7.06945 8.15223 7.07274C8.30758 6.75232 8.0614 6.3187 7.69227 6.38231ZM8.69032 1.72313C9.11035 1.61732 10.1708 1.90654 10.5839 2.08153C12.1761 2.75648 13.4423 4.46569 13.5155 6.21532C13.5407 6.81775 13.4838 7.13782 12.7597 6.99657C12.3736 6.92088 12.5135 6.31131 12.4657 6.01122C12.2302 4.53827 11.0312 3.15282 9.56044 2.82372C9.29861 2.76515 8.63971 2.79266 8.52333 2.58056C8.42292 2.39652 8.42838 1.78927 8.69032 1.72313ZM8.99794 3.61766C10.0217 3.49673 11.4103 4.65574 11.5956 5.65282C11.7437 6.4509 10.9592 6.46744 10.7763 6.062C10.5385 5.53507 10.6087 5.21626 10.0214 4.82079C9.55464 4.50653 9.25994 4.6364 8.87001 4.48778C8.51994 4.35422 8.58644 3.6664 8.99794 3.61766Z"
229
+ fill="#3B3E5F"
230
+ />
231
+ </svg>
232
+ )}
159
233
  </div>
160
234
  )}
161
235
 
@@ -197,23 +271,31 @@ export default function SidePanel({ className }: { className?: string }) {
197
271
  <div className="h-full w-1/2 animate-pulse rounded bg-[#3E36DC]" />
198
272
  </div>
199
273
  )}
274
+ {groundingError && (
275
+ <div className="text-[#767A9F] text-sm mt-2.5">
276
+ Unable to search the Internet.
277
+ </div>
278
+ )}
279
+ {googleGroundingResponse &&
280
+ !groundingError &&
281
+ !isGoogleGroundingLoading && (
282
+ <>
283
+ <div className="mt-4 flex gap-2 items-center">
284
+ <div className="w-[8px] h-[8px] bg-green-500 rounded-full "></div>
200
285
 
201
- {googleGroundingResponse && (
202
- <>
203
- <div className="mt-4 flex gap-2 items-center">
204
- <div className="w-[8px] h-[8px] bg-green-500 rounded-full "></div>
205
-
206
- <div className="text-sm font-semibold leading-4">
207
- 1. Results from the internet
286
+ <div className="text-sm font-semibold leading-4">
287
+ 1. Results from the internet
288
+ </div>
208
289
  </div>
209
- </div>
210
- <div className="mt-3 text-[10px]">
211
- <GroundingSpecs data={groundingProduct} />
212
- </div>
213
- </>
214
- )}
215
- {groundingFilterResult?.length > 0 &&
216
- showingGroundingFilterResult && (
290
+ <div className="mt-3 text-[10px]">
291
+ <GroundingSpecs data={groundingProduct} />
292
+ </div>
293
+ </>
294
+ )}
295
+ {effectiveGroundingResults?.length > 0 &&
296
+ showingGroundingFilterResult &&
297
+ !groundingError &&
298
+ !isGoogleGroundingLoading && (
217
299
  <div className="mt-3.5 text-[#3B3E5F]">
218
300
  <div className="flex gap-2 items-center">
219
301
  <div className="w-[8px] h-[8px] bg-green-500 rounded-full "></div>
@@ -225,12 +307,12 @@ export default function SidePanel({ className }: { className?: string }) {
225
307
  <div className="text-xs text-[#22C55E] mb-1 mt-1">
226
308
  Found{' '}
227
309
  <span className="font-bold leading-4">
228
- {groundingFilterResult?.length} matching{' '}
310
+ {effectiveGroundingResults?.length} matching{' '}
229
311
  </span>{' '}
230
312
  product
231
313
  </div>
232
314
  <div className="flex flex-col gap-2">
233
- {groundingFilterResult?.map(
315
+ {effectiveGroundingResults?.map(
234
316
  (item: any, index: number) => (
235
317
  <div
236
318
  key={`${item?.sku || 'sku'}-${index}`}
@@ -0,0 +1,320 @@
1
+ import React, { useMemo } from 'react';
2
+ import ItemSpecification from './ItemSpecification';
3
+ import { Icon } from '@nyris/nyris-react-components';
4
+ import useRequestStore from 'stores/request/requestStore';
5
+ import useResultStore from 'stores/result/resultStore';
6
+ import { rerankSmartFilters, SmartFilterRerankItem } from 'services/vizo';
7
+
8
+ function SmartFilter({
9
+ imageAnalysis,
10
+ specificationFilter,
11
+ variant = 'results',
12
+ }: {
13
+ imageAnalysis?: any;
14
+ specificationFilter?: any;
15
+ variant?: 'sidepanel' | 'results';
16
+ }) {
17
+ const settings = window.settings;
18
+ const isSmartFiltersEnabled = !!settings.useSmartFilters;
19
+ const smartFilters = useResultStore(state => state.smartFilters);
20
+ const setSmartFilters = useResultStore(state => state.setSmartFilters);
21
+ const productsFromFindApi = useResultStore(state => state.productsFromFindApi);
22
+ const productsFromAlgolia = useResultStore(state => state.productsFromAlgolia);
23
+ const firstSearchResults = useResultStore(state => state.firstSearchResults);
24
+ const setFindApiProducts = useResultStore(state => state.setFindApiProducts);
25
+ const setAlgoliaProducts = useResultStore(state => state.setAlgoliaProducts);
26
+ const requestImages = useRequestStore(state => state.requestImages);
27
+ const setAlgoliaFilter = useRequestStore(state => state.setAlgoliaFilter);
28
+ const isAlgoliaEnabled = !!window.settings?.algolia?.enabled;
29
+
30
+ const activeProducts =
31
+ isAlgoliaEnabled && productsFromAlgolia.length > 0
32
+ ? productsFromAlgolia
33
+ : productsFromFindApi;
34
+
35
+ const rerankItems: SmartFilterRerankItem[] = useMemo(() => {
36
+ return (activeProducts || [])
37
+ .slice(0, 50)
38
+ .map((product: any): SmartFilterRerankItem | null => {
39
+ const sku = product?.sku || product?.objectID;
40
+ const imageUrl =
41
+ product?.matches?.[0]?.url ||
42
+ product?.['image(main_similarity)'] ||
43
+ product?.main_image_link ||
44
+ product?.image ||
45
+ product?.images?.[0];
46
+
47
+ if (!sku || !imageUrl) {
48
+ return null;
49
+ }
50
+
51
+ return {
52
+ sku: String(sku),
53
+ imageUrl: String(imageUrl),
54
+ metadata: {
55
+ title: product?.title || product?.name || product?.Bezeichnung || '',
56
+ description:
57
+ product?.description ||
58
+ product?.VK_Text_Englisch ||
59
+ product?.VK_Text_Deutsch ||
60
+ '',
61
+ language: window.settings.language || 'en',
62
+ sku: String(sku),
63
+ score:
64
+ typeof (product?.score ?? product?.similarity) === 'number'
65
+ ? Number(product?.score ?? product?.similarity)
66
+ : null,
67
+ },
68
+ };
69
+ })
70
+ .filter((item): item is SmartFilterRerankItem => item !== null);
71
+ }, [activeProducts]);
72
+
73
+ const applySkuOrderingToCurrentResults = (orderedSkus: string[]) => {
74
+ const sourceResults = activeProducts || [];
75
+ const map = new Map<string, any>(
76
+ sourceResults.map((item: any) => [
77
+ String(item?.sku || item?.objectID || '').trim(),
78
+ item,
79
+ ]),
80
+ );
81
+
82
+ const reordered = orderedSkus
83
+ .map(sku => map.get(String(sku).trim()))
84
+ .filter(Boolean);
85
+
86
+ if (reordered.length === 0) {
87
+ return false;
88
+ }
89
+
90
+ setFindApiProducts(reordered);
91
+ if (isAlgoliaEnabled) {
92
+ setAlgoliaProducts(reordered);
93
+ const nonEmptyFilter: string[] = ['sku:DOES_NOT_EXIST<score=1> '];
94
+ const rankedFilters = orderedSkus
95
+ .slice()
96
+ .reverse()
97
+ .map((sku, i) => `sku:'${sku}'<score=${i}> `);
98
+ setAlgoliaFilter([...nonEmptyFilter, ...rankedFilters].join('OR '));
99
+ }
100
+ return true;
101
+ };
102
+
103
+ const onFilterClick = async (filter: { key: string; value: string }) => {
104
+ if (!isSmartFiltersEnabled) {
105
+ return;
106
+ }
107
+ if (
108
+ smartFilters.selectedFilterKey === filter.key &&
109
+ smartFilters.selectedFilterValue === filter.value
110
+ ) {
111
+ setSmartFilters({
112
+ ...smartFilters,
113
+ selectedFilterKey: null,
114
+ selectedFilterValue: null,
115
+ rerankStatus: 'idle',
116
+ errorMessage: null,
117
+ });
118
+ if (firstSearchResults.length > 0) {
119
+ setFindApiProducts(firstSearchResults);
120
+ }
121
+ return;
122
+ }
123
+
124
+ if (!requestImages[0] || rerankItems.length === 0) {
125
+ setSmartFilters({
126
+ ...smartFilters,
127
+ selectedFilterKey: filter.key,
128
+ selectedFilterValue: filter.value,
129
+ rerankStatus: 'error',
130
+ errorMessage: 'Smart filters are unavailable right now.',
131
+ });
132
+ return;
133
+ }
134
+
135
+ setSmartFilters({
136
+ ...smartFilters,
137
+ selectedFilterKey: filter.key,
138
+ selectedFilterValue: filter.value,
139
+ rerankStatus: 'loading',
140
+ errorMessage: null,
141
+ });
142
+
143
+ try {
144
+ const rerankResponse = await rerankSmartFilters({
145
+ image: requestImages[0],
146
+ payload: {
147
+ filters: { [filter.key]: filter.value },
148
+ items: rerankItems,
149
+ },
150
+ });
151
+
152
+ const rerankedSkus = (rerankResponse?.filtered_items || []).map(item =>
153
+ String(item.sku),
154
+ );
155
+ const rerankingApplied =
156
+ (rerankResponse?.reranked as boolean | undefined) ??
157
+ rerankResponse?.rerankingApplied ??
158
+ (rerankedSkus.length > 0 ? true : false);
159
+
160
+ if (!Array.isArray(rerankResponse?.filtered_items)) {
161
+ setSmartFilters({
162
+ ...smartFilters,
163
+ selectedFilterKey: filter.key,
164
+ selectedFilterValue: filter.value,
165
+ rerankStatus: 'error',
166
+ errorMessage: 'Could not rerank results.',
167
+ });
168
+ return;
169
+ }
170
+
171
+ if (!rerankingApplied) {
172
+ setSmartFilters({
173
+ ...smartFilters,
174
+ selectedFilterKey: filter.key,
175
+ selectedFilterValue: filter.value,
176
+ rerankStatus: 'success',
177
+ errorMessage: null,
178
+ });
179
+ return;
180
+ }
181
+
182
+ if (rerankedSkus.length === 0 || !applySkuOrderingToCurrentResults(rerankedSkus)) {
183
+ setSmartFilters({
184
+ ...smartFilters,
185
+ selectedFilterKey: filter.key,
186
+ selectedFilterValue: filter.value,
187
+ rerankStatus: 'error',
188
+ errorMessage: 'Could not rerank results.',
189
+ });
190
+ return;
191
+ }
192
+
193
+ setSmartFilters({
194
+ ...smartFilters,
195
+ selectedFilterKey: filter.key,
196
+ selectedFilterValue: filter.value,
197
+ rerankedSkus,
198
+ rerankStatus: 'success',
199
+ errorMessage: null,
200
+ });
201
+ } catch (error: any) {
202
+ const timeout = String(error?.message || '').includes('SMART_FILTER_TIMEOUT');
203
+ setSmartFilters({
204
+ ...smartFilters,
205
+ selectedFilterKey: filter.key,
206
+ selectedFilterValue: filter.value,
207
+ rerankStatus: 'error',
208
+ errorMessage: timeout
209
+ ? 'Smart filters are unavailable right now.'
210
+ : 'Could not rerank results.',
211
+ });
212
+ }
213
+ };
214
+
215
+ if (!isSmartFiltersEnabled) {
216
+ return null;
217
+ }
218
+
219
+ return (
220
+ <div>
221
+ <div className="flex gap-2 ml-[30px]">
222
+ <div className="text-[#3B3E5F] font-bold text-sm">
223
+ Smart generated filters
224
+ </div>
225
+ <Icon name="smart_filter_indication" className="mt-1" />
226
+ </div>
227
+ <div className="flex flex-wrap mx-4 gap-2 mt-4">
228
+ {Object.keys(imageAnalysis?.specification || {})
229
+ .filter(key => key !== 'is_nameplate' && key !== 'prefilter_value')
230
+ .map(key => {
231
+ const value = imageAnalysis?.specification[key];
232
+ if (!value) {
233
+ return null;
234
+ }
235
+ return (
236
+ <ItemSpecification
237
+ key={key}
238
+ attr={key}
239
+ value={value}
240
+ specificationFilter={specificationFilter}
241
+ imageAnalysis={imageAnalysis}
242
+ />
243
+ );
244
+ })}
245
+ </div>
246
+ {smartFilters.status === 'loading' && (
247
+ <div className="mx-4 mt-3 text-xs text-[#3E36DC]">
248
+ Loading smart filters...
249
+ </div>
250
+ )}
251
+ {smartFilters.status === 'error' && (
252
+ <div className="mx-4 mt-3 text-xs text-[#D14343]">
253
+ {smartFilters.errorMessage || 'Could not generate smart filters.'}
254
+ </div>
255
+ )}
256
+ {smartFilters.status === 'success' && smartFilters.filters.length === 0 && (
257
+ <div className="mx-4 mt-3 text-xs text-[#767A9F]">
258
+ No smart filters available for this image.
259
+ </div>
260
+ )}
261
+ {smartFilters.rerankStatus === 'loading' && (
262
+ <div className="mx-4 mt-1 text-xs text-[#3E36DC]">Applying rerank...</div>
263
+ )}
264
+ <div
265
+ className={
266
+ variant === 'results'
267
+ ? 'smartFilters--results mx-4 mt-4 overflow-x-auto overflow-y-hidden'
268
+ : 'smartFilters--sidepanel mx-4 mt-4'
269
+ }
270
+ style={
271
+ variant === 'results'
272
+ ? {
273
+ WebkitOverflowScrolling: 'touch',
274
+ scrollbarWidth: 'none',
275
+ msOverflowStyle: 'none',
276
+ scrollBehavior: 'smooth',
277
+ }
278
+ : undefined
279
+ }
280
+ >
281
+ <div
282
+ className={
283
+ variant === 'results'
284
+ ? 'flex flex-row flex-nowrap gap-2 pb-1'
285
+ : 'flex flex-row flex-wrap gap-2 pb-1'
286
+ }
287
+ style={variant === 'results' ? { width: 'max-content' } : { width: 'auto' }}
288
+ >
289
+ {smartFilters.filters.map((filter, index) => {
290
+ const isSelected =
291
+ smartFilters.selectedFilterKey === filter.key &&
292
+ smartFilters.selectedFilterValue === filter.value;
293
+ return (
294
+ <button
295
+ key={`${filter.key}-${filter.value}-${index}`}
296
+ style={variant === 'results' ? { flex: '0 0 auto' } : undefined}
297
+ className={`px-3.5 py-2 rounded-lg border text-sm ${
298
+ isSelected
299
+ ? 'bg-[#545987] text-white border-[#545987]'
300
+ : 'bg-[#E7E8F1] text-[#767A9F] border-transparent'
301
+ }`}
302
+ onClick={() => {
303
+ if (smartFilters.rerankStatus === 'loading') return;
304
+ void onFilterClick({
305
+ key: filter.key,
306
+ value: filter.value,
307
+ });
308
+ }}
309
+ >
310
+ {filter.label}: {filter.value}
311
+ </button>
312
+ );
313
+ })}
314
+ </div>
315
+ </div>
316
+ </div>
317
+ );
318
+ }
319
+
320
+ export default SmartFilter;