@commerce-ai-tool/angular 2.4.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/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +16 -0
- package/dist/index.cjs +2811 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +1015 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2780 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/camera.util.d.ts +7 -0
- package/dist/lib/camera.util.d.ts.map +1 -0
- package/dist/lib/commerce-ai-api.service.d.ts +35 -0
- package/dist/lib/commerce-ai-api.service.d.ts.map +1 -0
- package/dist/lib/commerce-ai-cart-panel.component.d.ts +34 -0
- package/dist/lib/commerce-ai-cart-panel.component.d.ts.map +1 -0
- package/dist/lib/commerce-ai-cart.service.d.ts +61 -0
- package/dist/lib/commerce-ai-cart.service.d.ts.map +1 -0
- package/dist/lib/commerce-ai-mission-results.component.d.ts +26 -0
- package/dist/lib/commerce-ai-mission-results.component.d.ts.map +1 -0
- package/dist/lib/commerce-ai-search.component.d.ts +141 -0
- package/dist/lib/commerce-ai-search.component.d.ts.map +1 -0
- package/dist/lib/mission.util.d.ts +6 -0
- package/dist/lib/mission.util.d.ts.map +1 -0
- package/dist/vendor-core/client.d.cts +12 -0
- package/dist/vendor-core/client.d.ts +12 -0
- package/dist/vendor-core/commercetools/facet-color.d.ts +12 -0
- package/dist/vendor-core/commercetools/facets.d.ts +31 -0
- package/dist/vendor-core/messages/index.d.ts +110 -0
- package/dist/vendor-core/payments/types.d.ts +35 -0
- package/dist/vendor-core/search/compound-shopping-list.d.ts +6 -0
- package/dist/vendor-core/types/index.d.ts +510 -0
- package/package.json +83 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2780 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/lib/commerce-ai-search.component.ts
|
|
5
|
+
import { ChangeDetectorRef, Component as Component3, ElementRef, EventEmitter as EventEmitter3, Input as Input3, Output as Output3, ViewChild, ViewEncapsulation, inject } from "@angular/core";
|
|
6
|
+
import { FormsModule as FormsModule2 } from "@angular/forms";
|
|
7
|
+
|
|
8
|
+
// ../core/dist/client.js
|
|
9
|
+
var CART_SESSION_HEADER = "x-commerce-ai-cart-session";
|
|
10
|
+
var DEFAULT_COMMERCE_AI_SEARCH_MESSAGES = {
|
|
11
|
+
placeholder: "What are you looking for?",
|
|
12
|
+
searchAriaLabel: "Search query",
|
|
13
|
+
productSearchAriaLabel: "Product search",
|
|
14
|
+
searchResultsAriaLabel: "Search results",
|
|
15
|
+
searching: "Searching...",
|
|
16
|
+
noProductsFound: "No products found",
|
|
17
|
+
searchedFor: "Searched for:",
|
|
18
|
+
dropImageToSearch: "Drop image to search",
|
|
19
|
+
voiceSearch: "Voice search",
|
|
20
|
+
stopRecording: "Stop recording",
|
|
21
|
+
searchByCamera: "Search by camera",
|
|
22
|
+
searchByImage: "Search by image",
|
|
23
|
+
replayVoiceSummary: "Replay voice result summary",
|
|
24
|
+
listening: "Listening\u2026",
|
|
25
|
+
tapMicToStop: "Tap mic to stop",
|
|
26
|
+
understandingQuery: "Understanding your query\u2026",
|
|
27
|
+
preparingAudioSummary: "Preparing audio summary\u2026",
|
|
28
|
+
cameraCapture: "Camera capture",
|
|
29
|
+
cameraPreview: "Camera preview",
|
|
30
|
+
capturePhoto: "Capture",
|
|
31
|
+
cancel: "Cancel",
|
|
32
|
+
dismiss: "Dismiss",
|
|
33
|
+
searchFailed: "Search failed",
|
|
34
|
+
imageSearchFailed: "Image search failed",
|
|
35
|
+
couldNotCapturePhoto: "Could not capture photo",
|
|
36
|
+
suggestionsAriaLabel: "Search suggestions",
|
|
37
|
+
loadingSuggestions: "Loading suggestions...",
|
|
38
|
+
noSuggestions: "No suggestions",
|
|
39
|
+
filtersAriaLabel: "Search filters",
|
|
40
|
+
clearFilters: "Clear filters",
|
|
41
|
+
narrowResults: "Narrow your results",
|
|
42
|
+
newSearch: "New search",
|
|
43
|
+
addToCart: "Add to cart",
|
|
44
|
+
cart: "Cart",
|
|
45
|
+
emptyCart: "Your cart is empty",
|
|
46
|
+
removeItem: "Remove item",
|
|
47
|
+
total: "Total",
|
|
48
|
+
each: "each",
|
|
49
|
+
cartAriaLabel: "Shopping cart",
|
|
50
|
+
itemAdded: "Added to cart",
|
|
51
|
+
increaseQuantity: "Increase quantity",
|
|
52
|
+
decreaseQuantity: "Decrease quantity",
|
|
53
|
+
closeCart: "Close cart",
|
|
54
|
+
unableToAddToCart: "This product cannot be added to the cart",
|
|
55
|
+
missionTitle: "Shopping list",
|
|
56
|
+
missionQuantity: "Looking for",
|
|
57
|
+
missionAddAll: "Add {count} top picks to cart",
|
|
58
|
+
missionItemsAdded: "Added to cart",
|
|
59
|
+
missionSelectProduct: "Select this product",
|
|
60
|
+
missionIntentEmpty: "No matching products",
|
|
61
|
+
missionIntentFailed: "Couldn't search this item",
|
|
62
|
+
signIn: "Sign in",
|
|
63
|
+
signInToSyncCart: "Sign in to sync your cart",
|
|
64
|
+
signOut: "Sign out",
|
|
65
|
+
email: "Email",
|
|
66
|
+
password: "Password",
|
|
67
|
+
signedInAs: "Signed in as",
|
|
68
|
+
invalidCredentials: "Invalid email or password",
|
|
69
|
+
signInFailed: "Sign in failed",
|
|
70
|
+
checkout: "Checkout",
|
|
71
|
+
checkoutTitle: "Complete your order",
|
|
72
|
+
orderSummary: "Order summary",
|
|
73
|
+
shippingAddress: "Shipping address",
|
|
74
|
+
billingAddress: "Billing address",
|
|
75
|
+
billingSameAsShipping: "Billing address is the same as shipping",
|
|
76
|
+
continueToDelivery: "Continue to delivery",
|
|
77
|
+
shippingMethod: "Delivery method",
|
|
78
|
+
selectShippingMethod: "Select a delivery method",
|
|
79
|
+
noShippingMethods: "No delivery methods are available for this address",
|
|
80
|
+
completeAddressToContinue: "Add a shipping address to continue",
|
|
81
|
+
selectDeliveryToContinue: "Select a delivery method to continue",
|
|
82
|
+
selectPaymentToContinue: "Select a payment method to continue",
|
|
83
|
+
checkoutStepAddress: "Address",
|
|
84
|
+
checkoutStepDelivery: "Delivery",
|
|
85
|
+
checkoutStepPayment: "Payment",
|
|
86
|
+
paymentMethod: "Payment method",
|
|
87
|
+
selectPaymentMethod: "Select a payment method",
|
|
88
|
+
paymentAuthorized: "Payment authorized",
|
|
89
|
+
paymentDeclined: "Payment was declined",
|
|
90
|
+
continueToPayment: "Continue to payment",
|
|
91
|
+
placeOrder: "Place order",
|
|
92
|
+
placingOrder: "Placing order\u2026",
|
|
93
|
+
orderPlaced: "Order placed",
|
|
94
|
+
viewOrderStatus: "View order status",
|
|
95
|
+
continueShopping: "Continue shopping",
|
|
96
|
+
checkoutFailed: "Checkout failed",
|
|
97
|
+
orderStatus: "Order status",
|
|
98
|
+
orderPlacedAt: "Placed",
|
|
99
|
+
paymentStateLabel: "Payment",
|
|
100
|
+
shipmentStateLabel: "Shipment",
|
|
101
|
+
trackingNumber: "Tracking number",
|
|
102
|
+
carrier: "Carrier",
|
|
103
|
+
noTrackingYet: "Tracking details will appear when the order ships",
|
|
104
|
+
orderNotFound: "Order not found",
|
|
105
|
+
noOrdersYet: "No orders yet",
|
|
106
|
+
yourOrders: "Your orders",
|
|
107
|
+
firstName: "First name",
|
|
108
|
+
lastName: "Last name",
|
|
109
|
+
streetName: "Street address",
|
|
110
|
+
additionalAddress: "Apartment, suite, etc. (optional)",
|
|
111
|
+
postalCode: "Postal code",
|
|
112
|
+
city: "City",
|
|
113
|
+
region: "State or region (optional)",
|
|
114
|
+
country: "Country"
|
|
115
|
+
};
|
|
116
|
+
function resolveCommerceAISearchMessages(overrides) {
|
|
117
|
+
return {
|
|
118
|
+
...DEFAULT_COMMERCE_AI_SEARCH_MESSAGES,
|
|
119
|
+
...overrides
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
__name(resolveCommerceAISearchMessages, "resolveCommerceAISearchMessages");
|
|
123
|
+
var COMPOUND_SHOPPING_LIST_PATTERN = /\band\b|,|\bplus\b|\boraz\b/i;
|
|
124
|
+
function looksLikeCompoundShoppingList(query) {
|
|
125
|
+
return COMPOUND_SHOPPING_LIST_PATTERN.test(query.trim());
|
|
126
|
+
}
|
|
127
|
+
__name(looksLikeCompoundShoppingList, "looksLikeCompoundShoppingList");
|
|
128
|
+
var CSS_NAMED_COLOR_TABLE = `
|
|
129
|
+
Alice Blue:f0f8ff
|
|
130
|
+
Antique White:faebd7
|
|
131
|
+
Aqua:00ffff
|
|
132
|
+
Aquamarine:7fffd4
|
|
133
|
+
Azure:f0ffff
|
|
134
|
+
Beige:f5f5dc
|
|
135
|
+
Bisque:ffe4c4
|
|
136
|
+
Black:000000
|
|
137
|
+
Blanched Almond:ffebcd
|
|
138
|
+
Blue:0000ff
|
|
139
|
+
Blue Violet:8a2be2
|
|
140
|
+
Brown:a52a2a
|
|
141
|
+
Burlywood:deb887
|
|
142
|
+
Cadet Blue:5f9ea0
|
|
143
|
+
Chartreuse:7fff00
|
|
144
|
+
Chocolate:d2691e
|
|
145
|
+
Coral:ff7f50
|
|
146
|
+
Cornflower Blue:6495ed
|
|
147
|
+
Cornsilk:fff8dc
|
|
148
|
+
Crimson:dc143c
|
|
149
|
+
Dark Blue:00008b
|
|
150
|
+
Dark Cyan:008b8b
|
|
151
|
+
Dark Goldenrod:b8860b
|
|
152
|
+
Dark Gray:a9a9a9
|
|
153
|
+
Dark Green:006400
|
|
154
|
+
Dark Khaki:bdb76b
|
|
155
|
+
Dark Magenta:8b008b
|
|
156
|
+
Dark Olive Green:556b2f
|
|
157
|
+
Dark Orange:ff8c00
|
|
158
|
+
Dark Orchid:9932cc
|
|
159
|
+
Dark Red:8b0000
|
|
160
|
+
Dark Salmon:e9967a
|
|
161
|
+
Dark Sea Green:8fbc8f
|
|
162
|
+
Dark Slate Blue:483d8b
|
|
163
|
+
Dark Slate Gray:2f4f4f
|
|
164
|
+
Dark Turquoise:00ced1
|
|
165
|
+
Dark Violet:9400d3
|
|
166
|
+
Deep Pink:ff1493
|
|
167
|
+
Deep Sky Blue:00bfff
|
|
168
|
+
Dim Gray:696969
|
|
169
|
+
Dodger Blue:1e90ff
|
|
170
|
+
Firebrick:b22222
|
|
171
|
+
Floral White:fffaf0
|
|
172
|
+
Forest Green:228b22
|
|
173
|
+
Gainsboro:dcdcdc
|
|
174
|
+
Ghost White:f8f8ff
|
|
175
|
+
Gold:ffd700
|
|
176
|
+
Goldenrod:daa520
|
|
177
|
+
Gray:808080
|
|
178
|
+
Green:008000
|
|
179
|
+
Green Yellow:adff2f
|
|
180
|
+
Honeydew:f0fff0
|
|
181
|
+
Hot Pink:ff69b4
|
|
182
|
+
Indian Red:cd5c5c
|
|
183
|
+
Indigo:4b0082
|
|
184
|
+
Ivory:fffff0
|
|
185
|
+
Khaki:f0e68c
|
|
186
|
+
Lavender:e6e6fa
|
|
187
|
+
Lavender Blush:fff0f5
|
|
188
|
+
Lawn Green:7cfc00
|
|
189
|
+
Lemon Chiffon:fffacd
|
|
190
|
+
Light Blue:add8e6
|
|
191
|
+
Light Coral:f08080
|
|
192
|
+
Light Cyan:e0ffff
|
|
193
|
+
Light Goldenrod Yellow:fafad2
|
|
194
|
+
Light Gray:d3d3d3
|
|
195
|
+
Light Green:90ee90
|
|
196
|
+
Light Pink:ffb6c1
|
|
197
|
+
Light Salmon:ffa07a
|
|
198
|
+
Light Sea Green:20b2aa
|
|
199
|
+
Light Sky Blue:87cefa
|
|
200
|
+
Light Slate Gray:778899
|
|
201
|
+
Light Steel Blue:b0c4de
|
|
202
|
+
Light Yellow:ffffe0
|
|
203
|
+
Lime:00ff00
|
|
204
|
+
Lime Green:32cd32
|
|
205
|
+
Linen:faf0e6
|
|
206
|
+
Magenta:ff00ff
|
|
207
|
+
Maroon:800000
|
|
208
|
+
Medium Aquamarine:66cdaa
|
|
209
|
+
Medium Blue:0000cd
|
|
210
|
+
Medium Orchid:ba55d3
|
|
211
|
+
Medium Purple:9370db
|
|
212
|
+
Medium Sea Green:3cb371
|
|
213
|
+
Medium Slate Blue:7b68ee
|
|
214
|
+
Medium Spring Green:00fa9a
|
|
215
|
+
Medium Turquoise:48d1cc
|
|
216
|
+
Medium Violet Red:c71585
|
|
217
|
+
Midnight Blue:191970
|
|
218
|
+
Mint Cream:f5fffa
|
|
219
|
+
Misty Rose:ffe4e1
|
|
220
|
+
Moccasin:ffe4b5
|
|
221
|
+
Navajo White:ffdead
|
|
222
|
+
Navy:000080
|
|
223
|
+
Old Lace:fdf5e6
|
|
224
|
+
Olive:808000
|
|
225
|
+
Olive Drab:6b8e23
|
|
226
|
+
Orange:ffa500
|
|
227
|
+
Orange Red:ff4500
|
|
228
|
+
Orchid:da70d6
|
|
229
|
+
Pale Goldenrod:eee8aa
|
|
230
|
+
Pale Green:98fb98
|
|
231
|
+
Pale Turquoise:afeeee
|
|
232
|
+
Pale Violet Red:db7093
|
|
233
|
+
Papaya Whip:ffefd5
|
|
234
|
+
Peach Puff:ffdab9
|
|
235
|
+
Peru:cd853f
|
|
236
|
+
Pink:ffc0cb
|
|
237
|
+
Plum:dda0dd
|
|
238
|
+
Powder Blue:b0e0e6
|
|
239
|
+
Purple:800080
|
|
240
|
+
Rebecca Purple:663399
|
|
241
|
+
Red:ff0000
|
|
242
|
+
Rosy Brown:bc8f8f
|
|
243
|
+
Royal Blue:4169e1
|
|
244
|
+
Saddle Brown:8b4513
|
|
245
|
+
Salmon:fa8072
|
|
246
|
+
Sandy Brown:f4a460
|
|
247
|
+
Sea Green:2e8b57
|
|
248
|
+
Seashell:fff5ee
|
|
249
|
+
Sienna:a0522d
|
|
250
|
+
Silver:c0c0c0
|
|
251
|
+
Sky Blue:87ceeb
|
|
252
|
+
Slate Blue:6a5acd
|
|
253
|
+
Slate Gray:708090
|
|
254
|
+
Snow:fffafa
|
|
255
|
+
Spring Green:00ff7f
|
|
256
|
+
Steel Blue:4682b4
|
|
257
|
+
Tan:d2b48c
|
|
258
|
+
Teal:008080
|
|
259
|
+
Thistle:d8bfd8
|
|
260
|
+
Tomato:ff6347
|
|
261
|
+
Turquoise:40e0d0
|
|
262
|
+
Violet:ee82ee
|
|
263
|
+
Wheat:f5deb3
|
|
264
|
+
White:ffffff
|
|
265
|
+
White Smoke:f5f5f5
|
|
266
|
+
Yellow:ffff00
|
|
267
|
+
Yellow Green:9acd32
|
|
268
|
+
`;
|
|
269
|
+
var HEX_COLOR_RE = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
270
|
+
var NAMED_COLORS = CSS_NAMED_COLOR_TABLE.trim().split("\n").flatMap((line) => {
|
|
271
|
+
const separator = line.indexOf(":");
|
|
272
|
+
if (separator <= 0) {
|
|
273
|
+
return [];
|
|
274
|
+
}
|
|
275
|
+
const name = line.slice(0, separator);
|
|
276
|
+
const hex = line.slice(separator + 1);
|
|
277
|
+
const n = Number.parseInt(hex, 16);
|
|
278
|
+
if (!name || !Number.isFinite(n)) {
|
|
279
|
+
return [];
|
|
280
|
+
}
|
|
281
|
+
return [
|
|
282
|
+
{
|
|
283
|
+
name,
|
|
284
|
+
r: n >> 16 & 255,
|
|
285
|
+
g: n >> 8 & 255,
|
|
286
|
+
b: n & 255
|
|
287
|
+
}
|
|
288
|
+
];
|
|
289
|
+
});
|
|
290
|
+
function parseHexRgb(value) {
|
|
291
|
+
const match = HEX_COLOR_RE.exec(value.trim());
|
|
292
|
+
const captured = match?.[1];
|
|
293
|
+
if (!captured) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
const hex = captured.length === 3 ? `${captured[0]}${captured[0]}${captured[1]}${captured[1]}${captured[2]}${captured[2]}` : captured;
|
|
297
|
+
const n = Number.parseInt(hex, 16);
|
|
298
|
+
return [
|
|
299
|
+
n >> 16 & 255,
|
|
300
|
+
n >> 8 & 255,
|
|
301
|
+
n & 255
|
|
302
|
+
];
|
|
303
|
+
}
|
|
304
|
+
__name(parseHexRgb, "parseHexRgb");
|
|
305
|
+
var MAX_NAMED_COLOR_DISTANCE_SQ = 40 * 40;
|
|
306
|
+
var COLOR_LIKE_FACET_NAME = /(?:^|[-_])colou?r(?:[-_]|$)|(?:^|[-_])finish(?:[-_]|$)/i;
|
|
307
|
+
function isColorLikeFacetName(name) {
|
|
308
|
+
return COLOR_LIKE_FACET_NAME.test(name.trim());
|
|
309
|
+
}
|
|
310
|
+
__name(isColorLikeFacetName, "isColorLikeFacetName");
|
|
311
|
+
function hexColorSwatchValue(value) {
|
|
312
|
+
const rgb = parseHexRgb(value);
|
|
313
|
+
if (!rgb) {
|
|
314
|
+
return void 0;
|
|
315
|
+
}
|
|
316
|
+
return `#${rgb.map((channel) => channel.toString(16).padStart(2, "0")).join("")}`;
|
|
317
|
+
}
|
|
318
|
+
__name(hexColorSwatchValue, "hexColorSwatchValue");
|
|
319
|
+
var PRICE_FACET_ID = "price";
|
|
320
|
+
var CATEGORIES_FACET_ID = "categories";
|
|
321
|
+
var DEFAULT_PRICE_RANGES = [
|
|
322
|
+
{
|
|
323
|
+
key: "under-50",
|
|
324
|
+
to: 5e3
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
key: "50-to-100",
|
|
328
|
+
from: 5e3,
|
|
329
|
+
to: 1e4
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
key: "100-plus",
|
|
333
|
+
from: 1e4
|
|
334
|
+
}
|
|
335
|
+
];
|
|
336
|
+
function priceRangeToFilterValues(range) {
|
|
337
|
+
const filters = {};
|
|
338
|
+
if ("from" in range && range.from !== void 0) {
|
|
339
|
+
filters.priceMin = String(range.from / 100);
|
|
340
|
+
}
|
|
341
|
+
if ("to" in range && range.to !== void 0) {
|
|
342
|
+
filters.priceMax = String(range.to / 100);
|
|
343
|
+
}
|
|
344
|
+
return filters;
|
|
345
|
+
}
|
|
346
|
+
__name(priceRangeToFilterValues, "priceRangeToFilterValues");
|
|
347
|
+
function priceBucketKeyFromFilters(filters) {
|
|
348
|
+
const minCents = parseMajorToCents(filters.priceMin);
|
|
349
|
+
const maxCents = parseMajorToCents(filters.priceMax);
|
|
350
|
+
for (const range of DEFAULT_PRICE_RANGES) {
|
|
351
|
+
const from = "from" in range ? range.from : void 0;
|
|
352
|
+
const to = "to" in range ? range.to : void 0;
|
|
353
|
+
if (minCents === from && maxCents === to) {
|
|
354
|
+
return range.key;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return void 0;
|
|
358
|
+
}
|
|
359
|
+
__name(priceBucketKeyFromFilters, "priceBucketKeyFromFilters");
|
|
360
|
+
function isFacetFilterSelected(filters, facetId, bucketKey) {
|
|
361
|
+
if (facetId === CATEGORIES_FACET_ID) {
|
|
362
|
+
return filters.category === bucketKey;
|
|
363
|
+
}
|
|
364
|
+
if (facetId === PRICE_FACET_ID) {
|
|
365
|
+
return priceBucketKeyFromFilters(filters) === bucketKey;
|
|
366
|
+
}
|
|
367
|
+
return filters[facetId] === bucketKey;
|
|
368
|
+
}
|
|
369
|
+
__name(isFacetFilterSelected, "isFacetFilterSelected");
|
|
370
|
+
function toggleFacetFilter(filters, facetId, bucketKey) {
|
|
371
|
+
const next = {
|
|
372
|
+
...filters
|
|
373
|
+
};
|
|
374
|
+
if (facetId === PRICE_FACET_ID) {
|
|
375
|
+
if (priceBucketKeyFromFilters(next) === bucketKey) {
|
|
376
|
+
delete next.priceMin;
|
|
377
|
+
delete next.priceMax;
|
|
378
|
+
return next;
|
|
379
|
+
}
|
|
380
|
+
delete next.priceMin;
|
|
381
|
+
delete next.priceMax;
|
|
382
|
+
const range = DEFAULT_PRICE_RANGES.find((candidate) => candidate.key === bucketKey);
|
|
383
|
+
if (!range) {
|
|
384
|
+
return next;
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
...next,
|
|
388
|
+
...priceRangeToFilterValues(range)
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
const filterKey = facetId === CATEGORIES_FACET_ID ? "category" : facetId;
|
|
392
|
+
if (next[filterKey] === bucketKey) {
|
|
393
|
+
delete next[filterKey];
|
|
394
|
+
} else {
|
|
395
|
+
next[filterKey] = bucketKey;
|
|
396
|
+
}
|
|
397
|
+
return next;
|
|
398
|
+
}
|
|
399
|
+
__name(toggleFacetFilter, "toggleFacetFilter");
|
|
400
|
+
function parseMajorToCents(value) {
|
|
401
|
+
if (!value) {
|
|
402
|
+
return void 0;
|
|
403
|
+
}
|
|
404
|
+
const parsed = Number(value);
|
|
405
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
406
|
+
return void 0;
|
|
407
|
+
}
|
|
408
|
+
return Math.round(parsed * 100);
|
|
409
|
+
}
|
|
410
|
+
__name(parseMajorToCents, "parseMajorToCents");
|
|
411
|
+
|
|
412
|
+
// src/lib/commerce-ai-api.service.ts
|
|
413
|
+
import { Injectable } from "@angular/core";
|
|
414
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
415
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
416
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
417
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
418
|
+
} else {
|
|
419
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
420
|
+
if (d = decorators[i]) {
|
|
421
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
426
|
+
}
|
|
427
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
428
|
+
function buildLocalePayload(options) {
|
|
429
|
+
const payload = {};
|
|
430
|
+
const queryLocale = options.queryLocale ?? options.locale;
|
|
431
|
+
if (options.catalogLocale) {
|
|
432
|
+
payload.catalogLocale = options.catalogLocale;
|
|
433
|
+
}
|
|
434
|
+
if (queryLocale) {
|
|
435
|
+
payload.queryLocale = queryLocale;
|
|
436
|
+
}
|
|
437
|
+
return payload;
|
|
438
|
+
}
|
|
439
|
+
__name(buildLocalePayload, "buildLocalePayload");
|
|
440
|
+
var CommerceAiApiService = class {
|
|
441
|
+
static {
|
|
442
|
+
__name(this, "CommerceAiApiService");
|
|
443
|
+
}
|
|
444
|
+
search(apiBaseUrl, query, locales = {}, signal2, options = {}) {
|
|
445
|
+
const baseUrl = apiBaseUrl.replace(/\/$/, "");
|
|
446
|
+
return fetch(`${baseUrl}/search`, {
|
|
447
|
+
method: "POST",
|
|
448
|
+
headers: {
|
|
449
|
+
"Content-Type": "application/json"
|
|
450
|
+
},
|
|
451
|
+
body: JSON.stringify({
|
|
452
|
+
query,
|
|
453
|
+
...options,
|
|
454
|
+
...buildLocalePayload(locales)
|
|
455
|
+
}),
|
|
456
|
+
signal: signal2
|
|
457
|
+
}).then(async (response) => {
|
|
458
|
+
if (!response.ok) {
|
|
459
|
+
const data = await response.json();
|
|
460
|
+
throw new Error(data.error ?? "Search failed");
|
|
461
|
+
}
|
|
462
|
+
return response.json();
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
suggest(apiBaseUrl, query, locales = {}, signal2) {
|
|
466
|
+
const baseUrl = apiBaseUrl.replace(/\/$/, "");
|
|
467
|
+
return fetch(`${baseUrl}/search/suggestions`, {
|
|
468
|
+
method: "POST",
|
|
469
|
+
headers: {
|
|
470
|
+
"Content-Type": "application/json"
|
|
471
|
+
},
|
|
472
|
+
body: JSON.stringify({
|
|
473
|
+
query,
|
|
474
|
+
...buildLocalePayload(locales)
|
|
475
|
+
}),
|
|
476
|
+
signal: signal2
|
|
477
|
+
}).then(async (response) => {
|
|
478
|
+
if (!response.ok) {
|
|
479
|
+
const data = await response.json();
|
|
480
|
+
throw new Error(data.error ?? "Suggestions failed");
|
|
481
|
+
}
|
|
482
|
+
return response.json();
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
searchByImage(apiBaseUrl, file, locales = {}, enableMissions = false) {
|
|
486
|
+
const baseUrl = apiBaseUrl.replace(/\/$/, "");
|
|
487
|
+
const formData = new FormData();
|
|
488
|
+
formData.append("image", file);
|
|
489
|
+
for (const [key, value] of Object.entries(buildLocalePayload(locales))) {
|
|
490
|
+
formData.append(key, value);
|
|
491
|
+
}
|
|
492
|
+
if (enableMissions) {
|
|
493
|
+
formData.append("enableMissions", "true");
|
|
494
|
+
}
|
|
495
|
+
return fetch(`${baseUrl}/search/image`, {
|
|
496
|
+
method: "POST",
|
|
497
|
+
body: formData
|
|
498
|
+
}).then(async (response) => {
|
|
499
|
+
if (!response.ok) {
|
|
500
|
+
const data = await response.json();
|
|
501
|
+
throw new Error(data.error ?? "Image search failed");
|
|
502
|
+
}
|
|
503
|
+
return response.json();
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
searchByVoice(apiBaseUrl, audio, locales = {}, enableTts = true, enableMissions = false) {
|
|
507
|
+
const baseUrl = apiBaseUrl.replace(/\/$/, "");
|
|
508
|
+
const formData = new FormData();
|
|
509
|
+
formData.append("audio", audio, "recording.webm");
|
|
510
|
+
for (const [key, value] of Object.entries(buildLocalePayload(locales))) {
|
|
511
|
+
formData.append(key, value);
|
|
512
|
+
}
|
|
513
|
+
formData.append("enableTts", String(enableTts));
|
|
514
|
+
if (enableMissions) {
|
|
515
|
+
formData.append("enableMissions", "true");
|
|
516
|
+
}
|
|
517
|
+
return fetch(`${baseUrl}/search/voice`, {
|
|
518
|
+
method: "POST",
|
|
519
|
+
body: formData
|
|
520
|
+
}).then(async (response) => {
|
|
521
|
+
if (!response.ok) {
|
|
522
|
+
const data = await response.json();
|
|
523
|
+
throw new Error(data.error ?? "Voice search failed");
|
|
524
|
+
}
|
|
525
|
+
return response.json();
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
synthesizeSpeech(apiBaseUrl, text) {
|
|
529
|
+
const baseUrl = apiBaseUrl.replace(/\/$/, "");
|
|
530
|
+
return fetch(`${baseUrl}/tts`, {
|
|
531
|
+
method: "POST",
|
|
532
|
+
headers: {
|
|
533
|
+
"Content-Type": "application/json"
|
|
534
|
+
},
|
|
535
|
+
body: JSON.stringify({
|
|
536
|
+
text
|
|
537
|
+
})
|
|
538
|
+
}).then(async (response) => {
|
|
539
|
+
if (!response.ok) {
|
|
540
|
+
throw new Error("TTS failed");
|
|
541
|
+
}
|
|
542
|
+
return response.blob();
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
CommerceAiApiService = _ts_decorate([
|
|
547
|
+
Injectable({
|
|
548
|
+
providedIn: "root"
|
|
549
|
+
})
|
|
550
|
+
], CommerceAiApiService);
|
|
551
|
+
|
|
552
|
+
// src/lib/commerce-ai-cart-panel.component.ts
|
|
553
|
+
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
|
554
|
+
import { FormsModule } from "@angular/forms";
|
|
555
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
556
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
557
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
558
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
559
|
+
} else {
|
|
560
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
561
|
+
if (d = decorators[i]) {
|
|
562
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
567
|
+
}
|
|
568
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
569
|
+
function _ts_metadata(metadataKey, metadataValue) {
|
|
570
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
571
|
+
return Reflect.metadata(metadataKey, metadataValue);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
575
|
+
function displayCartError(error, messages) {
|
|
576
|
+
if (!error) {
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
if (error === "Invalid credentials") {
|
|
580
|
+
return messages.invalidCredentials;
|
|
581
|
+
}
|
|
582
|
+
if (error === "Sign in failed" || error === "Login failed") {
|
|
583
|
+
return messages.signInFailed;
|
|
584
|
+
}
|
|
585
|
+
return error;
|
|
586
|
+
}
|
|
587
|
+
__name(displayCartError, "displayCartError");
|
|
588
|
+
function formatLineTotal(price, quantity, locale) {
|
|
589
|
+
if (!price) {
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
return new Intl.NumberFormat(locale || void 0, {
|
|
593
|
+
style: "currency",
|
|
594
|
+
currency: price.currency
|
|
595
|
+
}).format(price.amount * quantity);
|
|
596
|
+
}
|
|
597
|
+
__name(formatLineTotal, "formatLineTotal");
|
|
598
|
+
var CommerceAiCartPanelComponent = class {
|
|
599
|
+
static {
|
|
600
|
+
__name(this, "CommerceAiCartPanelComponent");
|
|
601
|
+
}
|
|
602
|
+
cart = null;
|
|
603
|
+
customer = null;
|
|
604
|
+
isLoading = false;
|
|
605
|
+
isLoggingIn = false;
|
|
606
|
+
error = null;
|
|
607
|
+
messages;
|
|
608
|
+
catalogLocale;
|
|
609
|
+
showCheckout = false;
|
|
610
|
+
close = new EventEmitter();
|
|
611
|
+
remove = new EventEmitter();
|
|
612
|
+
quantityChange = new EventEmitter();
|
|
613
|
+
login = new EventEmitter();
|
|
614
|
+
logout = new EventEmitter();
|
|
615
|
+
checkout = new EventEmitter();
|
|
616
|
+
email = "";
|
|
617
|
+
password = "";
|
|
618
|
+
isAuthExpanded = false;
|
|
619
|
+
get isEmpty() {
|
|
620
|
+
return !this.cart || this.cart.lineItems.length === 0;
|
|
621
|
+
}
|
|
622
|
+
get visibleError() {
|
|
623
|
+
return displayCartError(this.error, this.messages);
|
|
624
|
+
}
|
|
625
|
+
lineTotal(item) {
|
|
626
|
+
return formatLineTotal(item.price, item.quantity, this.catalogLocale);
|
|
627
|
+
}
|
|
628
|
+
onLoginSubmit() {
|
|
629
|
+
this.login.emit({
|
|
630
|
+
email: this.email,
|
|
631
|
+
password: this.password
|
|
632
|
+
});
|
|
633
|
+
this.password = "";
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
_ts_decorate2([
|
|
637
|
+
Input(),
|
|
638
|
+
_ts_metadata("design:type", Object)
|
|
639
|
+
], CommerceAiCartPanelComponent.prototype, "cart", void 0);
|
|
640
|
+
_ts_decorate2([
|
|
641
|
+
Input(),
|
|
642
|
+
_ts_metadata("design:type", Object)
|
|
643
|
+
], CommerceAiCartPanelComponent.prototype, "customer", void 0);
|
|
644
|
+
_ts_decorate2([
|
|
645
|
+
Input()
|
|
646
|
+
], CommerceAiCartPanelComponent.prototype, "isLoading", void 0);
|
|
647
|
+
_ts_decorate2([
|
|
648
|
+
Input()
|
|
649
|
+
], CommerceAiCartPanelComponent.prototype, "isLoggingIn", void 0);
|
|
650
|
+
_ts_decorate2([
|
|
651
|
+
Input(),
|
|
652
|
+
_ts_metadata("design:type", Object)
|
|
653
|
+
], CommerceAiCartPanelComponent.prototype, "error", void 0);
|
|
654
|
+
_ts_decorate2([
|
|
655
|
+
Input({
|
|
656
|
+
required: true
|
|
657
|
+
}),
|
|
658
|
+
_ts_metadata("design:type", typeof CommerceAISearchMessages === "undefined" ? Object : CommerceAISearchMessages)
|
|
659
|
+
], CommerceAiCartPanelComponent.prototype, "messages", void 0);
|
|
660
|
+
_ts_decorate2([
|
|
661
|
+
Input(),
|
|
662
|
+
_ts_metadata("design:type", String)
|
|
663
|
+
], CommerceAiCartPanelComponent.prototype, "catalogLocale", void 0);
|
|
664
|
+
_ts_decorate2([
|
|
665
|
+
Input()
|
|
666
|
+
], CommerceAiCartPanelComponent.prototype, "showCheckout", void 0);
|
|
667
|
+
_ts_decorate2([
|
|
668
|
+
Output()
|
|
669
|
+
], CommerceAiCartPanelComponent.prototype, "close", void 0);
|
|
670
|
+
_ts_decorate2([
|
|
671
|
+
Output()
|
|
672
|
+
], CommerceAiCartPanelComponent.prototype, "remove", void 0);
|
|
673
|
+
_ts_decorate2([
|
|
674
|
+
Output()
|
|
675
|
+
], CommerceAiCartPanelComponent.prototype, "quantityChange", void 0);
|
|
676
|
+
_ts_decorate2([
|
|
677
|
+
Output()
|
|
678
|
+
], CommerceAiCartPanelComponent.prototype, "login", void 0);
|
|
679
|
+
_ts_decorate2([
|
|
680
|
+
Output()
|
|
681
|
+
], CommerceAiCartPanelComponent.prototype, "logout", void 0);
|
|
682
|
+
_ts_decorate2([
|
|
683
|
+
Output()
|
|
684
|
+
], CommerceAiCartPanelComponent.prototype, "checkout", void 0);
|
|
685
|
+
CommerceAiCartPanelComponent = _ts_decorate2([
|
|
686
|
+
Component({
|
|
687
|
+
selector: "commerce-ai-cart-panel",
|
|
688
|
+
standalone: true,
|
|
689
|
+
imports: [
|
|
690
|
+
FormsModule
|
|
691
|
+
],
|
|
692
|
+
template: `
|
|
693
|
+
<section
|
|
694
|
+
class="cat-cart-panel"
|
|
695
|
+
[class.cat-cart-panel--with-items]="!isEmpty"
|
|
696
|
+
[attr.aria-label]="messages.cartAriaLabel"
|
|
697
|
+
>
|
|
698
|
+
<header class="cat-cart-panel__header">
|
|
699
|
+
<h2 class="cat-cart-panel__title">{{ messages.cart }}</h2>
|
|
700
|
+
<button type="button" class="cat-icon-btn" (click)="close.emit()" [attr.aria-label]="messages.closeCart">
|
|
701
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
702
|
+
<path d="M18 6 6 18" />
|
|
703
|
+
<path d="m6 6 12 12" />
|
|
704
|
+
</svg>
|
|
705
|
+
</button>
|
|
706
|
+
</header>
|
|
707
|
+
|
|
708
|
+
@if (visibleError) {
|
|
709
|
+
<div class="cat-status cat-status--error" role="alert">{{ visibleError }}</div>
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
@if (isEmpty) {
|
|
713
|
+
<div class="cat-cart-panel__empty">
|
|
714
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
715
|
+
<path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4Z" />
|
|
716
|
+
<path d="M3 6h18" />
|
|
717
|
+
<path d="M16 10a4 4 0 0 1-8 0" />
|
|
718
|
+
</svg>
|
|
719
|
+
<span>{{ messages.emptyCart }}</span>
|
|
720
|
+
</div>
|
|
721
|
+
} @else {
|
|
722
|
+
<ul class="cat-cart-panel__items">
|
|
723
|
+
@for (item of cart!.lineItems; track item.id) {
|
|
724
|
+
<li class="cat-cart-item">
|
|
725
|
+
@if (item.imageUrl) {
|
|
726
|
+
<img [src]="item.imageUrl" alt="" class="cat-cart-item__image" />
|
|
727
|
+
} @else {
|
|
728
|
+
<div class="cat-cart-item__image cat-cart-item__image--placeholder">
|
|
729
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
730
|
+
<path d="M16.5 9.4 7.55 4.24" />
|
|
731
|
+
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
|
|
732
|
+
<polyline points="3.29 7 12 12 20.71 7" />
|
|
733
|
+
<line x1="12" x2="12" y1="22" y2="12" />
|
|
734
|
+
</svg>
|
|
735
|
+
</div>
|
|
736
|
+
}
|
|
737
|
+
<div class="cat-cart-item__info">
|
|
738
|
+
<div class="cat-cart-item__name">{{ item.name }}</div>
|
|
739
|
+
@if (item.price) {
|
|
740
|
+
<div class="cat-cart-item__price">
|
|
741
|
+
<span>{{ item.price.formatted }} {{ messages.each }}</span>
|
|
742
|
+
<strong>{{ item.totalPrice?.formatted ?? lineTotal(item) }}</strong>
|
|
743
|
+
</div>
|
|
744
|
+
}
|
|
745
|
+
<div class="cat-cart-item__qty">
|
|
746
|
+
<button
|
|
747
|
+
type="button"
|
|
748
|
+
class="cat-cart-item__qty-btn"
|
|
749
|
+
[attr.aria-label]="messages.decreaseQuantity"
|
|
750
|
+
[disabled]="isLoading || item.quantity <= 1"
|
|
751
|
+
(click)="quantityChange.emit({ lineItemId: item.id, quantity: item.quantity - 1 })"
|
|
752
|
+
>
|
|
753
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
754
|
+
<path d="M5 12h14" />
|
|
755
|
+
</svg>
|
|
756
|
+
</button>
|
|
757
|
+
<span class="cat-cart-item__qty-value">{{ item.quantity }}</span>
|
|
758
|
+
<button
|
|
759
|
+
type="button"
|
|
760
|
+
class="cat-cart-item__qty-btn"
|
|
761
|
+
[attr.aria-label]="messages.increaseQuantity"
|
|
762
|
+
[disabled]="isLoading"
|
|
763
|
+
(click)="quantityChange.emit({ lineItemId: item.id, quantity: item.quantity + 1 })"
|
|
764
|
+
>
|
|
765
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
766
|
+
<path d="M5 12h14" />
|
|
767
|
+
<path d="M12 5v14" />
|
|
768
|
+
</svg>
|
|
769
|
+
</button>
|
|
770
|
+
</div>
|
|
771
|
+
</div>
|
|
772
|
+
<button
|
|
773
|
+
type="button"
|
|
774
|
+
class="cat-icon-btn"
|
|
775
|
+
[attr.aria-label]="messages.removeItem"
|
|
776
|
+
[disabled]="isLoading"
|
|
777
|
+
(click)="remove.emit(item.id)"
|
|
778
|
+
>
|
|
779
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
780
|
+
<path d="M3 6h18" />
|
|
781
|
+
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
|
|
782
|
+
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
|
|
783
|
+
</svg>
|
|
784
|
+
</button>
|
|
785
|
+
</li>
|
|
786
|
+
}
|
|
787
|
+
</ul>
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
<div class="cat-cart-panel__auth">
|
|
791
|
+
@if (customer) {
|
|
792
|
+
<div class="cat-cart-panel__signed-in">
|
|
793
|
+
<span>{{ messages.signedInAs }} {{ customer.email }}</span>
|
|
794
|
+
<button type="button" class="cat-cart-panel__sign-out" (click)="logout.emit()">
|
|
795
|
+
{{ messages.signOut }}
|
|
796
|
+
</button>
|
|
797
|
+
</div>
|
|
798
|
+
} @else {
|
|
799
|
+
<button
|
|
800
|
+
type="button"
|
|
801
|
+
class="cat-cart-panel__auth-toggle"
|
|
802
|
+
[disabled]="isLoggingIn"
|
|
803
|
+
[attr.aria-expanded]="isAuthExpanded"
|
|
804
|
+
(click)="isAuthExpanded = !isAuthExpanded"
|
|
805
|
+
>
|
|
806
|
+
<span class="cat-cart-panel__auth-toggle-copy">
|
|
807
|
+
<span class="cat-cart-panel__auth-icon" aria-hidden="true">
|
|
808
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
809
|
+
<circle cx="12" cy="8" r="5" />
|
|
810
|
+
<path d="M20 21a8 8 0 0 0-16 0" />
|
|
811
|
+
</svg>
|
|
812
|
+
</span>
|
|
813
|
+
{{ messages.signInToSyncCart }}
|
|
814
|
+
</span>
|
|
815
|
+
<svg
|
|
816
|
+
width="15"
|
|
817
|
+
height="15"
|
|
818
|
+
viewBox="0 0 24 24"
|
|
819
|
+
fill="none"
|
|
820
|
+
stroke="currentColor"
|
|
821
|
+
stroke-width="2"
|
|
822
|
+
aria-hidden="true"
|
|
823
|
+
[class.cat-cart-panel__auth-chevron--open]="isAuthExpanded"
|
|
824
|
+
>
|
|
825
|
+
<path d="m6 9 6 6 6-6" />
|
|
826
|
+
</svg>
|
|
827
|
+
</button>
|
|
828
|
+
@if (isAuthExpanded) {
|
|
829
|
+
<form class="cat-cart-panel__auth-form" (ngSubmit)="onLoginSubmit()">
|
|
830
|
+
<label class="cat-cart-panel__field">
|
|
831
|
+
<span class="cat-cart-panel__label">{{ messages.email }}</span>
|
|
832
|
+
<input
|
|
833
|
+
class="cat-cart-panel__input"
|
|
834
|
+
type="email"
|
|
835
|
+
name="email"
|
|
836
|
+
autocomplete="email"
|
|
837
|
+
required
|
|
838
|
+
[disabled]="isLoggingIn"
|
|
839
|
+
[(ngModel)]="email"
|
|
840
|
+
/>
|
|
841
|
+
</label>
|
|
842
|
+
<label class="cat-cart-panel__field">
|
|
843
|
+
<span class="cat-cart-panel__label">{{ messages.password }}</span>
|
|
844
|
+
<input
|
|
845
|
+
class="cat-cart-panel__input"
|
|
846
|
+
type="password"
|
|
847
|
+
name="password"
|
|
848
|
+
autocomplete="current-password"
|
|
849
|
+
required
|
|
850
|
+
[disabled]="isLoggingIn"
|
|
851
|
+
[(ngModel)]="password"
|
|
852
|
+
/>
|
|
853
|
+
</label>
|
|
854
|
+
<button type="submit" class="cat-cart-panel__submit" [disabled]="isLoggingIn">
|
|
855
|
+
{{ messages.signIn }}
|
|
856
|
+
</button>
|
|
857
|
+
</form>
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
</div>
|
|
861
|
+
|
|
862
|
+
@if (!isEmpty) {
|
|
863
|
+
<footer class="cat-cart-panel__footer">
|
|
864
|
+
<div class="cat-cart-panel__total">
|
|
865
|
+
<span>{{ messages.total }}</span>
|
|
866
|
+
<strong>{{ cart!.totalPrice.formatted }}</strong>
|
|
867
|
+
</div>
|
|
868
|
+
@if (showCheckout && cart) {
|
|
869
|
+
<button
|
|
870
|
+
type="button"
|
|
871
|
+
class="cat-checkout-cta"
|
|
872
|
+
[disabled]="isLoading"
|
|
873
|
+
(click)="checkout.emit(cart)"
|
|
874
|
+
>
|
|
875
|
+
<span>{{ messages.checkout }}</span>
|
|
876
|
+
<span class="cat-checkout-cta__icon" aria-hidden="true">
|
|
877
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
878
|
+
<path d="M5 12h14" />
|
|
879
|
+
<path d="m12 5 7 7-7 7" />
|
|
880
|
+
</svg>
|
|
881
|
+
</span>
|
|
882
|
+
</button>
|
|
883
|
+
}
|
|
884
|
+
</footer>
|
|
885
|
+
}
|
|
886
|
+
</section>
|
|
887
|
+
`
|
|
888
|
+
})
|
|
889
|
+
], CommerceAiCartPanelComponent);
|
|
890
|
+
|
|
891
|
+
// src/lib/commerce-ai-cart.service.ts
|
|
892
|
+
import { Injectable as Injectable2, computed, signal } from "@angular/core";
|
|
893
|
+
function _ts_decorate3(decorators, target, key, desc) {
|
|
894
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
895
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
896
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
897
|
+
} else {
|
|
898
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
899
|
+
if (d = decorators[i]) {
|
|
900
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
905
|
+
}
|
|
906
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
907
|
+
var ANONYMOUS_ID_STORAGE_KEY = "commerce-ai-tool:anonymousId";
|
|
908
|
+
var CUSTOMER_SESSION_STORAGE_KEY = "commerce-ai-tool:customerSession";
|
|
909
|
+
var CUSTOMER_STORAGE_KEY = "commerce-ai-tool:customer";
|
|
910
|
+
function readStorage(key) {
|
|
911
|
+
if (typeof window === "undefined") {
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
try {
|
|
915
|
+
return window.localStorage.getItem(key);
|
|
916
|
+
} catch {
|
|
917
|
+
return null;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
__name(readStorage, "readStorage");
|
|
921
|
+
function writeStorage(key, value) {
|
|
922
|
+
if (typeof window === "undefined") {
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
try {
|
|
926
|
+
window.localStorage.setItem(key, value);
|
|
927
|
+
} catch {
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
__name(writeStorage, "writeStorage");
|
|
931
|
+
function removeStorage(key) {
|
|
932
|
+
if (typeof window === "undefined") {
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
try {
|
|
936
|
+
window.localStorage.removeItem(key);
|
|
937
|
+
} catch {
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
__name(removeStorage, "removeStorage");
|
|
941
|
+
function readStoredCustomer() {
|
|
942
|
+
const raw = readStorage(CUSTOMER_STORAGE_KEY);
|
|
943
|
+
if (!raw) {
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
try {
|
|
947
|
+
const parsed = JSON.parse(raw);
|
|
948
|
+
if (typeof parsed.id === "string" && typeof parsed.email === "string") {
|
|
949
|
+
return parsed;
|
|
950
|
+
}
|
|
951
|
+
} catch {
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
return null;
|
|
955
|
+
}
|
|
956
|
+
__name(readStoredCustomer, "readStoredCustomer");
|
|
957
|
+
function createAnonymousId() {
|
|
958
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
959
|
+
return crypto.randomUUID();
|
|
960
|
+
}
|
|
961
|
+
return `anon-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
962
|
+
}
|
|
963
|
+
__name(createAnonymousId, "createAnonymousId");
|
|
964
|
+
function getOrCreateAnonymousId() {
|
|
965
|
+
const existing = readStorage(ANONYMOUS_ID_STORAGE_KEY);
|
|
966
|
+
if (existing) {
|
|
967
|
+
return existing;
|
|
968
|
+
}
|
|
969
|
+
const created = createAnonymousId();
|
|
970
|
+
writeStorage(ANONYMOUS_ID_STORAGE_KEY, created);
|
|
971
|
+
return created;
|
|
972
|
+
}
|
|
973
|
+
__name(getOrCreateAnonymousId, "getOrCreateAnonymousId");
|
|
974
|
+
function rotateAnonymousId() {
|
|
975
|
+
const created = createAnonymousId();
|
|
976
|
+
writeStorage(ANONYMOUS_ID_STORAGE_KEY, created);
|
|
977
|
+
return created;
|
|
978
|
+
}
|
|
979
|
+
__name(rotateAnonymousId, "rotateAnonymousId");
|
|
980
|
+
var CartRequestError = class CartRequestError2 extends Error {
|
|
981
|
+
static {
|
|
982
|
+
__name(this, "CartRequestError");
|
|
983
|
+
}
|
|
984
|
+
status;
|
|
985
|
+
constructor(message, status) {
|
|
986
|
+
super(message);
|
|
987
|
+
this.name = "CartRequestError";
|
|
988
|
+
this.status = status;
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
async function parseCartApi(response) {
|
|
992
|
+
const body = await response.json();
|
|
993
|
+
if (!response.ok) {
|
|
994
|
+
throw new CartRequestError(body.error ?? "Cart request failed", response.status);
|
|
995
|
+
}
|
|
996
|
+
return body;
|
|
997
|
+
}
|
|
998
|
+
__name(parseCartApi, "parseCartApi");
|
|
999
|
+
var CommerceAiCartService = class {
|
|
1000
|
+
static {
|
|
1001
|
+
__name(this, "CommerceAiCartService");
|
|
1002
|
+
}
|
|
1003
|
+
cart = signal(null);
|
|
1004
|
+
anonymousId = signal("");
|
|
1005
|
+
customer = signal(null);
|
|
1006
|
+
isLoading = signal(false);
|
|
1007
|
+
isMutating = signal(false);
|
|
1008
|
+
isLoggingIn = signal(false);
|
|
1009
|
+
error = signal(null);
|
|
1010
|
+
isCartOpen = signal(false);
|
|
1011
|
+
isAuthenticated = computed(() => Boolean(this.customer() && this.sessionToken()));
|
|
1012
|
+
apiBaseUrl = "";
|
|
1013
|
+
currency;
|
|
1014
|
+
country;
|
|
1015
|
+
catalogLocale;
|
|
1016
|
+
enabled = false;
|
|
1017
|
+
onCartChange;
|
|
1018
|
+
sessionTokenValue = null;
|
|
1019
|
+
cartValue = null;
|
|
1020
|
+
mutationChain = Promise.resolve();
|
|
1021
|
+
started = false;
|
|
1022
|
+
sessionToken() {
|
|
1023
|
+
return this.sessionTokenValue;
|
|
1024
|
+
}
|
|
1025
|
+
configure(options) {
|
|
1026
|
+
this.apiBaseUrl = options.apiBaseUrl.replace(/\/$/, "");
|
|
1027
|
+
this.currency = options.currency;
|
|
1028
|
+
this.country = options.country;
|
|
1029
|
+
this.catalogLocale = options.catalogLocale;
|
|
1030
|
+
this.enabled = options.enabled !== false;
|
|
1031
|
+
this.onCartChange = options.onCartChange;
|
|
1032
|
+
if (!this.enabled) {
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
if (!this.started) {
|
|
1036
|
+
this.started = true;
|
|
1037
|
+
this.anonymousId.set(getOrCreateAnonymousId());
|
|
1038
|
+
const storedToken = readStorage(CUSTOMER_SESSION_STORAGE_KEY);
|
|
1039
|
+
const storedCustomer = readStoredCustomer();
|
|
1040
|
+
if (storedToken && storedCustomer) {
|
|
1041
|
+
this.sessionTokenValue = storedToken;
|
|
1042
|
+
this.customer.set(storedCustomer);
|
|
1043
|
+
}
|
|
1044
|
+
void this.refresh();
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
openCart() {
|
|
1048
|
+
this.isCartOpen.set(true);
|
|
1049
|
+
}
|
|
1050
|
+
closeCart() {
|
|
1051
|
+
this.isCartOpen.set(false);
|
|
1052
|
+
}
|
|
1053
|
+
toggleCart() {
|
|
1054
|
+
this.isCartOpen.update((open) => !open);
|
|
1055
|
+
}
|
|
1056
|
+
async refresh() {
|
|
1057
|
+
const anonymousId = this.anonymousId();
|
|
1058
|
+
if (!this.enabled || !anonymousId) {
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
const params = new URLSearchParams();
|
|
1062
|
+
const headers = {};
|
|
1063
|
+
const token = this.sessionTokenValue;
|
|
1064
|
+
if (token) {
|
|
1065
|
+
headers[CART_SESSION_HEADER] = token;
|
|
1066
|
+
} else {
|
|
1067
|
+
params.set("anonymousId", anonymousId);
|
|
1068
|
+
}
|
|
1069
|
+
if (this.catalogLocale) {
|
|
1070
|
+
params.set("catalogLocale", this.catalogLocale);
|
|
1071
|
+
}
|
|
1072
|
+
this.isLoading.set(true);
|
|
1073
|
+
this.error.set(null);
|
|
1074
|
+
try {
|
|
1075
|
+
const query = params.toString();
|
|
1076
|
+
const url = `${this.apiBaseUrl}/cart${query ? `?${query}` : ""}`;
|
|
1077
|
+
const response = await (token ? fetch(url, {
|
|
1078
|
+
headers
|
|
1079
|
+
}) : fetch(url));
|
|
1080
|
+
const body = await parseCartApi(response);
|
|
1081
|
+
this.applyCart(body.cart ?? null);
|
|
1082
|
+
} catch (err) {
|
|
1083
|
+
if (err instanceof CartRequestError && err.status === 401 && this.sessionTokenValue) {
|
|
1084
|
+
this.clearSession();
|
|
1085
|
+
this.anonymousId.set(rotateAnonymousId());
|
|
1086
|
+
this.applyCart(null);
|
|
1087
|
+
this.error.set(err.message);
|
|
1088
|
+
this.isLoading.set(false);
|
|
1089
|
+
await this.refresh();
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
this.error.set(err instanceof Error ? err.message : "Cart request failed");
|
|
1093
|
+
} finally {
|
|
1094
|
+
this.isLoading.set(false);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
addToCart(item) {
|
|
1098
|
+
return this.mutate("/cart/add", {
|
|
1099
|
+
sku: item.sku,
|
|
1100
|
+
productId: item.productId,
|
|
1101
|
+
variantId: item.variantId,
|
|
1102
|
+
quantity: item.quantity
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
addItems(items) {
|
|
1106
|
+
return this.mutate("/cart/add-items", {
|
|
1107
|
+
items
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
removeFromCart(lineItemId) {
|
|
1111
|
+
return this.mutate("/cart/remove", {
|
|
1112
|
+
lineItemId
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
updateQuantity(lineItemId, quantity) {
|
|
1116
|
+
return this.mutate("/cart/update-quantity", {
|
|
1117
|
+
lineItemId,
|
|
1118
|
+
quantity
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
async login(input) {
|
|
1122
|
+
if (!this.enabled) {
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
1125
|
+
this.isLoggingIn.set(true);
|
|
1126
|
+
this.error.set(null);
|
|
1127
|
+
try {
|
|
1128
|
+
const response = await fetch(`${this.apiBaseUrl}/cart/login`, {
|
|
1129
|
+
method: "POST",
|
|
1130
|
+
headers: {
|
|
1131
|
+
"Content-Type": "application/json"
|
|
1132
|
+
},
|
|
1133
|
+
body: JSON.stringify({
|
|
1134
|
+
email: input.email,
|
|
1135
|
+
password: input.password,
|
|
1136
|
+
anonymousId: this.anonymousId(),
|
|
1137
|
+
catalogLocale: this.catalogLocale,
|
|
1138
|
+
cartId: this.cartValue?.id
|
|
1139
|
+
})
|
|
1140
|
+
});
|
|
1141
|
+
const body = await parseCartApi(response);
|
|
1142
|
+
if (!body.sessionToken || !body.customer) {
|
|
1143
|
+
throw new CartRequestError("Sign in failed", response.status);
|
|
1144
|
+
}
|
|
1145
|
+
this.persistSession(body.sessionToken, body.customer);
|
|
1146
|
+
this.applyCart(body.cart ?? null);
|
|
1147
|
+
return body.cart ?? null;
|
|
1148
|
+
} catch (err) {
|
|
1149
|
+
this.error.set(err instanceof Error ? err.message : "Sign in failed");
|
|
1150
|
+
return null;
|
|
1151
|
+
} finally {
|
|
1152
|
+
this.isLoggingIn.set(false);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
async logout() {
|
|
1156
|
+
try {
|
|
1157
|
+
await fetch(`${this.apiBaseUrl}/cart/logout`, {
|
|
1158
|
+
method: "POST",
|
|
1159
|
+
headers: {
|
|
1160
|
+
"Content-Type": "application/json"
|
|
1161
|
+
},
|
|
1162
|
+
body: JSON.stringify({})
|
|
1163
|
+
});
|
|
1164
|
+
} catch {
|
|
1165
|
+
}
|
|
1166
|
+
this.clearSession();
|
|
1167
|
+
this.applyCart(null);
|
|
1168
|
+
this.anonymousId.set(rotateAnonymousId());
|
|
1169
|
+
}
|
|
1170
|
+
mutate(path, body) {
|
|
1171
|
+
const run = /* @__PURE__ */ __name(async () => {
|
|
1172
|
+
const anonymousId = this.anonymousId();
|
|
1173
|
+
if (!this.enabled || !anonymousId) {
|
|
1174
|
+
return null;
|
|
1175
|
+
}
|
|
1176
|
+
this.isMutating.set(true);
|
|
1177
|
+
this.error.set(null);
|
|
1178
|
+
try {
|
|
1179
|
+
const response = await fetch(`${this.apiBaseUrl}${path}`, {
|
|
1180
|
+
method: "POST",
|
|
1181
|
+
headers: {
|
|
1182
|
+
"Content-Type": "application/json"
|
|
1183
|
+
},
|
|
1184
|
+
body: JSON.stringify({
|
|
1185
|
+
anonymousId,
|
|
1186
|
+
sessionToken: this.sessionTokenValue ?? void 0,
|
|
1187
|
+
currency: this.currency,
|
|
1188
|
+
country: this.country,
|
|
1189
|
+
catalogLocale: this.catalogLocale,
|
|
1190
|
+
cartId: this.cartValue?.id,
|
|
1191
|
+
cartVersion: this.cartValue?.version,
|
|
1192
|
+
...body
|
|
1193
|
+
})
|
|
1194
|
+
});
|
|
1195
|
+
const next = await parseCartApi(response);
|
|
1196
|
+
this.applyCart(next.cart ?? null);
|
|
1197
|
+
return next.cart ?? null;
|
|
1198
|
+
} catch (err) {
|
|
1199
|
+
if (err instanceof CartRequestError && err.status === 401 && this.sessionTokenValue) {
|
|
1200
|
+
this.clearSession();
|
|
1201
|
+
this.anonymousId.set(rotateAnonymousId());
|
|
1202
|
+
this.applyCart(null);
|
|
1203
|
+
}
|
|
1204
|
+
this.error.set(err instanceof Error ? err.message : "Cart request failed");
|
|
1205
|
+
return null;
|
|
1206
|
+
} finally {
|
|
1207
|
+
this.isMutating.set(false);
|
|
1208
|
+
}
|
|
1209
|
+
}, "run");
|
|
1210
|
+
const pending = this.mutationChain.then(run, run);
|
|
1211
|
+
this.mutationChain = pending.then(() => void 0, () => void 0);
|
|
1212
|
+
return pending;
|
|
1213
|
+
}
|
|
1214
|
+
applyCart(next) {
|
|
1215
|
+
this.cartValue = next;
|
|
1216
|
+
this.cart.set(next);
|
|
1217
|
+
this.onCartChange?.(next);
|
|
1218
|
+
}
|
|
1219
|
+
persistSession(token, nextCustomer) {
|
|
1220
|
+
this.sessionTokenValue = token;
|
|
1221
|
+
this.customer.set(nextCustomer);
|
|
1222
|
+
writeStorage(CUSTOMER_SESSION_STORAGE_KEY, token);
|
|
1223
|
+
writeStorage(CUSTOMER_STORAGE_KEY, JSON.stringify(nextCustomer));
|
|
1224
|
+
}
|
|
1225
|
+
clearSession() {
|
|
1226
|
+
this.sessionTokenValue = null;
|
|
1227
|
+
this.customer.set(null);
|
|
1228
|
+
removeStorage(CUSTOMER_SESSION_STORAGE_KEY);
|
|
1229
|
+
removeStorage(CUSTOMER_STORAGE_KEY);
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1232
|
+
CommerceAiCartService = _ts_decorate3([
|
|
1233
|
+
Injectable2()
|
|
1234
|
+
], CommerceAiCartService);
|
|
1235
|
+
|
|
1236
|
+
// src/lib/commerce-ai-mission-results.component.ts
|
|
1237
|
+
import { NgTemplateOutlet } from "@angular/common";
|
|
1238
|
+
import { Component as Component2, EventEmitter as EventEmitter2, Input as Input2, Output as Output2 } from "@angular/core";
|
|
1239
|
+
|
|
1240
|
+
// src/lib/mission.util.ts
|
|
1241
|
+
function formatMissionAddAll(template, count) {
|
|
1242
|
+
const withPlural = count === 1 ? template.replaceAll("top picks", "top pick") : template;
|
|
1243
|
+
if (withPlural.includes("{count}")) {
|
|
1244
|
+
return withPlural.replaceAll("{count}", String(count));
|
|
1245
|
+
}
|
|
1246
|
+
return `${withPlural} (${count})`;
|
|
1247
|
+
}
|
|
1248
|
+
__name(formatMissionAddAll, "formatMissionAddAll");
|
|
1249
|
+
function toCartItem(product, quantity) {
|
|
1250
|
+
return product.sku ? {
|
|
1251
|
+
sku: product.sku,
|
|
1252
|
+
quantity
|
|
1253
|
+
} : {
|
|
1254
|
+
productId: product.id,
|
|
1255
|
+
variantId: product.variantId,
|
|
1256
|
+
quantity
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
__name(toCartItem, "toCartItem");
|
|
1260
|
+
function lineItemKey(item) {
|
|
1261
|
+
if (item.sku) {
|
|
1262
|
+
return `sku:${item.sku}`;
|
|
1263
|
+
}
|
|
1264
|
+
return `id:${item.productId ?? ""}:${item.variantId ?? ""}`;
|
|
1265
|
+
}
|
|
1266
|
+
__name(lineItemKey, "lineItemKey");
|
|
1267
|
+
function starterBundleItems(mission) {
|
|
1268
|
+
const items = [];
|
|
1269
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1270
|
+
for (const group of mission.intents) {
|
|
1271
|
+
if (group.failed) {
|
|
1272
|
+
continue;
|
|
1273
|
+
}
|
|
1274
|
+
const product = group.products[0];
|
|
1275
|
+
if (!product || !product.sku && !product.id) {
|
|
1276
|
+
continue;
|
|
1277
|
+
}
|
|
1278
|
+
const item = toCartItem(product, 1);
|
|
1279
|
+
const key = lineItemKey(item);
|
|
1280
|
+
if (seen.has(key)) {
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1283
|
+
seen.add(key);
|
|
1284
|
+
items.push(item);
|
|
1285
|
+
}
|
|
1286
|
+
return items;
|
|
1287
|
+
}
|
|
1288
|
+
__name(starterBundleItems, "starterBundleItems");
|
|
1289
|
+
|
|
1290
|
+
// src/lib/commerce-ai-mission-results.component.ts
|
|
1291
|
+
function _ts_decorate4(decorators, target, key, desc) {
|
|
1292
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1293
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
1294
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
1295
|
+
} else {
|
|
1296
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
1297
|
+
if (d = decorators[i]) {
|
|
1298
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1303
|
+
}
|
|
1304
|
+
__name(_ts_decorate4, "_ts_decorate");
|
|
1305
|
+
function _ts_metadata2(metadataKey, metadataValue) {
|
|
1306
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
1307
|
+
return Reflect.metadata(metadataKey, metadataValue);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
1311
|
+
var missionTitleCounter = 0;
|
|
1312
|
+
var CommerceAiMissionResultsComponent = class {
|
|
1313
|
+
static {
|
|
1314
|
+
__name(this, "CommerceAiMissionResultsComponent");
|
|
1315
|
+
}
|
|
1316
|
+
mission;
|
|
1317
|
+
messages;
|
|
1318
|
+
enableCart = false;
|
|
1319
|
+
isMutating = false;
|
|
1320
|
+
addedProductIds = {};
|
|
1321
|
+
addAllHandler;
|
|
1322
|
+
productSelect = new EventEmitter2();
|
|
1323
|
+
addItem = new EventEmitter2();
|
|
1324
|
+
addAll = new EventEmitter2();
|
|
1325
|
+
titleId = `cat-mission-title-${missionTitleCounter += 1}`;
|
|
1326
|
+
justAddedAll = false;
|
|
1327
|
+
addedAllTimeout = null;
|
|
1328
|
+
lastMission = null;
|
|
1329
|
+
cachedBundleMission = null;
|
|
1330
|
+
cachedBundleItems = [];
|
|
1331
|
+
get bundleItems() {
|
|
1332
|
+
if (this.cachedBundleMission !== this.mission) {
|
|
1333
|
+
this.cachedBundleMission = this.mission;
|
|
1334
|
+
this.cachedBundleItems = starterBundleItems(this.mission);
|
|
1335
|
+
}
|
|
1336
|
+
return this.cachedBundleItems;
|
|
1337
|
+
}
|
|
1338
|
+
get addAllLabel() {
|
|
1339
|
+
const totalQuantity = this.bundleItems.reduce((sum, item) => sum + (item.quantity ?? 1), 0);
|
|
1340
|
+
return formatMissionAddAll(this.messages.missionAddAll, totalQuantity);
|
|
1341
|
+
}
|
|
1342
|
+
ngOnChanges() {
|
|
1343
|
+
if (this.mission !== this.lastMission) {
|
|
1344
|
+
this.lastMission = this.mission;
|
|
1345
|
+
this.justAddedAll = false;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
ngOnDestroy() {
|
|
1349
|
+
if (this.addedAllTimeout) {
|
|
1350
|
+
clearTimeout(this.addedAllTimeout);
|
|
1351
|
+
this.addedAllTimeout = null;
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
addLabel(product) {
|
|
1355
|
+
if (this.addedProductIds[product.id]) {
|
|
1356
|
+
return this.messages.itemAdded;
|
|
1357
|
+
}
|
|
1358
|
+
return product.sku || product.id ? this.messages.addToCart : this.messages.unableToAddToCart;
|
|
1359
|
+
}
|
|
1360
|
+
onAddAllClick() {
|
|
1361
|
+
const items = this.bundleItems;
|
|
1362
|
+
this.addAll.emit(items);
|
|
1363
|
+
void this.addAllHandler?.(items).then((result) => {
|
|
1364
|
+
if (!result) {
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
this.justAddedAll = true;
|
|
1368
|
+
if (this.addedAllTimeout) {
|
|
1369
|
+
clearTimeout(this.addedAllTimeout);
|
|
1370
|
+
}
|
|
1371
|
+
this.addedAllTimeout = setTimeout(() => {
|
|
1372
|
+
this.justAddedAll = false;
|
|
1373
|
+
this.addedAllTimeout = null;
|
|
1374
|
+
}, 1200);
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1378
|
+
_ts_decorate4([
|
|
1379
|
+
Input2({
|
|
1380
|
+
required: true
|
|
1381
|
+
}),
|
|
1382
|
+
_ts_metadata2("design:type", typeof MissionSearchResult === "undefined" ? Object : MissionSearchResult)
|
|
1383
|
+
], CommerceAiMissionResultsComponent.prototype, "mission", void 0);
|
|
1384
|
+
_ts_decorate4([
|
|
1385
|
+
Input2({
|
|
1386
|
+
required: true
|
|
1387
|
+
}),
|
|
1388
|
+
_ts_metadata2("design:type", typeof CommerceAISearchMessages === "undefined" ? Object : CommerceAISearchMessages)
|
|
1389
|
+
], CommerceAiMissionResultsComponent.prototype, "messages", void 0);
|
|
1390
|
+
_ts_decorate4([
|
|
1391
|
+
Input2()
|
|
1392
|
+
], CommerceAiMissionResultsComponent.prototype, "enableCart", void 0);
|
|
1393
|
+
_ts_decorate4([
|
|
1394
|
+
Input2()
|
|
1395
|
+
], CommerceAiMissionResultsComponent.prototype, "isMutating", void 0);
|
|
1396
|
+
_ts_decorate4([
|
|
1397
|
+
Input2(),
|
|
1398
|
+
_ts_metadata2("design:type", typeof Record === "undefined" ? Object : Record)
|
|
1399
|
+
], CommerceAiMissionResultsComponent.prototype, "addedProductIds", void 0);
|
|
1400
|
+
_ts_decorate4([
|
|
1401
|
+
Input2(),
|
|
1402
|
+
_ts_metadata2("design:type", Function)
|
|
1403
|
+
], CommerceAiMissionResultsComponent.prototype, "addAllHandler", void 0);
|
|
1404
|
+
_ts_decorate4([
|
|
1405
|
+
Output2()
|
|
1406
|
+
], CommerceAiMissionResultsComponent.prototype, "productSelect", void 0);
|
|
1407
|
+
_ts_decorate4([
|
|
1408
|
+
Output2()
|
|
1409
|
+
], CommerceAiMissionResultsComponent.prototype, "addItem", void 0);
|
|
1410
|
+
_ts_decorate4([
|
|
1411
|
+
Output2()
|
|
1412
|
+
], CommerceAiMissionResultsComponent.prototype, "addAll", void 0);
|
|
1413
|
+
CommerceAiMissionResultsComponent = _ts_decorate4([
|
|
1414
|
+
Component2({
|
|
1415
|
+
selector: "commerce-ai-mission-results",
|
|
1416
|
+
standalone: true,
|
|
1417
|
+
imports: [
|
|
1418
|
+
NgTemplateOutlet
|
|
1419
|
+
],
|
|
1420
|
+
template: `
|
|
1421
|
+
<div class="cat-mission">
|
|
1422
|
+
<div class="cat-mission__header">
|
|
1423
|
+
<h2 [id]="titleId" class="cat-mission__title">{{ messages.missionTitle }}</h2>
|
|
1424
|
+
@if (mission.interpretation) {
|
|
1425
|
+
<p class="cat-mission__interpretation">{{ mission.interpretation }}</p>
|
|
1426
|
+
}
|
|
1427
|
+
</div>
|
|
1428
|
+
|
|
1429
|
+
<div class="cat-mission__lanes" role="list" [attr.aria-labelledby]="titleId">
|
|
1430
|
+
@for (group of mission.intents; track group.intent.id) {
|
|
1431
|
+
<section class="cat-mission-group" role="listitem">
|
|
1432
|
+
<div class="cat-mission-group__header">
|
|
1433
|
+
<h3 class="cat-mission-group__label">{{ group.intent.label }}</h3>
|
|
1434
|
+
@if (group.intent.quantity > 1) {
|
|
1435
|
+
<span class="cat-mission-group__qty">
|
|
1436
|
+
{{ messages.missionQuantity }} {{ group.intent.quantity }}
|
|
1437
|
+
</span>
|
|
1438
|
+
}
|
|
1439
|
+
</div>
|
|
1440
|
+
|
|
1441
|
+
@if (group.failed) {
|
|
1442
|
+
<div class="cat-status cat-status--empty" role="status">
|
|
1443
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
1444
|
+
<circle cx="11" cy="11" r="8" />
|
|
1445
|
+
<path d="m21 21-4.3-4.3" />
|
|
1446
|
+
<path d="m13 9-4 4" />
|
|
1447
|
+
<path d="m9 9 4 4" />
|
|
1448
|
+
</svg>
|
|
1449
|
+
<div class="cat-status__content">
|
|
1450
|
+
<div class="cat-status__title">{{ messages.missionIntentFailed }}</div>
|
|
1451
|
+
</div>
|
|
1452
|
+
</div>
|
|
1453
|
+
} @else if (group.products.length === 0) {
|
|
1454
|
+
<div class="cat-status cat-status--empty" role="status">
|
|
1455
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
1456
|
+
<circle cx="11" cy="11" r="8" />
|
|
1457
|
+
<path d="m21 21-4.3-4.3" />
|
|
1458
|
+
<path d="m13 9-4 4" />
|
|
1459
|
+
<path d="m9 9 4 4" />
|
|
1460
|
+
</svg>
|
|
1461
|
+
<div class="cat-status__content">
|
|
1462
|
+
<div class="cat-status__title">{{ messages.missionIntentEmpty }}</div>
|
|
1463
|
+
</div>
|
|
1464
|
+
</div>
|
|
1465
|
+
} @else {
|
|
1466
|
+
<div class="cat-mission-group__products">
|
|
1467
|
+
@for (product of group.products; track product.id; let index = $index) {
|
|
1468
|
+
<article
|
|
1469
|
+
class="cat-result-card"
|
|
1470
|
+
[class.cat-result-card--primary]="index === 0"
|
|
1471
|
+
[class.cat-result-card--compact]="index !== 0"
|
|
1472
|
+
>
|
|
1473
|
+
<div class="cat-result-card__core">
|
|
1474
|
+
@if (productSelect.observed) {
|
|
1475
|
+
<button type="button" class="cat-result-card__select" (click)="productSelect.emit(product)">
|
|
1476
|
+
<ng-container [ngTemplateOutlet]="cardBody" [ngTemplateOutletContext]="{ $implicit: product }" />
|
|
1477
|
+
</button>
|
|
1478
|
+
} @else {
|
|
1479
|
+
<div class="cat-result-card__select">
|
|
1480
|
+
<ng-container [ngTemplateOutlet]="cardBody" [ngTemplateOutletContext]="{ $implicit: product }" />
|
|
1481
|
+
</div>
|
|
1482
|
+
}
|
|
1483
|
+
@if (enableCart) {
|
|
1484
|
+
<button
|
|
1485
|
+
type="button"
|
|
1486
|
+
class="cat-icon-btn cat-result-card__add"
|
|
1487
|
+
[class.cat-result-card__add--added]="addedProductIds[product.id]"
|
|
1488
|
+
[attr.aria-label]="addLabel(product)"
|
|
1489
|
+
[disabled]="!(product.sku || product.id) || isMutating"
|
|
1490
|
+
(click)="addItem.emit(product)"
|
|
1491
|
+
>
|
|
1492
|
+
@if (addedProductIds[product.id]) {
|
|
1493
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
1494
|
+
<path d="M20 6 9 17l-5-5" />
|
|
1495
|
+
</svg>
|
|
1496
|
+
} @else {
|
|
1497
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
1498
|
+
<circle cx="8" cy="21" r="1" />
|
|
1499
|
+
<circle cx="19" cy="21" r="1" />
|
|
1500
|
+
<path d="M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12" />
|
|
1501
|
+
</svg>
|
|
1502
|
+
}
|
|
1503
|
+
</button>
|
|
1504
|
+
}
|
|
1505
|
+
</div>
|
|
1506
|
+
</article>
|
|
1507
|
+
}
|
|
1508
|
+
</div>
|
|
1509
|
+
}
|
|
1510
|
+
</section>
|
|
1511
|
+
}
|
|
1512
|
+
</div>
|
|
1513
|
+
|
|
1514
|
+
@if (enableCart) {
|
|
1515
|
+
<div class="cat-mission__footer">
|
|
1516
|
+
<button
|
|
1517
|
+
type="button"
|
|
1518
|
+
class="cat-checkout-cta cat-mission__add-all"
|
|
1519
|
+
[class.cat-mission__add-all--added]="justAddedAll"
|
|
1520
|
+
[disabled]="isMutating || bundleItems.length === 0"
|
|
1521
|
+
(click)="onAddAllClick()"
|
|
1522
|
+
>
|
|
1523
|
+
{{ justAddedAll ? messages.missionItemsAdded : addAllLabel }}
|
|
1524
|
+
</button>
|
|
1525
|
+
</div>
|
|
1526
|
+
}
|
|
1527
|
+
</div>
|
|
1528
|
+
|
|
1529
|
+
<ng-template #cardBody let-product>
|
|
1530
|
+
@if (product.imageUrl) {
|
|
1531
|
+
<img [src]="product.imageUrl" alt="" class="cat-result-image" loading="lazy" />
|
|
1532
|
+
} @else {
|
|
1533
|
+
<div class="cat-result-image cat-result-image--placeholder">
|
|
1534
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
1535
|
+
<path d="M16.5 9.4 7.55 4.24" />
|
|
1536
|
+
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
|
|
1537
|
+
<polyline points="3.29 7 12 12 20.71 7" />
|
|
1538
|
+
<line x1="12" x2="12" y1="22" y2="12" />
|
|
1539
|
+
</svg>
|
|
1540
|
+
</div>
|
|
1541
|
+
}
|
|
1542
|
+
<div class="cat-result-info">
|
|
1543
|
+
<div class="cat-result-name">{{ product.name }}</div>
|
|
1544
|
+
@if (product.price) {
|
|
1545
|
+
<div class="cat-result-price">{{ product.price.formatted }}</div>
|
|
1546
|
+
}
|
|
1547
|
+
</div>
|
|
1548
|
+
</ng-template>
|
|
1549
|
+
`
|
|
1550
|
+
})
|
|
1551
|
+
], CommerceAiMissionResultsComponent);
|
|
1552
|
+
|
|
1553
|
+
// src/lib/camera.util.ts
|
|
1554
|
+
function prefersNativeCamera() {
|
|
1555
|
+
if (typeof navigator === "undefined") return false;
|
|
1556
|
+
return /Android|iPhone|iPad|iPod/i.test(navigator.userAgent) || navigator.maxTouchPoints > 1 && matchMedia("(pointer: coarse)").matches;
|
|
1557
|
+
}
|
|
1558
|
+
__name(prefersNativeCamera, "prefersNativeCamera");
|
|
1559
|
+
function stopMediaStream(stream) {
|
|
1560
|
+
stream?.getTracks().forEach((track) => track.stop());
|
|
1561
|
+
}
|
|
1562
|
+
__name(stopMediaStream, "stopMediaStream");
|
|
1563
|
+
function getCameraErrorMessage(error) {
|
|
1564
|
+
if (error instanceof DOMException) {
|
|
1565
|
+
if (error.name === "NotAllowedError" || error.name === "PermissionDeniedError") {
|
|
1566
|
+
return "Camera access denied";
|
|
1567
|
+
}
|
|
1568
|
+
if (error.name === "NotFoundError" || error.name === "DevicesNotFoundError") {
|
|
1569
|
+
return "No camera found";
|
|
1570
|
+
}
|
|
1571
|
+
if (error.name === "NotReadableError" || error.name === "TrackStartError") {
|
|
1572
|
+
return "Camera is already in use";
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
return "Camera unavailable";
|
|
1576
|
+
}
|
|
1577
|
+
__name(getCameraErrorMessage, "getCameraErrorMessage");
|
|
1578
|
+
function createJpegFileFromVideo(video, fileName = "camera-capture.jpg") {
|
|
1579
|
+
const width = video.videoWidth;
|
|
1580
|
+
const height = video.videoHeight;
|
|
1581
|
+
if (!width || !height) {
|
|
1582
|
+
return Promise.reject(new Error("Camera preview is not ready"));
|
|
1583
|
+
}
|
|
1584
|
+
const canvas = document.createElement("canvas");
|
|
1585
|
+
canvas.width = width;
|
|
1586
|
+
canvas.height = height;
|
|
1587
|
+
const context = canvas.getContext("2d");
|
|
1588
|
+
if (!context) {
|
|
1589
|
+
return Promise.reject(new Error("Could not capture photo"));
|
|
1590
|
+
}
|
|
1591
|
+
context.drawImage(video, 0, 0, width, height);
|
|
1592
|
+
return new Promise((resolve, reject) => {
|
|
1593
|
+
canvas.toBlob((blob) => {
|
|
1594
|
+
if (!blob) {
|
|
1595
|
+
reject(new Error("Could not capture photo"));
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
resolve(new File([
|
|
1599
|
+
blob
|
|
1600
|
+
], fileName, {
|
|
1601
|
+
type: "image/jpeg"
|
|
1602
|
+
}));
|
|
1603
|
+
}, "image/jpeg", 0.92);
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
__name(createJpegFileFromVideo, "createJpegFileFromVideo");
|
|
1607
|
+
function buildCameraConstraints(facingMode) {
|
|
1608
|
+
return {
|
|
1609
|
+
video: {
|
|
1610
|
+
facingMode: {
|
|
1611
|
+
ideal: facingMode
|
|
1612
|
+
},
|
|
1613
|
+
width: {
|
|
1614
|
+
ideal: 1920
|
|
1615
|
+
},
|
|
1616
|
+
height: {
|
|
1617
|
+
ideal: 1080
|
|
1618
|
+
}
|
|
1619
|
+
},
|
|
1620
|
+
audio: false
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
__name(buildCameraConstraints, "buildCameraConstraints");
|
|
1624
|
+
|
|
1625
|
+
// src/lib/commerce-ai-search.component.ts
|
|
1626
|
+
function _ts_decorate5(decorators, target, key, desc) {
|
|
1627
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1628
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
1629
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
1630
|
+
} else {
|
|
1631
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
1632
|
+
if (d = decorators[i]) {
|
|
1633
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1638
|
+
}
|
|
1639
|
+
__name(_ts_decorate5, "_ts_decorate");
|
|
1640
|
+
function _ts_metadata3(metadataKey, metadataValue) {
|
|
1641
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
1642
|
+
return Reflect.metadata(metadataKey, metadataValue);
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
__name(_ts_metadata3, "_ts_metadata");
|
|
1646
|
+
var CommerceAiSearchComponent = class {
|
|
1647
|
+
static {
|
|
1648
|
+
__name(this, "CommerceAiSearchComponent");
|
|
1649
|
+
}
|
|
1650
|
+
api = inject(CommerceAiApiService);
|
|
1651
|
+
cdr = inject(ChangeDetectorRef);
|
|
1652
|
+
cart = inject(CommerceAiCartService);
|
|
1653
|
+
apiBaseUrl = "/api/commerce-ai";
|
|
1654
|
+
theme = "auto";
|
|
1655
|
+
catalogLocale;
|
|
1656
|
+
queryLocale;
|
|
1657
|
+
/** @deprecated Use queryLocale */
|
|
1658
|
+
locale;
|
|
1659
|
+
placeholder = "What are you looking for?";
|
|
1660
|
+
messages;
|
|
1661
|
+
enableAutocomplete = false;
|
|
1662
|
+
enableFacets = false;
|
|
1663
|
+
/** Show microphone search controls. */
|
|
1664
|
+
enableVoice = true;
|
|
1665
|
+
/** Show local image upload and drag-and-drop search controls. */
|
|
1666
|
+
enableImageSearch = true;
|
|
1667
|
+
/** Show camera capture search controls independently of image upload. */
|
|
1668
|
+
enableCameraSearch = true;
|
|
1669
|
+
cameraFacingMode = "environment";
|
|
1670
|
+
enableTts = true;
|
|
1671
|
+
/** Enable built-in add-to-cart buttons and cart preview panel. Default false. */
|
|
1672
|
+
enableCart = false;
|
|
1673
|
+
/** Enable multi-item shopping missions on text, voice, and image search. Default false. */
|
|
1674
|
+
enableMissions = false;
|
|
1675
|
+
/** Currency for cart creation. Required when `enableCart` is true unless the server has a default. */
|
|
1676
|
+
currency;
|
|
1677
|
+
/** Optional ISO country code used for commercetools price selection. */
|
|
1678
|
+
country;
|
|
1679
|
+
productSelect = new EventEmitter3();
|
|
1680
|
+
/** Fires after every cart fetch or mutation when `enableCart` is true. */
|
|
1681
|
+
cartChange = new EventEmitter3();
|
|
1682
|
+
/** Opens the host-owned checkout route for the current non-empty cart. */
|
|
1683
|
+
checkout = new EventEmitter3();
|
|
1684
|
+
cameraInputRef;
|
|
1685
|
+
cameraVideoRef;
|
|
1686
|
+
query = "";
|
|
1687
|
+
suggestions = [];
|
|
1688
|
+
activeSuggestionIndex = -1;
|
|
1689
|
+
isLoadingSuggestions = false;
|
|
1690
|
+
suggestionsError = null;
|
|
1691
|
+
suggestionsReady = false;
|
|
1692
|
+
suggestionsDismissed = false;
|
|
1693
|
+
results = [];
|
|
1694
|
+
mission = null;
|
|
1695
|
+
hasFacetSession = false;
|
|
1696
|
+
addedProductIds = {};
|
|
1697
|
+
meta = null;
|
|
1698
|
+
facets = [];
|
|
1699
|
+
suggestedFacets = [];
|
|
1700
|
+
isLoading = false;
|
|
1701
|
+
hasSearched = false;
|
|
1702
|
+
error = null;
|
|
1703
|
+
isDragging = false;
|
|
1704
|
+
isRecording = false;
|
|
1705
|
+
isProcessing = false;
|
|
1706
|
+
isCameraOpen = false;
|
|
1707
|
+
cameraError = null;
|
|
1708
|
+
lastSearchMode = null;
|
|
1709
|
+
audioSummary = null;
|
|
1710
|
+
mediaRecorder = null;
|
|
1711
|
+
audioChunks = [];
|
|
1712
|
+
cameraStream = null;
|
|
1713
|
+
debounceTimer = null;
|
|
1714
|
+
suggestionsTimer = null;
|
|
1715
|
+
searchAbort = null;
|
|
1716
|
+
suggestionsAbort = null;
|
|
1717
|
+
searchRequestId = 0;
|
|
1718
|
+
suggestionsRequestId = 0;
|
|
1719
|
+
addedTimeouts = /* @__PURE__ */ new Map();
|
|
1720
|
+
destroyed = false;
|
|
1721
|
+
addMissionItems = /* @__PURE__ */ __name((items) => this.cart.addItems(items), "addMissionItems");
|
|
1722
|
+
ngOnInit() {
|
|
1723
|
+
this.syncCart();
|
|
1724
|
+
}
|
|
1725
|
+
ngOnChanges() {
|
|
1726
|
+
this.syncCart();
|
|
1727
|
+
}
|
|
1728
|
+
ngOnDestroy() {
|
|
1729
|
+
this.destroyed = true;
|
|
1730
|
+
for (const timeout of this.addedTimeouts.values()) {
|
|
1731
|
+
clearTimeout(timeout);
|
|
1732
|
+
}
|
|
1733
|
+
this.closeCamera();
|
|
1734
|
+
this.searchAbort?.abort();
|
|
1735
|
+
this.suggestionsAbort?.abort();
|
|
1736
|
+
}
|
|
1737
|
+
get resolvedMessages() {
|
|
1738
|
+
return resolveCommerceAISearchMessages({
|
|
1739
|
+
...this.messages,
|
|
1740
|
+
placeholder: this.messages?.placeholder ?? this.placeholder
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
get showSuggestions() {
|
|
1744
|
+
return this.enableAutocomplete && !this.suggestionsDismissed && !this.showResults && this.query.trim().length >= 2 && (this.isLoadingSuggestions || this.suggestions.length > 0 || !!this.suggestionsError || this.suggestionsReady);
|
|
1745
|
+
}
|
|
1746
|
+
get activeSuggestionId() {
|
|
1747
|
+
return this.activeSuggestionIndex >= 0 ? `cat-suggestion-${this.activeSuggestionIndex}` : null;
|
|
1748
|
+
}
|
|
1749
|
+
get showMission() {
|
|
1750
|
+
return Boolean(this.mission);
|
|
1751
|
+
}
|
|
1752
|
+
get showMissionVisible() {
|
|
1753
|
+
return this.showMission && !this.isLoading && !this.error;
|
|
1754
|
+
}
|
|
1755
|
+
get missionLaneCount() {
|
|
1756
|
+
if (!this.showMissionVisible) {
|
|
1757
|
+
return 0;
|
|
1758
|
+
}
|
|
1759
|
+
return Math.min(Math.max(this.mission?.intents.length ?? 1, 1), 5);
|
|
1760
|
+
}
|
|
1761
|
+
get cartQuantity() {
|
|
1762
|
+
return this.cart.cart()?.totalQuantity ?? 0;
|
|
1763
|
+
}
|
|
1764
|
+
get cartBadgeLabel() {
|
|
1765
|
+
return this.cartQuantity > 99 ? "99+" : String(this.cartQuantity);
|
|
1766
|
+
}
|
|
1767
|
+
get cartAriaLabel() {
|
|
1768
|
+
return this.cartQuantity > 0 ? `${this.resolvedMessages.cartAriaLabel} (${this.cartBadgeLabel})` : this.resolvedMessages.cartAriaLabel;
|
|
1769
|
+
}
|
|
1770
|
+
get showResults() {
|
|
1771
|
+
const hasQuery = this.query.trim().length > 0;
|
|
1772
|
+
const showEmptyResults = !this.isLoading && !this.error && this.hasSearched && this.results.length === 0 && !this.showMission && hasQuery;
|
|
1773
|
+
return hasQuery && (this.results.length > 0 || this.isLoading || !!this.error || showEmptyResults || this.showMission);
|
|
1774
|
+
}
|
|
1775
|
+
get showVoiceReplay() {
|
|
1776
|
+
return this.enableTts && this.lastSearchMode === "voice" && !!this.audioSummary;
|
|
1777
|
+
}
|
|
1778
|
+
onQueryChange(value) {
|
|
1779
|
+
this.activeSuggestionIndex = -1;
|
|
1780
|
+
this.suggestionsDismissed = false;
|
|
1781
|
+
if (this.debounceTimer) {
|
|
1782
|
+
clearTimeout(this.debounceTimer);
|
|
1783
|
+
this.debounceTimer = null;
|
|
1784
|
+
}
|
|
1785
|
+
if (this.suggestionsTimer) {
|
|
1786
|
+
clearTimeout(this.suggestionsTimer);
|
|
1787
|
+
this.suggestionsTimer = null;
|
|
1788
|
+
}
|
|
1789
|
+
const trimmed = value.trim();
|
|
1790
|
+
if (trimmed.length === 0 || trimmed.length < 2) {
|
|
1791
|
+
this.lastSearchMode = null;
|
|
1792
|
+
this.clearVoiceAudio();
|
|
1793
|
+
this.clearResults();
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
this.lastSearchMode = "text";
|
|
1797
|
+
this.clearVoiceAudio();
|
|
1798
|
+
if (this.enableAutocomplete) {
|
|
1799
|
+
this.results = [];
|
|
1800
|
+
this.mission = null;
|
|
1801
|
+
this.error = null;
|
|
1802
|
+
this.isLoading = false;
|
|
1803
|
+
this.hasSearched = false;
|
|
1804
|
+
this.isLoadingSuggestions = true;
|
|
1805
|
+
this.suggestionsTimer = setTimeout(() => this.fetchSuggestions(trimmed), 200);
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
this.isLoading = true;
|
|
1809
|
+
this.error = null;
|
|
1810
|
+
this.results = [];
|
|
1811
|
+
this.mission = null;
|
|
1812
|
+
this.meta = null;
|
|
1813
|
+
this.debounceTimer = setTimeout(() => this.onSubmit(), 250);
|
|
1814
|
+
}
|
|
1815
|
+
onSuggestionSelect(suggestion) {
|
|
1816
|
+
this.activeSuggestionIndex = -1;
|
|
1817
|
+
this.query = suggestion;
|
|
1818
|
+
this.suggestions = [];
|
|
1819
|
+
this.onSubmit();
|
|
1820
|
+
}
|
|
1821
|
+
onInputKeyDown(event) {
|
|
1822
|
+
if (event.key === "Escape") {
|
|
1823
|
+
this.activeSuggestionIndex = -1;
|
|
1824
|
+
this.suggestionsDismissed = true;
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
if (!this.showSuggestions || this.suggestions.length === 0) {
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
if (event.key === "ArrowDown") {
|
|
1831
|
+
event.preventDefault();
|
|
1832
|
+
this.activeSuggestionIndex = this.activeSuggestionIndex < this.suggestions.length - 1 ? this.activeSuggestionIndex + 1 : 0;
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
if (event.key === "ArrowUp") {
|
|
1836
|
+
event.preventDefault();
|
|
1837
|
+
this.activeSuggestionIndex = this.activeSuggestionIndex > 0 ? this.activeSuggestionIndex - 1 : this.suggestions.length - 1;
|
|
1838
|
+
return;
|
|
1839
|
+
}
|
|
1840
|
+
if (event.key === "Enter" && this.activeSuggestionIndex >= 0) {
|
|
1841
|
+
event.preventDefault();
|
|
1842
|
+
const suggestion = this.suggestions[this.activeSuggestionIndex];
|
|
1843
|
+
if (suggestion) {
|
|
1844
|
+
this.onSuggestionSelect(suggestion);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
fetchSuggestions(searchQuery) {
|
|
1849
|
+
this.suggestionsAbort?.abort();
|
|
1850
|
+
const controller = new AbortController();
|
|
1851
|
+
this.suggestionsAbort = controller;
|
|
1852
|
+
const requestId = ++this.suggestionsRequestId;
|
|
1853
|
+
this.isLoadingSuggestions = true;
|
|
1854
|
+
this.suggestionsError = null;
|
|
1855
|
+
this.suggestionsReady = false;
|
|
1856
|
+
void this.api.suggest(this.apiBaseUrl, searchQuery, this.localeFields, controller.signal).then((data) => {
|
|
1857
|
+
if (requestId !== this.suggestionsRequestId) return;
|
|
1858
|
+
this.suggestions = data.suggestions;
|
|
1859
|
+
this.isLoadingSuggestions = false;
|
|
1860
|
+
this.suggestionsReady = true;
|
|
1861
|
+
this.cdr.detectChanges();
|
|
1862
|
+
}).catch((err) => {
|
|
1863
|
+
if (err.name === "AbortError") return;
|
|
1864
|
+
if (requestId !== this.suggestionsRequestId) return;
|
|
1865
|
+
this.suggestions = [];
|
|
1866
|
+
this.suggestionsError = err.message || "Suggestions failed";
|
|
1867
|
+
this.isLoadingSuggestions = false;
|
|
1868
|
+
this.suggestionsReady = true;
|
|
1869
|
+
this.cdr.detectChanges();
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
clearResults() {
|
|
1873
|
+
this.searchAbort?.abort();
|
|
1874
|
+
this.searchAbort = null;
|
|
1875
|
+
this.suggestionsAbort?.abort();
|
|
1876
|
+
this.suggestionsAbort = null;
|
|
1877
|
+
if (this.debounceTimer) {
|
|
1878
|
+
clearTimeout(this.debounceTimer);
|
|
1879
|
+
this.debounceTimer = null;
|
|
1880
|
+
}
|
|
1881
|
+
if (this.suggestionsTimer) {
|
|
1882
|
+
clearTimeout(this.suggestionsTimer);
|
|
1883
|
+
this.suggestionsTimer = null;
|
|
1884
|
+
}
|
|
1885
|
+
this.results = [];
|
|
1886
|
+
this.mission = null;
|
|
1887
|
+
this.hasFacetSession = false;
|
|
1888
|
+
this.meta = null;
|
|
1889
|
+
this.error = null;
|
|
1890
|
+
this.isLoading = false;
|
|
1891
|
+
this.hasSearched = false;
|
|
1892
|
+
this.suggestions = [];
|
|
1893
|
+
this.isLoadingSuggestions = false;
|
|
1894
|
+
this.suggestionsError = null;
|
|
1895
|
+
this.suggestionsReady = false;
|
|
1896
|
+
this.suggestionsDismissed = false;
|
|
1897
|
+
this.activeSuggestionIndex = -1;
|
|
1898
|
+
}
|
|
1899
|
+
get localeFields() {
|
|
1900
|
+
return {
|
|
1901
|
+
catalogLocale: this.catalogLocale,
|
|
1902
|
+
queryLocale: this.queryLocale,
|
|
1903
|
+
locale: this.locale
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
isFacetSelected(facetId, key) {
|
|
1907
|
+
return isFacetFilterSelected(this.meta?.appliedFilters ?? {}, facetId, key);
|
|
1908
|
+
}
|
|
1909
|
+
facetSwatch(facetId, key) {
|
|
1910
|
+
return isColorLikeFacetName(facetId) ? hexColorSwatchValue(key) : void 0;
|
|
1911
|
+
}
|
|
1912
|
+
get hasAppliedFilters() {
|
|
1913
|
+
return Object.keys(this.meta?.appliedFilters ?? {}).length > 0;
|
|
1914
|
+
}
|
|
1915
|
+
toggleFacet(facetId, key) {
|
|
1916
|
+
if (!this.meta?.searchTerms) return;
|
|
1917
|
+
const filters = toggleFacetFilter(this.meta.appliedFilters ?? {}, facetId, key);
|
|
1918
|
+
this.searchWithFacets(filters);
|
|
1919
|
+
}
|
|
1920
|
+
clearFacetFilters() {
|
|
1921
|
+
if (!this.meta?.searchTerms) return;
|
|
1922
|
+
this.searchWithFacets({});
|
|
1923
|
+
}
|
|
1924
|
+
startNewSearch() {
|
|
1925
|
+
const q = this.query.trim();
|
|
1926
|
+
if (!q) return;
|
|
1927
|
+
this.suggestedFacets = [];
|
|
1928
|
+
this.meta = this.meta ? {
|
|
1929
|
+
...this.meta,
|
|
1930
|
+
searchTerms: void 0,
|
|
1931
|
+
appliedFilters: void 0
|
|
1932
|
+
} : null;
|
|
1933
|
+
this.lastSearchMode = "text";
|
|
1934
|
+
this.clearVoiceAudio();
|
|
1935
|
+
this.suggestions = [];
|
|
1936
|
+
this.activeSuggestionIndex = -1;
|
|
1937
|
+
this.searchAbort?.abort();
|
|
1938
|
+
const controller = new AbortController();
|
|
1939
|
+
this.searchAbort = controller;
|
|
1940
|
+
const requestId = ++this.searchRequestId;
|
|
1941
|
+
this.isLoading = true;
|
|
1942
|
+
this.error = null;
|
|
1943
|
+
this.results = [];
|
|
1944
|
+
this.mission = null;
|
|
1945
|
+
void this.api.search(this.apiBaseUrl, q, this.localeFields, controller.signal, {
|
|
1946
|
+
includeFacets: this.enableFacets,
|
|
1947
|
+
...this.enableMissions ? {
|
|
1948
|
+
enableMissions: true
|
|
1949
|
+
} : {}
|
|
1950
|
+
}).then((data) => {
|
|
1951
|
+
if (requestId !== this.searchRequestId) return;
|
|
1952
|
+
this.applySearchResult(data);
|
|
1953
|
+
this.isLoading = false;
|
|
1954
|
+
this.hasSearched = true;
|
|
1955
|
+
this.cdr.detectChanges();
|
|
1956
|
+
}).catch((err) => {
|
|
1957
|
+
if (err.name === "AbortError") return;
|
|
1958
|
+
if (requestId !== this.searchRequestId) return;
|
|
1959
|
+
this.error = err.message;
|
|
1960
|
+
this.results = [];
|
|
1961
|
+
this.mission = null;
|
|
1962
|
+
this.hasFacetSession = false;
|
|
1963
|
+
this.meta = null;
|
|
1964
|
+
this.isLoading = false;
|
|
1965
|
+
this.hasSearched = true;
|
|
1966
|
+
this.cdr.detectChanges();
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
searchWithFacets(filters, refineQuery) {
|
|
1970
|
+
if (!this.meta?.searchTerms) return;
|
|
1971
|
+
this.isLoading = true;
|
|
1972
|
+
this.error = null;
|
|
1973
|
+
const controller = new AbortController();
|
|
1974
|
+
this.searchAbort?.abort();
|
|
1975
|
+
this.searchAbort = controller;
|
|
1976
|
+
const requestId = ++this.searchRequestId;
|
|
1977
|
+
void this.api.search(this.apiBaseUrl, this.meta.queryInterpretation ?? this.query, this.localeFields, controller.signal, {
|
|
1978
|
+
filters,
|
|
1979
|
+
searchTerms: this.meta.searchTerms,
|
|
1980
|
+
refineQuery,
|
|
1981
|
+
suggestedFacets: this.suggestedFacets,
|
|
1982
|
+
includeFacets: true
|
|
1983
|
+
}).then((data) => {
|
|
1984
|
+
if (requestId !== this.searchRequestId) return;
|
|
1985
|
+
this.applySearchResult(data);
|
|
1986
|
+
this.isLoading = false;
|
|
1987
|
+
this.hasSearched = true;
|
|
1988
|
+
this.cdr.detectChanges();
|
|
1989
|
+
}).catch((error) => {
|
|
1990
|
+
if (requestId !== this.searchRequestId || error.name === "AbortError") return;
|
|
1991
|
+
this.error = error.message;
|
|
1992
|
+
this.isLoading = false;
|
|
1993
|
+
this.cdr.detectChanges();
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
onSubmit() {
|
|
1997
|
+
const q = this.query.trim();
|
|
1998
|
+
if (!q) return;
|
|
1999
|
+
if (this.enableAutocomplete && this.activeSuggestionIndex >= 0 && this.suggestions[this.activeSuggestionIndex]) {
|
|
2000
|
+
this.onSuggestionSelect(this.suggestions[this.activeSuggestionIndex]);
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
this.lastSearchMode = "text";
|
|
2004
|
+
this.clearVoiceAudio();
|
|
2005
|
+
this.suggestions = [];
|
|
2006
|
+
this.activeSuggestionIndex = -1;
|
|
2007
|
+
this.searchAbort?.abort();
|
|
2008
|
+
const controller = new AbortController();
|
|
2009
|
+
this.searchAbort = controller;
|
|
2010
|
+
const requestId = ++this.searchRequestId;
|
|
2011
|
+
this.isLoading = true;
|
|
2012
|
+
this.error = null;
|
|
2013
|
+
this.results = [];
|
|
2014
|
+
this.mission = null;
|
|
2015
|
+
const previousMeta = this.meta;
|
|
2016
|
+
this.meta = null;
|
|
2017
|
+
const isRefinement = this.enableFacets && this.hasFacetSession && !(this.enableMissions && looksLikeCompoundShoppingList(q));
|
|
2018
|
+
void this.api.search(this.apiBaseUrl, isRefinement ? previousMeta?.queryInterpretation ?? q : q, this.localeFields, controller.signal, isRefinement ? {
|
|
2019
|
+
searchTerms: previousMeta?.searchTerms,
|
|
2020
|
+
filters: previousMeta?.appliedFilters,
|
|
2021
|
+
refineQuery: q,
|
|
2022
|
+
suggestedFacets: this.suggestedFacets,
|
|
2023
|
+
includeFacets: true
|
|
2024
|
+
} : {
|
|
2025
|
+
includeFacets: this.enableFacets,
|
|
2026
|
+
...this.enableMissions ? {
|
|
2027
|
+
enableMissions: true
|
|
2028
|
+
} : {}
|
|
2029
|
+
}).then((data) => {
|
|
2030
|
+
if (requestId !== this.searchRequestId) return;
|
|
2031
|
+
this.applySearchResult(data);
|
|
2032
|
+
this.isLoading = false;
|
|
2033
|
+
this.hasSearched = true;
|
|
2034
|
+
}, (err) => {
|
|
2035
|
+
if (err.name === "AbortError") return;
|
|
2036
|
+
if (requestId !== this.searchRequestId) return;
|
|
2037
|
+
this.error = err.message;
|
|
2038
|
+
this.results = [];
|
|
2039
|
+
this.mission = null;
|
|
2040
|
+
this.hasFacetSession = false;
|
|
2041
|
+
this.meta = null;
|
|
2042
|
+
this.isLoading = false;
|
|
2043
|
+
this.hasSearched = true;
|
|
2044
|
+
});
|
|
2045
|
+
}
|
|
2046
|
+
onFileSelected(event) {
|
|
2047
|
+
const input = event.target;
|
|
2048
|
+
const file = input.files?.[0];
|
|
2049
|
+
if (file) void this.searchByImage(file);
|
|
2050
|
+
}
|
|
2051
|
+
onCameraFileSelected(event) {
|
|
2052
|
+
const input = event.target;
|
|
2053
|
+
const file = input.files?.[0];
|
|
2054
|
+
input.value = "";
|
|
2055
|
+
if (file) void this.searchByImage(file);
|
|
2056
|
+
}
|
|
2057
|
+
openCamera() {
|
|
2058
|
+
this.cameraError = null;
|
|
2059
|
+
if (prefersNativeCamera()) {
|
|
2060
|
+
this.cameraInputRef?.nativeElement.click();
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
void this.openCameraOverlay();
|
|
2064
|
+
}
|
|
2065
|
+
async openCameraOverlay() {
|
|
2066
|
+
this.cameraError = null;
|
|
2067
|
+
try {
|
|
2068
|
+
const stream = await navigator.mediaDevices.getUserMedia(buildCameraConstraints(this.cameraFacingMode));
|
|
2069
|
+
this.cameraStream = stream;
|
|
2070
|
+
this.isCameraOpen = true;
|
|
2071
|
+
this.cdr.detectChanges();
|
|
2072
|
+
const video = this.cameraVideoRef?.nativeElement;
|
|
2073
|
+
if (video) {
|
|
2074
|
+
video.srcObject = stream;
|
|
2075
|
+
await video.play();
|
|
2076
|
+
}
|
|
2077
|
+
} catch (err) {
|
|
2078
|
+
this.cameraError = getCameraErrorMessage(err);
|
|
2079
|
+
this.closeCamera();
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
async capturePhoto() {
|
|
2083
|
+
const video = this.cameraVideoRef?.nativeElement;
|
|
2084
|
+
if (!video) return;
|
|
2085
|
+
try {
|
|
2086
|
+
const file = await createJpegFileFromVideo(video);
|
|
2087
|
+
this.closeCamera();
|
|
2088
|
+
void this.searchByImage(file);
|
|
2089
|
+
} catch (err) {
|
|
2090
|
+
this.closeCamera();
|
|
2091
|
+
this.error = err instanceof Error ? err.message : this.resolvedMessages.couldNotCapturePhoto;
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
closeCamera() {
|
|
2095
|
+
stopMediaStream(this.cameraStream);
|
|
2096
|
+
this.cameraStream = null;
|
|
2097
|
+
this.isCameraOpen = false;
|
|
2098
|
+
const video = this.cameraVideoRef?.nativeElement;
|
|
2099
|
+
if (video) {
|
|
2100
|
+
video.srcObject = null;
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
clearCameraError() {
|
|
2104
|
+
this.cameraError = null;
|
|
2105
|
+
}
|
|
2106
|
+
onDragOver(event) {
|
|
2107
|
+
if (!this.enableImageSearch) {
|
|
2108
|
+
return;
|
|
2109
|
+
}
|
|
2110
|
+
event.preventDefault();
|
|
2111
|
+
this.isDragging = true;
|
|
2112
|
+
}
|
|
2113
|
+
onDrop(event) {
|
|
2114
|
+
event.preventDefault();
|
|
2115
|
+
this.isDragging = false;
|
|
2116
|
+
if (!this.enableImageSearch) {
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
const file = event.dataTransfer?.files[0];
|
|
2120
|
+
if (file) void this.searchByImage(file);
|
|
2121
|
+
}
|
|
2122
|
+
searchByImage(file) {
|
|
2123
|
+
if (!file.type.startsWith("image/")) return;
|
|
2124
|
+
this.lastSearchMode = "image";
|
|
2125
|
+
this.clearVoiceAudio();
|
|
2126
|
+
this.searchAbort?.abort();
|
|
2127
|
+
if (this.debounceTimer) {
|
|
2128
|
+
clearTimeout(this.debounceTimer);
|
|
2129
|
+
this.debounceTimer = null;
|
|
2130
|
+
}
|
|
2131
|
+
this.isLoading = true;
|
|
2132
|
+
this.error = null;
|
|
2133
|
+
this.results = [];
|
|
2134
|
+
this.mission = null;
|
|
2135
|
+
this.meta = null;
|
|
2136
|
+
void this.api.searchByImage(this.apiBaseUrl, file, this.localeFields, this.enableMissions).then((data) => {
|
|
2137
|
+
this.applySearchResult(data);
|
|
2138
|
+
if (data.interpretation) this.query = data.interpretation;
|
|
2139
|
+
this.isLoading = false;
|
|
2140
|
+
this.hasSearched = true;
|
|
2141
|
+
}, (err) => {
|
|
2142
|
+
this.error = err.message;
|
|
2143
|
+
this.isLoading = false;
|
|
2144
|
+
this.hasSearched = true;
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
async toggleRecording() {
|
|
2148
|
+
if (this.isRecording) {
|
|
2149
|
+
this.mediaRecorder?.stop();
|
|
2150
|
+
this.isRecording = false;
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
try {
|
|
2154
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
2155
|
+
audio: true
|
|
2156
|
+
});
|
|
2157
|
+
this.mediaRecorder = new MediaRecorder(stream);
|
|
2158
|
+
this.audioChunks = [];
|
|
2159
|
+
this.mediaRecorder.ondataavailable = (e) => {
|
|
2160
|
+
if (e.data.size > 0) this.audioChunks.push(e.data);
|
|
2161
|
+
};
|
|
2162
|
+
this.mediaRecorder.onstop = () => {
|
|
2163
|
+
stream.getTracks().forEach((t) => t.stop());
|
|
2164
|
+
const blob = new Blob(this.audioChunks, {
|
|
2165
|
+
type: "audio/webm"
|
|
2166
|
+
});
|
|
2167
|
+
this.isProcessing = true;
|
|
2168
|
+
this.clearVoiceAudio();
|
|
2169
|
+
void this.api.searchByVoice(this.apiBaseUrl, blob, this.localeFields, this.enableTts, this.enableMissions).then(async (data) => {
|
|
2170
|
+
this.lastSearchMode = "voice";
|
|
2171
|
+
this.query = data.transcript;
|
|
2172
|
+
this.applySearchResult({
|
|
2173
|
+
products: data.products,
|
|
2174
|
+
meta: data.meta,
|
|
2175
|
+
mission: data.mission
|
|
2176
|
+
});
|
|
2177
|
+
this.error = null;
|
|
2178
|
+
this.hasSearched = true;
|
|
2179
|
+
if (data.audioSummary) {
|
|
2180
|
+
this.audioSummary = data.audioSummary;
|
|
2181
|
+
const audio = new Audio(`data:audio/mpeg;base64,${data.audioSummary}`);
|
|
2182
|
+
void audio.play();
|
|
2183
|
+
} else if (this.enableTts && data.ttsText && data.ttsPending) {
|
|
2184
|
+
try {
|
|
2185
|
+
const blob2 = await this.api.synthesizeSpeech(this.apiBaseUrl, data.ttsText);
|
|
2186
|
+
const buffer = await blob2.arrayBuffer();
|
|
2187
|
+
const bytes = new Uint8Array(buffer);
|
|
2188
|
+
let binary = "";
|
|
2189
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
2190
|
+
binary += String.fromCharCode(bytes[i]);
|
|
2191
|
+
}
|
|
2192
|
+
this.audioSummary = btoa(binary);
|
|
2193
|
+
const audio = new Audio(`data:audio/mpeg;base64,${this.audioSummary}`);
|
|
2194
|
+
void audio.play();
|
|
2195
|
+
} catch {
|
|
2196
|
+
this.audioSummary = null;
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
}).catch((err) => {
|
|
2200
|
+
this.error = err.message;
|
|
2201
|
+
}).finally(() => {
|
|
2202
|
+
this.isProcessing = false;
|
|
2203
|
+
});
|
|
2204
|
+
};
|
|
2205
|
+
this.mediaRecorder.start();
|
|
2206
|
+
this.isRecording = true;
|
|
2207
|
+
} catch {
|
|
2208
|
+
this.error = "Microphone access denied";
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
replayVoiceSummary() {
|
|
2212
|
+
if (!this.audioSummary) return;
|
|
2213
|
+
const audio = new Audio(`data:audio/mpeg;base64,${this.audioSummary}`);
|
|
2214
|
+
void audio.play();
|
|
2215
|
+
}
|
|
2216
|
+
addToCartLabel(product) {
|
|
2217
|
+
if (this.addedProductIds[product.id]) {
|
|
2218
|
+
return this.resolvedMessages.itemAdded;
|
|
2219
|
+
}
|
|
2220
|
+
return product.sku || product.id ? this.resolvedMessages.addToCart : this.resolvedMessages.unableToAddToCart;
|
|
2221
|
+
}
|
|
2222
|
+
onAddItem(product) {
|
|
2223
|
+
void this.handleAddToCart(product);
|
|
2224
|
+
}
|
|
2225
|
+
onCartRemove(lineItemId) {
|
|
2226
|
+
void this.cart.removeFromCart(lineItemId);
|
|
2227
|
+
}
|
|
2228
|
+
onCartQuantityChange(event) {
|
|
2229
|
+
void this.cart.updateQuantity(event.lineItemId, event.quantity);
|
|
2230
|
+
}
|
|
2231
|
+
onCartLogin(input) {
|
|
2232
|
+
void this.cart.login(input);
|
|
2233
|
+
}
|
|
2234
|
+
onCartLogout() {
|
|
2235
|
+
void this.cart.logout();
|
|
2236
|
+
}
|
|
2237
|
+
applySearchResult(data) {
|
|
2238
|
+
this.results = data.products;
|
|
2239
|
+
this.mission = data.mission ?? null;
|
|
2240
|
+
this.meta = data.meta;
|
|
2241
|
+
this.facets = data.facets ?? [];
|
|
2242
|
+
this.suggestedFacets = data.suggestedFacets ?? [];
|
|
2243
|
+
this.hasFacetSession = !data.mission && this.enableFacets && Boolean(data.meta.searchTerms);
|
|
2244
|
+
}
|
|
2245
|
+
syncCart() {
|
|
2246
|
+
this.cart.configure({
|
|
2247
|
+
apiBaseUrl: this.apiBaseUrl,
|
|
2248
|
+
currency: this.currency,
|
|
2249
|
+
country: this.country,
|
|
2250
|
+
catalogLocale: this.catalogLocale,
|
|
2251
|
+
enabled: this.enableCart,
|
|
2252
|
+
onCartChange: /* @__PURE__ */ __name((snapshot) => this.cartChange.emit(snapshot), "onCartChange")
|
|
2253
|
+
});
|
|
2254
|
+
}
|
|
2255
|
+
async handleAddToCart(product) {
|
|
2256
|
+
if (!product.sku && !product.id) {
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
const next = await this.cart.addToCart(toCartItem(product, 1));
|
|
2260
|
+
if (!next || this.destroyed) {
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
this.addedProductIds = {
|
|
2264
|
+
...this.addedProductIds,
|
|
2265
|
+
[product.id]: true
|
|
2266
|
+
};
|
|
2267
|
+
const existing = this.addedTimeouts.get(product.id);
|
|
2268
|
+
if (existing) {
|
|
2269
|
+
clearTimeout(existing);
|
|
2270
|
+
}
|
|
2271
|
+
this.addedTimeouts.set(product.id, setTimeout(() => {
|
|
2272
|
+
if (this.destroyed) {
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2275
|
+
const { [product.id]: _removed, ...rest } = this.addedProductIds;
|
|
2276
|
+
this.addedProductIds = rest;
|
|
2277
|
+
this.addedTimeouts.delete(product.id);
|
|
2278
|
+
this.cdr.detectChanges();
|
|
2279
|
+
}, 1200));
|
|
2280
|
+
this.cdr.detectChanges();
|
|
2281
|
+
}
|
|
2282
|
+
clearVoiceAudio() {
|
|
2283
|
+
this.audioSummary = null;
|
|
2284
|
+
}
|
|
2285
|
+
};
|
|
2286
|
+
_ts_decorate5([
|
|
2287
|
+
Input3()
|
|
2288
|
+
], CommerceAiSearchComponent.prototype, "apiBaseUrl", void 0);
|
|
2289
|
+
_ts_decorate5([
|
|
2290
|
+
Input3(),
|
|
2291
|
+
_ts_metadata3("design:type", typeof ThemeMode === "undefined" ? Object : ThemeMode)
|
|
2292
|
+
], CommerceAiSearchComponent.prototype, "theme", void 0);
|
|
2293
|
+
_ts_decorate5([
|
|
2294
|
+
Input3(),
|
|
2295
|
+
_ts_metadata3("design:type", String)
|
|
2296
|
+
], CommerceAiSearchComponent.prototype, "catalogLocale", void 0);
|
|
2297
|
+
_ts_decorate5([
|
|
2298
|
+
Input3(),
|
|
2299
|
+
_ts_metadata3("design:type", String)
|
|
2300
|
+
], CommerceAiSearchComponent.prototype, "queryLocale", void 0);
|
|
2301
|
+
_ts_decorate5([
|
|
2302
|
+
Input3(),
|
|
2303
|
+
_ts_metadata3("design:type", String)
|
|
2304
|
+
], CommerceAiSearchComponent.prototype, "locale", void 0);
|
|
2305
|
+
_ts_decorate5([
|
|
2306
|
+
Input3()
|
|
2307
|
+
], CommerceAiSearchComponent.prototype, "placeholder", void 0);
|
|
2308
|
+
_ts_decorate5([
|
|
2309
|
+
Input3(),
|
|
2310
|
+
_ts_metadata3("design:type", typeof Partial === "undefined" ? Object : Partial)
|
|
2311
|
+
], CommerceAiSearchComponent.prototype, "messages", void 0);
|
|
2312
|
+
_ts_decorate5([
|
|
2313
|
+
Input3()
|
|
2314
|
+
], CommerceAiSearchComponent.prototype, "enableAutocomplete", void 0);
|
|
2315
|
+
_ts_decorate5([
|
|
2316
|
+
Input3()
|
|
2317
|
+
], CommerceAiSearchComponent.prototype, "enableFacets", void 0);
|
|
2318
|
+
_ts_decorate5([
|
|
2319
|
+
Input3()
|
|
2320
|
+
], CommerceAiSearchComponent.prototype, "enableVoice", void 0);
|
|
2321
|
+
_ts_decorate5([
|
|
2322
|
+
Input3()
|
|
2323
|
+
], CommerceAiSearchComponent.prototype, "enableImageSearch", void 0);
|
|
2324
|
+
_ts_decorate5([
|
|
2325
|
+
Input3()
|
|
2326
|
+
], CommerceAiSearchComponent.prototype, "enableCameraSearch", void 0);
|
|
2327
|
+
_ts_decorate5([
|
|
2328
|
+
Input3(),
|
|
2329
|
+
_ts_metadata3("design:type", typeof CameraFacingMode === "undefined" ? Object : CameraFacingMode)
|
|
2330
|
+
], CommerceAiSearchComponent.prototype, "cameraFacingMode", void 0);
|
|
2331
|
+
_ts_decorate5([
|
|
2332
|
+
Input3()
|
|
2333
|
+
], CommerceAiSearchComponent.prototype, "enableTts", void 0);
|
|
2334
|
+
_ts_decorate5([
|
|
2335
|
+
Input3()
|
|
2336
|
+
], CommerceAiSearchComponent.prototype, "enableCart", void 0);
|
|
2337
|
+
_ts_decorate5([
|
|
2338
|
+
Input3()
|
|
2339
|
+
], CommerceAiSearchComponent.prototype, "enableMissions", void 0);
|
|
2340
|
+
_ts_decorate5([
|
|
2341
|
+
Input3(),
|
|
2342
|
+
_ts_metadata3("design:type", String)
|
|
2343
|
+
], CommerceAiSearchComponent.prototype, "currency", void 0);
|
|
2344
|
+
_ts_decorate5([
|
|
2345
|
+
Input3(),
|
|
2346
|
+
_ts_metadata3("design:type", String)
|
|
2347
|
+
], CommerceAiSearchComponent.prototype, "country", void 0);
|
|
2348
|
+
_ts_decorate5([
|
|
2349
|
+
Output3()
|
|
2350
|
+
], CommerceAiSearchComponent.prototype, "productSelect", void 0);
|
|
2351
|
+
_ts_decorate5([
|
|
2352
|
+
Output3()
|
|
2353
|
+
], CommerceAiSearchComponent.prototype, "cartChange", void 0);
|
|
2354
|
+
_ts_decorate5([
|
|
2355
|
+
Output3()
|
|
2356
|
+
], CommerceAiSearchComponent.prototype, "checkout", void 0);
|
|
2357
|
+
_ts_decorate5([
|
|
2358
|
+
ViewChild("cameraInput"),
|
|
2359
|
+
_ts_metadata3("design:type", typeof ElementRef === "undefined" ? Object : ElementRef)
|
|
2360
|
+
], CommerceAiSearchComponent.prototype, "cameraInputRef", void 0);
|
|
2361
|
+
_ts_decorate5([
|
|
2362
|
+
ViewChild("cameraVideo"),
|
|
2363
|
+
_ts_metadata3("design:type", typeof ElementRef === "undefined" ? Object : ElementRef)
|
|
2364
|
+
], CommerceAiSearchComponent.prototype, "cameraVideoRef", void 0);
|
|
2365
|
+
CommerceAiSearchComponent = _ts_decorate5([
|
|
2366
|
+
Component3({
|
|
2367
|
+
selector: "commerce-ai-search",
|
|
2368
|
+
standalone: true,
|
|
2369
|
+
imports: [
|
|
2370
|
+
FormsModule2,
|
|
2371
|
+
CommerceAiMissionResultsComponent,
|
|
2372
|
+
CommerceAiCartPanelComponent
|
|
2373
|
+
],
|
|
2374
|
+
providers: [
|
|
2375
|
+
CommerceAiCartService
|
|
2376
|
+
],
|
|
2377
|
+
template: `
|
|
2378
|
+
<div
|
|
2379
|
+
class="cat-root cat-wrapper"
|
|
2380
|
+
[class.cat-root--mission]="showMissionVisible"
|
|
2381
|
+
[class.cat-root--mission-lanes-1]="missionLaneCount === 1"
|
|
2382
|
+
[class.cat-root--mission-lanes-2]="missionLaneCount === 2"
|
|
2383
|
+
[class.cat-root--mission-lanes-3]="missionLaneCount === 3"
|
|
2384
|
+
[class.cat-root--mission-lanes-4]="missionLaneCount === 4"
|
|
2385
|
+
[class.cat-root--mission-lanes-5]="missionLaneCount >= 5"
|
|
2386
|
+
[attr.data-theme]="theme"
|
|
2387
|
+
role="search"
|
|
2388
|
+
[attr.aria-label]="resolvedMessages.productSearchAriaLabel"
|
|
2389
|
+
(dragover)="onDragOver($event)"
|
|
2390
|
+
(dragleave)="isDragging = false"
|
|
2391
|
+
(drop)="onDrop($event)"
|
|
2392
|
+
>
|
|
2393
|
+
@if (isDragging && enableImageSearch) {
|
|
2394
|
+
<div class="cat-drag-overlay" aria-hidden="true">{{ resolvedMessages.dropImageToSearch }}</div>
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
<form class="cat-search-bar" (ngSubmit)="onSubmit()">
|
|
2398
|
+
<svg
|
|
2399
|
+
width="18"
|
|
2400
|
+
height="18"
|
|
2401
|
+
viewBox="0 0 24 24"
|
|
2402
|
+
fill="none"
|
|
2403
|
+
stroke="currentColor"
|
|
2404
|
+
stroke-width="2"
|
|
2405
|
+
aria-hidden="true"
|
|
2406
|
+
>
|
|
2407
|
+
<circle cx="11" cy="11" r="8" />
|
|
2408
|
+
<path d="m21 21-4.3-4.3" />
|
|
2409
|
+
</svg>
|
|
2410
|
+
|
|
2411
|
+
<div class="cat-search-input-wrap">
|
|
2412
|
+
<input
|
|
2413
|
+
type="search"
|
|
2414
|
+
class="cat-search-input"
|
|
2415
|
+
[(ngModel)]="query"
|
|
2416
|
+
name="query"
|
|
2417
|
+
[placeholder]="resolvedMessages.placeholder"
|
|
2418
|
+
[attr.aria-label]="resolvedMessages.searchAriaLabel"
|
|
2419
|
+
[attr.role]="enableAutocomplete ? 'combobox' : null"
|
|
2420
|
+
[attr.aria-haspopup]="enableAutocomplete ? 'listbox' : null"
|
|
2421
|
+
[attr.aria-autocomplete]="enableAutocomplete ? 'list' : null"
|
|
2422
|
+
[attr.aria-expanded]="showSuggestions"
|
|
2423
|
+
[attr.aria-controls]="showSuggestions ? 'cat-suggestions-listbox' : null"
|
|
2424
|
+
[attr.aria-activedescendant]="activeSuggestionId"
|
|
2425
|
+
autocomplete="off"
|
|
2426
|
+
(ngModelChange)="onQueryChange($event)"
|
|
2427
|
+
(keydown)="onInputKeyDown($event)"
|
|
2428
|
+
/>
|
|
2429
|
+
</div>
|
|
2430
|
+
|
|
2431
|
+
@if (enableVoice) {
|
|
2432
|
+
<button
|
|
2433
|
+
type="button"
|
|
2434
|
+
class="cat-icon-btn"
|
|
2435
|
+
[class.cat-icon-btn--active]="isRecording"
|
|
2436
|
+
[disabled]="isProcessing"
|
|
2437
|
+
[attr.aria-label]="isRecording ? resolvedMessages.stopRecording : resolvedMessages.voiceSearch"
|
|
2438
|
+
[attr.aria-pressed]="isRecording"
|
|
2439
|
+
(click)="toggleRecording()"
|
|
2440
|
+
>
|
|
2441
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2442
|
+
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" />
|
|
2443
|
+
<path d="M19 10v2a7 7 0 0 1-14 0v-2" />
|
|
2444
|
+
<line x1="12" x2="12" y1="19" y2="22" />
|
|
2445
|
+
</svg>
|
|
2446
|
+
</button>
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
@if (enableCameraSearch) {
|
|
2450
|
+
<button
|
|
2451
|
+
type="button"
|
|
2452
|
+
class="cat-icon-btn"
|
|
2453
|
+
[disabled]="isLoading"
|
|
2454
|
+
aria-label="{{ resolvedMessages.searchByCamera }}"
|
|
2455
|
+
(click)="openCamera()"
|
|
2456
|
+
>
|
|
2457
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2458
|
+
<path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z" />
|
|
2459
|
+
<circle cx="12" cy="13" r="3" />
|
|
2460
|
+
</svg>
|
|
2461
|
+
</button>
|
|
2462
|
+
<input
|
|
2463
|
+
#cameraInput
|
|
2464
|
+
type="file"
|
|
2465
|
+
accept="image/*"
|
|
2466
|
+
[attr.capture]="cameraFacingMode"
|
|
2467
|
+
class="cat-hidden-input"
|
|
2468
|
+
(change)="onCameraFileSelected($event)"
|
|
2469
|
+
/>
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
@if (enableImageSearch) {
|
|
2473
|
+
<button
|
|
2474
|
+
type="button"
|
|
2475
|
+
class="cat-icon-btn"
|
|
2476
|
+
[disabled]="isLoading"
|
|
2477
|
+
aria-label="{{ resolvedMessages.searchByImage }}"
|
|
2478
|
+
(click)="fileInput.click()"
|
|
2479
|
+
>
|
|
2480
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2481
|
+
<rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
|
|
2482
|
+
<circle cx="9" cy="9" r="2" />
|
|
2483
|
+
<path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />
|
|
2484
|
+
</svg>
|
|
2485
|
+
</button>
|
|
2486
|
+
<input
|
|
2487
|
+
#fileInput
|
|
2488
|
+
type="file"
|
|
2489
|
+
accept="image/*"
|
|
2490
|
+
class="cat-hidden-input"
|
|
2491
|
+
(change)="onFileSelected($event)"
|
|
2492
|
+
/>
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
@if (showVoiceReplay) {
|
|
2496
|
+
<button
|
|
2497
|
+
type="button"
|
|
2498
|
+
class="cat-icon-btn"
|
|
2499
|
+
aria-label="{{ resolvedMessages.replayVoiceSummary }}"
|
|
2500
|
+
(click)="replayVoiceSummary()"
|
|
2501
|
+
>
|
|
2502
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2503
|
+
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" />
|
|
2504
|
+
<path d="M15.54 8.46a5 5 0 0 1 0 7.07" />
|
|
2505
|
+
</svg>
|
|
2506
|
+
</button>
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
@if (enableCart) {
|
|
2510
|
+
<button
|
|
2511
|
+
type="button"
|
|
2512
|
+
class="cat-icon-btn cat-cart-toggle"
|
|
2513
|
+
[class.cat-cart-toggle--open]="cart.isCartOpen()"
|
|
2514
|
+
[attr.aria-label]="cartAriaLabel"
|
|
2515
|
+
[attr.aria-expanded]="cart.isCartOpen()"
|
|
2516
|
+
[attr.aria-pressed]="cart.isCartOpen()"
|
|
2517
|
+
(click)="cart.toggleCart()"
|
|
2518
|
+
>
|
|
2519
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
2520
|
+
<circle cx="8" cy="21" r="1" />
|
|
2521
|
+
<circle cx="19" cy="21" r="1" />
|
|
2522
|
+
<path d="M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12" />
|
|
2523
|
+
</svg>
|
|
2524
|
+
@if (cartQuantity > 0) {
|
|
2525
|
+
<span class="cat-cart-badge" aria-hidden="true">{{ cartBadgeLabel }}</span>
|
|
2526
|
+
}
|
|
2527
|
+
</button>
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
@if (showSuggestions) {
|
|
2531
|
+
<div
|
|
2532
|
+
id="cat-suggestions-listbox"
|
|
2533
|
+
class="cat-suggestions"
|
|
2534
|
+
role="listbox"
|
|
2535
|
+
[attr.aria-label]="resolvedMessages.suggestionsAriaLabel"
|
|
2536
|
+
>
|
|
2537
|
+
@if (isLoadingSuggestions && suggestions.length === 0 && !suggestionsError) {
|
|
2538
|
+
<div class="cat-suggestions__status">{{ resolvedMessages.loadingSuggestions }}</div>
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
@if (suggestionsError) {
|
|
2542
|
+
<div class="cat-suggestions__status cat-suggestions__status--error" role="alert">
|
|
2543
|
+
{{ suggestionsError }}
|
|
2544
|
+
</div>
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
@if (
|
|
2548
|
+
!isLoadingSuggestions &&
|
|
2549
|
+
!suggestionsError &&
|
|
2550
|
+
suggestionsReady &&
|
|
2551
|
+
suggestions.length === 0
|
|
2552
|
+
) {
|
|
2553
|
+
<div class="cat-suggestions__status">{{ resolvedMessages.noSuggestions }}</div>
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
@for (suggestion of suggestions; track $index; let index = $index) {
|
|
2557
|
+
<button
|
|
2558
|
+
type="button"
|
|
2559
|
+
class="cat-suggestions__item"
|
|
2560
|
+
[class.cat-suggestions__item--active]="index === activeSuggestionIndex"
|
|
2561
|
+
role="option"
|
|
2562
|
+
[attr.id]="'cat-suggestion-' + index"
|
|
2563
|
+
[attr.aria-selected]="index === activeSuggestionIndex"
|
|
2564
|
+
(mousedown)="$event.preventDefault()"
|
|
2565
|
+
(click)="onSuggestionSelect(suggestion)"
|
|
2566
|
+
>
|
|
2567
|
+
{{ suggestion }}
|
|
2568
|
+
</button>
|
|
2569
|
+
}
|
|
2570
|
+
</div>
|
|
2571
|
+
}
|
|
2572
|
+
</form>
|
|
2573
|
+
|
|
2574
|
+
@if (enableCameraSearch && (isCameraOpen || cameraError)) {
|
|
2575
|
+
<div class="cat-camera-overlay" role="dialog" [attr.aria-label]="resolvedMessages.cameraCapture">
|
|
2576
|
+
<div class="cat-camera-overlay__panel">
|
|
2577
|
+
@if (cameraError) {
|
|
2578
|
+
<div class="cat-camera-overlay__error" role="alert">
|
|
2579
|
+
<p>{{ cameraError }}</p>
|
|
2580
|
+
<button type="button" class="cat-camera-overlay__btn" (click)="clearCameraError()">
|
|
2581
|
+
{{ resolvedMessages.dismiss }}
|
|
2582
|
+
</button>
|
|
2583
|
+
</div>
|
|
2584
|
+
} @else {
|
|
2585
|
+
<video
|
|
2586
|
+
#cameraVideo
|
|
2587
|
+
class="cat-camera-preview"
|
|
2588
|
+
autoplay
|
|
2589
|
+
playsinline
|
|
2590
|
+
muted
|
|
2591
|
+
[attr.aria-label]="resolvedMessages.cameraPreview"
|
|
2592
|
+
></video>
|
|
2593
|
+
<div class="cat-camera-actions">
|
|
2594
|
+
<button
|
|
2595
|
+
type="button"
|
|
2596
|
+
class="cat-camera-overlay__btn cat-camera-overlay__btn--secondary"
|
|
2597
|
+
[attr.aria-label]="resolvedMessages.cancel"
|
|
2598
|
+
(click)="closeCamera()"
|
|
2599
|
+
>
|
|
2600
|
+
{{ resolvedMessages.cancel }}
|
|
2601
|
+
</button>
|
|
2602
|
+
<button
|
|
2603
|
+
type="button"
|
|
2604
|
+
class="cat-camera-overlay__btn cat-camera-overlay__btn--primary"
|
|
2605
|
+
[attr.aria-label]="resolvedMessages.capturePhoto"
|
|
2606
|
+
(click)="capturePhoto()"
|
|
2607
|
+
>
|
|
2608
|
+
{{ resolvedMessages.capturePhoto }}
|
|
2609
|
+
</button>
|
|
2610
|
+
</div>
|
|
2611
|
+
}
|
|
2612
|
+
</div>
|
|
2613
|
+
</div>
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
@if (enableCart && cart.isCartOpen()) {
|
|
2617
|
+
<commerce-ai-cart-panel
|
|
2618
|
+
[cart]="cart.cart()"
|
|
2619
|
+
[customer]="cart.customer()"
|
|
2620
|
+
[isLoading]="cart.isLoading() || cart.isMutating()"
|
|
2621
|
+
[isLoggingIn]="cart.isLoggingIn()"
|
|
2622
|
+
[error]="cart.error()"
|
|
2623
|
+
[messages]="resolvedMessages"
|
|
2624
|
+
[catalogLocale]="catalogLocale"
|
|
2625
|
+
[showCheckout]="checkout.observed"
|
|
2626
|
+
(close)="cart.closeCart()"
|
|
2627
|
+
(remove)="onCartRemove($event)"
|
|
2628
|
+
(quantityChange)="onCartQuantityChange($event)"
|
|
2629
|
+
(login)="onCartLogin($event)"
|
|
2630
|
+
(logout)="onCartLogout()"
|
|
2631
|
+
(checkout)="checkout.emit($event)"
|
|
2632
|
+
/>
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
@if (enableFacets && lastSearchMode === "text" && hasSearched && facets.length && !showMission) {
|
|
2636
|
+
<section class="cat-facets" [attr.aria-label]="resolvedMessages.filtersAriaLabel">
|
|
2637
|
+
<div class="cat-facets__header">
|
|
2638
|
+
<span>{{ resolvedMessages.narrowResults }}</span>
|
|
2639
|
+
<div class="cat-facets__actions">
|
|
2640
|
+
@if (hasAppliedFilters) {
|
|
2641
|
+
<button type="button" class="cat-facets__clear" (click)="clearFacetFilters()">
|
|
2642
|
+
{{ resolvedMessages.clearFilters }}
|
|
2643
|
+
</button>
|
|
2644
|
+
}
|
|
2645
|
+
<button type="button" class="cat-facets__clear" (click)="startNewSearch()">
|
|
2646
|
+
{{ resolvedMessages.newSearch }}
|
|
2647
|
+
</button>
|
|
2648
|
+
</div>
|
|
2649
|
+
</div>
|
|
2650
|
+
@for (facet of facets; track facet.id) {
|
|
2651
|
+
<div class="cat-facet-group" role="group" [attr.aria-label]="facet.label">
|
|
2652
|
+
<span class="cat-facet-group__label">{{ facet.label }}</span>
|
|
2653
|
+
<div class="cat-facet-group__options">
|
|
2654
|
+
@for (bucket of facet.buckets; track bucket.key) {
|
|
2655
|
+
<button
|
|
2656
|
+
type="button"
|
|
2657
|
+
class="cat-facet-chip"
|
|
2658
|
+
[class.cat-facet-chip--selected]="isFacetSelected(facet.id, bucket.key)"
|
|
2659
|
+
[attr.aria-pressed]="isFacetSelected(facet.id, bucket.key)"
|
|
2660
|
+
(click)="toggleFacet(facet.id, bucket.key)"
|
|
2661
|
+
>
|
|
2662
|
+
@if (facetSwatch(facet.id, bucket.key); as swatch) {
|
|
2663
|
+
<span class="cat-facet-chip__swatch" [style.background-color]="swatch" aria-hidden="true"></span>
|
|
2664
|
+
}
|
|
2665
|
+
{{ bucket.label }} <span aria-hidden="true">{{ bucket.count }}</span>
|
|
2666
|
+
</button>
|
|
2667
|
+
}
|
|
2668
|
+
</div>
|
|
2669
|
+
</div>
|
|
2670
|
+
}
|
|
2671
|
+
</section>
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
@if (showResults && isLoading) {
|
|
2675
|
+
<div class="cat-status">{{ resolvedMessages.searching }}</div>
|
|
2676
|
+
}
|
|
2677
|
+
@if (showResults && error) {
|
|
2678
|
+
<div class="cat-status cat-status--error">{{ error }}</div>
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
@if (!isLoading && !error && showResults && showMission && mission) {
|
|
2682
|
+
<commerce-ai-mission-results
|
|
2683
|
+
[mission]="mission"
|
|
2684
|
+
[messages]="resolvedMessages"
|
|
2685
|
+
[enableCart]="enableCart"
|
|
2686
|
+
[isMutating]="cart.isMutating()"
|
|
2687
|
+
[addedProductIds]="addedProductIds"
|
|
2688
|
+
[addAllHandler]="addMissionItems"
|
|
2689
|
+
(productSelect)="productSelect.emit($event)"
|
|
2690
|
+
(addItem)="onAddItem($event)"
|
|
2691
|
+
/>
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
@if (showResults && !showMission && !isLoading && !error) {
|
|
2695
|
+
<div
|
|
2696
|
+
class="cat-results"
|
|
2697
|
+
[attr.role]="results.length > 0 ? 'list' : null"
|
|
2698
|
+
[attr.aria-label]="results.length > 0 ? resolvedMessages.searchResultsAriaLabel : null"
|
|
2699
|
+
>
|
|
2700
|
+
@for (product of results; track product.id; let index = $index) {
|
|
2701
|
+
<article
|
|
2702
|
+
class="cat-result-card"
|
|
2703
|
+
role="listitem"
|
|
2704
|
+
[class.cat-result-card--featured]="index === 0"
|
|
2705
|
+
>
|
|
2706
|
+
<div class="cat-result-card__core">
|
|
2707
|
+
<button type="button" class="cat-result-card__select" (click)="productSelect.emit(product)">
|
|
2708
|
+
@if (product.imageUrl) {
|
|
2709
|
+
<img [src]="product.imageUrl" alt="" class="cat-result-image" loading="lazy" />
|
|
2710
|
+
} @else {
|
|
2711
|
+
<div class="cat-result-image cat-result-image--placeholder">\u{1F4E6}</div>
|
|
2712
|
+
}
|
|
2713
|
+
<div class="cat-result-info">
|
|
2714
|
+
<div class="cat-result-name">{{ product.name }}</div>
|
|
2715
|
+
@if (product.price) {
|
|
2716
|
+
<div class="cat-result-price">{{ product.price.formatted }}</div>
|
|
2717
|
+
}
|
|
2718
|
+
</div>
|
|
2719
|
+
</button>
|
|
2720
|
+
@if (enableCart) {
|
|
2721
|
+
<button
|
|
2722
|
+
type="button"
|
|
2723
|
+
class="cat-icon-btn cat-result-card__add"
|
|
2724
|
+
[class.cat-result-card__add--added]="addedProductIds[product.id]"
|
|
2725
|
+
[attr.aria-label]="addToCartLabel(product)"
|
|
2726
|
+
[disabled]="!(product.sku || product.id) || cart.isMutating()"
|
|
2727
|
+
(click)="onAddItem(product)"
|
|
2728
|
+
>
|
|
2729
|
+
@if (addedProductIds[product.id]) {
|
|
2730
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
2731
|
+
<path d="M20 6 9 17l-5-5" />
|
|
2732
|
+
</svg>
|
|
2733
|
+
} @else {
|
|
2734
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
2735
|
+
<circle cx="8" cy="21" r="1" />
|
|
2736
|
+
<circle cx="19" cy="21" r="1" />
|
|
2737
|
+
<path d="M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12" />
|
|
2738
|
+
</svg>
|
|
2739
|
+
}
|
|
2740
|
+
</button>
|
|
2741
|
+
}
|
|
2742
|
+
</div>
|
|
2743
|
+
</article>
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
@if (results.length === 0 && hasSearched && query.trim()) {
|
|
2747
|
+
<div class="cat-status cat-status--empty" role="status" aria-live="polite">
|
|
2748
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
2749
|
+
<circle cx="11" cy="11" r="8" />
|
|
2750
|
+
<path d="m21 21-4.3-4.3" />
|
|
2751
|
+
<path d="m13 9-4 4" />
|
|
2752
|
+
<path d="m9 9 4 4" />
|
|
2753
|
+
</svg>
|
|
2754
|
+
<div class="cat-status__content">
|
|
2755
|
+
<div class="cat-status__title">{{ resolvedMessages.noProductsFound }}</div>
|
|
2756
|
+
@if (meta?.queryInterpretation) {
|
|
2757
|
+
<div class="cat-status__hint">
|
|
2758
|
+
{{ resolvedMessages.searchedFor }} {{ meta?.queryInterpretation }}
|
|
2759
|
+
</div>
|
|
2760
|
+
}
|
|
2761
|
+
</div>
|
|
2762
|
+
</div>
|
|
2763
|
+
}
|
|
2764
|
+
</div>
|
|
2765
|
+
}
|
|
2766
|
+
</div>
|
|
2767
|
+
`,
|
|
2768
|
+
encapsulation: ViewEncapsulation.None
|
|
2769
|
+
})
|
|
2770
|
+
], CommerceAiSearchComponent);
|
|
2771
|
+
export {
|
|
2772
|
+
CommerceAiApiService,
|
|
2773
|
+
CommerceAiCartPanelComponent,
|
|
2774
|
+
CommerceAiCartService,
|
|
2775
|
+
CommerceAiMissionResultsComponent,
|
|
2776
|
+
CommerceAiSearchComponent,
|
|
2777
|
+
DEFAULT_COMMERCE_AI_SEARCH_MESSAGES,
|
|
2778
|
+
resolveCommerceAISearchMessages
|
|
2779
|
+
};
|
|
2780
|
+
//# sourceMappingURL=index.js.map
|