@nyris/nyris-webapp 0.3.89 → 0.3.91

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 (70) hide show
  1. package/build/_headers +2 -0
  2. package/build/asset-manifest.json +6 -6
  3. package/build/index.html +1 -1
  4. package/build/js/settings.example.js +17 -0
  5. package/build/static/css/main.734b52e1.css +4 -0
  6. package/build/static/css/main.734b52e1.css.map +1 -0
  7. package/build/static/js/main.f2255597.js +3 -0
  8. package/build/static/js/{main.ca8b95bc.js.map → main.f2255597.js.map} +1 -1
  9. package/package.json +3 -3
  10. package/public/_headers +2 -0
  11. package/public/index.html +1 -1
  12. package/public/js/settings.example.js +17 -0
  13. package/src/App.tsx +5 -3
  14. package/src/components/Cart.tsx +321 -0
  15. package/src/components/CustomCameraDrawer.tsx +4 -22
  16. package/src/components/DragDropFile.tsx +57 -38
  17. package/src/components/ExperienceVisualSearch/ExperienceVisualSearch.tsx +6 -1
  18. package/src/components/ExperienceVisualSearch/ExperienceVisualSearchTrigger.tsx +2 -2
  19. package/src/components/GroundingSpecs.tsx +47 -0
  20. package/src/components/Header.tsx +94 -93
  21. package/src/components/HitsPerPage.tsx +4 -2
  22. package/src/components/ImagePreview.tsx +64 -31
  23. package/src/components/ImageUpload.tsx +247 -0
  24. package/src/components/ItemSpecification.tsx +164 -0
  25. package/src/components/MatchNotificationBanner.tsx +165 -0
  26. package/src/components/PostFilter/PostFilter.tsx +22 -1
  27. package/src/components/PostFilter/PostFilterComponent.tsx +59 -26
  28. package/src/components/PostFilter/PostFilterFindApi.tsx +242 -0
  29. package/src/components/PoweredBy.tsx +16 -0
  30. package/src/components/PreFilter/PreFilter.tsx +77 -54
  31. package/src/components/Product/Product.tsx +186 -28
  32. package/src/components/Product/ProductAttribute.tsx +2 -2
  33. package/src/components/Product/ProductDetailView.tsx +123 -18
  34. package/src/components/Product/ProductDetailViewModal.tsx +3 -0
  35. package/src/components/Product/ProductList.tsx +78 -8
  36. package/src/components/SidePanel.tsx +212 -120
  37. package/src/components/TextSearch.tsx +82 -203
  38. package/src/components/Toaster.tsx +34 -15
  39. package/src/helpers/ToastHelper.ts +6 -2
  40. package/src/hooks/useCadSearch.ts +5 -0
  41. package/src/hooks/useImageSearch.ts +102 -13
  42. package/src/index.css +59 -0
  43. package/src/layouts/AppLayout.tsx +16 -14
  44. package/src/pages/Home.tsx +61 -13
  45. package/src/pages/Result.tsx +287 -295
  46. package/src/services/vizo.ts +161 -0
  47. package/src/stores/request/Misc/misc.initialstate.ts +1 -0
  48. package/src/stores/request/Misc/misc.slice.ts +1 -0
  49. package/src/stores/request/filter/filter.initialState.ts +3 -0
  50. package/src/stores/request/filter/filter.slice.ts +23 -0
  51. package/src/stores/result/prodcuts/products.initialState.ts +4 -0
  52. package/src/stores/result/prodcuts/products.slice.ts +15 -0
  53. package/src/stores/types.ts +27 -1
  54. package/src/stores/ui/loading/loading.initialState.ts +1 -0
  55. package/src/stores/ui/loading/loading.slice.ts +4 -0
  56. package/src/stores/ui/sidePanel/sidePanel.initialState.ts +5 -0
  57. package/src/stores/ui/sidePanel/sidePanel.slice.ts +11 -0
  58. package/src/stores/ui/uiStore.ts +4 -1
  59. package/src/styles/Cart.scss +210 -0
  60. package/src/styles/common.scss +10 -0
  61. package/src/translations.ts +4 -4
  62. package/src/types.ts +11 -3
  63. package/src/utils/prepareImageList.ts +6 -5
  64. package/src/utils/textSearchFilter.ts +203 -0
  65. package/tailwind.config.js +1 -0
  66. package/build/static/css/main.ba1c7479.css +0 -4
  67. package/build/static/css/main.ba1c7479.css.map +0 -1
  68. package/build/static/js/main.ca8b95bc.js +0 -3
  69. package/src/components/Footer.tsx +0 -21
  70. /package/build/static/js/{main.ca8b95bc.js.LICENSE.txt → main.f2255597.js.LICENSE.txt} +0 -0
