@nyris/nyris-webapp 0.3.97 → 0.3.99

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 (94) hide show
  1. package/build/asset-manifest.json +7 -6
  2. package/build/index.html +1 -1
  3. package/build/js/settings.example.js +41 -0
  4. package/build/static/css/main.5cb72c09.css +4 -0
  5. package/build/static/css/main.5cb72c09.css.map +1 -0
  6. package/build/static/js/main.2339271b.js +3 -0
  7. package/build/static/js/{main.9e7fb38a.js.map → main.2339271b.js.map} +1 -1
  8. package/build/static/media/plate_info.6beaea36d35053be3a45.png +0 -0
  9. package/package.json +3 -3
  10. package/public/js/settings.example.js +41 -0
  11. package/src/common/assets/images/plate_info.png +0 -0
  12. package/src/components/AuthApiKeySync.tsx +34 -0
  13. package/src/components/Cart.tsx +41 -0
  14. package/src/components/ChatAssistant/useChatAssistantLogic.ts +3 -2
  15. package/src/components/GoBackButton.tsx +29 -2
  16. package/src/components/Header.tsx +3 -0
  17. package/src/components/HitsPerPage.tsx +55 -18
  18. package/src/components/ImagePreview.tsx +31 -1
  19. package/src/components/ImageUpload.tsx +14 -3
  20. package/src/components/Inquiry/InquiryModal.tsx +3 -0
  21. package/src/components/LogoutModal.tsx +2 -0
  22. package/src/components/Pagination.tsx +33 -6
  23. package/src/components/PostFilter/PostFilter.tsx +8 -1
  24. package/src/components/PostFilter/PostFilterComponent.tsx +8 -22
  25. package/src/components/PostFilter/PostFilterFindApi.test.tsx +249 -0
  26. package/src/components/PostFilter/PostFilterFindApi.tsx +128 -52
  27. package/src/components/PreFilter/PreFilter.tsx +10 -2
  28. package/src/components/Product/Product.tsx +60 -10
  29. package/src/components/Product/ProductDetailView.tsx +23 -1
  30. package/src/components/Product/ProductDetailViewModal.tsx +5 -1
  31. package/src/components/Product/ProductList.tsx +51 -16
  32. package/src/components/Rfq/RfqModal.tsx +3 -0
  33. package/src/components/ScopeSelect/ScopeSelectForm.tsx +514 -0
  34. package/src/components/ScopeSelect/ScopeSelectImageActions.tsx +62 -0
  35. package/src/components/ScopeSelect/ScopeSelectImagePreview.tsx +141 -0
  36. package/src/components/ScopeSelect/ScopeSelectInfo.tsx +68 -0
  37. package/src/components/ScopeSelect/ScopeSelectInput.tsx +123 -0
  38. package/src/components/ScopeSelect/ScopeSelectModal.tsx +310 -0
  39. package/src/components/ScopeSelect/identifyTypePlateImage.test.ts +129 -0
  40. package/src/components/ScopeSelect/identifyTypePlateImage.ts +83 -0
  41. package/src/components/ScopeSelect/parseScopeIdResponse.test.ts +103 -0
  42. package/src/components/ScopeSelect/parseScopeIdResponse.ts +72 -0
  43. package/src/components/ScopeSelect/scopeSelectHelpers.ts +54 -0
  44. package/src/components/SidePanel.tsx +43 -0
  45. package/src/components/TextSearch.tsx +80 -14
  46. package/src/components/Tooltip/TooltipComponent.tsx +12 -4
  47. package/src/components/TypePlateUpload.tsx +132 -0
  48. package/src/hooks/useEffectiveGroundingResults.ts +8 -6
  49. package/src/hooks/useGoodResultsChat.ts +8 -22
  50. package/src/hooks/useImageSearch.ts +325 -20
  51. package/src/hooks/useMachinePartsListingPagination.ts +95 -0
  52. package/src/hooks/useResultEvaluator.ts +43 -19
  53. package/src/index.css +1 -1
  54. package/src/index.tsx +4 -0
  55. package/src/layouts/AppLayout.tsx +95 -9
  56. package/src/pages/Home.tsx +60 -14
  57. package/src/pages/Logout.tsx +2 -0
  58. package/src/pages/Result.tsx +206 -20
  59. package/src/providers/AuthProvider.tsx +5 -0
  60. package/src/services/Feedback.ts +224 -35
  61. package/src/services/feedbackSubtypes.ts +63 -0
  62. package/src/services/filter.ts +4 -3
  63. package/src/services/initialMachinePartsText.payload.test.ts +61 -0
  64. package/src/services/initialMachinePartsText.test.ts +33 -0
  65. package/src/services/initialMachinePartsText.ts +11 -0
  66. package/src/services/machinePartsPage.test.ts +74 -0
  67. package/src/services/machinePartsPage.ts +120 -0
  68. package/src/services/normalizeChatResponse.ts +26 -0
  69. package/src/services/scopeLookup.test.ts +67 -0
  70. package/src/services/scopeLookup.ts +39 -0
  71. package/src/services/visualSearch.listingOptions.test.ts +79 -0
  72. package/src/services/visualSearch.ts +76 -7
  73. package/src/services/vizo.ts +17 -5
  74. package/src/stores/machinePartsListing/machinePartsListingStore.ts +35 -0
  75. package/src/stores/request/filter/filter.initialState.ts +1 -0
  76. package/src/stores/request/filter/filter.slice.ts +2 -0
  77. package/src/stores/result/prodcuts/products.initialState.ts +1 -0
  78. package/src/stores/result/prodcuts/products.slice.ts +4 -0
  79. package/src/stores/scope/scopeStore.ts +67 -4
  80. package/src/stores/types.ts +19 -14
  81. package/src/translations.ts +213 -0
  82. package/src/types.ts +40 -0
  83. package/src/utils/derivePostFilterFacets.ts +68 -0
  84. package/src/utils/fetchProductImage.ts +1 -0
  85. package/src/utils/normalizeChatAssistantResponse.ts +227 -0
  86. package/src/utils/nyrisApiKey.ts +115 -0
  87. package/src/utils/productCardImageUrl.test.ts +41 -0
  88. package/src/utils/productCardImageUrl.ts +49 -0
  89. package/src/utils/textSearchFilter.test.ts +41 -0
  90. package/src/utils/textSearchFilter.ts +13 -6
  91. package/build/static/css/main.7c7f5447.css +0 -4
  92. package/build/static/css/main.7c7f5447.css.map +0 -1
  93. package/build/static/js/main.9e7fb38a.js +0 -3
  94. /package/build/static/js/{main.9e7fb38a.js.LICENSE.txt → main.2339271b.js.LICENSE.txt} +0 -0
