@nyris/nyris-webapp 0.3.98 → 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 (85) hide show
  1. package/build/asset-manifest.json +7 -6
  2. package/build/index.html +1 -1
  3. package/build/js/settings.example.js +40 -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.93fdbd6e.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 +40 -0
  11. package/src/common/assets/images/plate_info.png +0 -0
  12. package/src/components/Cart.tsx +41 -0
  13. package/src/components/GoBackButton.tsx +29 -2
  14. package/src/components/Header.tsx +3 -0
  15. package/src/components/HitsPerPage.tsx +55 -18
  16. package/src/components/ImagePreview.tsx +31 -1
  17. package/src/components/ImageUpload.tsx +12 -1
  18. package/src/components/Inquiry/InquiryModal.tsx +3 -0
  19. package/src/components/Pagination.tsx +33 -6
  20. package/src/components/PostFilter/PostFilter.tsx +8 -1
  21. package/src/components/PostFilter/PostFilterFindApi.test.tsx +249 -0
  22. package/src/components/PostFilter/PostFilterFindApi.tsx +128 -52
  23. package/src/components/PreFilter/PreFilter.tsx +10 -2
  24. package/src/components/Product/Product.tsx +60 -10
  25. package/src/components/Product/ProductDetailView.tsx +23 -1
  26. package/src/components/Product/ProductDetailViewModal.tsx +5 -1
  27. package/src/components/Product/ProductList.tsx +51 -18
  28. package/src/components/Rfq/RfqModal.tsx +3 -0
  29. package/src/components/ScopeSelect/ScopeSelectForm.tsx +514 -0
  30. package/src/components/ScopeSelect/ScopeSelectImageActions.tsx +62 -0
  31. package/src/components/ScopeSelect/ScopeSelectImagePreview.tsx +141 -0
  32. package/src/components/ScopeSelect/ScopeSelectInfo.tsx +68 -0
  33. package/src/components/ScopeSelect/ScopeSelectInput.tsx +123 -0
  34. package/src/components/ScopeSelect/ScopeSelectModal.tsx +310 -0
  35. package/src/components/ScopeSelect/identifyTypePlateImage.test.ts +129 -0
  36. package/src/components/ScopeSelect/identifyTypePlateImage.ts +83 -0
  37. package/src/components/ScopeSelect/parseScopeIdResponse.test.ts +103 -0
  38. package/src/components/ScopeSelect/parseScopeIdResponse.ts +72 -0
  39. package/src/components/ScopeSelect/scopeSelectHelpers.ts +54 -0
  40. package/src/components/SidePanel.tsx +33 -0
  41. package/src/components/TextSearch.tsx +77 -11
  42. package/src/components/Tooltip/TooltipComponent.tsx +12 -4
  43. package/src/components/TypePlateUpload.tsx +132 -0
  44. package/src/hooks/useEffectiveGroundingResults.ts +8 -6
  45. package/src/hooks/useGoodResultsChat.ts +8 -22
  46. package/src/hooks/useImageSearch.ts +290 -12
  47. package/src/hooks/useMachinePartsListingPagination.ts +95 -0
  48. package/src/hooks/useResultEvaluator.ts +43 -19
  49. package/src/index.css +1 -1
  50. package/src/layouts/AppLayout.tsx +43 -3
  51. package/src/pages/Home.tsx +60 -14
  52. package/src/pages/Result.tsx +194 -14
  53. package/src/services/Feedback.ts +223 -35
  54. package/src/services/feedbackSubtypes.ts +63 -0
  55. package/src/services/filter.ts +1 -1
  56. package/src/services/initialMachinePartsText.payload.test.ts +61 -0
  57. package/src/services/initialMachinePartsText.test.ts +33 -0
  58. package/src/services/initialMachinePartsText.ts +11 -0
  59. package/src/services/machinePartsPage.test.ts +74 -0
  60. package/src/services/machinePartsPage.ts +120 -0
  61. package/src/services/normalizeChatResponse.ts +26 -0
  62. package/src/services/scopeLookup.test.ts +67 -0
  63. package/src/services/scopeLookup.ts +39 -0
  64. package/src/services/visualSearch.listingOptions.test.ts +79 -0
  65. package/src/services/visualSearch.ts +71 -3
  66. package/src/services/vizo.ts +4 -3
  67. package/src/stores/machinePartsListing/machinePartsListingStore.ts +35 -0
  68. package/src/stores/request/filter/filter.initialState.ts +1 -0
  69. package/src/stores/request/filter/filter.slice.ts +2 -0
  70. package/src/stores/result/prodcuts/products.initialState.ts +1 -0
  71. package/src/stores/result/prodcuts/products.slice.ts +4 -0
  72. package/src/stores/scope/scopeStore.ts +67 -4
  73. package/src/stores/types.ts +19 -14
  74. package/src/translations.ts +213 -0
  75. package/src/types.ts +40 -0
  76. package/src/utils/fetchProductImage.ts +1 -0
  77. package/src/utils/normalizeChatAssistantResponse.ts +227 -0
  78. package/src/utils/productCardImageUrl.test.ts +41 -0
  79. package/src/utils/productCardImageUrl.ts +49 -0
  80. package/src/utils/textSearchFilter.test.ts +41 -0
  81. package/src/utils/textSearchFilter.ts +13 -6
  82. package/build/static/css/main.81216c5d.css +0 -4
  83. package/build/static/css/main.81216c5d.css.map +0 -1
  84. package/build/static/js/main.93fdbd6e.js +0 -3
  85. /package/build/static/js/{main.93fdbd6e.js.LICENSE.txt → main.2339271b.js.LICENSE.txt} +0 -0