@@ -7,6 +7,7 @@ import useUiStore from 'stores/ui/uiStore';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import { twMerge } from 'tailwind-merge';
9
9
  import { filterProducts } from 'utils/specificationFilter';
10
+ import { filterProductsByText } from 'utils/textSearchFilter';
10
11
 
11
12
  interface Props {
12
13
  sendFeedBackAction?: any;
@@ -23,6 +24,15 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
23
24
  const productsFromFindApi = useResultStore(
24
25
  state => state.productsFromFindApi,
25
26
  );
27
+
28
+ const groundingFilterResult = useResultStore(
29
+ state => state.groundingFilterResult,
30
+ );
31
+
32
+ const showingGroundingFilterResult = useResultStore(
33
+ state => state.showingGroundingFilterResult,
34
+ );
35
+
26
36
  const isAlgoliaLoading = useUiStore(state => state.isAlgoliaLoading);
27
37
  const isFindApiLoading = useUiStore(state => state.isFindApiLoading);
28
38
 
@@ -34,6 +44,44 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
34
44
  const specificationFilter = useRequestStore(
35
45
  state => state.specificationFilter,
36
46
  );
47
+ const postFilterSelections = useRequestStore(
48
+ state => state.postFilterSelections,
49
+ );
50
+
51
+ const textFilteredProducts = useMemo(() => {
52
+ if (!requestImages?.length) {
53
+ return productsFromFindApi;
54
+ }
55
+
56
+ if (!query?.trim()) {
57
+ return productsFromFindApi;
58
+ }
59
+
60
+ return filterProductsByText(query || '', productsFromFindApi);
61
+ }, [productsFromFindApi, query, requestImages]);
62
+
63
+ const postFilteredProducts = useMemo(() => {
64
+ const entries = Object.entries(postFilterSelections || {}).filter(
65
+ ([, values]) => values?.length,
66
+ );
67
+
68
+ if (entries.length === 0) {
69
+ return textFilteredProducts;
70
+ }
71
+
72
+ return textFilteredProducts.filter(product => {
73
+ const productFilters = product?.filters || {};
74
+
75
+ return entries.every(([attribute, selectedValues]) => {
76
+ const productValues = productFilters?.[attribute];
77
+ if (!Array.isArray(productValues)) {
78
+ return false;
79
+ }
80
+
81
+ return selectedValues.some(value => productValues.includes(value));
82
+ });
83
+ });
84
+ }, [postFilterSelections, textFilteredProducts]);
37
85
 
38
86
  const getUrlToCanvasFile = async (url: string) => {
39
87
  setQuery('');
@@ -50,20 +98,21 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
50
98
  const isAlgoliaEnabled = window.settings?.algolia?.enabled;
51
99
  const products = useMemo(() => {
52
100
  const filter = Object.values(specificationFilter || {})[0];
101
+ const baseFindApiProducts = postFilteredProducts;
53
102
 
54
103
  if (filter) {
55
104
  return filterProducts(
56
105
  filter,
57
- isAlgoliaEnabled ? productsFromAlgolia : productsFromFindApi,
106
+ isAlgoliaEnabled ? productsFromAlgolia : baseFindApiProducts,
58
107
  );
59
108
  }
60
109
 
61
110
  if (!isAlgoliaEnabled) {
62
- return productsFromFindApi;
111
+ return baseFindApiProducts;
63
112
  }
64
113
 
65
114
  if (productsFromAlgolia.length === 0 && isAlgoliaLoading && !query) {
66
- return productsFromFindApi;
115
+ return baseFindApiProducts;
67
116
  }
68
117
  return productsFromAlgolia;
69
118
  }, [
@@ -72,13 +121,34 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
72
121
  productsFromAlgolia,
73
122
  isAlgoliaLoading,
74
123
  query,
75
- productsFromFindApi,
124
+ postFilteredProducts,
76
125
  ]);
77
126
 
127
+ const groundedFilteredProducts = useMemo(() => {
128
+ if (groundingFilterResult && groundingFilterResult.length > 0) {
129
+ const filtered = products.filter((product: any) =>
130
+ groundingFilterResult.some((result: any) => product.sku === result.sku),
131
+ );
132
+ if (filtered.length > 0) {
133
+ return filtered;
134
+ }
135
+ return products;
136
+ }
137
+ return () => {};
138
+ // eslint-disable-next-line react-hooks/exhaustive-deps
139
+ }, [groundingFilterResult]);
140
+
78
141
  const renderItem = useMemo(() => {
142
+ const productsToRender =
143
+ groundedFilteredProducts &&
144
+ groundedFilteredProducts.length > 0 &&
145
+ showingGroundingFilterResult
146
+ ? groundedFilteredProducts
147
+ : products;
148
+
79
149
  return (
80
150
  <div className={twMerge(['contents'])}>
81
- {products?.map((product: any, i: number) => {
151
+ {productsToRender?.map((product: any, i: number) => {
82
152
  return (
83
153
  <Product
84
154
  key={i}
@@ -96,8 +166,8 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
96
166
  !isAlgoliaEnabled
97
167
  ? product['image']
98
168
  : product['image(main_similarity)']
99
- ? product['image(main_similarity)']
100
- : product['main_image_link']
169
+ ? product['image(main_similarity)']
170
+ : product['main_image_link']
101
171
  }
102
172
  />
103
173
  );
@@ -115,7 +185,7 @@ function ProductList({ sendFeedBackAction }: Props): JSX.Element {
115
185
  </div>
116
186
  );
117
187
  // eslint-disable-next-line react-hooks/exhaustive-deps
118
- }, [products]);
188
+ }, [products, groundedFilteredProducts, showingGroundingFilterResult]);
119
189
 
120
190
  if (
121
191
  products?.length === 0 &&
@@ -4,8 +4,9 @@ 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 { Icon } from '@nyris/nyris-react-components';
8
- import Tooltip from './Tooltip/TooltipComponent';
7
+ import ItemSpecification from './ItemSpecification';
8
+ import useUiStore from 'stores/ui/uiStore';
9
+ import GroundingSpecs from './GroundingSpecs';
9
10
 
10
11
  export default function SidePanel({ className }: { className?: string }) {
11
12
  const requestImages = useRequestStore(state => state.requestImages);
@@ -14,10 +15,30 @@ export default function SidePanel({ className }: { className?: string }) {
14
15
  state => state.specificationFilter,
15
16
  );
16
17
 
18
+ const showSidePanel = useUiStore(state => state.showSidePanel);
19
+
17
20
  const imageAnalysis = useResultStore(state => state.imageAnalysis);
18
21
 
19
22
  const showPostFilter = window.settings?.postFilterOption;
20
23
 
24
+ const showingGroundingFilterResult = useResultStore(
25
+ state => state.showingGroundingFilterResult,
26
+ );
27
+
28
+ const isGoogleGroundingLoading = useUiStore(
29
+ state => state.isGoogleGroundingLoading,
30
+ );
31
+ const googleGroundingResponse = useResultStore(
32
+ state => state.googleGroundingResponse,
33
+ );
34
+ const groundingFilterResult = useResultStore(
35
+ state => state.groundingFilterResult,
36
+ );
37
+ const groundingProduct =
38
+ googleGroundingResponse?.identified_product ||
39
+ googleGroundingResponse ||
40
+ null;
41
+
21
42
  if (!showPostFilter && requestImages.length === 0) {
22
43
  return <></>;
23
44
  }
@@ -40,134 +61,205 @@ export default function SidePanel({ className }: { className?: string }) {
40
61
  )}
41
62
  >
42
63
  <div
43
- className={twMerge([
44
- 'w-full',
45
- 'h-fit',
46
- 'min-h-auto',
47
- 'relative',
48
- 'flex',
49
- 'justify-center',
50
- 'items-center',
51
- 'min-w-[283px]',
52
- // 'mx-4',
53
- // 'mt-4',
54
- // 'rounded',
55
- ])}
64
+ className={twMerge(
65
+ ' w-full h-full overflow-y-auto overflow-x-hidden',
66
+ showSidePanel ? 'block' : 'hidden',
67
+ )}
56
68
  >
57
- {requestImages[0] && <ImagePreview />}
58
- </div>
59
-
60
- {window.settings.showImageDetails && (imageAnalysis?.imageDescription ||
61
- Object.keys(imageAnalysis?.specification || {}).length > 0) && (
62
- <div className="self-stretch p-4 bg-[#f3f3f5] rounded inline-flex flex-col justify-start items-start gap-1.5 mt-4 mx-4 ">
63
- {imageAnalysis?.imageDescription !== 'No description available' && (
64
- <div className="self-stretch flex flex-col justify-start items-start">
65
- <div className="justify-start text-black text-base font-semibold">
66
- Image description
67
- </div>
68
- <div className="self-stretch justify-start text-black text-xs font-normal">
69
- {imageAnalysis?.imageDescription || ''}
70
- </div>
71
- </div>
72
- )}
73
-
74
- <div className="justify-start text-black text-base font-semibold mt-2">
75
- Identified Attributes
69
+ <div className="w-full h-full">
70
+ <div
71
+ className={twMerge([
72
+ 'w-full',
73
+ 'h-fit',
74
+ 'min-h-auto',
75
+ 'relative',
76
+ 'flex',
77
+ 'justify-center',
78
+ 'items-center',
79
+ 'min-w-[283px]',
80
+ // 'mx-4',
81
+ // 'mt-4',
82
+ // 'rounded',
83
+ ])}
84
+ >
85
+ {requestImages[0] && <ImagePreview />}
76
86
  </div>
87
+ <div className="pb-4">
88
+ {window.settings.showImageDetails &&
89
+ (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
+ )}
77
103
 
78
- {Object.keys(imageAnalysis?.specification || {})
79
- .filter((key) => (key !== 'is_nameplate' && key !== 'prefilter_value'))
80
- .map(key => {
81
- const value = imageAnalysis?.specification[key];
82
- if (!value) {
83
- return null;
84
- }
85
- return (
86
- <div
87
- key={key}
88
- className="flex justify-between w-full gap-2 items-center"
89
- >
90
- <div className="self-stretch inline-flex justify-start items-center gap-1.5">
91
- <div className="justify-start text-black text-xs font-semibold">
92
- {key}:
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
93
112
  </div>
94
- <Tooltip
95
- content={
96
- specificationFilter[key]
97
- ? 'Filter applied. Clear to choose a different value.'
98
- : 'Click to apply as a search filter.'
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;
99
124
  }
100
- delayDuration={1000}
101
- disabled={!value}
102
- >
103
- <div
104
- className={twMerge(
105
- `px-1 py-1 bg-[#e4e3ff] rounded-[1px] flex justify-center items-center gap-1.5`,
106
- 'border border-solid border-transparent hover:border-[#3E36DC]',
107
- 'cursor-pointer',
108
- specificationFilter[key]
109
- ? 'border-[#3E36DC] bg-[#3E36DC] '
110
- : '',
111
- )}
112
- onClick={() => {
113
- if (!value) {
114
- return;
115
- }
116
- const setSpecificationFilter =
117
- useRequestStore.getState().setSpecificationFilter;
118
-
119
- const setSpecificationFilteredProducts =
120
- useResultStore.getState()
121
- .setSpecificationFilteredProducts;
122
-
123
- if (specificationFilter[key]) {
124
- setSpecificationFilter({});
125
- setSpecificationFilteredProducts([]);
126
- // setProducts(results);
127
- } else {
128
- setSpecificationFilter({
129
- [key]: value,
130
- });
131
- }
132
- }}
125
+ return (
126
+ <ItemSpecification
127
+ attr={key}
128
+ value={value}
129
+ specificationFilter={specificationFilter}
130
+ imageAnalysis={imageAnalysis}
131
+ />
132
+ );
133
+ })}
134
+ </div>
135
+ )}
136
+
137
+ {(isGoogleGroundingLoading || googleGroundingResponse) && (
138
+ <div className="mx-4 mb-4 mt-4 rounded-3xl bg-[#F3F4F8] border border-[#e4e3ff] p-3.5 text-[#3B3E5F] ">
139
+ <div className="flex items-center justify-between">
140
+ {!isGoogleGroundingLoading && (
141
+ <div className="flex items-center gap-2 text-base font-semibold">
142
+ <span className="text-[#3B3E5F] font-bold text-sm leading-4">
143
+ Smart Product Search
144
+ </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"
133
151
  >
134
- <div
135
- className={twMerge(
136
- 'justify-start text-[#3e36dc] text-[10px] leading-none px-0.5',
137
- 'font-normal hover:font-bold hover:px-0',
138
- specificationFilter[key]
139
- ? 'font-bold text-white hover:px-0.5'
140
- : '',
141
- 'max-line-1',
142
- )}
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>
159
+ </div>
160
+ )}
161
+
162
+ {isGoogleGroundingLoading && (
163
+ <div className="flex items-center gap-2 text-xs text-[#3E36DC]">
164
+ <span className="inline-flex items-center gap-1">
165
+ <svg
166
+ className="h-3 w-3 animate-spin"
167
+ viewBox="0 0 24 24"
168
+ aria-hidden="true"
143
169
  >
144
- {imageAnalysis?.specification[key] || 'N/A'}
170
+ <circle
171
+ className="opacity-25"
172
+ cx="12"
173
+ cy="12"
174
+ r="10"
175
+ stroke="#3E36DC"
176
+ strokeWidth="4"
177
+ fill="none"
178
+ />
179
+ <path
180
+ className="opacity-75"
181
+ fill="#3E36DC"
182
+ d="M12 2a10 10 0 0 1 10 10h-4a6 6 0 0 0-6-6V2z"
183
+ />
184
+ </svg>
185
+ Searching the Internet
186
+ <span className="inline-flex w-5 justify-start">
187
+ <span className="animate-pulse">.</span>
188
+ <span className="animate-pulse delay-150">.</span>
189
+ <span className="animate-pulse delay-300">.</span>
190
+ </span>
191
+ </span>
192
+ </div>
193
+ )}
194
+ </div>
195
+ {isGoogleGroundingLoading && (
196
+ <div className="mt-2 h-1.5 w-full overflow-hidden rounded bg-[#e4e3ff]">
197
+ <div className="h-full w-1/2 animate-pulse rounded bg-[#3E36DC]" />
198
+ </div>
199
+ )}
200
+
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
208
+ </div>
209
+ </div>
210
+ <div className="mt-3 text-[10px]">
211
+ <GroundingSpecs data={groundingProduct} />
212
+ </div>
213
+ </>
214
+ )}
215
+ {groundingFilterResult?.length > 0 &&
216
+ showingGroundingFilterResult && (
217
+ <div className="mt-3.5 text-[#3B3E5F]">
218
+ <div className="flex gap-2 items-center">
219
+ <div className="w-[8px] h-[8px] bg-green-500 rounded-full "></div>
220
+
221
+ <div className="text-sm font-semibold leading-4">
222
+ 2. Matched products in visual results
145
223
  </div>
146
224
  </div>
147
- </Tooltip>
148
- </div>
149
- <div
150
- onClick={() => {
151
- navigator.clipboard.writeText(
152
- imageAnalysis?.specification[key] || '',
153
- );
154
- }}
155
- >
156
- <Icon
157
- name="copy"
158
- className="text-[#AAABB5] w-[12px] h-[12px] hover:text-[#3E36DC] cursor-pointer"
159
- />
160
- </div>
161
- </div>
162
- );
163
- })}
225
+ <div className="text-xs text-[#22C55E] mb-1 mt-1">
226
+ Found{' '}
227
+ <span className="font-bold leading-4">
228
+ {groundingFilterResult?.length} matching{' '}
229
+ </span>{' '}
230
+ product
231
+ </div>
232
+ <div className="flex flex-col gap-2">
233
+ {groundingFilterResult?.map(
234
+ (item: any, index: number) => (
235
+ <div
236
+ key={`${item?.sku || 'sku'}-${index}`}
237
+ className=""
238
+ >
239
+ <div className="flex items-center justify-between">
240
+ <div className="text-[10px] font-semibold leading-4">
241
+ SKU {item?.sku || 'N/A'} :
242
+ <span className="text-[10px] mt-1 font-normal leading-4">
243
+ {item?.reason || 'No reason provided.'}
244
+ </span>
245
+ </div>
246
+ </div>
247
+ </div>
248
+ ),
249
+ )}
250
+ </div>
251
+ </div>
252
+ )}
253
+ </div>
254
+ )}
255
+ {showPostFilter && (
256
+ <PostFilterComponent
257
+ className={requestImages.length === 0 ? 'mt-9' : ''}
258
+ />
259
+ )}
260
+ </div>
164
261
  </div>
165
- )}
166
- {showPostFilter && (
167
- <PostFilterComponent
168
- className={requestImages.length === 0 ? 'mt-9' : ''}
169
- />
170
- )}
262
+ </div>
171
263
  </div>
172
264
  );
173
265
  }