@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
@@ -51,6 +51,30 @@ export const translations = {
51
51
  'Are these results useful?': 'Are these results useful?',
52
52
  'Thank you for your feedback': 'Thank you for your feedback',
53
53
  'Select a Filter': 'Select a Filter',
54
+ 'Select scope': 'Select scope',
55
+ 'Identify your machine': 'Identify your machine',
56
+ 'Machine identified': 'Machine identified',
57
+ 'Identify machine': 'Identify machine',
58
+ 'Find part': 'Find part',
59
+ 'Scan type plate': 'Scan type plate',
60
+ 'Scan Type plate': 'Scan Type plate',
61
+ 'Searching within parts for {{scopeId}}. You may scan another type plate.':
62
+ 'Searching within parts for {{scopeId}}. You may scan another type plate.',
63
+ "Scan your machine's type plate first to see only parts that fit it":
64
+ "Scan your machine's type plate first to see only parts that fit it",
65
+ 'Upload image': 'Upload image',
66
+ 'Upload new type plate': 'Upload new type plate',
67
+ 'Take a photo': 'Take a photo',
68
+ 'Find machine': 'Find machine',
69
+ 'Skip and search all parts': 'Skip and search all parts',
70
+ 'Back to results': 'Back to results',
71
+ 'Skip and search with all parts': 'Skip and search with all parts',
72
+ 'Where to find it': 'Where to find it',
73
+ 'Serial number': 'Serial number',
74
+ 'A metal or foil label on the machine housing, usually near the motor or the main panel. Make sure the serial number is sharp and readable.':
75
+ 'A metal or foil label on the machine housing, usually near the motor or the main panel. Make sure the serial number is sharp and readable.',
76
+ 'The serial number we read is not in our index.':
77
+ 'The serial number we read is not in our index.',
54
78
  'Get personalised help from our team of product experts.':
55
79
  'Get personalised help from our team of product experts.',
56
80
  'Adjust the selection frame for better results':
@@ -70,6 +94,52 @@ export const translations = {
70
94
  'We have successfully defined the search criteria':
71
95
  'Nameplate detected and matched to {{prefilter_value}}. Items are shown for this {{preFilterTitle}}. You can now upload an image or enter a keyword to search.',
72
96
  'Type plates': 'Type plates',
97
+ 'Start with a type plate': 'Start with a type plate',
98
+ 'Scope ID is': 'Scope ID is',
99
+ 'Upload typeplate': 'Upload typeplate',
100
+ 'Enter value here': 'Enter value here',
101
+ "Can't scan? Enter serial number manually.":
102
+ "Can't scan? Enter serial number manually.",
103
+ 'Enter serial number manually': 'Enter serial number manually',
104
+ 'e.g. 2234GH': 'e.g. 2234GH',
105
+ 'Configurable: Manual input placeholder':
106
+ 'Configurable: Manual input placeholder',
107
+ 'Scope data loaded successfully.': 'Scope data loaded successfully.',
108
+ 'Scope data could not be loaded.': 'Scope data could not be loaded.',
109
+ 'Clear the type-plate image to enter a scope ID.':
110
+ 'Clear the type-plate image to enter a scope ID.',
111
+ 'Clear the scope ID to upload a type plate.':
112
+ 'Clear the scope ID to upload a type plate.',
113
+ 'Scope loaded successfully.': 'Scope loaded successfully.',
114
+ 'Scope {{scopeId}} loaded successfully.':
115
+ 'Scope {{scopeId}} loaded successfully.',
116
+ 'You can now upload an image to search.':
117
+ 'You can now upload an image to search.',
118
+ 'Sorry, no matching scope was found.':
119
+ 'Sorry, no matching scope was found.',
120
+ 'Sorry, the search returned no results.':
121
+ 'Sorry, the search returned no results.',
122
+ 'You may start from type plate uploading':
123
+ 'You may start from type plate uploading',
124
+ 'Searching with': 'Searching with',
125
+ 'Searching within scope': 'Searching within scope',
126
+ 'Parts for machine': 'Parts for machine',
127
+ 'Searching within parts for {{scope_id}}. You can search with keyword or image to find the right item':
128
+ 'Searching within parts for {{scope_id}}. You can search with keyword or image to find the right item',
129
+ 'Scan a new type plate': 'Scan a new type plate',
130
+ 'Search part names, SKU, or description...':
131
+ 'Search part names, SKU, or description...',
132
+ 'Check the number against the plate and correct it if needed.':
133
+ 'Check the number against the plate and correct it if needed.',
134
+ 'The serial number was not found. Please check again.':
135
+ 'The serial number was not found. Please check again.',
136
+ 'Applying...': 'Applying...',
137
+ 'Showing {{resultCount}} results from all machines':
138
+ 'Showing {{resultCount}} results from all machines',
139
+ 'Scan your type plate to see only parts for your equipment.':
140
+ 'Scan your type plate to see only parts for your equipment.',
141
+ Close: 'Close',
142
+ 'Search all products': 'Search all products',
73
143
  Parts: 'Parts',
74
144
  Labels: 'Labels',
75
145
  Barcodes: 'Barcodes',
@@ -134,6 +204,30 @@ export const translations = {
134
204
  'Are these results useful?': 'Sind diese Ergebnisse nützlich?',
135
205
  'Thank you for your feedback': 'Vielen Dank für Ihr Feedback',
136
206
  'Select a Filter': 'Filter wählen',
207
+ 'Select scope': 'Scope auswählen',
208
+ 'Identify your machine': 'Identifizieren Sie Ihre Maschine',
209
+ 'Machine identified': 'Maschine identifiziert',
210
+ 'Identify machine': 'Maschine identifizieren',
211
+ 'Find part': 'Teil finden',
212
+ 'Scan type plate': 'Typenschild scannen',
213
+ 'Scan Type plate': 'Typenschild scannen',
214
+ 'Searching within parts for {{scopeId}}. You may scan another type plate.':
215
+ 'Suche innerhalb der Teile für {{scopeId}}. Sie können ein anderes Typenschild scannen.',
216
+ "Scan your machine's type plate first to see only parts that fit it":
217
+ 'Scannen Sie zuerst das Typenschild Ihrer Maschine, um nur passende Teile zu sehen',
218
+ 'Upload image': 'Bild hochladen',
219
+ 'Upload new type plate': 'Neues Typenschild hochladen',
220
+ 'Take a photo': 'Foto aufnehmen',
221
+ 'Find machine': 'Maschine finden',
222
+ 'Skip and search all parts': 'Überspringen und alle Teile durchsuchen',
223
+ 'Back to results': 'Zurück zu den Ergebnissen',
224
+ 'Skip and search with all parts': 'Überspringen und alle Teile durchsuchen',
225
+ 'Where to find it': 'Wo Sie es finden',
226
+ 'Serial number': 'Seriennummer',
227
+ 'A metal or foil label on the machine housing, usually near the motor or the main panel. Make sure the serial number is sharp and readable.':
228
+ 'Ein Metall- oder Folienetikett am Maschinengehäuse, meist in der Nähe des Motors oder der Haupttafel. Stellen Sie sicher, dass die Seriennummer scharf und lesbar ist.',
229
+ 'The serial number we read is not in our index.':
230
+ 'Die gelesene Seriennummer ist nicht in unserem Index.',
137
231
  'Get personalised help from our team of product experts.':
138
232
  'Holen Sie sich persönliche Hilfe von unserem Team von Produktexperten.',
139
233
  'No matches found': 'Keine Treffer gefunden',
@@ -154,6 +248,53 @@ export const translations = {
154
248
  'We have successfully defined the search criteria':
155
249
  'Typenschild erkannt und mit {{prefilter_value}} abgeglichen. Es werden Artikel für dieses {{preFilterTitle}}. Sie können nun ein Bild hochladen oder ein Stichwort zur Suche eingeben.',
156
250
  'Type plates': 'Typenschilder',
251
+ 'Start with a type plate': 'Mit Typenschild starten',
252
+ 'Scope ID is': 'Scope-ID ist',
253
+ 'Upload typeplate': 'Typenschild hochladen',
254
+ 'Enter value here': 'Wert hier eingeben',
255
+ "Can't scan? Enter serial number manually.":
256
+ 'Kein Scan möglich? Seriennummer manuell eingeben.',
257
+ 'Enter serial number manually': 'Seriennummer manuell eingeben',
258
+ 'e.g. 2234GH': 'z. B. 2234GH',
259
+ 'Configurable: Manual input placeholder':
260
+ 'Konfigurierbar: Platzhalter für manuelle Eingabe',
261
+ 'Scope data loaded successfully.': 'Scope-Daten erfolgreich geladen.',
262
+ 'Scope data could not be loaded.':
263
+ 'Scope-Daten konnten nicht geladen werden.',
264
+ 'Clear the type-plate image to enter a scope ID.':
265
+ 'Löschen Sie das Typenschild-Bild, um eine Scope-ID einzugeben.',
266
+ 'Clear the scope ID to upload a type plate.':
267
+ 'Löschen Sie die Scope-ID, um ein Typenschild hochzuladen.',
268
+ 'Scope loaded successfully.': 'Scope erfolgreich geladen.',
269
+ 'Scope {{scopeId}} loaded successfully.':
270
+ 'Scope {{scopeId}} erfolgreich geladen.',
271
+ 'You can now upload an image to search.':
272
+ 'Sie können jetzt ein Bild hochladen, um zu suchen.',
273
+ 'Sorry, no matching scope was found.':
274
+ 'Entschuldigung, es wurde kein passender Scope gefunden.',
275
+ 'Sorry, the search returned no results.':
276
+ 'Entschuldigung, die Suche hat keine Ergebnisse geliefert.',
277
+ 'You may start from type plate uploading':
278
+ 'Sie können mit dem Hochladen des Typenschilds beginnen',
279
+ 'Searching with': 'Suche mit',
280
+ 'Searching within scope': 'Suche innerhalb des Scopes',
281
+ 'Parts for machine': 'Teile für Maschine',
282
+ 'Searching within parts for {{scope_id}}. You can search with keyword or image to find the right item':
283
+ 'Suche innerhalb der Teile für {{scope_id}}. Sie können mit einem Suchbegriff oder Bild das passende Teil finden',
284
+ 'Scan a new type plate': 'Neues Typenschild scannen',
285
+ 'Search part names, SKU, or description...':
286
+ 'Teilename, SKU oder Beschreibung suchen...',
287
+ 'Check the number against the plate and correct it if needed.':
288
+ 'Nummer anhand des Typenschilds prüfen und ggf. korrigieren.',
289
+ 'The serial number was not found. Please check again.':
290
+ 'Die Seriennummer wurde nicht gefunden. Bitte versuchen Sie es erneut.',
291
+ 'Applying...': 'Wird angewendet...',
292
+ 'Showing {{resultCount}} results from all machines':
293
+ '{{resultCount}} Ergebnisse von allen Maschinen werden angezeigt',
294
+ 'Scan your type plate to see only parts for your equipment.':
295
+ 'Scannen Sie Ihr Typenschild, um nur Teile für Ihre Ausrüstung zu sehen.',
296
+ Close: 'Schließen',
297
+ 'Search all products': 'Alle Produkte durchsuchen',
157
298
  Parts: 'Teile',
158
299
  Labels: 'Etiketten',
159
300
  Barcodes: 'Barcodes',
@@ -219,6 +360,30 @@ export const translations = {
219
360
  'Comece sua busca visual selecionando uma imagem abaixo.',
220
361
  'Thank you for your feedback': 'Obrigado pelo seu feedback',
221
362
  'Select a Filter': 'Selecione um filtro',
363
+ 'Select scope': 'Selecionar scope',
364
+ 'Identify your machine': 'Identifique a sua máquina',
365
+ 'Machine identified': 'Máquina identificada',
366
+ 'Identify machine': 'Identificar máquina',
367
+ 'Find part': 'Encontrar peça',
368
+ 'Scan type plate': 'Digitalizar placa de identificação',
369
+ 'Scan Type plate': 'Digitalizar placa de identificação',
370
+ 'Searching within parts for {{scopeId}}. You may scan another type plate.':
371
+ 'A pesquisar dentro das peças para {{scopeId}}. Pode digitalizar outra placa de identificação.',
372
+ "Scan your machine's type plate first to see only parts that fit it":
373
+ 'Digitalize primeiro a placa de identificação da máquina para ver apenas as peças compatíveis',
374
+ 'Upload image': 'Carregar imagem',
375
+ 'Upload new type plate': 'Carregar nova placa de identificação',
376
+ 'Take a photo': 'Tirar uma foto',
377
+ 'Find machine': 'Encontrar máquina',
378
+ 'Skip and search all parts': 'Ignorar e pesquisar todas as peças',
379
+ 'Back to results': 'Voltar aos resultados',
380
+ 'Skip and search with all parts': 'Ignorar e pesquisar todas as peças',
381
+ 'Where to find it': 'Onde encontrar',
382
+ 'Serial number': 'Número de série',
383
+ 'A metal or foil label on the machine housing, usually near the motor or the main panel. Make sure the serial number is sharp and readable.':
384
+ 'Uma etiqueta de metal ou folha no invólucro da máquina, geralmente perto do motor ou do painel principal. Certifique-se de que o número de série está nítido e legível.',
385
+ 'The serial number we read is not in our index.':
386
+ 'O número de série que lemos não está no nosso índice.',
222
387
  'Get personalised help from our team of product experts.':
223
388
  'Obtenha ajuda personalizada de nossa equipe de especialistas em produtos.',
224
389
  'Adjust the selection frame for better results':
@@ -239,6 +404,54 @@ export const translations = {
239
404
  'We have successfully defined the search criteria':
240
405
  'Placa de identificação detetada e correspondente a {{prefilter_value}}. São apresentados os itens para esta {{preFilterTitle}}. Agora pode carregar uma imagem ou introduzir uma palavra-chave para pesquisar.',
241
406
  'Type plates': 'Placa de identificação',
407
+ 'Start with a type plate': 'Começar com uma placa de identificação',
408
+ 'Scope ID is': 'O Scope ID é',
409
+ 'Upload typeplate': 'Carregar placa de identificação',
410
+ 'Enter value here': 'Introduza o valor aqui',
411
+ "Can't scan? Enter serial number manually.":
412
+ 'Não consegue digitalizar? Introduza o número de série manualmente.',
413
+ 'Enter serial number manually': 'Inserir número de série manualmente',
414
+ 'e.g. 2234GH': 'ex. 2234GH',
415
+ 'Configurable: Manual input placeholder':
416
+ 'Configurável: Marcador de posição de entrada manual',
417
+ 'Scope data loaded successfully.':
418
+ 'Dados de scope carregados com sucesso.',
419
+ 'Scope data could not be loaded.':
420
+ 'Não foi possível carregar os dados de scope.',
421
+ 'Clear the type-plate image to enter a scope ID.':
422
+ 'Limpe a imagem da placa para introduzir um Scope ID.',
423
+ 'Clear the scope ID to upload a type plate.':
424
+ 'Limpe o Scope ID para carregar uma placa de identificação.',
425
+ 'Scope loaded successfully.': 'Scope carregado com sucesso.',
426
+ 'Scope {{scopeId}} loaded successfully.':
427
+ 'Scope {{scopeId}} carregado com sucesso.',
428
+ 'You can now upload an image to search.':
429
+ 'Agora pode carregar uma imagem para pesquisar.',
430
+ 'Sorry, no matching scope was found.':
431
+ 'Lamentamos, não foi encontrado nenhum scope correspondente.',
432
+ 'Sorry, the search returned no results.':
433
+ 'Lamentamos, a pesquisa não devolveu resultados.',
434
+ 'You may start from type plate uploading':
435
+ 'Você pode começar enviando a placa de identificação',
436
+ 'Searching with': 'Pesquisando com',
437
+ 'Searching within scope': 'A pesquisar dentro do scope',
438
+ 'Parts for machine': 'Peças para máquina',
439
+ 'Searching within parts for {{scope_id}}. You can search with keyword or image to find the right item':
440
+ 'A pesquisar dentro das peças para {{scope_id}}. Pode pesquisar por palavra-chave ou imagem para encontrar o item certo',
441
+ 'Scan a new type plate': 'Digitalizar nova placa de identificação',
442
+ 'Search part names, SKU, or description...':
443
+ 'Pesquisar nomes de peças, SKU ou descrição...',
444
+ 'Check the number against the plate and correct it if needed.':
445
+ 'Verifique o número na placa e corrija-o se necessário.',
446
+ 'The serial number was not found. Please check again.':
447
+ 'O número de série não foi encontrado. Por favor, tente novamente.',
448
+ 'Applying...': 'A aplicar...',
449
+ 'Showing {{resultCount}} results from all machines':
450
+ 'A mostrar {{resultCount}} resultados de todas as máquinas',
451
+ 'Scan your type plate to see only parts for your equipment.':
452
+ 'Digitalize a sua placa de identificação para ver apenas peças do seu equipamento.',
453
+ Close: 'Fechar',
454
+ 'Search all products': 'Pesquisar todos os produtos',
242
455
  Parts: 'Peças',
243
456
  Labels: 'Etiquetas',
244
457
  Barcodes: 'Códigos de barras',
package/src/types.ts CHANGED
@@ -27,6 +27,37 @@ export interface Rfq {
27
27
  enabled?: boolean;
28
28
  emailTemplateId?: string;
29
29
  }
30
+
31
+ export interface ScopesSettings {
32
+ scopeID?: boolean;
33
+ typePlateButtonLabel?: string;
34
+ typePlateStepOne?: string;
35
+ typePlateStepTwo?: string;
36
+ typePlatePopupTitle?: string;
37
+ typePlatePopupTitleSuccess?: string;
38
+ typePlatePopupCtaButton?: string;
39
+ typePlatePopupCtaTextButton?: string;
40
+ typePlatePopupFindMachineButton?: string;
41
+ typePlatePopupSkipButton?: string;
42
+ typePlatePopupBackButton?: string;
43
+ typePlatePopupUploadButton?: string;
44
+ typePlatePopupUploadNewButton?: string;
45
+ typePlatePopupTakePhotoButton?: string;
46
+ typePlatePopupApplyingLabel?: string;
47
+ typePlateResultsScopeLabel?: string;
48
+ typePlateResultsNoScopeLabel?: string;
49
+ typePlateResultsClearScopeLabel?: string;
50
+ typePlatePopupErrorMessage?: string;
51
+ typePlateManualInputLabel?: string;
52
+ typePlateManualInputPlaceholder?: string;
53
+ typePlateWhereToFindTitle?: string;
54
+ typePlateWhereToFindDescription?: string;
55
+ typePlateSerialNumberLabel?: string;
56
+ /** Legacy label aliases. Not set in settings.js. */
57
+ typePlateButtonText?: string;
58
+ typePlateFindMachineButton?: string;
59
+ typePlateSkipButton?: string;
60
+ }
30
61
  export interface Cadenas {
31
62
  cadenas3dWebView?: boolean;
32
63
  cadenasAPIKey?: string;
@@ -86,6 +117,12 @@ export interface AppSettings extends NyrisAPISettings {
86
117
  };
87
118
  clarityId?: string;
88
119
  CTAButton?: CTAButtonSettings;
120
+ /**
121
+ * Feedback API events that must never be sent. Entries may be event types
122
+ * (`click`, `conversion`, `feedback`, `region`) or subtypes
123
+ * (`copy_details`, `add_to_cart`, ...). Case-insensitive.
124
+ */
125
+ disabledFeedbackEvents?: string[];
89
126
  experienceVisualSearch?: boolean;
90
127
  experienceVisualSearchImages?: string[];
91
128
  geoLocation?: boolean;
@@ -106,6 +143,7 @@ export interface AppSettings extends NyrisAPISettings {
106
143
  noImageUrl?: string;
107
144
  noSimilarSearch?: boolean;
108
145
  postFilterOption?: boolean;
146
+ postFilterUseApi?: boolean;
109
147
  preFilterOption?: boolean;
110
148
  preFilterTitle?: string;
111
149
  preview: boolean;
@@ -115,6 +153,7 @@ export interface AppSettings extends NyrisAPISettings {
115
153
  regions: boolean;
116
154
  rfq?: Rfq;
117
155
  scopeFilter?: boolean;
156
+ scopes?: ScopesSettings;
118
157
  secondaryCTAButton?: SecondaryCTAButton;
119
158
  secondaryCTAButtonText?: string;
120
159
  secondaryTitle: string;
@@ -125,6 +164,7 @@ export interface AppSettings extends NyrisAPISettings {
125
164
  showGroup?: boolean;
126
165
  showImageDetails?: boolean;
127
166
  useSmartFilters?: boolean;
167
+ requestQuality?: boolean;
128
168
  showPoweredByNyris?: boolean;
129
169
  simpleCardView?: boolean;
130
170
  support: Support;
@@ -0,0 +1,68 @@
1
+ interface PostFilterFacetValue {
2
+ value: string;
3
+ count: number;
4
+ }
5
+
6
+ export interface PostFilterFacet {
7
+ name: string;
8
+ values: PostFilterFacetValue[];
9
+ }
10
+
11
+ interface ProductWithFilters {
12
+ filters?: Record<string, unknown>;
13
+ }
14
+
15
+ /**
16
+ * Aggregates post-filter facet options from result.filters when the find API
17
+ * does not return top-level facets (e.g. visual search). Matches the pre-02ede11
18
+ * PostFilterFindApi counting behavior.
19
+ */
20
+ export function derivePostFilterFacetsFromResults(
21
+ results: ProductWithFilters[] | undefined | null,
22
+ ): PostFilterFacet[] {
23
+ if (!results?.length) {
24
+ return [];
25
+ }
26
+
27
+ const countsByAttribute: Record<string, Record<string, number>> = {};
28
+
29
+ results.forEach(product => {
30
+ const filters = product?.filters;
31
+ if (!filters || typeof filters !== 'object') {
32
+ return;
33
+ }
34
+
35
+ Object.entries(filters).forEach(([attribute, filterValues]) => {
36
+ if (!Array.isArray(filterValues)) {
37
+ return;
38
+ }
39
+
40
+ if (!countsByAttribute[attribute]) {
41
+ countsByAttribute[attribute] = {};
42
+ }
43
+
44
+ filterValues.forEach((val: unknown) => {
45
+ if (typeof val === 'string' && val) {
46
+ countsByAttribute[attribute][val] =
47
+ (countsByAttribute[attribute][val] || 0) + 1;
48
+ }
49
+ });
50
+ });
51
+ });
52
+
53
+ return Object.entries(countsByAttribute).map(([name, counts]) => ({
54
+ name,
55
+ values: Object.entries(counts).map(([value, count]) => ({ value, count })),
56
+ }));
57
+ }
58
+
59
+ export function resolvePostFilterFacets(
60
+ apiFacets: unknown,
61
+ results: ProductWithFilters[] | undefined | null,
62
+ ): PostFilterFacet[] {
63
+ if (Array.isArray(apiFacets) && apiFacets.length > 0) {
64
+ return apiFacets as PostFilterFacet[];
65
+ }
66
+
67
+ return derivePostFilterFacetsFromResults(results);
68
+ }
@@ -101,6 +101,7 @@ export async function fetchProductImageBase64(
101
101
  export function getProductImageUrl(product: any): string | null {
102
102
  return (
103
103
  product.matches?.[0]?.url ||
104
+ product['image(main_similarity)'] ||
104
105
  product.main_image_link ||
105
106
  product.image ||
106
107
  product.images?.[0] ||
@@ -0,0 +1,227 @@
1
+ import { ActionType, QuickReplyOption } from 'stores/chat/conversationStore';
2
+
3
+ export interface P3Response {
4
+ message: string;
5
+ action: {
6
+ type: ActionType;
7
+ key?: string;
8
+ value?: string;
9
+ matched_ids?: string[];
10
+ matched_indices?: number[];
11
+ };
12
+ options: QuickReplyOption[];
13
+ }
14
+
15
+ const EMPTY_RESPONSE: P3Response = {
16
+ message: '',
17
+ action: {
18
+ type: 'none',
19
+ matched_ids: [],
20
+ },
21
+ options: [],
22
+ };
23
+
24
+ /** Fresh empty P3Response — never share nested refs with prior responses. */
25
+ function createEmptyResponse(): P3Response {
26
+ return {
27
+ message: EMPTY_RESPONSE.message,
28
+ action: {
29
+ type: EMPTY_RESPONSE.action.type,
30
+ matched_ids: [],
31
+ },
32
+ options: [],
33
+ };
34
+ }
35
+
36
+ /** Generic helper preambles that are clearly separate from the answer. */
37
+ const GENERIC_PREAMBLE_PATTERN = /^I can help you[^.]*\.\s+/i;
38
+
39
+ function isCompatibleP3Response(value: unknown): value is P3Response {
40
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
41
+ return false;
42
+ }
43
+
44
+ const candidate = value as Record<string, unknown>;
45
+ return typeof candidate.message === 'string';
46
+ }
47
+
48
+ /**
49
+ * Finds the first balanced JSON object in `text` that parses and looks like
50
+ * a P3Response. Respects nested braces and braces inside quoted strings.
51
+ */
52
+ function extractCompatibleJsonObject(text: string): P3Response | null {
53
+ for (let start = 0; start < text.length; start += 1) {
54
+ if (text[start] !== '{') {
55
+ continue;
56
+ }
57
+
58
+ let depth = 0;
59
+ let inString = false;
60
+ let isEscaped = false;
61
+
62
+ for (let end = start; end < text.length; end += 1) {
63
+ const char = text[end];
64
+
65
+ if (inString) {
66
+ if (isEscaped) {
67
+ isEscaped = false;
68
+ } else if (char === '\\') {
69
+ isEscaped = true;
70
+ } else if (char === '"') {
71
+ inString = false;
72
+ }
73
+ continue;
74
+ }
75
+
76
+ if (char === '"') {
77
+ inString = true;
78
+ continue;
79
+ }
80
+
81
+ if (char === '{') {
82
+ depth += 1;
83
+ continue;
84
+ }
85
+
86
+ if (char === '}') {
87
+ depth -= 1;
88
+ if (depth !== 0) {
89
+ continue;
90
+ }
91
+
92
+ const candidate = text.slice(start, end + 1);
93
+ try {
94
+ const parsed: unknown = JSON.parse(candidate);
95
+ if (isCompatibleP3Response(parsed)) {
96
+ return parsed;
97
+ }
98
+ } catch {
99
+ // Not valid JSON for this brace pair; try the next `{`.
100
+ }
101
+ break;
102
+ }
103
+ }
104
+ }
105
+
106
+ return null;
107
+ }
108
+
109
+ /**
110
+ * Locates a `matched_ids: [...]` fragment, parses the array, and returns
111
+ * the ids plus the text with that fragment removed.
112
+ */
113
+ function extractMatchedIdsFromPlainText(text: string): {
114
+ matchedIds: string[];
115
+ message: string;
116
+ } {
117
+ const markerMatch = /matched_ids\s*:\s*/i.exec(text);
118
+ if (!markerMatch || markerMatch.index === undefined) {
119
+ return { matchedIds: [], message: text };
120
+ }
121
+
122
+ const arrayStart = markerMatch.index + markerMatch[0].length;
123
+ if (text[arrayStart] !== '[') {
124
+ return { matchedIds: [], message: text };
125
+ }
126
+
127
+ let depth = 0;
128
+ let inString = false;
129
+ let isEscaped = false;
130
+
131
+ for (let index = arrayStart; index < text.length; index += 1) {
132
+ const char = text[index];
133
+
134
+ if (inString) {
135
+ if (isEscaped) {
136
+ isEscaped = false;
137
+ } else if (char === '\\') {
138
+ isEscaped = true;
139
+ } else if (char === '"') {
140
+ inString = false;
141
+ }
142
+ continue;
143
+ }
144
+
145
+ if (char === '"') {
146
+ inString = true;
147
+ continue;
148
+ }
149
+
150
+ if (char === '[') {
151
+ depth += 1;
152
+ continue;
153
+ }
154
+
155
+ if (char === ']') {
156
+ depth -= 1;
157
+ if (depth !== 0) {
158
+ continue;
159
+ }
160
+
161
+ const arrayLiteral = text.slice(arrayStart, index + 1);
162
+ try {
163
+ const parsed: unknown = JSON.parse(arrayLiteral);
164
+ if (
165
+ Array.isArray(parsed) &&
166
+ parsed.every((item): item is string => typeof item === 'string')
167
+ ) {
168
+ const before = text.slice(0, markerMatch.index).trimEnd();
169
+ const after = text.slice(index + 1).trimStart();
170
+ const message = [before, after].filter(Boolean).join(' ').trim();
171
+
172
+ return { matchedIds: parsed, message };
173
+ }
174
+ } catch {
175
+ // Fall through to return the original text unchanged.
176
+ }
177
+
178
+ return { matchedIds: [], message: text };
179
+ }
180
+ }
181
+
182
+ return { matchedIds: [], message: text };
183
+ }
184
+
185
+ function normalizePlainText(text: string): P3Response {
186
+ const { matchedIds, message: withoutIds } =
187
+ extractMatchedIdsFromPlainText(text);
188
+ const message = withoutIds.replace(GENERIC_PREAMBLE_PATTERN, '').trim();
189
+
190
+ return {
191
+ message,
192
+ action: {
193
+ type: 'none',
194
+ matched_ids: matchedIds,
195
+ },
196
+ options: [],
197
+ };
198
+ }
199
+
200
+ /**
201
+ * Normalizes chat_assistant_prompt output into a P3Response.
202
+ * Handles object responses, JSON embedded in prose, and plain-text fallbacks.
203
+ * Never throws — always returns a safe P3Response.
204
+ */
205
+ export function normalizeChatAssistantResponse(
206
+ response: P3Response | string | unknown,
207
+ ): P3Response {
208
+ if (isCompatibleP3Response(response)) {
209
+ return response;
210
+ }
211
+
212
+ if (typeof response !== 'string') {
213
+ return createEmptyResponse();
214
+ }
215
+
216
+ const trimmed = response.trim();
217
+ if (!trimmed) {
218
+ return createEmptyResponse();
219
+ }
220
+
221
+ const fromJson = extractCompatibleJsonObject(trimmed);
222
+ if (fromJson) {
223
+ return fromJson;
224
+ }
225
+
226
+ return normalizePlainText(trimmed);
227
+ }