@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.
- package/build/asset-manifest.json +7 -6
- package/build/index.html +1 -1
- package/build/js/settings.example.js +40 -0
- package/build/static/css/main.5cb72c09.css +4 -0
- package/build/static/css/main.5cb72c09.css.map +1 -0
- package/build/static/js/main.2339271b.js +3 -0
- package/build/static/js/{main.93fdbd6e.js.map → main.2339271b.js.map} +1 -1
- package/build/static/media/plate_info.6beaea36d35053be3a45.png +0 -0
- package/package.json +3 -3
- package/public/js/settings.example.js +40 -0
- package/src/common/assets/images/plate_info.png +0 -0
- package/src/components/Cart.tsx +41 -0
- package/src/components/GoBackButton.tsx +29 -2
- package/src/components/Header.tsx +3 -0
- package/src/components/HitsPerPage.tsx +55 -18
- package/src/components/ImagePreview.tsx +31 -1
- package/src/components/ImageUpload.tsx +12 -1
- package/src/components/Inquiry/InquiryModal.tsx +3 -0
- package/src/components/Pagination.tsx +33 -6
- package/src/components/PostFilter/PostFilter.tsx +8 -1
- package/src/components/PostFilter/PostFilterFindApi.test.tsx +249 -0
- package/src/components/PostFilter/PostFilterFindApi.tsx +128 -52
- package/src/components/PreFilter/PreFilter.tsx +10 -2
- package/src/components/Product/Product.tsx +60 -10
- package/src/components/Product/ProductDetailView.tsx +23 -1
- package/src/components/Product/ProductDetailViewModal.tsx +5 -1
- package/src/components/Product/ProductList.tsx +51 -18
- package/src/components/Rfq/RfqModal.tsx +3 -0
- package/src/components/ScopeSelect/ScopeSelectForm.tsx +514 -0
- package/src/components/ScopeSelect/ScopeSelectImageActions.tsx +62 -0
- package/src/components/ScopeSelect/ScopeSelectImagePreview.tsx +141 -0
- package/src/components/ScopeSelect/ScopeSelectInfo.tsx +68 -0
- package/src/components/ScopeSelect/ScopeSelectInput.tsx +123 -0
- package/src/components/ScopeSelect/ScopeSelectModal.tsx +310 -0
- package/src/components/ScopeSelect/identifyTypePlateImage.test.ts +129 -0
- package/src/components/ScopeSelect/identifyTypePlateImage.ts +83 -0
- package/src/components/ScopeSelect/parseScopeIdResponse.test.ts +103 -0
- package/src/components/ScopeSelect/parseScopeIdResponse.ts +72 -0
- package/src/components/ScopeSelect/scopeSelectHelpers.ts +54 -0
- package/src/components/SidePanel.tsx +33 -0
- package/src/components/TextSearch.tsx +77 -11
- package/src/components/Tooltip/TooltipComponent.tsx +12 -4
- package/src/components/TypePlateUpload.tsx +132 -0
- package/src/hooks/useEffectiveGroundingResults.ts +8 -6
- package/src/hooks/useGoodResultsChat.ts +8 -22
- package/src/hooks/useImageSearch.ts +290 -12
- package/src/hooks/useMachinePartsListingPagination.ts +95 -0
- package/src/hooks/useResultEvaluator.ts +43 -19
- package/src/index.css +1 -1
- package/src/layouts/AppLayout.tsx +43 -3
- package/src/pages/Home.tsx +60 -14
- package/src/pages/Result.tsx +194 -14
- package/src/services/Feedback.ts +223 -35
- package/src/services/feedbackSubtypes.ts +63 -0
- package/src/services/filter.ts +1 -1
- package/src/services/initialMachinePartsText.payload.test.ts +61 -0
- package/src/services/initialMachinePartsText.test.ts +33 -0
- package/src/services/initialMachinePartsText.ts +11 -0
- package/src/services/machinePartsPage.test.ts +74 -0
- package/src/services/machinePartsPage.ts +120 -0
- package/src/services/normalizeChatResponse.ts +26 -0
- package/src/services/scopeLookup.test.ts +67 -0
- package/src/services/scopeLookup.ts +39 -0
- package/src/services/visualSearch.listingOptions.test.ts +79 -0
- package/src/services/visualSearch.ts +71 -3
- package/src/services/vizo.ts +4 -3
- package/src/stores/machinePartsListing/machinePartsListingStore.ts +35 -0
- package/src/stores/request/filter/filter.initialState.ts +1 -0
- package/src/stores/request/filter/filter.slice.ts +2 -0
- package/src/stores/result/prodcuts/products.initialState.ts +1 -0
- package/src/stores/result/prodcuts/products.slice.ts +4 -0
- package/src/stores/scope/scopeStore.ts +67 -4
- package/src/stores/types.ts +19 -14
- package/src/translations.ts +213 -0
- package/src/types.ts +40 -0
- package/src/utils/fetchProductImage.ts +1 -0
- package/src/utils/normalizeChatAssistantResponse.ts +227 -0
- package/src/utils/productCardImageUrl.test.ts +41 -0
- package/src/utils/productCardImageUrl.ts +49 -0
- package/src/utils/textSearchFilter.test.ts +41 -0
- package/src/utils/textSearchFilter.ts +13 -6
- package/build/static/css/main.81216c5d.css +0 -4
- package/build/static/css/main.81216c5d.css.map +0 -1
- package/build/static/js/main.93fdbd6e.js +0 -3
- /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:
|
|
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 :
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
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
|
-
|
|
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) {
|