@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.
Files changed (35) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +16 -0
  4. package/dist/index.cjs +2811 -0
  5. package/dist/index.cjs.map +1 -0
  6. package/dist/index.css +1015 -0
  7. package/dist/index.css.map +1 -0
  8. package/dist/index.d.cts +9 -0
  9. package/dist/index.d.ts +9 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +2780 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/lib/camera.util.d.ts +7 -0
  14. package/dist/lib/camera.util.d.ts.map +1 -0
  15. package/dist/lib/commerce-ai-api.service.d.ts +35 -0
  16. package/dist/lib/commerce-ai-api.service.d.ts.map +1 -0
  17. package/dist/lib/commerce-ai-cart-panel.component.d.ts +34 -0
  18. package/dist/lib/commerce-ai-cart-panel.component.d.ts.map +1 -0
  19. package/dist/lib/commerce-ai-cart.service.d.ts +61 -0
  20. package/dist/lib/commerce-ai-cart.service.d.ts.map +1 -0
  21. package/dist/lib/commerce-ai-mission-results.component.d.ts +26 -0
  22. package/dist/lib/commerce-ai-mission-results.component.d.ts.map +1 -0
  23. package/dist/lib/commerce-ai-search.component.d.ts +141 -0
  24. package/dist/lib/commerce-ai-search.component.d.ts.map +1 -0
  25. package/dist/lib/mission.util.d.ts +6 -0
  26. package/dist/lib/mission.util.d.ts.map +1 -0
  27. package/dist/vendor-core/client.d.cts +12 -0
  28. package/dist/vendor-core/client.d.ts +12 -0
  29. package/dist/vendor-core/commercetools/facet-color.d.ts +12 -0
  30. package/dist/vendor-core/commercetools/facets.d.ts +31 -0
  31. package/dist/vendor-core/messages/index.d.ts +110 -0
  32. package/dist/vendor-core/payments/types.d.ts +35 -0
  33. package/dist/vendor-core/search/compound-shopping-list.d.ts +6 -0
  34. package/dist/vendor-core/types/index.d.ts +510 -0
  35. 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