@@ -13,20 +13,28 @@ function Tooltip({
13
13
  disabled,
14
14
  sideOffset,
15
15
  delayDuration = 80,
16
+ open,
17
+ side = 'top',
16
18
  }: {
17
- content: string;
19
+ content: React.ReactNode;
18
20
  children: React.ReactNode;
19
21
  disabled?: boolean;
20
22
  sideOffset?: number;
21
23
  delayDuration?: number;
24
+ open?: boolean;
25
+ side?: 'top' | 'right' | 'bottom' | 'left';
22
26
  }) {
23
27
  return (
24
28
  <TooltipProvider delayDuration={delayDuration}>
25
- <RadixTooltip open={disabled ? false : undefined}>
29
+ <RadixTooltip open={disabled ? false : open}>
26
30
  <TooltipTrigger asChild>{children}</TooltipTrigger>
27
- <TooltipContent sideOffset={sideOffset}>
31
+ <TooltipContent side={side} sideOffset={sideOffset}>
28
32
  <TooltipArrow />
29
- <p className="text-white max-w-[350px]">{content}</p>
33
+ {typeof content === 'string' ? (
34
+ <p className="text-white max-w-[350px]">{content}</p>
35
+ ) : (
36
+ content
37
+ )}
30
38
  </TooltipContent>
31
39
  </RadixTooltip>
32
40
  </TooltipProvider>
@@ -0,0 +1,132 @@
1
+ import { useState } from 'react';
2
+ import { Icon } from '@nyris/nyris-react-components';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import useScopeStore from 'stores/scope/scopeStore';
6
+ import Tooltip from './Tooltip/TooltipComponent';
7
+ import { ScopeSelectModal } from './ScopeSelect/ScopeSelectModal';
8
+
9
+ export interface TypePlateUploadProps {
10
+ className?: string;
11
+ disabledTooltip?: boolean;
12
+ alternativeView?: boolean;
13
+ /** When false, the button only opens a modal owned by the parent. */
14
+ showModal?: boolean;
15
+ open?: boolean;
16
+ onOpenChange?: (open: boolean) => void;
17
+ /** Results-page text search: icon, tooltip, and active-scope marker. */
18
+ iconOnly?: boolean;
19
+ }
20
+
21
+ export function TypePlateUpload({
22
+ className,
23
+ alternativeView = false,
24
+ showModal = true,
25
+ open,
26
+ onOpenChange,
27
+ iconOnly = false,
28
+ }: TypePlateUploadProps = {}) {
29
+ const { t } = useTranslation();
30
+ const scopeId = useScopeStore(state => state.scopeId);
31
+ const scopeItemKeys = useScopeStore(state => state.itemKeys);
32
+ const hasActiveScope = Boolean(scopeId) && scopeItemKeys.length > 0;
33
+ const [internalOpen, setInternalOpen] = useState(false);
34
+ const isControlled = open !== undefined;
35
+ const isOpen = isControlled ? open : internalOpen;
36
+ const setIsOpen = (next: boolean) => {
37
+ if (!isControlled) {
38
+ setInternalOpen(next);
39
+ }
40
+ onOpenChange?.(next);
41
+ };
42
+
43
+ if (!window.settings.scopes?.scopeID) return null;
44
+
45
+ const buttonLabel =
46
+ window.settings.scopes?.typePlateButtonLabel ||
47
+ window.settings.scopes?.typePlateButtonText ||
48
+ 'Scan type plate';
49
+ const tooltipText =
50
+ iconOnly && hasActiveScope
51
+ ? t(
52
+ 'Searching within parts for {{scopeId}}. You may scan another type plate.',
53
+ { scopeId },
54
+ )
55
+ : t('Scan Type plate');
56
+ const accessibleName = iconOnly ? tooltipText : t(buttonLabel);
57
+
58
+ const button = (
59
+ <button
60
+ type="button"
61
+ className={twMerge(
62
+ 'p-2 desktop:p-3 flex items-center gap-2 rounded-full border border-solid cursor-pointer',
63
+ alternativeView
64
+ ? 'bg-theme-primary border-theme-primary'
65
+ : 'bg-[#f3f3f5] border-[#2B2C46] desktop:bg-[#FAFAFA]',
66
+ className,
67
+ )}
68
+ aria-label={accessibleName}
69
+ onClick={() => setIsOpen(true)}
70
+ >
71
+ <Icon
72
+ name="typeplate"
73
+ width={16}
74
+ height={16}
75
+ fill={alternativeView ? '#FFFFFF' : '#2B2C46'}
76
+ />
77
+ {!iconOnly && (
78
+ <span
79
+ className={twMerge(
80
+ 'whitespace-nowrap pr-1 text-[14px] leading-none',
81
+ alternativeView ? 'text-white' : 'text-[#2B2C46]',
82
+ )}
83
+ >
84
+ {t(buttonLabel)}
85
+ </span>
86
+ )}
87
+ </button>
88
+ );
89
+
90
+ return (
91
+ <>
92
+ {iconOnly ? (
93
+ <div className="relative">
94
+ <Tooltip content={tooltipText}>{button}</Tooltip>
95
+ {hasActiveScope && (
96
+ <div
97
+ className="pointer-events-none top-2 left-8 desktop:top-[1px] desktop:left-[26px]"
98
+ style={{
99
+ position: 'absolute',
100
+ display: 'flex',
101
+ justifyContent: 'center',
102
+ alignItems: 'center',
103
+ background: 'white',
104
+ width: '12px',
105
+ height: '12px',
106
+ borderRadius: '100%',
107
+ }}
108
+ >
109
+ <div
110
+ style={{
111
+ width: '8px',
112
+ height: '8px',
113
+ background: window.settings.theme?.primaryColor,
114
+ borderRadius: '100%',
115
+ strokeWidth: '2px',
116
+ }}
117
+ />
118
+ </div>
119
+ )}
120
+ </div>
121
+ ) : (
122
+ button
123
+ )}
124
+ {showModal && (
125
+ <ScopeSelectModal
126
+ openModal={isOpen}
127
+ handleClose={() => setIsOpen(false)}
128
+ />
129
+ )}
130
+ </>
131
+ );
132
+ }
@@ -26,12 +26,14 @@ export function useEffectiveGroundingResults() {
26
26
  const postEntries = Object.entries(postFilterSelections || {}).filter(
27
27
  ([, values]) => values?.length,
28
28
  );
29
- if (
30
- postEntries.length > 0 &&
31
- !(
32
- !window.settings.algolia?.enabled && window.settings.postFilterOption
33
- )
34
- ) {
29
+ // Text-only find-API post-filters re-query; skip client-side narrowing.
30
+ // Image search uses the old client-side post-filter behavior.
31
+ const isTextOnlyFindApiPostFilter =
32
+ !window.settings.algolia?.enabled &&
33
+ window.settings.postFilterOption &&
34
+ !(requestImages?.length > 0);
35
+
36
+ if (postEntries.length > 0 && !isTextOnlyFindApiPostFilter) {
35
37
  filteredProducts = filteredProducts.filter((product: any) => {
36
38
  const productFilters = product?.filters || {};
37
39
  return postEntries.every(([attribute, selectedValues]) => {
@@ -7,10 +7,7 @@
7
7
  */
8
8
 
9
9
  import { useCallback, useRef } from 'react';
10
- import useConversationStore, {
11
- QuickReplyOption,
12
- ActionType,
13
- } from 'stores/chat/conversationStore';
10
+ import useConversationStore from 'stores/chat/conversationStore';
14
11
  import useSmartFiltersStore from 'stores/smartFilters/smartFiltersStore';
15
12
  import useResultStore from 'stores/result/resultStore';
16
13
  import useRequestStore from 'stores/request/requestStore';
@@ -19,6 +16,10 @@ import {
19
16
  fetchProductImageBase64,
20
17
  getProductImageUrl,
21
18
  } from 'utils/fetchProductImage';
19
+ import {
20
+ normalizeChatAssistantResponse,
21
+ P3Response,
22
+ } from 'utils/normalizeChatAssistantResponse';
22
23
 
23
24
  /**
24
25
  * Extracts search keywords from a user's natural language description.
@@ -138,18 +139,6 @@ function extractSearchKeywords(description: string): string {
138
139
  );
139
140
  }
140
141
 
141
- interface P3Response {
142
- message: string;
143
- action: {
144
- type: ActionType;
145
- key?: string;
146
- value?: string;
147
- matched_ids?: string[];
148
- matched_indices?: number[]; // Legacy fallback
149
- };
150
- options: QuickReplyOption[];
151
- }
152
-
153
142
  export function useGoodResultsChat() {
154
143
  const {
155
144
  messages,
@@ -374,7 +363,7 @@ USER MESSAGE: ${userMessage}
374
363
  Respond to the user's message with an appropriate action and follow-up options.`,
375
364
  });
376
365
 
377
- const response: P3Response = await chatAnalysis({
366
+ const rawResponse = await chatAnalysis({
378
367
  promptKey: 'chat_assistant_prompt',
379
368
  multimodalInputs: userParts.map(part => ({
380
369
  metadata: part.text,
@@ -382,11 +371,8 @@ Respond to the user's message with an appropriate action and follow-up options.`
382
371
  mimeType: part.inlineData?.mimeType,
383
372
  })),
384
373
  });
385
-
386
- const responseText = response;
387
-
388
- // Parse the JSON response
389
- const p3Response: P3Response = responseText;
374
+ const p3Response: P3Response =
375
+ normalizeChatAssistantResponse(rawResponse);
390
376
 
391
377
  // Silent mode: only suggest filters, never apply actions
392
378
  if (options?.silent && p3Response.action) {