@nyris/nyris-webapp 0.3.91 → 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.f2255597.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.f2255597.js +0 -3
  105. package/src/utils/addAssets.ts +0 -40
  106. /package/build/static/js/{main.f2255597.js.LICENSE.txt → main.36b77705.js.LICENSE.txt} +0 -0
@@ -4,20 +4,26 @@ 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';
10
11
 
11
12
  export default function SidePanel({ className }: { className?: string }) {
13
+ const settings = window.settings;
14
+ const isSmartFiltersEnabled = !!settings.useSmartFilters;
12
15
  const requestImages = useRequestStore(state => state.requestImages);
13
16
 
14
17
  const specificationFilter = useRequestStore(
15
18
  state => state.specificationFilter,
16
19
  );
17
20
 
21
+ const findApiProducts = useResultStore(state => state.productsFromFindApi);
22
+
18
23
  const showSidePanel = useUiStore(state => state.showSidePanel);
19
24
 
20
25
  const imageAnalysis = useResultStore(state => state.imageAnalysis);
26
+ const smartFilters = useResultStore(state => state.smartFilters);
21
27
 
22
28
  const showPostFilter = window.settings?.postFilterOption;
23
29
 
@@ -31,42 +37,54 @@ export default function SidePanel({ className }: { className?: string }) {
31
37
  const googleGroundingResponse = useResultStore(
32
38
  state => state.googleGroundingResponse,
33
39
  );
34
- const groundingFilterResult = useResultStore(
35
- state => state.groundingFilterResult,
36
- );
40
+ const groundingError = useResultStore(state => state.groundingError);
37
41
  const groundingProduct =
38
42
  googleGroundingResponse?.identified_product ||
39
43
  googleGroundingResponse ||
40
44
  null;
41
45
 
46
+ const effectiveGroundingResults = useEffectiveGroundingResults();
47
+
42
48
  if (!showPostFilter && requestImages.length === 0) {
43
49
  return <></>;
44
50
  }
45
51
 
52
+ if (
53
+ requestImages.length === 0 &&
54
+ findApiProducts.length === 0 &&
55
+ !window.settings.algolia.enabled
56
+ ) {
57
+ return <></>;
58
+ }
59
+
46
60
  return (
47
61
  <div
48
62
  className={twMerge(
49
63
  [
50
- 'max-w-[325px]',
51
64
  'w-full',
52
- 'shadow-[3px_-2px_3px_-3px_#d3d4d8]',
53
- 'overflow-x-hidden',
54
- 'overflow-y-auto',
65
+ 'overflow-hidden',
55
66
  'bg-white',
56
67
  'relative',
57
68
  'flex',
58
69
  'flex-col',
70
+ 'ml-4',
71
+ 'mb-4',
72
+ showSidePanel && 'border border-solid border-[#DDDEE7]',
73
+ showSidePanel ? 'rounded-[32px]' : 'rounded-2xl',
74
+ 'transition-all',
75
+ showSidePanel ? 'w-[334px] min-w-[334px]' : 'w-0 min-w-0',
76
+ 'shadow-ds-1',
59
77
  ],
60
78
  className,
61
79
  )}
62
80
  >
63
81
  <div
64
82
  className={twMerge(
65
- ' w-full h-full overflow-y-auto overflow-x-hidden',
83
+ ' w-full overflow-y-auto overflow-x-hidden',
66
84
  showSidePanel ? 'block' : 'hidden',
67
85
  )}
68
86
  >
69
- <div className="w-full h-full">
87
+ <div className="w-full">
70
88
  <div
71
89
  className={twMerge([
72
90
  'w-full',
@@ -76,7 +94,8 @@ export default function SidePanel({ className }: { className?: string }) {
76
94
  'flex',
77
95
  'justify-center',
78
96
  'items-center',
79
- 'min-w-[283px]',
97
+ 'w-[295px]',
98
+ 'm-4',
80
99
  // 'mx-4',
81
100
  // 'mt-4',
82
101
  // 'rounded',
@@ -84,78 +103,84 @@ export default function SidePanel({ className }: { className?: string }) {
84
103
  >
85
104
  {requestImages[0] && <ImagePreview />}
86
105
  </div>
87
- <div className="pb-4">
88
- {window.settings.showImageDetails &&
106
+ <div className="pb-4 flex flex-col gap-6">
107
+ {isSmartFiltersEnabled &&
108
+ window.settings.showImageDetails &&
89
109
  (imageAnalysis?.imageDescription ||
90
- Object.keys(imageAnalysis?.specification || {}).length > 0) && (
91
- <div className="self-stretch p-3.5 bg-[#F3F4F8] rounded-3xl inline-flex flex-col justify-start items-start gap-1.5 mt-4 mx-4 ">
92
- {imageAnalysis?.imageDescription !==
93
- 'No description available' && (
94
- <div className="self-stretch flex flex-col justify-start items-start">
95
- <div className="justify-start text-[#3B3E5F] text-base font-semibold">
96
- Image description
97
- </div>
98
- <div className="self-stretch justify-start text-[#3B3E5F] text-xs font-normal">
99
- {imageAnalysis?.imageDescription || ''}
100
- </div>
101
- </div>
102
- )}
103
-
104
- {Object.keys(imageAnalysis?.specification || {})
105
- .filter(
106
- key =>
107
- key !== 'is_nameplate' && key !== 'prefilter_value',
108
- )
109
- .some(key => !!imageAnalysis?.specification[key]) && (
110
- <div className="justify-start text-[#3B3E5F] text-base font-semibold mt-2">
111
- Identified Attributes
112
- </div>
113
- )}
114
-
115
- {Object.keys(imageAnalysis?.specification || {})
116
- .filter(
117
- key =>
118
- key !== 'is_nameplate' && key !== 'prefilter_value',
119
- )
120
- .map(key => {
121
- const value = imageAnalysis?.specification[key];
122
- if (!value) {
123
- return null;
124
- }
125
- return (
126
- <ItemSpecification
127
- attr={key}
128
- value={value}
129
- specificationFilter={specificationFilter}
130
- imageAnalysis={imageAnalysis}
131
- />
132
- );
133
- })}
134
- </div>
110
+ Object.keys(imageAnalysis?.specification || {}).length > 0 ||
111
+ smartFilters.status !== 'idle') && (
112
+ <SmartFilter
113
+ imageAnalysis={imageAnalysis}
114
+ specificationFilter={specificationFilter}
115
+ variant="sidepanel"
116
+ />
135
117
  )}
136
118
 
137
- {(isGoogleGroundingLoading || googleGroundingResponse) && (
138
- <div className="mx-4 mb-4 mt-4 rounded-3xl bg-[#F3F4F8] border border-[#e4e3ff] p-3.5 text-[#3B3E5F] ">
119
+ {(isGoogleGroundingLoading ||
120
+ googleGroundingResponse ||
121
+ groundingError) && (
122
+ <div className="mx-4 rounded-3xl bg-[#F3F4F8] border border-[#e4e3ff] p-3.5 text-[#3B3E5F] ">
139
123
  <div className="flex items-center justify-between">
140
124
  {!isGoogleGroundingLoading && (
141
125
  <div className="flex items-center gap-2 text-base font-semibold">
142
126
  <span className="text-[#3B3E5F] font-bold text-sm leading-4">
143
127
  Smart Product Search
144
128
  </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>
129
+ {groundingError && (
130
+ <svg
131
+ width="19"
132
+ height="18"
133
+ viewBox="0 0 19 18"
134
+ fill="none"
135
+ xmlns="http://www.w3.org/2000/svg"
136
+ >
137
+ <mask
138
+ id="mask0_15292_25467"
139
+ style={{ maskType: 'luminance' }}
140
+ maskUnits="userSpaceOnUse"
141
+ x="0"
142
+ y="0"
143
+ width="19"
144
+ height="18"
145
+ >
146
+ <path
147
+ fill-rule="evenodd"
148
+ clip-rule="evenodd"
149
+ d="M0.5 1.02344H18.5V16.9764H0.5V1.02344Z"
150
+ fill="white"
151
+ stroke="white"
152
+ />
153
+ </mask>
154
+ <g mask="url(#mask0_15292_25467)">
155
+ <path
156
+ 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"
157
+ stroke="#3B3E5F"
158
+ />
159
+ </g>
160
+ <path
161
+ fill-rule="evenodd"
162
+ clip-rule="evenodd"
163
+ 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"
164
+ fill="#3B3E5F"
165
+ />
166
+ </svg>
167
+ )}
168
+ {!groundingError && (
169
+ <svg
170
+ width="18"
171
+ height="18"
172
+ viewBox="0 0 18 18"
173
+ fill="none"
174
+ xmlns="http://www.w3.org/2000/svg"
175
+ >
176
+ <path
177
+ fill-rule="evenodd"
178
+ clip-rule="evenodd"
179
+ 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"
180
+ fill="#3B3E5F"
181
+ />
182
+ </svg>
183
+ )}
159
184
  </div>
160
185
  )}
161
186
 
@@ -197,23 +222,31 @@ export default function SidePanel({ className }: { className?: string }) {
197
222
  <div className="h-full w-1/2 animate-pulse rounded bg-[#3E36DC]" />
198
223
  </div>
199
224
  )}
225
+ {groundingError && (
226
+ <div className="text-[#767A9F] text-sm mt-2.5">
227
+ Unable to search the Internet.
228
+ </div>
229
+ )}
230
+ {googleGroundingResponse &&
231
+ !groundingError &&
232
+ !isGoogleGroundingLoading && (
233
+ <>
234
+ <div className="mt-4 flex gap-2 items-center">
235
+ <div className="w-[8px] h-[8px] bg-green-500 rounded-full "></div>
200
236
 
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
237
+ <div className="text-sm font-semibold leading-4">
238
+ 1. Results from the internet
239
+ </div>
208
240
  </div>
209
- </div>
210
- <div className="mt-3 text-[10px]">
211
- <GroundingSpecs data={groundingProduct} />
212
- </div>
213
- </>
214
- )}
215
- {groundingFilterResult?.length > 0 &&
216
- showingGroundingFilterResult && (
241
+ <div className="mt-3 text-[10px]">
242
+ <GroundingSpecs data={groundingProduct} />
243
+ </div>
244
+ </>
245
+ )}
246
+ {effectiveGroundingResults?.length > 0 &&
247
+ showingGroundingFilterResult &&
248
+ !groundingError &&
249
+ !isGoogleGroundingLoading && (
217
250
  <div className="mt-3.5 text-[#3B3E5F]">
218
251
  <div className="flex gap-2 items-center">
219
252
  <div className="w-[8px] h-[8px] bg-green-500 rounded-full "></div>
@@ -225,12 +258,12 @@ export default function SidePanel({ className }: { className?: string }) {
225
258
  <div className="text-xs text-[#22C55E] mb-1 mt-1">
226
259
  Found{' '}
227
260
  <span className="font-bold leading-4">
228
- {groundingFilterResult?.length} matching{' '}
261
+ {effectiveGroundingResults?.length} matching{' '}
229
262
  </span>{' '}
230
263
  product
231
264
  </div>
232
265
  <div className="flex flex-col gap-2">
233
- {groundingFilterResult?.map(
266
+ {effectiveGroundingResults?.map(
234
267
  (item: any, index: number) => (
235
268
  <div
236
269
  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;