@@ -47,10 +47,18 @@ export function useResultEvaluator() {
47
47
  async ({
48
48
  imageAnalysis,
49
49
  activeProducts,
50
+ query,
50
51
  }: {
51
52
  imageAnalysis: any;
52
53
  activeProducts: any[];
54
+ query: string;
53
55
  }) => {
56
+ // Bind this run to the current session; a newer search calls
57
+ // startNewSession, which makes any in-flight evaluation stale.
58
+ const runSessionId = useConversationStore.getState().sessionId;
59
+ const isStale = () =>
60
+ useConversationStore.getState().sessionId !== runSessionId;
61
+
54
62
  setIsLoading(true);
55
63
  setError(null);
56
64
  setRelatedPartsTableSuggestions([]);
@@ -207,12 +215,22 @@ export function useResultEvaluator() {
207
215
  }
208
216
  });
209
217
 
218
+ const requestImage = useRequestStore.getState().requestImages;
219
+ const regions = useRequestStore.getState().regions;
220
+ const hasRequestImage = requestImage && requestImage[0];
221
+
222
+ const evaluationTarget = hasRequestImage
223
+ ? 'the image'
224
+ : 'the user query (keyword input)';
225
+
210
226
  const userPrompt = `Evaluate these search results:
211
227
 
212
- IMAGE DESCRIPTION: ${imageDescription}
228
+ user query: ${query || 'N/A'}
229
+
230
+ IMAGE DESCRIPTION: ${hasRequestImage ? imageDescription : 'N/A'}
213
231
 
214
232
  SPECIFICATION (from image analysis):
215
- ${JSON.stringify(p4Specification, null, 2)}
233
+ ${hasRequestImage ? JSON.stringify(p4Specification, null, 2) : 'N/A'}
216
234
 
217
235
  TOP 20 RESULTS WITH METADATA:
218
236
  ${resultsSummary
@@ -227,7 +245,7 @@ ${JSON.stringify(attributeFrequency, null, 2)}
227
245
 
228
246
  TOTAL RESULTS: ${resultsSummary.length}
229
247
 
230
- 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.`;
248
+ Evaluate if these results match ${evaluationTarget}. 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.`;
231
249
 
232
250
  const productsForImages = activeProducts.slice(0, 30);
233
251
  const imageResults = await Promise.all(
@@ -268,24 +286,27 @@ Evaluate if these results match the image. If they do (same type of product), ro
268
286
  }
269
287
  });
270
288
 
271
- const requestImage = useRequestStore.getState().requestImages;
272
- const regions = useRequestStore.getState().regions;
273
- const croppedCanvas = cropImage(requestImage[0], regions[0]);
274
- const base64Image = canvasToBase64(croppedCanvas, 'image/jpeg', 1);
289
+ const multimodalInputs: any[] = [
290
+ {
291
+ metadata: userPrompt,
292
+ },
293
+ ];
294
+
295
+ if (hasRequestImage) {
296
+ const croppedCanvas = cropImage(requestImage[0], regions[0]);
297
+ const base64Image = canvasToBase64(croppedCanvas, 'image/jpeg', 1);
298
+ multimodalInputs.push({
299
+ metadata: 'request_image',
300
+ mimeType: 'image/jpeg',
301
+ image: base64Image,
302
+ });
303
+ }
304
+
305
+ multimodalInputs.push(...productImageParts);
275
306
 
276
307
  const response = await chatAnalysis({
277
308
  promptKey: 'result_evaluator_prompt',
278
- multimodalInputs: [
279
- {
280
- metadata: userPrompt,
281
- },
282
- {
283
- metadata: 'request_image',
284
- mimeType: 'image/jpeg',
285
- image: base64Image,
286
- },
287
- ...productImageParts,
288
- ],
309
+ multimodalInputs,
289
310
  });
290
311
 
291
312
  const evaluation: P2EvaluationResult = response;
@@ -315,6 +336,8 @@ Evaluate if these results match the image. If they do (same type of product), ro
315
336
  // Check lookup table for related parts on fresh image searches and store globally.
316
337
  const table = await loadRelatedPartsTable();
317
338
 
339
+ if (isStale()) return;
340
+
318
341
  const tableSuggestions = lookupRelatedParts(
319
342
  table,
320
343
  imageAnalysis?.specification?.product_category || '',
@@ -371,6 +394,7 @@ Evaluate if these results match the image. If they do (same type of product), ro
371
394
  );
372
395
  } catch (err: any) {
373
396
  console.error('result_evaluator_prompt', err);
397
+ if (isStale()) return;
374
398
  setRelatedPartsTableSuggestions([]);
375
399
  setError(err.message || 'Failed to evaluate results');
376
400
 
@@ -390,7 +414,7 @@ Evaluate if these results match the image. If they do (same type of product), ro
390
414
  ],
391
415
  );
392
416
  } finally {
393
- setIsLoading(false);
417
+ if (!isStale()) setIsLoading(false);
394
418
  }
395
419
  },
396
420
  // eslint-disable-next-line react-hooks/exhaustive-deps
package/src/index.css CHANGED
@@ -283,7 +283,7 @@ h2 {
283
283
 
284
284
  h1 {
285
285
  font-size: 25px;
286
- margin: 0 auto 25px;
286
+ margin: 0 auto;
287
287
  }
288
288
 
289
289
  h1.entry-title a {
package/src/index.tsx CHANGED
@@ -1,6 +1,10 @@
1
1
  import ReactDOM from 'react-dom/client';
2
2
  import './index.css';
3
3
  import App from './App';
4
+ import { bootstrapAuth0ApiKeyGuard } from 'utils/nyrisApiKey';
5
+
6
+ // Clear settings.js apiKey before any React tree can issue Nyris requests (Auth0 mode).
7
+ bootstrapAuth0ApiKeyGuard();
4
8
 
5
9
  const root = ReactDOM.createRoot(
6
10
  document.getElementById('root') as HTMLElement,
@@ -1,4 +1,4 @@
1
- import { memo, useCallback, useEffect, useMemo } from 'react';
1
+ import { memo, useCallback, useEffect, useMemo, useRef } from 'react';
2
2
  import { Outlet, useLocation } from 'react-router';
3
3
 
4
4
  import { useAuth0 } from '@auth0/auth0-react';
@@ -22,7 +22,14 @@ import { getFilters } from 'services/filter';
22
22
  import { useUploadSearch } from 'hooks/useUploadSearch';
23
23
  import { ToastHelper } from 'helpers/ToastHelper';
24
24
  import { useTranslation } from 'react-i18next';
25
- import useScopeStore from 'stores/scope/scopeStore';
25
+ import {
26
+ clearAuthApiKey,
27
+ isNyrisApiKeyReady,
28
+ useAuthApiKeyStore,
29
+ } from 'utils/nyrisApiKey';
30
+ import useScopeStore, { hasActiveMachineScope } from 'stores/scope/scopeStore';
31
+ import useConversationStore from 'stores/chat/conversationStore';
32
+ import { useResultEvaluator } from 'hooks/useResultEvaluator';
26
33
 
27
34
  i18n.use(initReactI18next).init({
28
35
  resources: translations,
@@ -34,7 +41,7 @@ i18n.use(initReactI18next).init({
34
41
  });
35
42
 
36
43
  function AppLayout(): JSX.Element {
37
- const { isAuthenticated } = useAuth0();
44
+ const { isAuthenticated, logout } = useAuth0();
38
45
  const { t } = useTranslation();
39
46
  const { auth0, alogoliaFilterField, showPoweredByNyris } = window.settings;
40
47
  const { uploadFile } = useUploadSearch();
@@ -61,9 +68,13 @@ function AppLayout(): JSX.Element {
61
68
  const requestImages = useRequestStore(state => state.requestImages);
62
69
  const showLoading = useRequestStore(state => state.showLoading);
63
70
 
64
- const { status } = useInstantSearch();
71
+ const { status, results } = useInstantSearch();
65
72
  const { items } = useHits();
66
73
 
74
+ const isAiModeOpen = useUiStore(state => state.isAiModeOpen);
75
+ const { evaluateResults } = useResultEvaluator();
76
+ const lastAlgoliaEvaluationRef = useRef<string | null>(null);
77
+
67
78
  useEffect(() => {
68
79
  i18n.changeLanguage(window.settings.language);
69
80
  console.log('App version:', packageJson.version);
@@ -92,6 +103,40 @@ function AppLayout(): JSX.Element {
92
103
  // eslint-disable-next-line react-hooks/exhaustive-deps
93
104
  }, [items, scopeItemKeys]);
94
105
 
106
+ // Algolia text search never goes through singleImageSearch, so run P2 here
107
+ // on the Algolia hits once AI mode is open for the current query.
108
+ // Deliberately not keyed on the conversation session: singleImageSearch
109
+ // calls startNewSession before requestImages is set, and re-running on that
110
+ // session change would start a competing evaluation of stale Algolia hits.
111
+ useEffect(() => {
112
+ if (requestImages.length > 0 || hasActiveMachineScope()) {
113
+ // An image search or machine scope owns P2. Algolia hits are unscoped.
114
+ lastAlgoliaEvaluationRef.current = null;
115
+ return;
116
+ }
117
+
118
+ if (
119
+ !window.settings.algolia?.enabled ||
120
+ !window.settings.vizo?.chat ||
121
+ !isAiModeOpen ||
122
+ isFindApiLoading ||
123
+ !query ||
124
+ status !== 'idle' ||
125
+ results?.query !== query
126
+ ) {
127
+ return;
128
+ }
129
+
130
+ if (lastAlgoliaEvaluationRef.current === query) {
131
+ return;
132
+ }
133
+
134
+ useConversationStore.getState().startNewSession();
135
+ lastAlgoliaEvaluationRef.current = query;
136
+ evaluateResults({ activeProducts: items, imageAnalysis: undefined, query });
137
+ // eslint-disable-next-line react-hooks/exhaustive-deps
138
+ }, [isAiModeOpen, isFindApiLoading, query, status, results, requestImages]);
139
+
95
140
  useEffect(() => {
96
141
  if (status === 'loading' || status === 'stalled') {
97
142
  setIsAlgoliaLoading(true);
@@ -114,13 +159,13 @@ function AppLayout(): JSX.Element {
114
159
 
115
160
  const showPreFilter = useMemo(() => {
116
161
  if (settings.shouldUseUserMetadata && user) {
117
- if (user['/user_metadata'].value) {
162
+ if (user['/user_metadata']?.value) {
118
163
  setMetaFilter(user['/user_metadata'].value);
119
164
  }
120
165
  }
121
166
 
122
167
  if (settings.shouldUseUserMetadata && user) {
123
- if (settings.preFilterOption && !user['/user_metadata'].value) {
168
+ if (settings.preFilterOption && !user['/user_metadata']?.value) {
124
169
  return true;
125
170
  }
126
171
  return false;
@@ -134,7 +179,15 @@ function AppLayout(): JSX.Element {
134
179
  user,
135
180
  ]);
136
181
 
182
+ const authApiKeyVersion = useAuthApiKeyStore(state => state.version);
183
+ const isMissingAuthApiKey = useAuthApiKeyStore(state => state.isMissingClaim);
184
+ const isApiKeyReady = isNyrisApiKeyReady();
185
+
137
186
  const getPreFilters = useCallback(async () => {
187
+ if (!isNyrisApiKeyReady()) {
188
+ return;
189
+ }
190
+
138
191
  setPreFilterLoading(true);
139
192
  try {
140
193
  const res = await getFilters(1000, settings);
@@ -147,10 +200,10 @@ function AppLayout(): JSX.Element {
147
200
  }, [settings, setPreFilterList, setPreFilterLoading]);
148
201
 
149
202
  useEffect(() => {
150
- if (showPreFilter && !settings.scopeFilter) {
203
+ if (showPreFilter && !settings.scopeFilter && isApiKeyReady) {
151
204
  getPreFilters();
152
205
  }
153
- }, [getPreFilters, showPreFilter, settings.scopeFilter]);
206
+ }, [getPreFilters, showPreFilter, settings.scopeFilter, isApiKeyReady, authApiKeyVersion]);
154
207
 
155
208
  const getImageFromClipboard = useCallback(
156
209
  (event: ClipboardEvent): File | null => {
@@ -208,12 +261,17 @@ function AppLayout(): JSX.Element {
208
261
  return;
209
262
  }
210
263
 
264
+ // Auth0: only listen once authApiKey is resolved (not while waiting / missing claim).
265
+ if (auth0.enabled && !isApiKeyReady) {
266
+ return;
267
+ }
268
+
211
269
  document.addEventListener('paste', handleClipboardPaste);
212
270
 
213
271
  return () => {
214
272
  document.removeEventListener('paste', handleClipboardPaste);
215
273
  };
216
- }, [handleClipboardPaste, showLayout]);
274
+ }, [auth0.enabled, handleClipboardPaste, isApiKeyReady, showLayout]);
217
275
 
218
276
  const applyScopeFilter = (filters: string | undefined) => {
219
277
  const scopeFilterClause =
@@ -238,6 +296,34 @@ function AppLayout(): JSX.Element {
238
296
  return <Outlet />;
239
297
  }
240
298
 
299
+ // Auth0 mode: wait for AuthApiKeySync before rendering search UI / prefilters.
300
+ if (auth0.enabled && !isApiKeyReady && !isMissingAuthApiKey) {
301
+ return <div />;
302
+ }
303
+
304
+ if (isMissingAuthApiKey) {
305
+ return (
306
+ <div className="full-height flex flex-col items-center justify-center bg-[#F3F4F8] p-6 text-center">
307
+ <p className="mt-2 max-w-md text-sm text-gray-600">
308
+ Search is currently unavailable for your account. Please contact your
309
+ administrator.
310
+ </p>
311
+ <button
312
+ type="button"
313
+ className="mt-6 bg-[#2B2C46] px-6 py-3 text-sm text-white"
314
+ onClick={() => {
315
+ clearAuthApiKey();
316
+ logout({
317
+ logoutParams: { returnTo: window.location.origin },
318
+ });
319
+ }}
320
+ >
321
+ {t('Log out')}
322
+ </button>
323
+ </div>
324
+ );
325
+ }
326
+
241
327
  return (
242
328
  <div className="full-height flex flex-col bg-[#F3F4F8]">
243
329
  <div>
@@ -1,5 +1,7 @@
1
1
  import { useEffect, useState } from 'react';
2
2
  import { twMerge } from 'tailwind-merge';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { ArrowRight } from 'lucide-react';
3
5
 
4
6
  import DragDropFile from 'components/DragDropFile';
5
7
  import MobileCameraCTA from 'components/MobileCameraCTA';
@@ -15,6 +17,7 @@ import ImageUpload from 'components/ImageUpload';
15
17
 
16
18
  function Home() {
17
19
  const settings = window.settings;
20
+ const { t } = useTranslation();
18
21
  const { experienceVisualSearch, experienceVisualSearchImages, geoLocation } =
19
22
  settings;
20
23
  const navigate = useNavigate();
@@ -88,32 +91,75 @@ function Home() {
88
91
  ])}
89
92
  >
90
93
  {geoLocation && <LocationInfoPopup />}
91
- <div className="relative flex items-center justify-center w-full gap-2">
94
+ <div className="relative flex w-full flex-col items-center gap-6">
92
95
  {settings.headerText && (
93
96
  <div
94
97
  className={twMerge([
95
98
  'absolute',
96
- 'bottom-[49px]',
99
+ 'bottom-full',
100
+ 'mb-[25px]',
101
+ 'flex',
102
+ 'flex-col',
103
+ 'items-center',
104
+ 'gap-3',
97
105
  'text-primary',
98
106
  'font-bold',
99
107
  ])}
100
108
  >
101
- <h1 className="text-[25px]">{settings.headerText}</h1>
109
+ <h1 className="text-[25px] m-0 mb-0">{settings.headerText}</h1>
110
+ {settings.scopes?.scopeID &&
111
+ Boolean(settings.scopes?.typePlateStepOne?.trim()) &&
112
+ Boolean(settings.scopes?.typePlateStepTwo?.trim()) && (
113
+ <div
114
+ className="flex w-full items-center justify-center"
115
+ aria-label="Search flow"
116
+ >
117
+ <div className="flex items-center gap-1.5 shrink-0">
118
+ <span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-[#F3F4F8] border border-solid border-[#2B2C46] text-[#2B2C46] font-bold text-xs leading-none">
119
+ 1
120
+ </span>
121
+ <span className="font-semibold text-[14px] leading-none text-[#2B2C46] whitespace-nowrap">
122
+ {t(settings.scopes!.typePlateStepOne!)}
123
+ </span>
124
+ </div>
125
+
126
+ <div
127
+ className="flex items-center flex-1 min-w-[20px] max-w-[60px] mx-2.5"
128
+ aria-hidden="true"
129
+ >
130
+ <div className="h-[1.5px] flex-1 bg-[#2B2C46]" />
131
+ <ArrowRight
132
+ className="h-3.5 w-3.5 -ml-1 text-[#2B2C46] shrink-0"
133
+ strokeWidth={2}
134
+ />
135
+ </div>
136
+
137
+ <div className="flex items-center gap-1.5 shrink-0">
138
+ <span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-[#2B2C46] text-white font-bold text-xs leading-none">
139
+ 2
140
+ </span>
141
+ <span className="font-semibold text-[14px] leading-none text-[#2B2C46] whitespace-nowrap">
142
+ {t(settings.scopes!.typePlateStepTwo!)}
143
+ </span>
144
+ </div>
145
+ </div>
146
+ )}
102
147
  </div>
103
148
  )}
104
-
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 />
149
+ <div className="flex items-center justify-center w-full gap-2">
150
+ <TextSearch className="h-[56px]" showImageUpload={false} />
151
+ <div
152
+ className={twMerge(
153
+ 'h-14 w-14 flex items-center justify-center rounded-full bg-[#FFFFFF] hover:bg-[#F3F4F8] border border-solid border-[#DDDEE7]',
154
+ 'shadow-ds-2',
155
+ )}
156
+ >
157
+ <ImageUpload />
158
+ </div>
113
159
  </div>
114
160
  </div>
115
161
  <div className="max-w-[532px] relative w-full">
116
- <DragDropFile />
162
+ {!settings.scopes?.scopeID && <DragDropFile />}
117
163
  {settings.experienceVisualSearch && (
118
164
  <ExperienceVisualSearchTrigger
119
165
  experienceVisualSearchBlobs={experienceVisualSearchBlobs}
@@ -148,7 +194,7 @@ function Home() {
148
194
  />
149
195
  )}
150
196
 
151
- <div className="flex desktop:hidden w-full fixed bottom-12 px-2 gap-2">
197
+ <div className="flex desktop:hidden w-full fixed bottom-12 px-2 flex-col gap-4">
152
198
  <TextSearch className="flex md:hidden w-full " />
153
199
  </div>
154
200
  </div>
@@ -1,5 +1,6 @@
1
1
  import { useEffect } from 'react';
2
2
  import { useAuth0 } from '@auth0/auth0-react';
3
+ import { clearAuthApiKey } from 'utils/nyrisApiKey';
3
4
 
4
5
  const Logout = () => {
5
6
  const { user, isAuthenticated, isLoading, loginWithRedirect, logout } =
@@ -10,6 +11,7 @@ const Logout = () => {
10
11
  if (!isLoading && !user && !isAuthenticated && auth0.enabled) {
11
12
  loginWithRedirect();
12
13
  } else if (isAuthenticated) {
14
+ clearAuthApiKey();
13
15
  logout({ logoutParams: { returnTo: window.location.origin } });
14
16
  } else if (!auth0.enabled) {
15
17
  window.location.href = '/';