@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
@@ -0,0 +1,417 @@
1
+ /**
2
+ * P2 - Result Evaluator
3
+ *
4
+ * Fires once when results are returned from the API.
5
+ * Evaluates whether results match the uploaded image.
6
+ * Routes to P3 (good results) or P5 (bad results recovery).
7
+ */
8
+
9
+ import { useCallback, useRef } from 'react';
10
+ import useConversationStore, {
11
+ QuickReplyOption,
12
+ ConversationRoute,
13
+ ResultQuality,
14
+ } from 'stores/chat/conversationStore';
15
+ import useSmartFiltersStore from 'stores/smartFilters/smartFiltersStore';
16
+ import { chatAnalysis } from 'services/vizo';
17
+ import useRequestStore from 'stores/request/requestStore';
18
+ import { canvasToBase64, cropImage } from 'utils/cropImageToBase64';
19
+ import {
20
+ fetchProductImageBase64,
21
+ getProductImageUrl,
22
+ } from 'utils/fetchProductImage';
23
+ import { loadRelatedPartsTable, lookupRelatedParts } from 'utils/relatedParts';
24
+
25
+ interface P2EvaluationResult {
26
+ quality: ResultQuality;
27
+ route: ConversationRoute;
28
+ opening_message: string;
29
+ options: QuickReplyOption[];
30
+ keyword?: string;
31
+ }
32
+
33
+ export function useResultEvaluator() {
34
+ const {
35
+ isEvaluated,
36
+ relatedPartsUsed,
37
+ setEvaluationResult,
38
+ setIsLoading,
39
+ setError,
40
+ setRelatedPartsTableSuggestions,
41
+ } = useConversationStore();
42
+ const { filters } = useSmartFiltersStore();
43
+
44
+ const evaluatingRef = useRef(false);
45
+
46
+ const evaluateResults = useCallback(
47
+ async ({
48
+ imageAnalysis,
49
+ activeProducts,
50
+ }: {
51
+ imageAnalysis: any;
52
+ activeProducts: any[];
53
+ }) => {
54
+ setIsLoading(true);
55
+ setError(null);
56
+ setRelatedPartsTableSuggestions([]);
57
+
58
+ try {
59
+ // Build context for P2
60
+ const specification = imageAnalysis?.specification || {};
61
+ const imageDescription =
62
+ imageAnalysis?.imageDescription || 'No description available';
63
+
64
+ // Get results (up to 30) with DETAILED metadata for finding differentiating attributes
65
+
66
+ const resultsSummary = activeProducts
67
+ .slice(0, 30)
68
+ .map((p: any, i: number) => {
69
+ // Extract all potentially useful metadata
70
+ const metadata: Record<string, any> = {};
71
+ const metadataFields = [
72
+ 'voltage',
73
+ 'size',
74
+ 'brand',
75
+ 'manufacturer',
76
+ 'material',
77
+ 'type',
78
+ 'category',
79
+ 'color',
80
+ 'dimensions',
81
+ 'power',
82
+ 'current',
83
+ 'connector',
84
+ 'model',
85
+ 'series',
86
+ 'product_line',
87
+ ];
88
+
89
+ metadataFields.forEach(field => {
90
+ if (p[field]) metadata[field] = p[field];
91
+ });
92
+
93
+ // Extract from title/description using common patterns
94
+ const title = p.title || p.name || '';
95
+ const titleDesc = `${title} ${p.description || ''}`;
96
+
97
+ // Extract brand from title - often first word(s) in CAPS or before asterisk
98
+ if (!metadata.brand && !metadata.manufacturer) {
99
+ // Pattern 1: First word in all caps (e.g., "MANDRINO*...")
100
+ const capsMatch = title.match(/^([A-Z][A-Z0-9]+)(?:\*|\s)/);
101
+ if (capsMatch) {
102
+ metadata.brand = capsMatch[1];
103
+ }
104
+ // Pattern 2: Text before asterisk (e.g., "MANDRINO*TOOL")
105
+ const asteriskMatch = title.match(/^([^*]+)\*/);
106
+ if (asteriskMatch && asteriskMatch[1].length <= 20) {
107
+ metadata.brand = asteriskMatch[1].trim();
108
+ }
109
+ // Pattern 3: Generic all-caps token in title/description
110
+ const tokenMatch = titleDesc.match(/\b([A-Z][A-Z0-9]{2,})\b/);
111
+ if (tokenMatch) {
112
+ metadata.brand = tokenMatch[1].toUpperCase();
113
+ }
114
+ }
115
+
116
+ // Extract dimensions like "382 mm x 260 mm x 3 mm" or "344 x 221 x 3"
117
+ const dimensionMatch = titleDesc.match(
118
+ /(\d+)\s*(?:mm)?\s*[xX×]\s*(\d+)\s*(?:mm)?\s*(?:[xX×]\s*(\d+))?/i,
119
+ );
120
+ if (dimensionMatch && !metadata.dimensions) {
121
+ const dims = dimensionMatch[3]
122
+ ? `${dimensionMatch[1]}x${dimensionMatch[2]}x${dimensionMatch[3]}mm`
123
+ : `${dimensionMatch[1]}x${dimensionMatch[2]}mm`;
124
+ metadata.dimensions = dims;
125
+ }
126
+
127
+ // Extract voltage - multiple patterns for different formats
128
+ // Patterns: "24V DC", "12 V", "DC 24V", "24VDC", "AC 230V"
129
+ const voltagePatterns = [
130
+ /(\d+)\s*V\s*(DC|AC)?/i, // "24V DC", "12 V", "24V"
131
+ /(DC|AC)\s*(\d+)\s*V/i, // "DC 24V", "AC 230V"
132
+ /(\d+)\s*(VDC|VAC)/i, // "24VDC", "230VAC"
133
+ ];
134
+ for (const pattern of voltagePatterns) {
135
+ const voltageMatch = titleDesc.match(pattern);
136
+ if (voltageMatch && !metadata.voltage) {
137
+ // Normalize to format like "24V" or "24V DC"
138
+ const fullMatch = voltageMatch[0]
139
+ .toUpperCase()
140
+ .replace(/\s+/g, ' ')
141
+ .trim();
142
+ // Extract just the number and V
143
+ const numMatch = fullMatch.match(/(\d+)/);
144
+ if (numMatch) {
145
+ const voltage = numMatch[1];
146
+ const dcAc = fullMatch.includes('DC')
147
+ ? ' DC'
148
+ : fullMatch.includes('AC')
149
+ ? ' AC'
150
+ : '';
151
+ metadata.voltage = `${voltage}V${dcAc}`;
152
+ }
153
+ break;
154
+ }
155
+ }
156
+
157
+ // Extract generic product type keywords from title/description
158
+ if (!metadata.product_type) {
159
+ const typeMatch = titleDesc.match(
160
+ /\b(bearing|ring|relay|sensor|motor|fan|contactor|valve|pump|faucet|thermostat|filter|housing)\b/i,
161
+ );
162
+ if (typeMatch) {
163
+ metadata.product_type = typeMatch[1].toUpperCase();
164
+ }
165
+ }
166
+
167
+ // Extract material
168
+ const materialMatch = titleDesc.match(
169
+ /\b(steel|aluminum|aluminium|brass|copper|plastic|rubber|stainless)\b/i,
170
+ );
171
+ if (materialMatch && !metadata.material)
172
+ metadata.material = materialMatch[1].toLowerCase();
173
+
174
+ return {
175
+ rank: i + 1,
176
+ title: title || 'Untitled',
177
+ description: (p.description || '').substring(0, 100),
178
+ category: p.category || p.type || 'Unknown',
179
+ score: p.score || p.similarity || 0,
180
+ metadata: Object.keys(metadata).length > 0 ? metadata : null,
181
+ };
182
+ });
183
+
184
+ // Build specification from P4 (smart filters)
185
+ const p4Specification: Record<string, string> = {};
186
+ filters.forEach(f => {
187
+ p4Specification[f.label.toLowerCase()] = f.value;
188
+ });
189
+
190
+ // Add image analysis specification
191
+ Object.entries(specification).forEach(([key, value]) => {
192
+ if (value && typeof value === 'string') {
193
+ p4Specification[key] = value;
194
+ }
195
+ });
196
+
197
+ // Analyze common attributes across results for filter suggestions
198
+ const attributeFrequency: Record<string, Record<string, number>> = {};
199
+ resultsSummary.forEach(r => {
200
+ if (r.metadata) {
201
+ Object.entries(r.metadata).forEach(([key, value]) => {
202
+ if (!attributeFrequency[key]) attributeFrequency[key] = {};
203
+ const strValue = String(value);
204
+ attributeFrequency[key][strValue] =
205
+ (attributeFrequency[key][strValue] || 0) + 1;
206
+ });
207
+ }
208
+ });
209
+
210
+ const userPrompt = `Evaluate these search results:
211
+
212
+ IMAGE DESCRIPTION: ${imageDescription}
213
+
214
+ SPECIFICATION (from image analysis):
215
+ ${JSON.stringify(p4Specification, null, 2)}
216
+
217
+ TOP 20 RESULTS WITH METADATA:
218
+ ${resultsSummary
219
+ .map(
220
+ r =>
221
+ `${r.rank}. ${r.title}${r.description ? ` - ${r.description}` : ''} (Score: ${r.score})${r.metadata ? `\n Metadata: ${JSON.stringify(r.metadata)}` : ''}`,
222
+ ).join('\n')}
223
+
224
+ COMMON ATTRIBUTES FOUND IN RESULTS (for filter suggestions):
225
+ ${JSON.stringify(attributeFrequency, null, 2)}
226
+
227
+ TOTAL RESULTS: ${resultsSummary.length}
228
+
229
+ Evaluate if these results match the image. If they do (same type of product), route to P3 and suggest filter options based on the COMMON ATTRIBUTES found above (like voltage, size, brand). Do NOT suggest the obvious category match.`;
230
+
231
+ const productsForImages = activeProducts.slice(0, 30);
232
+ const imageResults = await Promise.all(
233
+ productsForImages.map(async (p: any, i: number) => {
234
+ const url = getProductImageUrl(p);
235
+ if (!url) return { rank: i + 1, base64: null };
236
+
237
+ const base64 = await fetchProductImageBase64(url);
238
+
239
+ return { rank: i + 1, base64 };
240
+ }),
241
+ );
242
+
243
+ const hasVisualContext =
244
+ imageResults.filter(r => r.base64 !== null).length >= 1;
245
+
246
+ const productImageParts: Array<{
247
+ metadata: string;
248
+ image?: string;
249
+ mimeType?: string;
250
+ }> = [];
251
+
252
+ resultsSummary.forEach((result, i) => {
253
+ const originalProduct = productsForImages[i];
254
+ const tags = originalProduct?._tags;
255
+ const tagStr = tags
256
+ ? ` | TAGS: type=${tags.productType}, color=${tags.color}, shape=${tags.shape}, material=${tags.material}`
257
+ : '';
258
+
259
+ const metaLine = `${result.rank}. ${result.title}${result.description ? ` - ${result.description}` : ''} (Score: ${result.score})${result.metadata ? ` | Metadata: ${JSON.stringify(result.metadata)}` : ''}${tagStr}`;
260
+
261
+ if (hasVisualContext && imageResults[i]?.base64) {
262
+ productImageParts.push({
263
+ metadata: metaLine,
264
+ image: imageResults[i].base64 || '',
265
+ mimeType: 'image/jpeg',
266
+ });
267
+ }
268
+ });
269
+
270
+ const requestImage = useRequestStore.getState().requestImages;
271
+ const regions = useRequestStore.getState().regions;
272
+ const croppedCanvas = cropImage(requestImage[0], regions[0]);
273
+ const base64Image = canvasToBase64(croppedCanvas, 'image/jpeg', 1);
274
+
275
+ const response = await chatAnalysis({
276
+ promptKey: 'result_evaluator_prompt',
277
+ multimodalInputs: [
278
+ {
279
+ metadata: userPrompt,
280
+ },
281
+ {
282
+ metadata: 'request_image',
283
+ mimeType: 'image/jpeg',
284
+ image: base64Image,
285
+ },
286
+ ...productImageParts,
287
+ ],
288
+ });
289
+
290
+ const evaluation: P2EvaluationResult = response;
291
+
292
+ let mappedOptions = (evaluation?.options || []).map(opt => ({
293
+ label: opt.label,
294
+ action_type: opt.action_type as
295
+ | 'filter'
296
+ | 'clarify'
297
+ | 'text_search_prompt'
298
+ | 'find_related_parts',
299
+ key: (opt as any).key,
300
+ value: (opt as any).value,
301
+ }));
302
+ if (relatedPartsUsed) {
303
+ mappedOptions = mappedOptions.filter(
304
+ opt => opt.action_type !== 'find_related_parts',
305
+ );
306
+ }
307
+ mappedOptions.push({
308
+ label: "Describe what you're looking for",
309
+ action_type: 'text_search_prompt',
310
+ key: 'text_search',
311
+ value: 'text_search',
312
+ });
313
+
314
+ // Check lookup table for related parts on fresh image searches and store globally.
315
+ const table = await loadRelatedPartsTable();
316
+
317
+ const tableSuggestions = lookupRelatedParts(
318
+ table,
319
+ imageAnalysis.specification.product_category || '',
320
+ );
321
+ const hasTableSuggestions =
322
+ !!tableSuggestions && tableSuggestions.length > 0;
323
+ setRelatedPartsTableSuggestions(tableSuggestions || []);
324
+
325
+ // GC Gruppe: offer "Find related parts" in initial P2 response
326
+ if (
327
+ window.settings.vizo?.findRelatedParts &&
328
+ hasTableSuggestions &&
329
+ !relatedPartsUsed
330
+ ) {
331
+ mappedOptions.push({
332
+ label: `Find parts related to ${imageAnalysis.specification.product_category || 'request image'}`,
333
+ action_type: 'find_related_parts',
334
+ key: 'find_related',
335
+ value: 'find_related_parts',
336
+ });
337
+ }
338
+
339
+ // Enforce ≤5 rule: if initial results are ≤5, strip filter options, keep only text_search_prompt
340
+ if (activeProducts.length <= 5 && activeProducts.length > 0) {
341
+ mappedOptions = [
342
+ {
343
+ label: "Describe what you're looking for",
344
+ action_type: 'text_search_prompt' as const,
345
+ key: 'text_search',
346
+ value: 'text_search',
347
+ },
348
+ ];
349
+
350
+ if (
351
+ window.settings.vizo?.findRelatedParts &&
352
+ hasTableSuggestions &&
353
+ !relatedPartsUsed
354
+ ) {
355
+ mappedOptions.push({
356
+ label: `Find parts related to ${imageAnalysis.specification.product_category || 'request image'}`,
357
+ action_type: 'find_related_parts',
358
+ key: 'find_related',
359
+ value: 'find_related_parts',
360
+ });
361
+ }
362
+ }
363
+
364
+ setEvaluationResult(
365
+ evaluation.quality,
366
+ evaluation.route,
367
+ evaluation.opening_message,
368
+ mappedOptions,
369
+ evaluation.keyword,
370
+ );
371
+ } catch (err: any) {
372
+ console.error('result_evaluator_prompt', err);
373
+ setRelatedPartsTableSuggestions([]);
374
+ setError(err.message || 'Failed to evaluate results');
375
+
376
+ // Fallback to good results on error
377
+ setEvaluationResult(
378
+ 'good',
379
+ 'P3',
380
+ "I found some results for your image. How can I help you find what you're looking for?",
381
+ [
382
+ { label: 'Show me options', action_type: 'filter' as const },
383
+ {
384
+ label: "Describe what you're looking for",
385
+ action_type: 'text_search_prompt' as const,
386
+ key: 'text_search',
387
+ value: 'text_search',
388
+ },
389
+ ],
390
+ );
391
+ } finally {
392
+ setIsLoading(false);
393
+ }
394
+ },
395
+ // eslint-disable-next-line react-hooks/exhaustive-deps
396
+ [
397
+ setIsLoading,
398
+ setError,
399
+ setRelatedPartsTableSuggestions,
400
+ filters,
401
+ relatedPartsUsed,
402
+ setEvaluationResult,
403
+ ],
404
+ );
405
+
406
+ // Reset the guard ref when a new session starts (isEvaluated goes back to false)
407
+ if (!isEvaluated) {
408
+ evaluatingRef.current = false;
409
+ }
410
+
411
+ return {
412
+ evaluateResults,
413
+ isEvaluated,
414
+ };
415
+ }
416
+
417
+ export default useResultEvaluator;
package/src/index.css CHANGED
@@ -9,7 +9,7 @@ body {
9
9
  sans-serif;
10
10
  -webkit-font-smoothing: antialiased;
11
11
  -moz-osx-font-smoothing: grayscale;
12
- color: #2B2C46
12
+ color: #3B3E5F
13
13
  }
