@wix/web5-core 1.56.0 → 1.57.0
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/dist/cjs/client/wixAuthFetch.js +2 -2
- package/dist/cjs/client/wixAuthFetch.js.map +1 -1
- package/dist/cjs/component/componentDefinitions/HeroEntitySectionDefinition.js +20 -15
- package/dist/cjs/component/componentDefinitions/HeroEntitySectionDefinition.js.map +1 -1
- package/dist/cjs/component/componentDefinitions/HeroSectionDefinition.js +12 -4
- package/dist/cjs/component/componentDefinitions/HeroSectionDefinition.js.map +1 -1
- package/dist/cjs/component/componentDefinitions/listItemsSectionDefinition.js +24 -29
- package/dist/cjs/component/componentDefinitions/listItemsSectionDefinition.js.map +1 -1
- package/dist/cjs/component/componentParser.js +8 -1
- package/dist/cjs/component/componentParser.js.map +1 -1
- package/dist/cjs/component/types.js.map +1 -1
- package/dist/cjs/components/placement/PlacementResponseRenderer.css +0 -4
- package/dist/cjs/components/placement/PlacementResponseRenderer.js +11 -33
- package/dist/cjs/components/placement/PlacementResponseRenderer.js.map +1 -1
- package/dist/cjs/components/placement/buildPlacementDependencies.js.map +1 -1
- package/dist/cjs/components/ui/OptimizedImage.js +59 -14
- package/dist/cjs/components/ui/OptimizedImage.js.map +1 -1
- package/dist/cjs/components/ui/SectionSkeleton.css +19 -0
- package/dist/cjs/components/ui/SectionSkeleton.js +8 -4
- package/dist/cjs/components/ui/SectionSkeleton.js.map +1 -1
- package/dist/cjs/components/ui/UserQuery.css +72 -0
- package/dist/cjs/components/ui/UserQuery.js +131 -37
- package/dist/cjs/components/ui/UserQuery.js.map +1 -1
- package/dist/cjs/context/UserQueryContext.js +34 -0
- package/dist/cjs/context/UserQueryContext.js.map +1 -0
- package/dist/cjs/entity/defaultExtractor.js +9 -2
- package/dist/cjs/entity/defaultExtractor.js.map +1 -1
- package/dist/cjs/entity/fetchEntityListData.js +13 -26
- package/dist/cjs/entity/fetchEntityListData.js.map +1 -1
- package/dist/cjs/entity/index.js +5 -1
- package/dist/cjs/entity/index.js.map +1 -1
- package/dist/cjs/entity/listEntityItems.js +90 -0
- package/dist/cjs/entity/listEntityItems.js.map +1 -0
- package/dist/cjs/hostScope.js +63 -0
- package/dist/cjs/hostScope.js.map +1 -0
- package/dist/cjs/index.js +38 -13
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/patternValidator/validator.test.js +61 -61
- package/dist/cjs/patternValidator/validator.test.js.map +1 -1
- package/dist/cjs/registry/ComponentRegistry.js +22 -2
- package/dist/cjs/registry/ComponentRegistry.js.map +1 -1
- package/dist/cjs/registry/index.js +1 -3
- package/dist/cjs/registry/index.js.map +1 -1
- package/dist/cjs/registry/types.js.map +1 -1
- package/dist/cjs/styles/base-utilities.css +35 -0
- package/dist/cjs/styles/components.css +1 -0
- package/dist/cjs/styles/host-reset.css +81 -0
- package/dist/cjs/styles/tailwind-theme.css +6 -6
- package/dist/cjs/utils/backendEnvironment.js +78 -0
- package/dist/cjs/utils/backendEnvironment.js.map +1 -0
- package/dist/cjs/utils/entityLinkParser.js +3 -1
- package/dist/cjs/utils/entityLinkParser.js.map +1 -1
- package/dist/cjs/utils/imageBackdrop.js +269 -0
- package/dist/cjs/utils/imageBackdrop.js.map +1 -0
- package/dist/cjs/utils/intentExtractor.js +20 -9
- package/dist/cjs/utils/intentExtractor.js.map +1 -1
- package/dist/cjs/utils/productBackHandoff.js +106 -0
- package/dist/cjs/utils/productBackHandoff.js.map +1 -0
- package/dist/cjs/utils/refreshPrompts.js +72 -0
- package/dist/cjs/utils/refreshPrompts.js.map +1 -0
- package/dist/esm/client/wixAuthFetch.js +2 -2
- package/dist/esm/client/wixAuthFetch.js.map +1 -1
- package/dist/esm/component/componentDefinitions/HeroEntitySectionDefinition.js +20 -15
- package/dist/esm/component/componentDefinitions/HeroEntitySectionDefinition.js.map +1 -1
- package/dist/esm/component/componentDefinitions/HeroSectionDefinition.js +12 -4
- package/dist/esm/component/componentDefinitions/HeroSectionDefinition.js.map +1 -1
- package/dist/esm/component/componentDefinitions/listItemsSectionDefinition.js +25 -30
- package/dist/esm/component/componentDefinitions/listItemsSectionDefinition.js.map +1 -1
- package/dist/esm/component/componentParser.js +8 -1
- package/dist/esm/component/componentParser.js.map +1 -1
- package/dist/esm/component/types.js.map +1 -1
- package/dist/esm/components/placement/PlacementResponseRenderer.css +0 -4
- package/dist/esm/components/placement/PlacementResponseRenderer.js +1 -16
- package/dist/esm/components/placement/PlacementResponseRenderer.js.map +1 -1
- package/dist/esm/components/placement/buildPlacementDependencies.js.map +1 -1
- package/dist/esm/components/ui/OptimizedImage.js +50 -5
- package/dist/esm/components/ui/OptimizedImage.js.map +1 -1
- package/dist/esm/components/ui/SectionSkeleton.css +19 -0
- package/dist/esm/components/ui/SectionSkeleton.js +5 -1
- package/dist/esm/components/ui/SectionSkeleton.js.map +1 -1
- package/dist/esm/components/ui/UserQuery.css +72 -0
- package/dist/esm/components/ui/UserQuery.js +52 -6
- package/dist/esm/components/ui/UserQuery.js.map +1 -1
- package/dist/esm/context/UserQueryContext.js +21 -0
- package/dist/esm/context/UserQueryContext.js.map +1 -0
- package/dist/esm/entity/defaultExtractor.js +9 -2
- package/dist/esm/entity/defaultExtractor.js.map +1 -1
- package/dist/esm/entity/fetchEntityListData.js +14 -26
- package/dist/esm/entity/fetchEntityListData.js.map +1 -1
- package/dist/esm/entity/index.js +1 -0
- package/dist/esm/entity/index.js.map +1 -1
- package/dist/esm/entity/listEntityItems.js +85 -0
- package/dist/esm/entity/listEntityItems.js.map +1 -0
- package/dist/esm/hostScope.js +59 -0
- package/dist/esm/hostScope.js.map +1 -0
- package/dist/esm/index.js +13 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/patternValidator/validator.test.js +61 -61
- package/dist/esm/patternValidator/validator.test.js.map +1 -1
- package/dist/esm/registry/ComponentRegistry.js +22 -2
- package/dist/esm/registry/ComponentRegistry.js.map +1 -1
- package/dist/esm/registry/index.js +0 -1
- package/dist/esm/registry/index.js.map +1 -1
- package/dist/esm/registry/types.js.map +1 -1
- package/dist/esm/styles/base-utilities.css +35 -0
- package/dist/esm/styles/components.css +1 -0
- package/dist/esm/styles/host-reset.css +81 -0
- package/dist/esm/styles/tailwind-theme.css +6 -6
- package/dist/esm/utils/backendEnvironment.js +71 -0
- package/dist/esm/utils/backendEnvironment.js.map +1 -0
- package/dist/esm/utils/entityLinkParser.js +3 -1
- package/dist/esm/utils/entityLinkParser.js.map +1 -1
- package/dist/esm/utils/imageBackdrop.js +262 -0
- package/dist/esm/utils/imageBackdrop.js.map +1 -0
- package/dist/esm/utils/intentExtractor.js +21 -12
- package/dist/esm/utils/intentExtractor.js.map +1 -1
- package/dist/esm/utils/productBackHandoff.js +100 -0
- package/dist/esm/utils/productBackHandoff.js.map +1 -0
- package/dist/esm/utils/refreshPrompts.js +67 -0
- package/dist/esm/utils/refreshPrompts.js.map +1 -0
- package/dist/types/component/componentDefinitions/HeroEntitySectionDefinition.d.ts.map +1 -1
- package/dist/types/component/componentDefinitions/HeroSectionDefinition.d.ts.map +1 -1
- package/dist/types/component/componentDefinitions/listItemsSectionDefinition.d.ts +2 -2
- package/dist/types/component/componentDefinitions/listItemsSectionDefinition.d.ts.map +1 -1
- package/dist/types/component/componentParser.d.ts.map +1 -1
- package/dist/types/component/types.d.ts +1 -5
- package/dist/types/component/types.d.ts.map +1 -1
- package/dist/types/components/placement/PlacementResponseRenderer.d.ts +1 -1
- package/dist/types/components/placement/PlacementResponseRenderer.d.ts.map +1 -1
- package/dist/types/components/placement/buildPlacementDependencies.d.ts +1 -2
- package/dist/types/components/placement/buildPlacementDependencies.d.ts.map +1 -1
- package/dist/types/components/ui/OptimizedImage.d.ts +9 -0
- package/dist/types/components/ui/OptimizedImage.d.ts.map +1 -1
- package/dist/types/components/ui/SectionSkeleton.d.ts.map +1 -1
- package/dist/types/components/ui/UserQuery.d.ts +21 -0
- package/dist/types/components/ui/UserQuery.d.ts.map +1 -1
- package/dist/types/context/UserQueryContext.d.ts +12 -0
- package/dist/types/context/UserQueryContext.d.ts.map +1 -0
- package/dist/types/entity/defaultExtractor.d.ts.map +1 -1
- package/dist/types/entity/fetchEntityListData.d.ts +0 -2
- package/dist/types/entity/fetchEntityListData.d.ts.map +1 -1
- package/dist/types/entity/index.d.ts +1 -0
- package/dist/types/entity/index.d.ts.map +1 -1
- package/dist/types/entity/listEntityItems.d.ts +40 -0
- package/dist/types/entity/listEntityItems.d.ts.map +1 -0
- package/dist/types/hostScope.d.ts +83 -0
- package/dist/types/hostScope.d.ts.map +1 -0
- package/dist/types/index.d.ts +11 -7
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/registry/ComponentRegistry.d.ts +8 -0
- package/dist/types/registry/ComponentRegistry.d.ts.map +1 -1
- package/dist/types/registry/index.d.ts +0 -1
- package/dist/types/registry/index.d.ts.map +1 -1
- package/dist/types/registry/types.d.ts +5 -6
- package/dist/types/registry/types.d.ts.map +1 -1
- package/dist/types/utils/backendEnvironment.d.ts +30 -0
- package/dist/types/utils/backendEnvironment.d.ts.map +1 -0
- package/dist/types/utils/entityLinkParser.d.ts.map +1 -1
- package/dist/types/utils/imageBackdrop.d.ts +102 -0
- package/dist/types/utils/imageBackdrop.d.ts.map +1 -0
- package/dist/types/utils/intentExtractor.d.ts +21 -4
- package/dist/types/utils/intentExtractor.d.ts.map +1 -1
- package/dist/types/utils/productBackHandoff.d.ts +43 -0
- package/dist/types/utils/productBackHandoff.d.ts.map +1 -0
- package/dist/types/utils/refreshPrompts.d.ts +32 -0
- package/dist/types/utils/refreshPrompts.d.ts.map +1 -0
- package/package.json +4 -3
- package/src/components/placement/PlacementResponseRenderer.css +265 -0
- package/src/components/ui/Disclaimer.css +35 -0
- package/src/components/ui/FeedbackBar.css +267 -0
- package/src/components/ui/PlacementLoader.css +101 -0
- package/src/components/ui/PromptEntryEmptyState.css +172 -0
- package/src/components/ui/SearchSection.css +538 -0
- package/src/components/ui/SectionSkeleton.css +128 -0
- package/src/components/ui/UserQuery.css +72 -0
- package/src/styles/base-utilities.css +35 -0
- package/src/styles/components.css +1 -0
- package/src/styles/host-reset.css +81 -0
- package/src/styles/tailwind-theme.css +6 -6
- package/dist/cjs/registry/Intent.js +0 -19
- package/dist/cjs/registry/Intent.js.map +0 -1
- package/dist/cjs/utils/conversationApi.js +0 -52
- package/dist/cjs/utils/conversationApi.js.map +0 -1
- package/dist/cjs/utils/conversationMode.js +0 -23
- package/dist/cjs/utils/conversationMode.js.map +0 -1
- package/dist/esm/registry/Intent.js +0 -15
- package/dist/esm/registry/Intent.js.map +0 -1
- package/dist/esm/utils/conversationApi.js +0 -45
- package/dist/esm/utils/conversationApi.js.map +0 -1
- package/dist/esm/utils/conversationMode.js +0 -18
- package/dist/esm/utils/conversationMode.js.map +0 -1
- package/dist/types/registry/Intent.d.ts +0 -12
- package/dist/types/registry/Intent.d.ts.map +0 -1
- package/dist/types/utils/conversationApi.d.ts +0 -26
- package/dist/types/utils/conversationApi.d.ts.map +0 -1
- package/dist/types/utils/conversationMode.d.ts +0 -16
- package/dist/types/utils/conversationMode.d.ts.map +0 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/* Product-page back badge (DL #114) — self-contained CSS so the badge renders
|
|
2
|
+
styled even where the consumer doesn't compile Tailwind from web5-core/src.
|
|
3
|
+
Mounted inside the shared `UserQuery` header; clients can re-skin via the
|
|
4
|
+
`--web5-product-back-*` custom properties, or replace the whole header via
|
|
5
|
+
the `UserQueryComponent` UI slot. */
|
|
6
|
+
|
|
7
|
+
/* Left column: the badge row stacked above the user's query, with the row→query
|
|
8
|
+
gap. Used whether or not a badge is present (harmless with a single child). */
|
|
9
|
+
.web5-user-query__left {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
gap: var(--web5-user-query-row-gap, 11px);
|
|
13
|
+
min-width: 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* Badge row: back arrow + thumbnail + product name. */
|
|
17
|
+
.web5-user-query__product-back {
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
gap: var(--web5-product-back-gap, 11px);
|
|
21
|
+
min-width: 0;
|
|
22
|
+
max-width: var(--web5-product-back-max-width, 440px);
|
|
23
|
+
margin: 0;
|
|
24
|
+
padding: 0;
|
|
25
|
+
background: transparent;
|
|
26
|
+
border: 0;
|
|
27
|
+
text-align: left;
|
|
28
|
+
color: inherit;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
transition: color 0.15s ease;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Non-clickable variant (no validated backUrl). */
|
|
34
|
+
.web5-user-query__product-back--static {
|
|
35
|
+
cursor: default;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.web5-user-query__product-back-icon {
|
|
39
|
+
flex: none;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.web5-user-query__product-back-thumb {
|
|
43
|
+
flex: none;
|
|
44
|
+
width: var(--web5-product-thumb-size, 34px);
|
|
45
|
+
height: var(--web5-product-thumb-size, 34px);
|
|
46
|
+
border-radius: var(--web5-product-thumb-radius, 3px);
|
|
47
|
+
border: 1px solid var(--web5-product-thumb-border, #f1f0ee);
|
|
48
|
+
overflow: hidden;
|
|
49
|
+
background: #fff;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.web5-user-query__product-back-thumb img {
|
|
53
|
+
display: block;
|
|
54
|
+
width: 100%;
|
|
55
|
+
height: 100%;
|
|
56
|
+
object-fit: cover;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.web5-user-query__product-back-title {
|
|
60
|
+
min-width: 0;
|
|
61
|
+
overflow: hidden;
|
|
62
|
+
text-overflow: ellipsis;
|
|
63
|
+
white-space: nowrap;
|
|
64
|
+
font-size: var(--web5-product-back-title-size, 16px);
|
|
65
|
+
line-height: var(--web5-product-back-title-line-height, 20px);
|
|
66
|
+
font-weight: var(--web5-product-back-title-weight, 500);
|
|
67
|
+
color: var(--web5-product-back-title-color, var(--body-text-color, #2b2b2b));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.web5-user-query__product-back:hover .web5-user-query__product-back-title {
|
|
71
|
+
text-decoration: underline;
|
|
72
|
+
}
|
|
@@ -8,6 +8,14 @@ import { CommentIcon } from './icons/CommentIcon.js';
|
|
|
8
8
|
import { getForwardStack, pushToForwardStack, popFromForwardStack, clearForwardStack } from '../../utils/navigationStack.js';
|
|
9
9
|
const DEFAULT_LOADING_MESSAGES = ['Thinking…', 'Recognizing intent…', 'Connecting the dots…', 'Synthesizing insights…', 'Formulating a response…'];
|
|
10
10
|
const headerClassName = 'web5-user-query sticky top-0 z-10 flex items-center justify-between bg-white/60 backdrop-blur-sm pl-5 pr-[10px] py-[7px] sm:px-[30px] sm:py-[17px]';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Product the visitor was redirected from (DL #114). Rendered as a back badge
|
|
14
|
+
* above the query: back arrow + thumbnail + product name. All fields are
|
|
15
|
+
* resolved and validated app-side (allowed-domain `backUrl`, `https:`
|
|
16
|
+
* `imageUrl`) before reaching this presentational component.
|
|
17
|
+
*/
|
|
18
|
+
|
|
11
19
|
export const UserQuery = _ref => {
|
|
12
20
|
let {
|
|
13
21
|
query,
|
|
@@ -22,7 +30,9 @@ export const UserQuery = _ref => {
|
|
|
22
30
|
loading,
|
|
23
31
|
newConversationLabel = 'New conversation',
|
|
24
32
|
copiedLabel = 'Link Copied',
|
|
25
|
-
copiedDurationMs = 2000
|
|
33
|
+
copiedDurationMs = 2000,
|
|
34
|
+
productBackContext,
|
|
35
|
+
onBackToProduct
|
|
26
36
|
} = _ref;
|
|
27
37
|
const forwardStack = useMemo(() => getForwardStack(), [currentTriggerId]);
|
|
28
38
|
const canGoForward = forwardStack.length > 0;
|
|
@@ -50,6 +60,10 @@ export const UserQuery = _ref => {
|
|
|
50
60
|
onNewConversation == null || onNewConversation();
|
|
51
61
|
}, [onNewConversation]);
|
|
52
62
|
const [shareState, setShareState] = useState('idle');
|
|
63
|
+
const [productImageError, setProductImageError] = useState(false);
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
setProductImageError(false);
|
|
66
|
+
}, [productBackContext == null ? void 0 : productBackContext.imageUrl]);
|
|
53
67
|
const handleShare = useCallback(async () => {
|
|
54
68
|
if (shareState !== 'idle') {
|
|
55
69
|
return;
|
|
@@ -89,6 +103,34 @@ export const UserQuery = _ref => {
|
|
|
89
103
|
}
|
|
90
104
|
const isCopied = shareState === 'copied';
|
|
91
105
|
const isLoading = shareState === 'loading';
|
|
106
|
+
const productBadge = productBackContext ? (() => {
|
|
107
|
+
const {
|
|
108
|
+
title,
|
|
109
|
+
imageUrl,
|
|
110
|
+
backUrl,
|
|
111
|
+
label
|
|
112
|
+
} = productBackContext;
|
|
113
|
+
const canNavigateBack = !!backUrl && !!onBackToProduct;
|
|
114
|
+
const content = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(UndoIcon, {
|
|
115
|
+
className: "web5-user-query__product-back-icon"
|
|
116
|
+
}), imageUrl && !productImageError && /*#__PURE__*/React.createElement("span", {
|
|
117
|
+
className: "web5-user-query__product-back-thumb"
|
|
118
|
+
}, /*#__PURE__*/React.createElement("img", {
|
|
119
|
+
src: imageUrl,
|
|
120
|
+
alt: "",
|
|
121
|
+
onError: () => setProductImageError(true)
|
|
122
|
+
})), /*#__PURE__*/React.createElement("span", {
|
|
123
|
+
className: "web5-user-query__product-back-title"
|
|
124
|
+
}, title));
|
|
125
|
+
return canNavigateBack ? /*#__PURE__*/React.createElement("button", {
|
|
126
|
+
type: "button",
|
|
127
|
+
onClick: () => onBackToProduct == null ? void 0 : onBackToProduct(backUrl),
|
|
128
|
+
"aria-label": label ?? `Back to ${title}`,
|
|
129
|
+
className: "web5-user-query__product-back"
|
|
130
|
+
}, content) : /*#__PURE__*/React.createElement("div", {
|
|
131
|
+
className: "web5-user-query__product-back web5-user-query__product-back--static"
|
|
132
|
+
}, content);
|
|
133
|
+
})() : null;
|
|
92
134
|
if (loading) {
|
|
93
135
|
const progressText = loading.progressMessage ?? cycledMessage;
|
|
94
136
|
return /*#__PURE__*/React.createElement("div", {
|
|
@@ -96,12 +138,14 @@ export const UserQuery = _ref => {
|
|
|
96
138
|
role: "status",
|
|
97
139
|
"aria-busy": "true",
|
|
98
140
|
"aria-live": "polite"
|
|
99
|
-
}, /*#__PURE__*/React.createElement("
|
|
100
|
-
className: "
|
|
141
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
142
|
+
className: "web5-user-query__left flex-1"
|
|
143
|
+
}, productBadge, /*#__PURE__*/React.createElement("p", {
|
|
144
|
+
className: "text-[14px] leading-5 text-text-body truncate min-w-0 max-w-[700px]",
|
|
101
145
|
title: `Asking: ${loading.pendingQuery}`
|
|
102
146
|
}, /*#__PURE__*/React.createElement("span", {
|
|
103
147
|
className: "text-text-body/70"
|
|
104
|
-
}, "Asking: "), loading.pendingQuery), progressText && /*#__PURE__*/React.createElement("div", {
|
|
148
|
+
}, "Asking: "), loading.pendingQuery)), progressText && /*#__PURE__*/React.createElement("div", {
|
|
105
149
|
className: "hidden sm:flex items-center gap-2 ml-[10px] shrink-0 min-w-0"
|
|
106
150
|
}, /*#__PURE__*/React.createElement("div", {
|
|
107
151
|
className: "w-4 h-4 shrink-0 animate-spin inline-block border-[2px] border-current border-t-transparent text-blue-600 dark:text-blue-500 rounded-full",
|
|
@@ -114,10 +158,12 @@ export const UserQuery = _ref => {
|
|
|
114
158
|
className: headerClassName
|
|
115
159
|
}, /*#__PURE__*/React.createElement("div", {
|
|
116
160
|
className: "flex items-center gap-[30px] flex-1 min-w-0"
|
|
117
|
-
}, /*#__PURE__*/React.createElement("
|
|
161
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
162
|
+
className: "web5-user-query__left"
|
|
163
|
+
}, productBadge, /*#__PURE__*/React.createElement("p", {
|
|
118
164
|
className: "web5-user-query__title text-[14px] leading-5 text-text-body truncate min-w-0 max-w-[700px]",
|
|
119
165
|
title: query
|
|
120
|
-
}, query), hasNavigation && /*#__PURE__*/React.createElement("div", {
|
|
166
|
+
}, query)), hasNavigation && /*#__PURE__*/React.createElement("div", {
|
|
121
167
|
className: "flex items-center gap-[9px]"
|
|
122
168
|
}, /*#__PURE__*/React.createElement("button", {
|
|
123
169
|
type: "button",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","useCallback","useEffect","useMemo","useState","ShareIcon","CheckIcon","PlusIcon","UndoIcon","RedoIcon","CommentIcon","getForwardStack","pushToForwardStack","popFromForwardStack","clearForwardStack","DEFAULT_LOADING_MESSAGES","headerClassName","UserQuery","_ref","query","onNewConversation","onNavigateToTriggerId","predecessorTriggerId","currentTriggerId","onShare","onToggleCommentMode","commentMode","commentCount","loading","newConversationLabel","copiedLabel","copiedDurationMs","forwardStack","canGoForward","length","canGoBack","hasNavigation","handleBack","handleForward","nextTid","handleNewConversation","shareState","setShareState","handleShare","urlToCopy","window","location","href","err","console","warn","navigator","clipboard","writeText","catch","setTimeout","cyclingMessages","messages","cycledMessage","setCycledMessage","progressMessage","index","interval","setInterval","clearInterval","isCopied","isLoading","progressText","createElement","className","role","title","pendingQuery","type","onClick","disabled"],"sources":["../../../../src/components/ui/UserQuery.tsx"],"sourcesContent":["import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';\nimport { ShareIcon } from './icons/ShareIcon';\nimport { CheckIcon } from './icons/CheckIcon';\nimport { PlusIcon } from './icons/PlusIcon';\nimport { UndoIcon } from './icons/UndoIcon';\nimport { RedoIcon } from './icons/RedoIcon';\nimport { CommentIcon } from './icons/CommentIcon';\nimport {\n getForwardStack,\n pushToForwardStack,\n popFromForwardStack,\n clearForwardStack,\n} from '../../utils/navigationStack';\n\ntype ShareState = 'idle' | 'loading' | 'copied';\n\nconst DEFAULT_LOADING_MESSAGES = [\n 'Thinking…',\n 'Recognizing intent…',\n 'Connecting the dots…',\n 'Synthesizing insights…',\n 'Formulating a response…',\n];\n\nconst headerClassName =\n 'web5-user-query sticky top-0 z-10 flex items-center justify-between bg-white/60 backdrop-blur-sm pl-5 pr-[10px] py-[7px] sm:px-[30px] sm:py-[17px]';\n\nexport interface UserQueryLoadingState {\n pendingQuery: string;\n /** Static progress message. If omitted, falls back to cycling through `messages`. */\n progressMessage?: string;\n /** Messages cycled while loading when `progressMessage` is not set. */\n messages?: string[];\n}\n\nexport interface UserQueryProps {\n query: string;\n onNewConversation?: () => void;\n onNavigateToTriggerId?: (triggerId: string) => void;\n predecessorTriggerId?: string;\n currentTriggerId?: string;\n onShare?: (triggerId: string) => Promise<string>;\n /** Optional comment-mode toggle. When provided, render the chat-bubble\n * toggle button next to Share. The button reflects `commentMode` via\n * `is-on` styling and `aria-pressed`. */\n onToggleCommentMode?: () => void;\n commentMode?: boolean;\n /** Total number of comments on this response — shown as a badge next to the\n * toggle when > 0. */\n commentCount?: number;\n loading?: UserQueryLoadingState;\n /** Label for the \"new conversation\" action. Defaults to \"New conversation\". */\n newConversationLabel?: string;\n /** Label shown after the share link is copied. Defaults to \"Link Copied\". */\n copiedLabel?: string;\n /** How long the copied state stays visible, in ms. Defaults to 2000. */\n copiedDurationMs?: number;\n}\n\nexport const UserQuery: FC<UserQueryProps> = ({\n query,\n onNewConversation,\n onNavigateToTriggerId,\n predecessorTriggerId,\n currentTriggerId,\n onShare,\n onToggleCommentMode,\n commentMode = false,\n commentCount = 0,\n loading,\n newConversationLabel = 'New conversation',\n copiedLabel = 'Link Copied',\n copiedDurationMs = 2000,\n}) => {\n const forwardStack = useMemo(() => getForwardStack(), [currentTriggerId]);\n const canGoForward = forwardStack.length > 0;\n const canGoBack = !!predecessorTriggerId;\n const hasNavigation = canGoBack || canGoForward;\n\n const handleBack = useCallback(() => {\n if (!predecessorTriggerId || !currentTriggerId || !onNavigateToTriggerId) {\n return;\n }\n pushToForwardStack(currentTriggerId);\n onNavigateToTriggerId(predecessorTriggerId);\n }, [predecessorTriggerId, currentTriggerId, onNavigateToTriggerId]);\n\n const handleForward = useCallback(() => {\n if (!onNavigateToTriggerId) {\n return;\n }\n const nextTid = popFromForwardStack();\n if (!nextTid) {\n return;\n }\n onNavigateToTriggerId(nextTid);\n }, [onNavigateToTriggerId]);\n\n const handleNewConversation = useCallback(() => {\n clearForwardStack();\n onNewConversation?.();\n }, [onNewConversation]);\n\n const [shareState, setShareState] = useState<ShareState>('idle');\n\n const handleShare = useCallback(async () => {\n if (shareState !== 'idle') {\n return;\n }\n setShareState('loading');\n let urlToCopy = window.location.href;\n try {\n if (onShare && currentTriggerId) {\n urlToCopy = await onShare(currentTriggerId);\n }\n } catch (err) {\n console.warn('[share] failed, falling back to current URL:', err);\n }\n navigator.clipboard\n .writeText(urlToCopy)\n .catch((err) => console.warn('[share] clipboard write failed:', err));\n setShareState('copied');\n setTimeout(() => setShareState('idle'), copiedDurationMs);\n }, [shareState, onShare, currentTriggerId, copiedDurationMs]);\n\n const cyclingMessages = useMemo(\n () =>\n loading?.messages && loading.messages.length > 0\n ? loading.messages\n : DEFAULT_LOADING_MESSAGES,\n [loading?.messages],\n );\n const [cycledMessage, setCycledMessage] = useState(cyclingMessages[0]);\n\n useEffect(() => {\n if (!loading || loading.progressMessage) {\n return;\n }\n setCycledMessage(cyclingMessages[0]);\n if (cyclingMessages.length <= 1) {\n return;\n }\n let index = 0;\n const interval = setInterval(() => {\n index = (index + 1) % cyclingMessages.length;\n setCycledMessage(cyclingMessages[index]);\n }, 4000);\n return () => clearInterval(interval);\n }, [loading, cyclingMessages]);\n\n if (!query && !loading) {\n return null;\n }\n\n const isCopied = shareState === 'copied';\n const isLoading = shareState === 'loading';\n\n if (loading) {\n const progressText = loading.progressMessage ?? cycledMessage;\n return (\n <div\n className={headerClassName}\n role=\"status\"\n aria-busy=\"true\"\n aria-live=\"polite\"\n >\n <p\n className=\"text-[14px] leading-5 text-text-body truncate min-w-0 max-w-[700px] flex-1\"\n title={`Asking: ${loading.pendingQuery}`}\n >\n <span className=\"text-text-body/70\">Asking: </span>\n {loading.pendingQuery}\n </p>\n {progressText && (\n <div className=\"hidden sm:flex items-center gap-2 ml-[10px] shrink-0 min-w-0\">\n <div\n className=\"w-4 h-4 shrink-0 animate-spin inline-block border-[2px] border-current border-t-transparent text-blue-600 dark:text-blue-500 rounded-full\"\n aria-hidden=\"true\"\n />\n <p className=\"text-[13px] leading-5 text-text-body/70 truncate max-w-[240px]\">\n {progressText}\n </p>\n </div>\n )}\n </div>\n );\n }\n\n return (\n <div className={headerClassName}>\n <div className=\"flex items-center gap-[30px] flex-1 min-w-0\">\n <p\n className=\"web5-user-query__title text-[14px] leading-5 text-text-body truncate min-w-0 max-w-[700px]\"\n title={query}\n >\n {query}\n </p>\n {hasNavigation && (\n <div className=\"flex items-center gap-[9px]\">\n <button\n type=\"button\"\n onClick={handleBack}\n disabled={!canGoBack}\n title=\"Go to previous conversation\"\n aria-label=\"Go to previous conversation\"\n className=\"w-6 h-6 flex items-center justify-center text-text-body hover:text-black transition-colors bg-transparent border-0 cursor-pointer disabled:opacity-30 disabled:cursor-default\"\n >\n <UndoIcon />\n </button>\n <button\n type=\"button\"\n onClick={handleForward}\n disabled={!canGoForward}\n title=\"Go to next conversation\"\n aria-label=\"Go to next conversation\"\n className=\"w-6 h-6 flex items-center justify-center text-text-body hover:text-black transition-colors bg-transparent border-0 cursor-pointer disabled:opacity-30 disabled:cursor-default\"\n >\n <RedoIcon />\n </button>\n </div>\n )}\n </div>\n <div className=\"flex items-center gap-[10px] ml-[10px] sm:ml-0 shrink-0\">\n {/* Share button — wraps button + mobile tooltip in a relative container */}\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={handleShare}\n disabled={isLoading || isCopied}\n className={`web5-user-query__share flex items-center gap-2 p-[6px] sm:px-[13px] sm:py-[5px] rounded-[30px] text-[13px] leading-5 border-0 cursor-pointer transition-colors disabled:cursor-default\n ${\n isCopied\n ? 'is-copied bg-[#2b2b2b] text-white'\n : 'text-text-body bg-white hover:bg-gray-50'\n }`}\n >\n {isCopied ? <CheckIcon /> : <ShareIcon />}\n <span className=\"hidden sm:inline\">\n {isLoading ? 'Sharing…' : isCopied ? copiedLabel : 'Share'}\n </span>\n </button>\n {/* Mobile-only tooltip: drops below the button when link is copied */}\n {isCopied && (\n <div className=\"web5-user-query__copied-toast sm:hidden absolute top-full right-0 mt-1 flex items-center gap-2 bg-[#2b2b2b] text-white text-[12px] leading-none px-3 py-2 rounded-[8px] whitespace-nowrap\">\n <CheckIcon />\n {copiedLabel}\n </div>\n )}\n </div>\n {onNewConversation && (\n <button\n type=\"button\"\n onClick={handleNewConversation}\n className=\"web5-user-query__new-conversation flex items-center gap-2 p-[6px] sm:px-[13px] sm:py-[5px] rounded-[30px] text-[13px] leading-5 text-text-body bg-white border-0 cursor-pointer hover:bg-gray-50 transition-colors\"\n >\n <PlusIcon />\n <span className=\"hidden sm:inline\">{newConversationLabel}</span>\n </button>\n )}\n {onToggleCommentMode && (\n <button\n type=\"button\"\n onClick={onToggleCommentMode}\n aria-pressed={commentMode}\n title=\"Add a comment\"\n aria-label=\"Add a comment\"\n className={`relative flex items-center gap-2 p-[6px] sm:px-[13px] sm:py-[5px] rounded-[30px] text-[13px] leading-5 border-0 cursor-pointer transition-colors ${\n commentMode\n ? 'bg-[#2b2b2b] text-white'\n : 'text-text-body bg-white hover:bg-gray-50'\n }`}\n >\n <CommentIcon />\n {commentCount > 0 && (\n <span\n className={`absolute -top-1 -right-1 min-w-[18px] h-[18px] px-1 rounded-full text-[10px] leading-[18px] text-center font-semibold ${\n commentMode ? 'bg-white text-[#2b2b2b]' : 'bg-[#2b2b2b] text-white'\n }`}\n >\n {commentCount}\n </span>\n )}\n </button>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["React","useCallback","useEffect","useMemo","useState","ShareIcon","CheckIcon","PlusIcon","UndoIcon","RedoIcon","CommentIcon","getForwardStack","pushToForwardStack","popFromForwardStack","clearForwardStack","DEFAULT_LOADING_MESSAGES","headerClassName","UserQuery","_ref","query","onNewConversation","onNavigateToTriggerId","predecessorTriggerId","currentTriggerId","onShare","onToggleCommentMode","commentMode","commentCount","loading","newConversationLabel","copiedLabel","copiedDurationMs","productBackContext","onBackToProduct","forwardStack","canGoForward","length","canGoBack","hasNavigation","handleBack","handleForward","nextTid","handleNewConversation","shareState","setShareState","productImageError","setProductImageError","imageUrl","handleShare","urlToCopy","window","location","href","err","console","warn","navigator","clipboard","writeText","catch","setTimeout","cyclingMessages","messages","cycledMessage","setCycledMessage","progressMessage","index","interval","setInterval","clearInterval","isCopied","isLoading","productBadge","title","backUrl","label","canNavigateBack","content","createElement","Fragment","className","src","alt","onError","type","onClick","progressText","role","pendingQuery","disabled"],"sources":["../../../../src/components/ui/UserQuery.tsx"],"sourcesContent":["import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';\nimport { ShareIcon } from './icons/ShareIcon';\nimport { CheckIcon } from './icons/CheckIcon';\nimport { PlusIcon } from './icons/PlusIcon';\nimport { UndoIcon } from './icons/UndoIcon';\nimport { RedoIcon } from './icons/RedoIcon';\nimport { CommentIcon } from './icons/CommentIcon';\nimport {\n getForwardStack,\n pushToForwardStack,\n popFromForwardStack,\n clearForwardStack,\n} from '../../utils/navigationStack';\n\ntype ShareState = 'idle' | 'loading' | 'copied';\n\nconst DEFAULT_LOADING_MESSAGES = [\n 'Thinking…',\n 'Recognizing intent…',\n 'Connecting the dots…',\n 'Synthesizing insights…',\n 'Formulating a response…',\n];\n\nconst headerClassName =\n 'web5-user-query sticky top-0 z-10 flex items-center justify-between bg-white/60 backdrop-blur-sm pl-5 pr-[10px] py-[7px] sm:px-[30px] sm:py-[17px]';\n\nexport interface UserQueryLoadingState {\n pendingQuery: string;\n /** Static progress message. If omitted, falls back to cycling through `messages`. */\n progressMessage?: string;\n /** Messages cycled while loading when `progressMessage` is not set. */\n messages?: string[];\n}\n\n/**\n * Product the visitor was redirected from (DL #114). Rendered as a back badge\n * above the query: back arrow + thumbnail + product name. All fields are\n * resolved and validated app-side (allowed-domain `backUrl`, `https:`\n * `imageUrl`) before reaching this presentational component.\n */\nexport interface ProductBackContext {\n /** Product display name. Required — without it the badge does not render. */\n title: string;\n /** Validated product thumbnail URL. Omitted/failed to load → arrow + title only. */\n imageUrl?: string;\n /** Validated product-page URL to return to. Omitted → badge is non-clickable. */\n backUrl?: string;\n /** Accessible label override. Defaults to `Back to ${title}`. */\n label?: string;\n}\n\nexport interface UserQueryProps {\n query: string;\n onNewConversation?: () => void;\n onNavigateToTriggerId?: (triggerId: string) => void;\n predecessorTriggerId?: string;\n currentTriggerId?: string;\n onShare?: (triggerId: string) => Promise<string>;\n /** Optional comment-mode toggle. When provided, render the chat-bubble\n * toggle button next to Share. The button reflects `commentMode` via\n * `is-on` styling and `aria-pressed`. */\n onToggleCommentMode?: () => void;\n commentMode?: boolean;\n /** Total number of comments on this response — shown as a badge next to the\n * toggle when > 0. */\n commentCount?: number;\n loading?: UserQueryLoadingState;\n /** Label for the \"new conversation\" action. Defaults to \"New conversation\". */\n newConversationLabel?: string;\n /** Label shown after the share link is copied. Defaults to \"Link Copied\". */\n copiedLabel?: string;\n /** How long the copied state stays visible, in ms. Defaults to 2000. */\n copiedDurationMs?: number;\n /** When set (visitor arrived from a product page), render a back-to-product\n * badge above the query (DL #114). */\n productBackContext?: ProductBackContext;\n /** Invoked with the validated `backUrl` when the badge is activated. */\n onBackToProduct?: (backUrl: string) => void;\n}\n\nexport const UserQuery: FC<UserQueryProps> = ({\n query,\n onNewConversation,\n onNavigateToTriggerId,\n predecessorTriggerId,\n currentTriggerId,\n onShare,\n onToggleCommentMode,\n commentMode = false,\n commentCount = 0,\n loading,\n newConversationLabel = 'New conversation',\n copiedLabel = 'Link Copied',\n copiedDurationMs = 2000,\n productBackContext,\n onBackToProduct,\n}) => {\n const forwardStack = useMemo(() => getForwardStack(), [currentTriggerId]);\n const canGoForward = forwardStack.length > 0;\n const canGoBack = !!predecessorTriggerId;\n const hasNavigation = canGoBack || canGoForward;\n\n const handleBack = useCallback(() => {\n if (!predecessorTriggerId || !currentTriggerId || !onNavigateToTriggerId) {\n return;\n }\n pushToForwardStack(currentTriggerId);\n onNavigateToTriggerId(predecessorTriggerId);\n }, [predecessorTriggerId, currentTriggerId, onNavigateToTriggerId]);\n\n const handleForward = useCallback(() => {\n if (!onNavigateToTriggerId) {\n return;\n }\n const nextTid = popFromForwardStack();\n if (!nextTid) {\n return;\n }\n onNavigateToTriggerId(nextTid);\n }, [onNavigateToTriggerId]);\n\n const handleNewConversation = useCallback(() => {\n clearForwardStack();\n onNewConversation?.();\n }, [onNewConversation]);\n\n const [shareState, setShareState] = useState<ShareState>('idle');\n\n const [productImageError, setProductImageError] = useState(false);\n useEffect(() => {\n setProductImageError(false);\n }, [productBackContext?.imageUrl]);\n\n const handleShare = useCallback(async () => {\n if (shareState !== 'idle') {\n return;\n }\n setShareState('loading');\n let urlToCopy = window.location.href;\n try {\n if (onShare && currentTriggerId) {\n urlToCopy = await onShare(currentTriggerId);\n }\n } catch (err) {\n console.warn('[share] failed, falling back to current URL:', err);\n }\n navigator.clipboard\n .writeText(urlToCopy)\n .catch((err) => console.warn('[share] clipboard write failed:', err));\n setShareState('copied');\n setTimeout(() => setShareState('idle'), copiedDurationMs);\n }, [shareState, onShare, currentTriggerId, copiedDurationMs]);\n\n const cyclingMessages = useMemo(\n () =>\n loading?.messages && loading.messages.length > 0\n ? loading.messages\n : DEFAULT_LOADING_MESSAGES,\n [loading?.messages],\n );\n const [cycledMessage, setCycledMessage] = useState(cyclingMessages[0]);\n\n useEffect(() => {\n if (!loading || loading.progressMessage) {\n return;\n }\n setCycledMessage(cyclingMessages[0]);\n if (cyclingMessages.length <= 1) {\n return;\n }\n let index = 0;\n const interval = setInterval(() => {\n index = (index + 1) % cyclingMessages.length;\n setCycledMessage(cyclingMessages[index]);\n }, 4000);\n return () => clearInterval(interval);\n }, [loading, cyclingMessages]);\n\n if (!query && !loading) {\n return null;\n }\n\n const isCopied = shareState === 'copied';\n const isLoading = shareState === 'loading';\n\n const productBadge = productBackContext\n ? (() => {\n const { title, imageUrl, backUrl, label } = productBackContext;\n const canNavigateBack = !!backUrl && !!onBackToProduct;\n const content = (\n <>\n <UndoIcon className=\"web5-user-query__product-back-icon\" />\n {imageUrl && !productImageError && (\n <span className=\"web5-user-query__product-back-thumb\">\n <img\n src={imageUrl}\n alt=\"\"\n onError={() => setProductImageError(true)}\n />\n </span>\n )}\n <span className=\"web5-user-query__product-back-title\">{title}</span>\n </>\n );\n return canNavigateBack ? (\n <button\n type=\"button\"\n onClick={() => onBackToProduct?.(backUrl!)}\n aria-label={label ?? `Back to ${title}`}\n className=\"web5-user-query__product-back\"\n >\n {content}\n </button>\n ) : (\n <div className=\"web5-user-query__product-back web5-user-query__product-back--static\">\n {content}\n </div>\n );\n })()\n : null;\n\n if (loading) {\n const progressText = loading.progressMessage ?? cycledMessage;\n return (\n <div\n className={headerClassName}\n role=\"status\"\n aria-busy=\"true\"\n aria-live=\"polite\"\n >\n <div className=\"web5-user-query__left flex-1\">\n {productBadge}\n <p\n className=\"text-[14px] leading-5 text-text-body truncate min-w-0 max-w-[700px]\"\n title={`Asking: ${loading.pendingQuery}`}\n >\n <span className=\"text-text-body/70\">Asking: </span>\n {loading.pendingQuery}\n </p>\n </div>\n {progressText && (\n <div className=\"hidden sm:flex items-center gap-2 ml-[10px] shrink-0 min-w-0\">\n <div\n className=\"w-4 h-4 shrink-0 animate-spin inline-block border-[2px] border-current border-t-transparent text-blue-600 dark:text-blue-500 rounded-full\"\n aria-hidden=\"true\"\n />\n <p className=\"text-[13px] leading-5 text-text-body/70 truncate max-w-[240px]\">\n {progressText}\n </p>\n </div>\n )}\n </div>\n );\n }\n\n return (\n <div className={headerClassName}>\n <div className=\"flex items-center gap-[30px] flex-1 min-w-0\">\n <div className=\"web5-user-query__left\">\n {productBadge}\n <p\n className=\"web5-user-query__title text-[14px] leading-5 text-text-body truncate min-w-0 max-w-[700px]\"\n title={query}\n >\n {query}\n </p>\n </div>\n {hasNavigation && (\n <div className=\"flex items-center gap-[9px]\">\n <button\n type=\"button\"\n onClick={handleBack}\n disabled={!canGoBack}\n title=\"Go to previous conversation\"\n aria-label=\"Go to previous conversation\"\n className=\"w-6 h-6 flex items-center justify-center text-text-body hover:text-black transition-colors bg-transparent border-0 cursor-pointer disabled:opacity-30 disabled:cursor-default\"\n >\n <UndoIcon />\n </button>\n <button\n type=\"button\"\n onClick={handleForward}\n disabled={!canGoForward}\n title=\"Go to next conversation\"\n aria-label=\"Go to next conversation\"\n className=\"w-6 h-6 flex items-center justify-center text-text-body hover:text-black transition-colors bg-transparent border-0 cursor-pointer disabled:opacity-30 disabled:cursor-default\"\n >\n <RedoIcon />\n </button>\n </div>\n )}\n </div>\n <div className=\"flex items-center gap-[10px] ml-[10px] sm:ml-0 shrink-0\">\n {/* Share button — wraps button + mobile tooltip in a relative container */}\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={handleShare}\n disabled={isLoading || isCopied}\n className={`web5-user-query__share flex items-center gap-2 p-[6px] sm:px-[13px] sm:py-[5px] rounded-[30px] text-[13px] leading-5 border-0 cursor-pointer transition-colors disabled:cursor-default\n ${\n isCopied\n ? 'is-copied bg-[#2b2b2b] text-white'\n : 'text-text-body bg-white hover:bg-gray-50'\n }`}\n >\n {isCopied ? <CheckIcon /> : <ShareIcon />}\n <span className=\"hidden sm:inline\">\n {isLoading ? 'Sharing…' : isCopied ? copiedLabel : 'Share'}\n </span>\n </button>\n {/* Mobile-only tooltip: drops below the button when link is copied */}\n {isCopied && (\n <div className=\"web5-user-query__copied-toast sm:hidden absolute top-full right-0 mt-1 flex items-center gap-2 bg-[#2b2b2b] text-white text-[12px] leading-none px-3 py-2 rounded-[8px] whitespace-nowrap\">\n <CheckIcon />\n {copiedLabel}\n </div>\n )}\n </div>\n {onNewConversation && (\n <button\n type=\"button\"\n onClick={handleNewConversation}\n className=\"web5-user-query__new-conversation flex items-center gap-2 p-[6px] sm:px-[13px] sm:py-[5px] rounded-[30px] text-[13px] leading-5 text-text-body bg-white border-0 cursor-pointer hover:bg-gray-50 transition-colors\"\n >\n <PlusIcon />\n <span className=\"hidden sm:inline\">{newConversationLabel}</span>\n </button>\n )}\n {onToggleCommentMode && (\n <button\n type=\"button\"\n onClick={onToggleCommentMode}\n aria-pressed={commentMode}\n title=\"Add a comment\"\n aria-label=\"Add a comment\"\n className={`relative flex items-center gap-2 p-[6px] sm:px-[13px] sm:py-[5px] rounded-[30px] text-[13px] leading-5 border-0 cursor-pointer transition-colors ${\n commentMode\n ? 'bg-[#2b2b2b] text-white'\n : 'text-text-body bg-white hover:bg-gray-50'\n }`}\n >\n <CommentIcon />\n {commentCount > 0 && (\n <span\n className={`absolute -top-1 -right-1 min-w-[18px] h-[18px] px-1 rounded-full text-[10px] leading-[18px] text-center font-semibold ${\n commentMode ? 'bg-white text-[#2b2b2b]' : 'bg-[#2b2b2b] text-white'\n }`}\n >\n {commentCount}\n </span>\n )}\n </button>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React, { createContext, useContext } from 'react';
|
|
2
|
+
const UserQueryContext = /*#__PURE__*/createContext('');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns the raw user query that produced the current response page.
|
|
6
|
+
* Defaults to an empty string when no provider is present (e.g. storybook,
|
|
7
|
+
* tests), so callers can use it without guarding.
|
|
8
|
+
*/
|
|
9
|
+
export function useUserQuery() {
|
|
10
|
+
return useContext(UserQueryContext);
|
|
11
|
+
}
|
|
12
|
+
export const UserQueryProvider = _ref => {
|
|
13
|
+
let {
|
|
14
|
+
query,
|
|
15
|
+
children
|
|
16
|
+
} = _ref;
|
|
17
|
+
return /*#__PURE__*/React.createElement(UserQueryContext.Provider, {
|
|
18
|
+
value: query
|
|
19
|
+
}, children);
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=UserQueryContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["React","createContext","useContext","UserQueryContext","useUserQuery","UserQueryProvider","_ref","query","children","createElement","Provider","value"],"sources":["../../../src/context/UserQueryContext.tsx"],"sourcesContent":["import React, {\n createContext,\n useContext,\n type FC,\n type PropsWithChildren,\n} from 'react';\n\nconst UserQueryContext = createContext<string>('');\n\n/**\n * Returns the raw user query that produced the current response page.\n * Defaults to an empty string when no provider is present (e.g. storybook,\n * tests), so callers can use it without guarding.\n */\nexport function useUserQuery(): string {\n return useContext(UserQueryContext);\n}\n\nexport interface UserQueryProviderProps extends PropsWithChildren {\n query: string;\n}\n\nexport const UserQueryProvider: FC<UserQueryProviderProps> = ({\n query,\n children,\n}) => {\n return (\n <UserQueryContext.Provider value={query}>\n {children}\n </UserQueryContext.Provider>\n );\n};\n"],"mappings":"AAAA,OAAOA,KAAK,IACVC,aAAa,EACbC,UAAU,QAGL,OAAO;AAEd,MAAMC,gBAAgB,gBAAGF,aAAa,CAAS,EAAE,CAAC;;AAElD;AACA;AACA;AACA;AACA;AACA,OAAO,SAASG,YAAYA,CAAA,EAAW;EACrC,OAAOF,UAAU,CAACC,gBAAgB,CAAC;AACrC;AAMA,OAAO,MAAME,iBAA6C,GAAGC,IAAA,IAGvD;EAAA,IAHwD;IAC5DC,KAAK;IACLC;EACF,CAAC,GAAAF,IAAA;EACC,oBACEN,KAAA,CAAAS,aAAA,CAACN,gBAAgB,CAACO,QAAQ;IAACC,KAAK,EAAEJ;EAAM,GACrCC,QACwB,CAAC;AAEhC,CAAC","ignoreList":[]}
|
|
@@ -20,6 +20,11 @@ function extractEntityIdFromUrl(entityUrl) {
|
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
22
|
function matchesUrl(payloadItem, entityId, config) {
|
|
23
|
+
// Entities resolve by `entityId` (the `<id>` in `web5://entity/<type>/<id>`).
|
|
24
|
+
// Accept a direct `id` match, then fall back to the configured url strategy.
|
|
25
|
+
if (payloadItem.id === entityId) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
23
28
|
const urlValue = payloadItem[config.urlMatchProperty];
|
|
24
29
|
if (typeof urlValue !== 'string') {
|
|
25
30
|
return false;
|
|
@@ -41,8 +46,10 @@ function findEntityInPayload(entityType, entityId, payload, entityConfig) {
|
|
|
41
46
|
if (!typeConfig) {
|
|
42
47
|
return null;
|
|
43
48
|
}
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
|
|
50
|
+
// Resolution keys off `entityId` (not the type): find the payload item whose
|
|
51
|
+
// id/url matches, using the type's configured url strategy.
|
|
52
|
+
const matchedItem = payload.find(item => matchesUrl(item, entityId, typeConfig));
|
|
46
53
|
return matchedItem || null;
|
|
47
54
|
}
|
|
48
55
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["Web5UrlType","isWeb5EntityUrl","parseWeb5Url","extractEntityIdFromUrl","entityUrl","trimmedUrl","trim","parsed","type","ENTITY","entityType","entityId","matchesUrl","payloadItem","config","urlValue","urlMatchProperty","pattern","urlMatchPattern","endsWith","includes","findEntityInPayload","payload","entityConfig","typeConfig","entityTypes","
|
|
1
|
+
{"version":3,"names":["Web5UrlType","isWeb5EntityUrl","parseWeb5Url","extractEntityIdFromUrl","entityUrl","trimmedUrl","trim","parsed","type","ENTITY","entityType","entityId","matchesUrl","payloadItem","config","id","urlValue","urlMatchProperty","pattern","urlMatchPattern","endsWith","includes","findEntityInPayload","payload","entityConfig","typeConfig","entityTypes","matchedItem","find","item","transformToSolutionEntityData","transformToGenericEntityData","formatEntityDate","timestamp","_parts$find","_parts$find2","_parts$find3","date","Date","isNaN","getTime","undefined","formatter","Intl","DateTimeFormat","day","month","timeZone","year","parts","formatToParts","part","value","transformToBlogPostEntityData","_payloadItem$labels","_payloadItem$tags","imageUrl","publishTimestamp","publishDate","published_at","img","imgAlreadyAbsolute","startsWith","imageBaseUrl","url","title","description","excerpt","update_ts","author","authorName","cmsItemId","cms_item_id","usage","tags","labels","length","defaultExtractor"],"sources":["../../../src/entity/defaultExtractor.ts"],"sourcesContent":["import type { SolutionEntityData, BlogEntityData, EntityItemData } from '../component/types';\nimport type {\n ApiPayloadItem,\n EntityConfig,\n EntityTypeConfig,\n} from '../types/entity';\nimport type { EntityExtractor } from './types';\nimport { Web5UrlType, isWeb5EntityUrl } from '../types/link-types';\nimport { parseWeb5Url } from '../utils/entityLinkParser';\n\n/**\n * Extract entity type and ID from a web5://entity/ URL.\n * Delegates to the central `parseWeb5Url` parser.\n */\nfunction extractEntityIdFromUrl(\n entityUrl: string,\n): { entityType: string; entityId: string } | null {\n const trimmedUrl = entityUrl?.trim();\n if (!trimmedUrl || !isWeb5EntityUrl(trimmedUrl)) {\n return null;\n }\n const parsed = parseWeb5Url(trimmedUrl);\n if (parsed?.type !== Web5UrlType.ENTITY || !parsed.entityType || !parsed.entityId) {\n return null;\n }\n return { entityType: parsed.entityType, entityId: parsed.entityId };\n}\n\nfunction matchesUrl(\n payloadItem: ApiPayloadItem,\n entityId: string,\n config: EntityTypeConfig,\n): boolean {\n // Entities resolve by `entityId` (the `<id>` in `web5://entity/<type>/<id>`).\n // Accept a direct `id` match, then fall back to the configured url strategy.\n if (payloadItem.id === entityId) {\n return true;\n }\n\n const urlValue = payloadItem[config.urlMatchProperty as keyof ApiPayloadItem];\n\n if (typeof urlValue !== 'string') {\n return false;\n }\n\n const pattern = config.urlMatchPattern || 'endsWith';\n\n switch (pattern) {\n case 'endsWith':\n return urlValue.endsWith(`/${entityId}`);\n case 'contains':\n return urlValue.includes(entityId);\n case 'equals':\n return urlValue === entityId;\n default:\n return urlValue.endsWith(`/${entityId}`);\n }\n}\n\nfunction findEntityInPayload(\n entityType: string,\n entityId: string,\n payload: ApiPayloadItem[],\n entityConfig: EntityConfig,\n): ApiPayloadItem | null {\n const typeConfig = entityConfig.entityTypes[entityType];\n\n if (!typeConfig) {\n return null;\n }\n\n // Resolution keys off `entityId` (not the type): find the payload item whose\n // id/url matches, using the type's configured url strategy.\n const matchedItem = payload.find((item) =>\n matchesUrl(item, entityId, typeConfig),\n );\n\n return matchedItem || null;\n}\n\n/** @deprecated Use transformToGenericEntityData */\nexport function transformToSolutionEntityData(\n payloadItem: ApiPayloadItem,\n config: EntityTypeConfig,\n): SolutionEntityData {\n return transformToGenericEntityData(payloadItem, config);\n}\n\nfunction formatEntityDate(timestamp: string): string | undefined {\n const date = new Date(timestamp);\n if (isNaN(date.getTime())) {\n return undefined;\n }\n const formatter = new Intl.DateTimeFormat('en-US', {\n day: 'numeric',\n month: 'short',\n timeZone: 'Europe/Kiev',\n year: 'numeric',\n });\n const parts = formatter.formatToParts(date);\n const day = parts.find((part) => part.type === 'day')?.value;\n const month = parts.find((part) => part.type === 'month')?.value;\n const year = parts.find((part) => part.type === 'year')?.value;\n\n if (!day || !month || !year) {\n return undefined;\n }\n\n return `${day} ${month} ${year}`;\n}\n\n/** @deprecated Use transformToGenericEntityData */\nexport function transformToBlogPostEntityData(\n payloadItem: ApiPayloadItem,\n config: EntityTypeConfig,\n): BlogEntityData {\n return transformToGenericEntityData(payloadItem, config);\n}\n\nexport function transformToGenericEntityData(\n payloadItem: ApiPayloadItem,\n config: EntityTypeConfig,\n): EntityItemData {\n let imageUrl: string | undefined;\n const publishTimestamp = payloadItem.publishDate || payloadItem.published_at;\n\n if (payloadItem.img) {\n const imgAlreadyAbsolute = payloadItem.img.startsWith('http');\n imageUrl = config.imageBaseUrl && !imgAlreadyAbsolute\n ? `${config.imageBaseUrl}${payloadItem.img}`\n : payloadItem.img;\n }\n\n return {\n url: payloadItem.url,\n title: payloadItem.title,\n type: payloadItem.type,\n description: payloadItem.description,\n excerpt: payloadItem.excerpt,\n imageUrl,\n date: payloadItem.update_ts\n ? formatEntityDate(payloadItem.update_ts)\n : undefined,\n publishDate: publishTimestamp\n ? formatEntityDate(publishTimestamp)\n : undefined,\n author: payloadItem.author || payloadItem.authorName,\n cmsItemId: payloadItem.cms_item_id,\n usage: payloadItem.usage,\n tags: payloadItem.labels?.length\n ? payloadItem.labels\n : payloadItem.tags?.length\n ? payloadItem.tags\n : undefined,\n };\n}\n\n/**\n * Default entity extractor - config-driven extraction for any client.\n * Uses entityConfig from client JSON to resolve web5://entity/ URLs against API payload.\n */\nexport const defaultExtractor: EntityExtractor = {\n extractEntityIdFromUrl,\n findEntityInPayload,\n};\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { listEntityItems } from './listEntityItems.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
3
|
-
* by id. Pure async helper — no React, no host environment. Shared
|
|
4
|
-
* the main app's `useEntityListData` hook (web50-server-ui) and the
|
|
4
|
+
* Resolves entities via the `list-items` RPC (POST `{ ids }`) and returns a
|
|
5
|
+
* Map keyed by id. Pure async helper — no React, no host environment. Shared
|
|
6
|
+
* between the main app's `useEntityListData` hook (web50-server-ui) and the
|
|
5
7
|
* placement bundle's `useResolveGenericEntityData` impl (DL #099). Both
|
|
6
8
|
* pass an injected `fetchImpl` so the call rides on whichever auth
|
|
7
9
|
* strategy the surface uses (`@wix/sdk` Wix-auth in the placement,
|
|
@@ -12,13 +14,6 @@
|
|
|
12
14
|
* again inside a placement (or vice-versa) reuses the same lookup.
|
|
13
15
|
*/
|
|
14
16
|
const entityCache = new Map();
|
|
15
|
-
const DEFAULT_ENDPOINT = 'https://www.wixapis.com/wix-assistant-web5/v1/items';
|
|
16
|
-
function buildUrl(endpoint, ids) {
|
|
17
|
-
const url = new URL(endpoint);
|
|
18
|
-
ids.forEach(id => url.searchParams.append('id', id));
|
|
19
|
-
return url;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
17
|
/**
|
|
23
18
|
* Returns a Map of `id → ApiPayloadItem`. Hits the cache first; only
|
|
24
19
|
* uncached ids ride the network. The Map includes both cached and freshly
|
|
@@ -45,26 +40,19 @@ export async function fetchEntityListData(ids, opts) {
|
|
|
45
40
|
}
|
|
46
41
|
if (uncachedIds.length === 0) return result;
|
|
47
42
|
const fetchImpl = opts.fetchImpl ?? (typeof window !== 'undefined' ? fetch.bind(window) : fetch);
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
|
|
44
|
+
// Entities resolve via the new-RAG `list-items` RPC (POST `{ ids }`).
|
|
45
|
+
// See listEntityItems.ts.
|
|
50
46
|
try {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
headers: {
|
|
54
|
-
'Content-Type': 'application/json'
|
|
55
|
-
}
|
|
47
|
+
const items = await listEntityItems(uncachedIds, {
|
|
48
|
+
fetchImpl
|
|
56
49
|
});
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const id = item.id;
|
|
61
|
-
if (id) {
|
|
62
|
-
entityCache.set(id, item);
|
|
63
|
-
result.set(id, item);
|
|
64
|
-
}
|
|
50
|
+
for (const [id, item] of items) {
|
|
51
|
+
entityCache.set(id, item);
|
|
52
|
+
result.set(id, item);
|
|
65
53
|
}
|
|
66
54
|
} catch {
|
|
67
|
-
// network / parse error — return whatever we have from cache
|
|
55
|
+
// network / HTTP / parse error — return whatever we have from cache
|
|
68
56
|
}
|
|
69
57
|
return result;
|
|
70
58
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["
|
|
1
|
+
{"version":3,"names":["listEntityItems","entityCache","Map","fetchEntityListData","ids","opts","result","length","uncachedIds","id","cached","get","set","push","fetchImpl","window","fetch","bind","items","item","__resetEntityListDataCache","clear"],"sources":["../../../src/entity/fetchEntityListData.ts"],"sourcesContent":["import type { ApiPayloadItem } from '../types/entity';\nimport { listEntityItems } from './listEntityItems';\n\n/**\n * Resolves entities via the `list-items` RPC (POST `{ ids }`) and returns a\n * Map keyed by id. Pure async helper — no React, no host environment. Shared\n * between the main app's `useEntityListData` hook (web50-server-ui) and the\n * placement bundle's `useResolveGenericEntityData` impl (DL #099). Both\n * pass an injected `fetchImpl` so the call rides on whichever auth\n * strategy the surface uses (`@wix/sdk` Wix-auth in the placement,\n * the app's `client.fetchWithAuth` in the main app).\n *\n * The same singleton cache used to live inside `useEntityListData`. Hoisting\n * it here means a section that renders first in the main app and then\n * again inside a placement (or vice-versa) reuses the same lookup.\n */\nconst entityCache = new Map<string, ApiPayloadItem>();\n\nexport interface FetchEntityListDataOptions {\n /** Auth-aware fetch. Required when the host needs Wix OAuth (any cross-origin call). */\n fetchImpl?: typeof fetch;\n}\n\n/**\n * Returns a Map of `id → ApiPayloadItem`. Hits the cache first; only\n * uncached ids ride the network. The Map includes both cached and freshly\n * fetched rows.\n *\n * On HTTP error or network failure, the returned Map omits the requested\n * id; callers decide whether to fire a diagnostic. The promise resolves\n * either way — the caller's UI should keep rendering with whatever it has.\n */\nexport async function fetchEntityListData(\n ids: string[],\n opts: FetchEntityListDataOptions = {},\n): Promise<Map<string, ApiPayloadItem>> {\n const result = new Map<string, ApiPayloadItem>();\n if (!ids.length) return result;\n\n const uncachedIds: string[] = [];\n for (const id of ids) {\n const cached = entityCache.get(id);\n if (cached) {\n result.set(id, cached);\n } else {\n uncachedIds.push(id);\n }\n }\n if (uncachedIds.length === 0) return result;\n\n const fetchImpl =\n opts.fetchImpl ??\n (typeof window !== 'undefined' ? fetch.bind(window) : fetch);\n\n // Entities resolve via the new-RAG `list-items` RPC (POST `{ ids }`).\n // See listEntityItems.ts.\n try {\n const items = await listEntityItems(uncachedIds, { fetchImpl });\n for (const [id, item] of items) {\n entityCache.set(id, item);\n result.set(id, item);\n }\n } catch {\n // network / HTTP / parse error — return whatever we have from cache\n }\n\n return result;\n}\n\n/** Test helper — drops the singleton cache so specs start clean. */\nexport function __resetEntityListDataCache(): void {\n entityCache.clear();\n}\n"],"mappings":"AACA,SAASA,eAAe,QAAQ,mBAAmB;;AAEnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAMC,WAAW,GAAG,IAAIC,GAAG,CAAyB,CAAC;AAOrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,eAAeC,mBAAmBA,CACvCC,GAAa,EACbC,IAAgC,EACM;EAAA,IADtCA,IAAgC;IAAhCA,IAAgC,GAAG,CAAC,CAAC;EAAA;EAErC,MAAMC,MAAM,GAAG,IAAIJ,GAAG,CAAyB,CAAC;EAChD,IAAI,CAACE,GAAG,CAACG,MAAM,EAAE,OAAOD,MAAM;EAE9B,MAAME,WAAqB,GAAG,EAAE;EAChC,KAAK,MAAMC,EAAE,IAAIL,GAAG,EAAE;IACpB,MAAMM,MAAM,GAAGT,WAAW,CAACU,GAAG,CAACF,EAAE,CAAC;IAClC,IAAIC,MAAM,EAAE;MACVJ,MAAM,CAACM,GAAG,CAACH,EAAE,EAAEC,MAAM,CAAC;IACxB,CAAC,MAAM;MACLF,WAAW,CAACK,IAAI,CAACJ,EAAE,CAAC;IACtB;EACF;EACA,IAAID,WAAW,CAACD,MAAM,KAAK,CAAC,EAAE,OAAOD,MAAM;EAE3C,MAAMQ,SAAS,GACbT,IAAI,CAACS,SAAS,KACb,OAAOC,MAAM,KAAK,WAAW,GAAGC,KAAK,CAACC,IAAI,CAACF,MAAM,CAAC,GAAGC,KAAK,CAAC;;EAE9D;EACA;EACA,IAAI;IACF,MAAME,KAAK,GAAG,MAAMlB,eAAe,CAACQ,WAAW,EAAE;MAAEM;IAAU,CAAC,CAAC;IAC/D,KAAK,MAAM,CAACL,EAAE,EAAEU,IAAI,CAAC,IAAID,KAAK,EAAE;MAC9BjB,WAAW,CAACW,GAAG,CAACH,EAAE,EAAEU,IAAI,CAAC;MACzBb,MAAM,CAACM,GAAG,CAACH,EAAE,EAAEU,IAAI,CAAC;IACtB;EACF,CAAC,CAAC,MAAM;IACN;EAAA;EAGF,OAAOb,MAAM;AACf;;AAEA;AACA,OAAO,SAASc,0BAA0BA,CAAA,EAAS;EACjDnB,WAAW,CAACoB,KAAK,CAAC,CAAC;AACrB","ignoreList":[]}
|
package/dist/esm/entity/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { defaultExtractor } from './defaultExtractor.js';
|
|
|
2
2
|
export { defaultExtractor, transformToSolutionEntityData, transformToBlogPostEntityData, transformToGenericEntityData } from './defaultExtractor.js';
|
|
3
3
|
export { enrichEntitiesFromPayload } from './enrichEntitiesFromPayload.js';
|
|
4
4
|
export { fetchEntityListData } from './fetchEntityListData.js';
|
|
5
|
+
export { listEntityItems, LIST_ITEMS_ENDPOINT } from './listEntityItems.js';
|
|
5
6
|
/**
|
|
6
7
|
* Registry of client-specific entity extractors.
|
|
7
8
|
* Only needed if a client requires custom extraction logic beyond the config-driven default.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["defaultExtractor","transformToSolutionEntityData","transformToBlogPostEntityData","transformToGenericEntityData","enrichEntitiesFromPayload","fetchEntityListData","extractorRegistry","getEntityExtractor","clientId","registerEntityExtractor","extractor"],"sources":["../../../src/entity/index.ts"],"sourcesContent":["import type { EntityExtractor } from './types';\nimport { defaultExtractor } from './defaultExtractor';\n\nexport {\n defaultExtractor,\n transformToSolutionEntityData,\n transformToBlogPostEntityData,\n transformToGenericEntityData,\n} from './defaultExtractor';\n\nexport { enrichEntitiesFromPayload } from './enrichEntitiesFromPayload';\nexport {\n fetchEntityListData,\n type FetchEntityListDataOptions,\n} from './fetchEntityListData';\n\nexport type { EntityExtractor } from './types';\n\n/**\n * Registry of client-specific entity extractors.\n * Only needed if a client requires custom extraction logic beyond the config-driven default.\n */\nconst extractorRegistry: Record<string, EntityExtractor> = {};\n\n/**\n * Get the entity extractor for a specific client.\n * The default extractor is config-driven and works for any client with entityConfig.\n */\nexport function getEntityExtractor(clientId?: string): EntityExtractor {\n if (clientId && extractorRegistry[clientId]) {\n return extractorRegistry[clientId];\n }\n return defaultExtractor;\n}\n\n/**\n * Register a custom entity extractor for a client\n * Useful for runtime registration or testing\n */\nexport function registerEntityExtractor(\n clientId: string,\n extractor: EntityExtractor,\n): void {\n extractorRegistry[clientId] = extractor;\n}\n"],"mappings":"AACA,SAASA,gBAAgB,QAAQ,oBAAoB;AAErD,SACEA,gBAAgB,EAChBC,6BAA6B,EAC7BC,6BAA6B,EAC7BC,4BAA4B,QACvB,oBAAoB;AAE3B,SAASC,yBAAyB,QAAQ,6BAA6B;AACvE,SACEC,mBAAmB,QAEd,uBAAuB;
|
|
1
|
+
{"version":3,"names":["defaultExtractor","transformToSolutionEntityData","transformToBlogPostEntityData","transformToGenericEntityData","enrichEntitiesFromPayload","fetchEntityListData","listEntityItems","LIST_ITEMS_ENDPOINT","extractorRegistry","getEntityExtractor","clientId","registerEntityExtractor","extractor"],"sources":["../../../src/entity/index.ts"],"sourcesContent":["import type { EntityExtractor } from './types';\nimport { defaultExtractor } from './defaultExtractor';\n\nexport {\n defaultExtractor,\n transformToSolutionEntityData,\n transformToBlogPostEntityData,\n transformToGenericEntityData,\n} from './defaultExtractor';\n\nexport { enrichEntitiesFromPayload } from './enrichEntitiesFromPayload';\nexport {\n fetchEntityListData,\n type FetchEntityListDataOptions,\n} from './fetchEntityListData';\nexport {\n listEntityItems,\n LIST_ITEMS_ENDPOINT,\n type ListEntityItemsOptions,\n} from './listEntityItems';\n\nexport type { EntityExtractor } from './types';\n\n/**\n * Registry of client-specific entity extractors.\n * Only needed if a client requires custom extraction logic beyond the config-driven default.\n */\nconst extractorRegistry: Record<string, EntityExtractor> = {};\n\n/**\n * Get the entity extractor for a specific client.\n * The default extractor is config-driven and works for any client with entityConfig.\n */\nexport function getEntityExtractor(clientId?: string): EntityExtractor {\n if (clientId && extractorRegistry[clientId]) {\n return extractorRegistry[clientId];\n }\n return defaultExtractor;\n}\n\n/**\n * Register a custom entity extractor for a client\n * Useful for runtime registration or testing\n */\nexport function registerEntityExtractor(\n clientId: string,\n extractor: EntityExtractor,\n): void {\n extractorRegistry[clientId] = extractor;\n}\n"],"mappings":"AACA,SAASA,gBAAgB,QAAQ,oBAAoB;AAErD,SACEA,gBAAgB,EAChBC,6BAA6B,EAC7BC,6BAA6B,EAC7BC,4BAA4B,QACvB,oBAAoB;AAE3B,SAASC,yBAAyB,QAAQ,6BAA6B;AACvE,SACEC,mBAAmB,QAEd,uBAAuB;AAC9B,SACEC,eAAe,EACfC,mBAAmB,QAEd,mBAAmB;AAI1B;AACA;AACA;AACA;AACA,MAAMC,iBAAkD,GAAG,CAAC,CAAC;;AAE7D;AACA;AACA;AACA;AACA,OAAO,SAASC,kBAAkBA,CAACC,QAAiB,EAAmB;EACrE,IAAIA,QAAQ,IAAIF,iBAAiB,CAACE,QAAQ,CAAC,EAAE;IAC3C,OAAOF,iBAAiB,CAACE,QAAQ,CAAC;EACpC;EACA,OAAOV,gBAAgB;AACzB;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASW,uBAAuBA,CACrCD,QAAgB,EAChBE,SAA0B,EACpB;EACNJ,iBAAiB,CAACE,QAAQ,CAAC,GAAGE,SAAS;AACzC","ignoreList":[]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity resolution via the `list-items` RPC on the ConversationConfiguration
|
|
3
|
+
* service.
|
|
4
|
+
*
|
|
5
|
+
* Entities are resolved through `list-items` (POST `{ ids }`). The response is
|
|
6
|
+
* `{ items: [...] }` where each row is keyed by `doc_id`. We normalize to
|
|
7
|
+
* `ApiPayloadItem` keyed by that id (and alias it onto `id`) so both callers
|
|
8
|
+
* get back the same `Map<id, ApiPayloadItem>` contract.
|
|
9
|
+
*
|
|
10
|
+
* TODO(ambassador): the `@wix/ambassador-enterprise-web-five-v1-configuration`
|
|
11
|
+
* `listItems` builder does NOT yet emit the public URL below — as of 1.0.27 it
|
|
12
|
+
* resolves to the internal `/v1/conversation-configuration/list-items` path,
|
|
13
|
+
* which 405s at the www edge (no public mapping). The gateway mapping was added
|
|
14
|
+
* out-of-band (`/web5/conversation-configuration` → service root), giving the
|
|
15
|
+
* URL below. Once an ambassador version ships with that mapping, replace the
|
|
16
|
+
* hand-built request with the builder + `client.fetchWithAuth`, mirroring
|
|
17
|
+
* `web50-server-ui/src/services/backendConfigService.ts`:
|
|
18
|
+
* const options = listItems({ ids })({ isSSR: false, host: 'www.wixapis.com' });
|
|
19
|
+
* const url = new URL(options.url!, 'https://www.wixapis.com');
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Public URL for the `list-items` RPC on `www.wixapis.com`. The `/web5/conversation-configuration`
|
|
24
|
+
* gateway prefix is prepended to the proto path, hence the repeated segment.
|
|
25
|
+
*/
|
|
26
|
+
export const LIST_ITEMS_ENDPOINT = 'https://www.wixapis.com/web5/conversation-configuration/v1/conversation-configuration/list-items';
|
|
27
|
+
|
|
28
|
+
/** Minimal fetch shape accepted by both the placement `fetchImpl` and the main app's `client.fetchWithAuth`. */
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* POSTs `{ ids }` to the `list-items` endpoint and returns a Map of
|
|
32
|
+
* `doc_id → ApiPayloadItem`. Throws on network/HTTP error so callers keep their
|
|
33
|
+
* own diagnostics; on success returns only the rows the BE resolved.
|
|
34
|
+
*/
|
|
35
|
+
export async function listEntityItems(ids, opts) {
|
|
36
|
+
const result = new Map();
|
|
37
|
+
if (!ids.length) return result;
|
|
38
|
+
const res = await opts.fetchImpl(LIST_ITEMS_ENDPOINT, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/json'
|
|
42
|
+
},
|
|
43
|
+
body: JSON.stringify({
|
|
44
|
+
ids
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
throw new Error(`list-items ${res.status} ${res.statusText}`);
|
|
49
|
+
}
|
|
50
|
+
const body = await res.json();
|
|
51
|
+
const items = body.items ?? [];
|
|
52
|
+
for (const item of items) {
|
|
53
|
+
const rec = item;
|
|
54
|
+
const id = rec.doc_id;
|
|
55
|
+
if (!id) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const normalized = {
|
|
59
|
+
...rec,
|
|
60
|
+
id
|
|
61
|
+
};
|
|
62
|
+
// Product docs carry the Shopify slug in `handle`; the raw `url` is the
|
|
63
|
+
// absolute canonical storefront URL (https://www.feature.com/products/…).
|
|
64
|
+
// Rebuild a host-relative `/products/<handle>` so the link stays same-origin
|
|
65
|
+
// on whatever host serves the page. `handle` is authoritative here
|
|
66
|
+
// (no origin/`www.`/path-parsing, unlike deriving it from `url`).
|
|
67
|
+
if (rec.doc_domain === 'product' && typeof rec.handle === 'string' && rec.handle) {
|
|
68
|
+
normalized.url = `/products/${rec.handle}`;
|
|
69
|
+
}
|
|
70
|
+
// `list-items` carries the image directly (`thumbnail` / `images[]`), but
|
|
71
|
+
// the shared transform (`transformToGenericEntityData`) reads `img`. Alias
|
|
72
|
+
// the image onto `img` so images resolve from the payload itself — no
|
|
73
|
+
// dependence on the SearchSpring catalog, which may not index every product.
|
|
74
|
+
if (normalized.img == null) {
|
|
75
|
+
if (typeof rec.thumbnail === 'string' && rec.thumbnail) {
|
|
76
|
+
normalized.img = rec.thumbnail;
|
|
77
|
+
} else if (Array.isArray(rec.images) && typeof rec.images[0] === 'string' && rec.images[0]) {
|
|
78
|
+
normalized.img = rec.images[0];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
result.set(id, normalized);
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=listEntityItems.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["LIST_ITEMS_ENDPOINT","listEntityItems","ids","opts","result","Map","length","res","fetchImpl","method","headers","body","JSON","stringify","ok","Error","status","statusText","json","items","item","rec","id","doc_id","normalized","doc_domain","handle","url","img","thumbnail","Array","isArray","images","set"],"sources":["../../../src/entity/listEntityItems.ts"],"sourcesContent":["import type { ApiPayloadItem } from '../types/entity';\n\n/**\n * Entity resolution via the `list-items` RPC on the ConversationConfiguration\n * service.\n *\n * Entities are resolved through `list-items` (POST `{ ids }`). The response is\n * `{ items: [...] }` where each row is keyed by `doc_id`. We normalize to\n * `ApiPayloadItem` keyed by that id (and alias it onto `id`) so both callers\n * get back the same `Map<id, ApiPayloadItem>` contract.\n *\n * TODO(ambassador): the `@wix/ambassador-enterprise-web-five-v1-configuration`\n * `listItems` builder does NOT yet emit the public URL below — as of 1.0.27 it\n * resolves to the internal `/v1/conversation-configuration/list-items` path,\n * which 405s at the www edge (no public mapping). The gateway mapping was added\n * out-of-band (`/web5/conversation-configuration` → service root), giving the\n * URL below. Once an ambassador version ships with that mapping, replace the\n * hand-built request with the builder + `client.fetchWithAuth`, mirroring\n * `web50-server-ui/src/services/backendConfigService.ts`:\n * const options = listItems({ ids })({ isSSR: false, host: 'www.wixapis.com' });\n * const url = new URL(options.url!, 'https://www.wixapis.com');\n */\n\n/**\n * Public URL for the `list-items` RPC on `www.wixapis.com`. The `/web5/conversation-configuration`\n * gateway prefix is prepended to the proto path, hence the repeated segment.\n */\nexport const LIST_ITEMS_ENDPOINT =\n 'https://www.wixapis.com/web5/conversation-configuration/v1/conversation-configuration/list-items';\n\n/** Minimal fetch shape accepted by both the placement `fetchImpl` and the main app's `client.fetchWithAuth`. */\ntype FetchLike = (\n input: string | URL,\n init?: RequestInit,\n) => Promise<Response>;\n\ninterface ListItemsResponse {\n items?: Record<string, unknown>[] | null;\n}\n\nexport interface ListEntityItemsOptions {\n /** Auth-aware fetch (`client.fetchWithAuth` in the main app, the placement's Wix-auth fetch). */\n fetchImpl: FetchLike;\n}\n\n/**\n * POSTs `{ ids }` to the `list-items` endpoint and returns a Map of\n * `doc_id → ApiPayloadItem`. Throws on network/HTTP error so callers keep their\n * own diagnostics; on success returns only the rows the BE resolved.\n */\nexport async function listEntityItems(\n ids: string[],\n opts: ListEntityItemsOptions,\n): Promise<Map<string, ApiPayloadItem>> {\n const result = new Map<string, ApiPayloadItem>();\n if (!ids.length) return result;\n\n const res = await opts.fetchImpl(LIST_ITEMS_ENDPOINT, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ ids }),\n });\n\n if (!res.ok) {\n throw new Error(`list-items ${res.status} ${res.statusText}`);\n }\n\n const body = (await res.json()) as ListItemsResponse;\n const items = body.items ?? [];\n for (const item of items) {\n const rec = item as Record<string, unknown>;\n const id = rec.doc_id as string | undefined;\n if (!id) {\n continue;\n }\n const normalized: Record<string, unknown> = { ...rec, id };\n // Product docs carry the Shopify slug in `handle`; the raw `url` is the\n // absolute canonical storefront URL (https://www.feature.com/products/…).\n // Rebuild a host-relative `/products/<handle>` so the link stays same-origin\n // on whatever host serves the page. `handle` is authoritative here\n // (no origin/`www.`/path-parsing, unlike deriving it from `url`).\n if (\n rec.doc_domain === 'product' &&\n typeof rec.handle === 'string' &&\n rec.handle\n ) {\n normalized.url = `/products/${rec.handle}`;\n }\n // `list-items` carries the image directly (`thumbnail` / `images[]`), but\n // the shared transform (`transformToGenericEntityData`) reads `img`. Alias\n // the image onto `img` so images resolve from the payload itself — no\n // dependence on the SearchSpring catalog, which may not index every product.\n if (normalized.img == null) {\n if (typeof rec.thumbnail === 'string' && rec.thumbnail) {\n normalized.img = rec.thumbnail;\n } else if (\n Array.isArray(rec.images) &&\n typeof rec.images[0] === 'string' &&\n rec.images[0]\n ) {\n normalized.img = rec.images[0];\n }\n }\n result.set(id, normalized as unknown as ApiPayloadItem);\n }\n return result;\n}\n"],"mappings":"AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,OAAO,MAAMA,mBAAmB,GAC9B,kGAAkG;;AAEpG;;AAeA;AACA;AACA;AACA;AACA;AACA,OAAO,eAAeC,eAAeA,CACnCC,GAAa,EACbC,IAA4B,EACU;EACtC,MAAMC,MAAM,GAAG,IAAIC,GAAG,CAAyB,CAAC;EAChD,IAAI,CAACH,GAAG,CAACI,MAAM,EAAE,OAAOF,MAAM;EAE9B,MAAMG,GAAG,GAAG,MAAMJ,IAAI,CAACK,SAAS,CAACR,mBAAmB,EAAE;IACpDS,MAAM,EAAE,MAAM;IACdC,OAAO,EAAE;MAAE,cAAc,EAAE;IAAmB,CAAC;IAC/CC,IAAI,EAAEC,IAAI,CAACC,SAAS,CAAC;MAAEX;IAAI,CAAC;EAC9B,CAAC,CAAC;EAEF,IAAI,CAACK,GAAG,CAACO,EAAE,EAAE;IACX,MAAM,IAAIC,KAAK,CAAC,cAAcR,GAAG,CAACS,MAAM,IAAIT,GAAG,CAACU,UAAU,EAAE,CAAC;EAC/D;EAEA,MAAMN,IAAI,GAAI,MAAMJ,GAAG,CAACW,IAAI,CAAC,CAAuB;EACpD,MAAMC,KAAK,GAAGR,IAAI,CAACQ,KAAK,IAAI,EAAE;EAC9B,KAAK,MAAMC,IAAI,IAAID,KAAK,EAAE;IACxB,MAAME,GAAG,GAAGD,IAA+B;IAC3C,MAAME,EAAE,GAAGD,GAAG,CAACE,MAA4B;IAC3C,IAAI,CAACD,EAAE,EAAE;MACP;IACF;IACA,MAAME,UAAmC,GAAG;MAAE,GAAGH,GAAG;MAAEC;IAAG,CAAC;IAC1D;IACA;IACA;IACA;IACA;IACA,IACED,GAAG,CAACI,UAAU,KAAK,SAAS,IAC5B,OAAOJ,GAAG,CAACK,MAAM,KAAK,QAAQ,IAC9BL,GAAG,CAACK,MAAM,EACV;MACAF,UAAU,CAACG,GAAG,GAAG,aAAaN,GAAG,CAACK,MAAM,EAAE;IAC5C;IACA;IACA;IACA;IACA;IACA,IAAIF,UAAU,CAACI,GAAG,IAAI,IAAI,EAAE;MAC1B,IAAI,OAAOP,GAAG,CAACQ,SAAS,KAAK,QAAQ,IAAIR,GAAG,CAACQ,SAAS,EAAE;QACtDL,UAAU,CAACI,GAAG,GAAGP,GAAG,CAACQ,SAAS;MAChC,CAAC,MAAM,IACLC,KAAK,CAACC,OAAO,CAACV,GAAG,CAACW,MAAM,CAAC,IACzB,OAAOX,GAAG,CAACW,MAAM,CAAC,CAAC,CAAC,KAAK,QAAQ,IACjCX,GAAG,CAACW,MAAM,CAAC,CAAC,CAAC,EACb;QACAR,UAAU,CAACI,GAAG,GAAGP,GAAG,CAACW,MAAM,CAAC,CAAC,CAAC;MAChC;IACF;IACA5B,MAAM,CAAC6B,GAAG,CAACX,EAAE,EAAEE,UAAuC,CAAC;EACzD;EACA,OAAOpB,MAAM;AACf","ignoreList":[]}
|