14
14
 
15
15
  /* Source Sans 3 fonts */
package/src/index.tsx CHANGED
@@ -1,4 +1,3 @@
1
- import React from 'react';
2
1
  import ReactDOM from 'react-dom/client';
3
2
  import './index.css';
4
3
  import App from './App';
@@ -1,4 +1,4 @@
1
- import { memo, useEffect } from 'react';
1
+ import { memo, useCallback, useEffect, useMemo } from 'react';
2
2
  import { Outlet, useLocation } from 'react-router';
3
3
 
4
4
  import { useAuth0 } from '@auth0/auth0-react';
@@ -18,6 +18,7 @@ import useRequestStore from 'stores/request/requestStore';
18
18
  import useResultStore from 'stores/result/resultStore';
19
19
  import useUiStore from 'stores/ui/uiStore';
20
20
  import { Toaster } from 'components/Toaster';
21
+ import { getFilters } from 'services/filter';
21
22
 
22
23
  i18n.use(initReactI18next).init({
23
24
  resources: translations,
@@ -92,12 +93,62 @@ function AppLayout(): JSX.Element {
92
93
  // eslint-disable-next-line react-hooks/exhaustive-deps
93
94
  }, [status]);
94
95
 
96
+ const user = useAuth0().user;
97
+
98
+ const setPreFilterList = useRequestStore(state => state.setPreFilterList);
99
+ const setPreFilterLoading = useRequestStore(
100
+ state => state.setPreFilterLoading,
101
+ );
102
+
103
+ const settings = window.settings;
104
+ const setMetaFilter = useRequestStore(state => state.setMetaFilter);
105
+
106
+ const showPreFilter = useMemo(() => {
107
+ if (settings.shouldUseUserMetadata && user) {
108
+ if (user['/user_metadata'].value) {
109
+ setMetaFilter(user['/user_metadata'].value);
110
+ }
111
+ }
112
+
113
+ if (settings.shouldUseUserMetadata && user) {
114
+ if (settings.preFilterOption && !user['/user_metadata'].value) {
115
+ return true;
116
+ }
117
+ return false;
118
+ }
119
+
120
+ return settings.preFilterOption;
121
+ }, [
122
+ setMetaFilter,
123
+ settings.preFilterOption,
124
+ settings.shouldUseUserMetadata,
125
+ user,
126
+ ]);
127
+
128
+ const getPreFilters = useCallback(async () => {
129
+ setPreFilterLoading(true);
130
+ try {
131
+ const res = await getFilters(1000, settings);
132
+ setPreFilterList(res);
133
+ } catch (e: any) {
134
+ console.log('err getDataFilterDesktop', e);
135
+ } finally {
136
+ setPreFilterLoading(false);
137
+ }
138
+ }, [settings, setPreFilterList, setPreFilterLoading]);
139
+
140
+ useEffect(() => {
141
+ if (showPreFilter) {
142
+ getPreFilters();
143
+ }
144
+ }, [getPreFilters, showPreFilter]);
145
+
95
146
  if (!showLayout) {
96
147
  return <Outlet />;
97
148
  }
98
149
 
99
150
  return (
100
- <div className="full-height flex flex-col ">
151
+ <div className="full-height flex flex-col bg-[#F3F4F8]">
101
152
  <div>
102
153
  <Toaster />
103
154
  </div>
@@ -1,5 +1,4 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
- import { useAuth0 } from '@auth0/auth0-react';
1
+ import { useEffect, useState } from 'react';
3
2
  import { twMerge } from 'tailwind-merge';
4
3
 
5
4
  import DragDropFile from 'components/DragDropFile';
@@ -12,11 +11,10 @@ import LocationInfoPopup from '../components/LocationInfoPopup';
12
11
  import Hint from '../components/Hint';
13
12
  import Loading from '../components/Loading';
14
13
  import useRequestStore from '../stores/request/requestStore';
15
- import { getFilters } from '../services/filter';
14
+ import ImageUpload from 'components/ImageUpload';
16
15
 
17
16
  function Home() {
18
17
  const settings = window.settings;
19
- const user = useAuth0().user;
20
18
  const { experienceVisualSearch, experienceVisualSearchImages, geoLocation } =
21
19
  settings;
22
20
  const navigate = useNavigate();
@@ -25,33 +23,6 @@ function Home() {
25
23
  useState<Blob[]>([]);
26
24
  const [isOpenModalCamera, setOpenModalCamera] = useState<boolean>(false);
27
25
  const showLoading = useRequestStore(store => store.showLoading);
28
- const setPreFilterList = useRequestStore(state => state.setPreFilterList);
29
- const setPreFilterLoading = useRequestStore(
30
- state => state.setPreFilterLoading,
31
- );
32
- const setMetaFilter = useRequestStore(state => state.setMetaFilter);
33
-
34
- const showPreFilter = useMemo(() => {
35
- if (settings.shouldUseUserMetadata && user) {
36
- if (user['/user_metadata'].value) {
37
- setMetaFilter(user['/user_metadata'].value);
38
- }
39
- }
40
-
41
- if (settings.shouldUseUserMetadata && user) {
42
- if (settings.preFilterOption && !user['/user_metadata'].value) {
43
- return true;
44
- }
45
- return false;
46
- }
47
-
48
- return settings.preFilterOption;
49
- }, [
50
- setMetaFilter,
51
- settings.preFilterOption,
52
- settings.shouldUseUserMetadata,
53
- user,
54
- ]);
55
26
 
56
27
  const fetchImage = async (url: string) => {
57
28
  const response = await fetch(url, { cache: 'force-cache' });
@@ -98,24 +69,6 @@ function Home() {
98
69
  // eslint-disable-next-line react-hooks/exhaustive-deps
99
70
  }, []);
100
71
 
101
- const getPreFilters = useCallback(async () => {
102
- setPreFilterLoading(true);
103
- try {
104
- const res = await getFilters(1000, settings);
105
- setPreFilterList(res);
106
- } catch (e: any) {
107
- console.log('err getDataFilterDesktop', e);
108
- } finally {
109
- setPreFilterLoading(false);
110
- }
111
- }, [settings, setPreFilterList, setPreFilterLoading]);
112
-
113
- useEffect(() => {
114
- if (showPreFilter) {
115
- getPreFilters();
116
- }
117
- }, [getPreFilters, showPreFilter]);
118
-
119
72
  return (
120
73
  <>
121
74
  {showLoading && (
@@ -149,8 +102,15 @@ function Home() {
149
102
  </div>
150
103
  )}
151
104
 
152
- <TextSearch />
153
- {/* <ImageUpload /> */}
105
+ <TextSearch className="h-[56px]" showImageUpload={false} />
106
+ <div
107
+ className={twMerge(
108
+ 'h-14 w-14 flex items-center justify-center rounded-full bg-[#FFFFFF] hover:bg-[#F3F4F8] border border-solid border-[#DDDEE7]',
109
+ 'shadow-ds-2',
110
+ )}
111
+ >
112
+ <ImageUpload />
113
+ </div>
154
114
  </div>
155
115
  <div className="max-w-[532px] relative w-full">
156
116
  <DragDropFile />
@@ -190,7 +150,6 @@ function Home() {
190
150
 
191
151
  <div className="flex desktop:hidden w-full fixed bottom-12 px-2 gap-2">
192
152
  <TextSearch className="flex md:hidden w-full " />
193
- {/* <ImageUpload /> */}
194
153
  </div>
195
154
  </div>
196
155
  </>
@@ -1,10 +1,9 @@
1
- import React, { useEffect } from 'react';
1
+ import { useEffect } from 'react';
2
2
  import { useAuth0 } from '@auth0/auth0-react';
3
3
 
4
4
  const Login = () => {
5
5
  const { user, isAuthenticated, isLoading, loginWithRedirect } = useAuth0();
6
6
  const { auth0 } = window.settings;
7
- console.log({ auth0 });
8
7
 
9
8
  useEffect(() => {
10
9
  if (!isLoading && !user && !isAuthenticated && auth0.enabled) {
@@ -1,4 +1,4 @@
1
- import React, { useEffect } from 'react';
1
+ import { useEffect } from 'react';
2
2
  import { useAuth0 } from '@auth0/auth0-react';
3
3
 
4
4
  const Logout = () => {