@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
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/commerce-ai-search.component.ts","../../core/src/types/index.ts","../../core/src/messages/index.ts","../../core/src/search/compound-shopping-list.ts","../../core/src/commercetools/facet-color.ts","../../core/src/commercetools/facets.ts","../src/lib/commerce-ai-api.service.ts","../src/lib/commerce-ai-cart-panel.component.ts","../src/lib/commerce-ai-cart.service.ts","../src/lib/commerce-ai-mission-results.component.ts","../src/lib/mission.util.ts","../src/lib/camera.util.ts"],"sourcesContent":["import {\n ChangeDetectorRef,\n Component,\n ElementRef,\n EventEmitter,\n Input,\n OnChanges,\n OnDestroy,\n OnInit,\n Output,\n ViewChild,\n ViewEncapsulation,\n inject,\n} from \"@angular/core\";\nimport { FormsModule } from \"@angular/forms\";\nimport type {\n AddToCartLineItem,\n CartSnapshot,\n CommerceAISearchMessages,\n InterpretedSearchFilters,\n MissionSearchResult,\n ProductCard,\n SearchFacetGroup,\n SearchResult,\n SuggestedFacet,\n ThemeMode,\n} from \"@commerce-ai-tool/core/client\";\nimport {\n hexColorSwatchValue,\n isColorLikeFacetName,\n isFacetFilterSelected,\n looksLikeCompoundShoppingList,\n resolveCommerceAISearchMessages,\n toggleFacetFilter,\n} from \"@commerce-ai-tool/core/client\";\nimport { CommerceAiApiService } from \"./commerce-ai-api.service.js\";\nimport { CommerceAiCartPanelComponent } from \"./commerce-ai-cart-panel.component.js\";\nimport { CommerceAiCartService } from \"./commerce-ai-cart.service.js\";\nimport { CommerceAiMissionResultsComponent } from \"./commerce-ai-mission-results.component.js\";\nimport {\n buildCameraConstraints,\n createJpegFileFromVideo,\n getCameraErrorMessage,\n prefersNativeCamera,\n stopMediaStream,\n type CameraFacingMode,\n} from \"./camera.util.js\";\nimport { toCartItem } from \"./mission.util.js\";\n\ntype SearchMode = \"text\" | \"image\" | \"voice\" | null;\n\n@Component({\n selector: \"commerce-ai-search\",\n standalone: true,\n imports: [FormsModule, CommerceAiMissionResultsComponent, CommerceAiCartPanelComponent],\n providers: [CommerceAiCartService],\n template: `\n <div\n class=\"cat-root cat-wrapper\"\n [class.cat-root--mission]=\"showMissionVisible\"\n [class.cat-root--mission-lanes-1]=\"missionLaneCount === 1\"\n [class.cat-root--mission-lanes-2]=\"missionLaneCount === 2\"\n [class.cat-root--mission-lanes-3]=\"missionLaneCount === 3\"\n [class.cat-root--mission-lanes-4]=\"missionLaneCount === 4\"\n [class.cat-root--mission-lanes-5]=\"missionLaneCount >= 5\"\n [attr.data-theme]=\"theme\"\n role=\"search\"\n [attr.aria-label]=\"resolvedMessages.productSearchAriaLabel\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"isDragging = false\"\n (drop)=\"onDrop($event)\"\n >\n @if (isDragging && enableImageSearch) {\n <div class=\"cat-drag-overlay\" aria-hidden=\"true\">{{ resolvedMessages.dropImageToSearch }}</div>\n }\n\n <form class=\"cat-search-bar\" (ngSubmit)=\"onSubmit()\">\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n aria-hidden=\"true\"\n >\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.3-4.3\" />\n </svg>\n\n <div class=\"cat-search-input-wrap\">\n <input\n type=\"search\"\n class=\"cat-search-input\"\n [(ngModel)]=\"query\"\n name=\"query\"\n [placeholder]=\"resolvedMessages.placeholder\"\n [attr.aria-label]=\"resolvedMessages.searchAriaLabel\"\n [attr.role]=\"enableAutocomplete ? 'combobox' : null\"\n [attr.aria-haspopup]=\"enableAutocomplete ? 'listbox' : null\"\n [attr.aria-autocomplete]=\"enableAutocomplete ? 'list' : null\"\n [attr.aria-expanded]=\"showSuggestions\"\n [attr.aria-controls]=\"showSuggestions ? 'cat-suggestions-listbox' : null\"\n [attr.aria-activedescendant]=\"activeSuggestionId\"\n autocomplete=\"off\"\n (ngModelChange)=\"onQueryChange($event)\"\n (keydown)=\"onInputKeyDown($event)\"\n />\n </div>\n\n @if (enableVoice) {\n <button\n type=\"button\"\n class=\"cat-icon-btn\"\n [class.cat-icon-btn--active]=\"isRecording\"\n [disabled]=\"isProcessing\"\n [attr.aria-label]=\"isRecording ? resolvedMessages.stopRecording : resolvedMessages.voiceSearch\"\n [attr.aria-pressed]=\"isRecording\"\n (click)=\"toggleRecording()\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z\" />\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\" />\n <line x1=\"12\" x2=\"12\" y1=\"19\" y2=\"22\" />\n </svg>\n </button>\n }\n\n @if (enableCameraSearch) {\n <button\n type=\"button\"\n class=\"cat-icon-btn\"\n [disabled]=\"isLoading\"\n aria-label=\"{{ resolvedMessages.searchByCamera }}\"\n (click)=\"openCamera()\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <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\" />\n <circle cx=\"12\" cy=\"13\" r=\"3\" />\n </svg>\n </button>\n <input\n #cameraInput\n type=\"file\"\n accept=\"image/*\"\n [attr.capture]=\"cameraFacingMode\"\n class=\"cat-hidden-input\"\n (change)=\"onCameraFileSelected($event)\"\n />\n }\n\n @if (enableImageSearch) {\n <button\n type=\"button\"\n class=\"cat-icon-btn\"\n [disabled]=\"isLoading\"\n aria-label=\"{{ resolvedMessages.searchByImage }}\"\n (click)=\"fileInput.click()\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\" />\n <circle cx=\"9\" cy=\"9\" r=\"2\" />\n <path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\" />\n </svg>\n </button>\n <input\n #fileInput\n type=\"file\"\n accept=\"image/*\"\n class=\"cat-hidden-input\"\n (change)=\"onFileSelected($event)\"\n />\n }\n\n @if (showVoiceReplay) {\n <button\n type=\"button\"\n class=\"cat-icon-btn\"\n aria-label=\"{{ resolvedMessages.replayVoiceSummary }}\"\n (click)=\"replayVoiceSummary()\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\" />\n <path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\" />\n </svg>\n </button>\n }\n\n @if (enableCart) {\n <button\n type=\"button\"\n class=\"cat-icon-btn cat-cart-toggle\"\n [class.cat-cart-toggle--open]=\"cart.isCartOpen()\"\n [attr.aria-label]=\"cartAriaLabel\"\n [attr.aria-expanded]=\"cart.isCartOpen()\"\n [attr.aria-pressed]=\"cart.isCartOpen()\"\n (click)=\"cart.toggleCart()\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n <circle cx=\"8\" cy=\"21\" r=\"1\" />\n <circle cx=\"19\" cy=\"21\" r=\"1\" />\n <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\" />\n </svg>\n @if (cartQuantity > 0) {\n <span class=\"cat-cart-badge\" aria-hidden=\"true\">{{ cartBadgeLabel }}</span>\n }\n </button>\n }\n\n @if (showSuggestions) {\n <div\n id=\"cat-suggestions-listbox\"\n class=\"cat-suggestions\"\n role=\"listbox\"\n [attr.aria-label]=\"resolvedMessages.suggestionsAriaLabel\"\n >\n @if (isLoadingSuggestions && suggestions.length === 0 && !suggestionsError) {\n <div class=\"cat-suggestions__status\">{{ resolvedMessages.loadingSuggestions }}</div>\n }\n\n @if (suggestionsError) {\n <div class=\"cat-suggestions__status cat-suggestions__status--error\" role=\"alert\">\n {{ suggestionsError }}\n </div>\n }\n\n @if (\n !isLoadingSuggestions &&\n !suggestionsError &&\n suggestionsReady &&\n suggestions.length === 0\n ) {\n <div class=\"cat-suggestions__status\">{{ resolvedMessages.noSuggestions }}</div>\n }\n\n @for (suggestion of suggestions; track $index; let index = $index) {\n <button\n type=\"button\"\n class=\"cat-suggestions__item\"\n [class.cat-suggestions__item--active]=\"index === activeSuggestionIndex\"\n role=\"option\"\n [attr.id]=\"'cat-suggestion-' + index\"\n [attr.aria-selected]=\"index === activeSuggestionIndex\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onSuggestionSelect(suggestion)\"\n >\n {{ suggestion }}\n </button>\n }\n </div>\n }\n </form>\n\n @if (enableCameraSearch && (isCameraOpen || cameraError)) {\n <div class=\"cat-camera-overlay\" role=\"dialog\" [attr.aria-label]=\"resolvedMessages.cameraCapture\">\n <div class=\"cat-camera-overlay__panel\">\n @if (cameraError) {\n <div class=\"cat-camera-overlay__error\" role=\"alert\">\n <p>{{ cameraError }}</p>\n <button type=\"button\" class=\"cat-camera-overlay__btn\" (click)=\"clearCameraError()\">\n {{ resolvedMessages.dismiss }}\n </button>\n </div>\n } @else {\n <video\n #cameraVideo\n class=\"cat-camera-preview\"\n autoplay\n playsinline\n muted\n [attr.aria-label]=\"resolvedMessages.cameraPreview\"\n ></video>\n <div class=\"cat-camera-actions\">\n <button\n type=\"button\"\n class=\"cat-camera-overlay__btn cat-camera-overlay__btn--secondary\"\n [attr.aria-label]=\"resolvedMessages.cancel\"\n (click)=\"closeCamera()\"\n >\n {{ resolvedMessages.cancel }}\n </button>\n <button\n type=\"button\"\n class=\"cat-camera-overlay__btn cat-camera-overlay__btn--primary\"\n [attr.aria-label]=\"resolvedMessages.capturePhoto\"\n (click)=\"capturePhoto()\"\n >\n {{ resolvedMessages.capturePhoto }}\n </button>\n </div>\n }\n </div>\n </div>\n }\n\n @if (enableCart && cart.isCartOpen()) {\n <commerce-ai-cart-panel\n [cart]=\"cart.cart()\"\n [customer]=\"cart.customer()\"\n [isLoading]=\"cart.isLoading() || cart.isMutating()\"\n [isLoggingIn]=\"cart.isLoggingIn()\"\n [error]=\"cart.error()\"\n [messages]=\"resolvedMessages\"\n [catalogLocale]=\"catalogLocale\"\n [showCheckout]=\"checkout.observed\"\n (close)=\"cart.closeCart()\"\n (remove)=\"onCartRemove($event)\"\n (quantityChange)=\"onCartQuantityChange($event)\"\n (login)=\"onCartLogin($event)\"\n (logout)=\"onCartLogout()\"\n (checkout)=\"checkout.emit($event)\"\n />\n }\n\n @if (enableFacets && lastSearchMode === \"text\" && hasSearched && facets.length && !showMission) {\n <section class=\"cat-facets\" [attr.aria-label]=\"resolvedMessages.filtersAriaLabel\">\n <div class=\"cat-facets__header\">\n <span>{{ resolvedMessages.narrowResults }}</span>\n <div class=\"cat-facets__actions\">\n @if (hasAppliedFilters) {\n <button type=\"button\" class=\"cat-facets__clear\" (click)=\"clearFacetFilters()\">\n {{ resolvedMessages.clearFilters }}\n </button>\n }\n <button type=\"button\" class=\"cat-facets__clear\" (click)=\"startNewSearch()\">\n {{ resolvedMessages.newSearch }}\n </button>\n </div>\n </div>\n @for (facet of facets; track facet.id) {\n <div class=\"cat-facet-group\" role=\"group\" [attr.aria-label]=\"facet.label\">\n <span class=\"cat-facet-group__label\">{{ facet.label }}</span>\n <div class=\"cat-facet-group__options\">\n @for (bucket of facet.buckets; track bucket.key) {\n <button\n type=\"button\"\n class=\"cat-facet-chip\"\n [class.cat-facet-chip--selected]=\"isFacetSelected(facet.id, bucket.key)\"\n [attr.aria-pressed]=\"isFacetSelected(facet.id, bucket.key)\"\n (click)=\"toggleFacet(facet.id, bucket.key)\"\n >\n @if (facetSwatch(facet.id, bucket.key); as swatch) {\n <span class=\"cat-facet-chip__swatch\" [style.background-color]=\"swatch\" aria-hidden=\"true\"></span>\n }\n {{ bucket.label }} <span aria-hidden=\"true\">{{ bucket.count }}</span>\n </button>\n }\n </div>\n </div>\n }\n </section>\n }\n\n @if (showResults && isLoading) {\n <div class=\"cat-status\">{{ resolvedMessages.searching }}</div>\n }\n @if (showResults && error) {\n <div class=\"cat-status cat-status--error\">{{ error }}</div>\n }\n\n @if (!isLoading && !error && showResults && showMission && mission) {\n <commerce-ai-mission-results\n [mission]=\"mission\"\n [messages]=\"resolvedMessages\"\n [enableCart]=\"enableCart\"\n [isMutating]=\"cart.isMutating()\"\n [addedProductIds]=\"addedProductIds\"\n [addAllHandler]=\"addMissionItems\"\n (productSelect)=\"productSelect.emit($event)\"\n (addItem)=\"onAddItem($event)\"\n />\n }\n\n @if (showResults && !showMission && !isLoading && !error) {\n <div\n class=\"cat-results\"\n [attr.role]=\"results.length > 0 ? 'list' : null\"\n [attr.aria-label]=\"results.length > 0 ? resolvedMessages.searchResultsAriaLabel : null\"\n >\n @for (product of results; track product.id; let index = $index) {\n <article\n class=\"cat-result-card\"\n role=\"listitem\"\n [class.cat-result-card--featured]=\"index === 0\"\n >\n <div class=\"cat-result-card__core\">\n <button type=\"button\" class=\"cat-result-card__select\" (click)=\"productSelect.emit(product)\">\n @if (product.imageUrl) {\n <img [src]=\"product.imageUrl\" alt=\"\" class=\"cat-result-image\" loading=\"lazy\" />\n } @else {\n <div class=\"cat-result-image cat-result-image--placeholder\">📦</div>\n }\n <div class=\"cat-result-info\">\n <div class=\"cat-result-name\">{{ product.name }}</div>\n @if (product.price) {\n <div class=\"cat-result-price\">{{ product.price.formatted }}</div>\n }\n </div>\n </button>\n @if (enableCart) {\n <button\n type=\"button\"\n class=\"cat-icon-btn cat-result-card__add\"\n [class.cat-result-card__add--added]=\"addedProductIds[product.id]\"\n [attr.aria-label]=\"addToCartLabel(product)\"\n [disabled]=\"!(product.sku || product.id) || cart.isMutating()\"\n (click)=\"onAddItem(product)\"\n >\n @if (addedProductIds[product.id]) {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n } @else {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n <circle cx=\"8\" cy=\"21\" r=\"1\" />\n <circle cx=\"19\" cy=\"21\" r=\"1\" />\n <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\" />\n </svg>\n }\n </button>\n }\n </div>\n </article>\n }\n\n @if (results.length === 0 && hasSearched && query.trim()) {\n <div class=\"cat-status cat-status--empty\" role=\"status\" aria-live=\"polite\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.3-4.3\" />\n <path d=\"m13 9-4 4\" />\n <path d=\"m9 9 4 4\" />\n </svg>\n <div class=\"cat-status__content\">\n <div class=\"cat-status__title\">{{ resolvedMessages.noProductsFound }}</div>\n @if (meta?.queryInterpretation) {\n <div class=\"cat-status__hint\">\n {{ resolvedMessages.searchedFor }} {{ meta?.queryInterpretation }}\n </div>\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n `,\n encapsulation: ViewEncapsulation.None,\n})\nexport class CommerceAiSearchComponent implements OnInit, OnChanges, OnDestroy {\n private readonly api = inject(CommerceAiApiService);\n private readonly cdr = inject(ChangeDetectorRef);\n readonly cart = inject(CommerceAiCartService);\n\n @Input() apiBaseUrl = \"/api/commerce-ai\";\n @Input() theme: ThemeMode = \"auto\";\n @Input() catalogLocale?: string;\n @Input() queryLocale?: string;\n /** @deprecated Use queryLocale */\n @Input() locale?: string;\n @Input() placeholder = \"What are you looking for?\";\n @Input() messages?: Partial<CommerceAISearchMessages>;\n @Input() enableAutocomplete = false;\n @Input() enableFacets = false;\n /** Show microphone search controls. */\n @Input() enableVoice = true;\n /** Show local image upload and drag-and-drop search controls. */\n @Input() enableImageSearch = true;\n /** Show camera capture search controls independently of image upload. */\n @Input() enableCameraSearch = true;\n @Input() cameraFacingMode: CameraFacingMode = \"environment\";\n @Input() enableTts = true;\n /** Enable built-in add-to-cart buttons and cart preview panel. Default false. */\n @Input() enableCart = false;\n /** Enable multi-item shopping missions on text, voice, and image search. Default false. */\n @Input() enableMissions = false;\n /** Currency for cart creation. Required when `enableCart` is true unless the server has a default. */\n @Input() currency?: string;\n /** Optional ISO country code used for commercetools price selection. */\n @Input() country?: string;\n\n @Output() productSelect = new EventEmitter<ProductCard>();\n /** Fires after every cart fetch or mutation when `enableCart` is true. */\n @Output() cartChange = new EventEmitter<CartSnapshot | null>();\n /** Opens the host-owned checkout route for the current non-empty cart. */\n @Output() checkout = new EventEmitter<CartSnapshot>();\n\n @ViewChild(\"cameraInput\") cameraInputRef?: ElementRef<HTMLInputElement>;\n @ViewChild(\"cameraVideo\") cameraVideoRef?: ElementRef<HTMLVideoElement>;\n\n query = \"\";\n suggestions: string[] = [];\n activeSuggestionIndex = -1;\n isLoadingSuggestions = false;\n suggestionsError: string | null = null;\n suggestionsReady = false;\n suggestionsDismissed = false;\n results: ProductCard[] = [];\n mission: MissionSearchResult | null = null;\n hasFacetSession = false;\n addedProductIds: Record<string, true> = {};\n meta: {\n queryInterpretation?: string;\n searchTerms?: string[];\n appliedFilters?: InterpretedSearchFilters;\n } | null = null;\n facets: SearchFacetGroup[] = [];\n suggestedFacets: SuggestedFacet[] = [];\n isLoading = false;\n hasSearched = false;\n error: string | null = null;\n isDragging = false;\n isRecording = false;\n isProcessing = false;\n isCameraOpen = false;\n cameraError: string | null = null;\n lastSearchMode: SearchMode = null;\n audioSummary: string | null = null;\n\n private mediaRecorder: MediaRecorder | null = null;\n private audioChunks: Blob[] = [];\n private cameraStream: MediaStream | null = null;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private suggestionsTimer: ReturnType<typeof setTimeout> | null = null;\n private searchAbort: AbortController | null = null;\n private suggestionsAbort: AbortController | null = null;\n private searchRequestId = 0;\n private suggestionsRequestId = 0;\n private addedTimeouts = new Map<string, ReturnType<typeof setTimeout>>();\n private destroyed = false;\n\n readonly addMissionItems = (items: AddToCartLineItem[]) => this.cart.addItems(items);\n\n ngOnInit(): void {\n this.syncCart();\n }\n\n ngOnChanges(): void {\n this.syncCart();\n }\n\n ngOnDestroy(): void {\n this.destroyed = true;\n for (const timeout of this.addedTimeouts.values()) {\n clearTimeout(timeout);\n }\n this.closeCamera();\n this.searchAbort?.abort();\n this.suggestionsAbort?.abort();\n }\n\n get resolvedMessages(): CommerceAISearchMessages {\n return resolveCommerceAISearchMessages({\n ...this.messages,\n placeholder: this.messages?.placeholder ?? this.placeholder,\n });\n }\n\n get showSuggestions(): boolean {\n return (\n this.enableAutocomplete &&\n !this.suggestionsDismissed &&\n !this.showResults &&\n this.query.trim().length >= 2 &&\n (this.isLoadingSuggestions ||\n this.suggestions.length > 0 ||\n !!this.suggestionsError ||\n this.suggestionsReady)\n );\n }\n\n get activeSuggestionId(): string | null {\n return this.activeSuggestionIndex >= 0\n ? `cat-suggestion-${this.activeSuggestionIndex}`\n : null;\n }\n\n get showMission(): boolean {\n return Boolean(this.mission);\n }\n\n get showMissionVisible(): boolean {\n return this.showMission && !this.isLoading && !this.error;\n }\n\n get missionLaneCount(): number {\n if (!this.showMissionVisible) {\n return 0;\n }\n return Math.min(Math.max(this.mission?.intents.length ?? 1, 1), 5);\n }\n\n get cartQuantity(): number {\n return this.cart.cart()?.totalQuantity ?? 0;\n }\n\n get cartBadgeLabel(): string {\n return this.cartQuantity > 99 ? \"99+\" : String(this.cartQuantity);\n }\n\n get cartAriaLabel(): string {\n return this.cartQuantity > 0\n ? `${this.resolvedMessages.cartAriaLabel} (${this.cartBadgeLabel})`\n : this.resolvedMessages.cartAriaLabel;\n }\n\n get showResults(): boolean {\n const hasQuery = this.query.trim().length > 0;\n const showEmptyResults =\n !this.isLoading &&\n !this.error &&\n this.hasSearched &&\n this.results.length === 0 &&\n !this.showMission &&\n hasQuery;\n\n return (\n hasQuery &&\n (this.results.length > 0 ||\n this.isLoading ||\n !!this.error ||\n showEmptyResults ||\n this.showMission)\n );\n }\n\n get showVoiceReplay(): boolean {\n return this.enableTts && this.lastSearchMode === \"voice\" && !!this.audioSummary;\n }\n\n onQueryChange(value: string): void {\n this.activeSuggestionIndex = -1;\n this.suggestionsDismissed = false;\n\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n if (this.suggestionsTimer) {\n clearTimeout(this.suggestionsTimer);\n this.suggestionsTimer = null;\n }\n\n const trimmed = value.trim();\n if (trimmed.length === 0 || trimmed.length < 2) {\n this.lastSearchMode = null;\n this.clearVoiceAudio();\n this.clearResults();\n return;\n }\n\n this.lastSearchMode = \"text\";\n this.clearVoiceAudio();\n\n if (this.enableAutocomplete) {\n // Hide previous results so autocomplete can show while typing after a search.\n this.results = [];\n this.mission = null;\n this.error = null;\n this.isLoading = false;\n this.hasSearched = false;\n this.isLoadingSuggestions = true;\n this.suggestionsTimer = setTimeout(() => this.fetchSuggestions(trimmed), 200);\n return;\n }\n\n this.isLoading = true;\n this.error = null;\n this.results = [];\n this.mission = null;\n this.meta = null;\n this.debounceTimer = setTimeout(() => this.onSubmit(), 250);\n }\n\n onSuggestionSelect(suggestion: string): void {\n this.activeSuggestionIndex = -1;\n this.query = suggestion;\n this.suggestions = [];\n this.onSubmit();\n }\n\n onInputKeyDown(event: KeyboardEvent): void {\n if (event.key === \"Escape\") {\n this.activeSuggestionIndex = -1;\n this.suggestionsDismissed = true;\n return;\n }\n\n if (!this.showSuggestions || this.suggestions.length === 0) {\n return;\n }\n\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n this.activeSuggestionIndex =\n this.activeSuggestionIndex < this.suggestions.length - 1\n ? this.activeSuggestionIndex + 1\n : 0;\n return;\n }\n\n if (event.key === \"ArrowUp\") {\n event.preventDefault();\n this.activeSuggestionIndex =\n this.activeSuggestionIndex > 0\n ? this.activeSuggestionIndex - 1\n : this.suggestions.length - 1;\n return;\n }\n\n if (event.key === \"Enter\" && this.activeSuggestionIndex >= 0) {\n event.preventDefault();\n const suggestion = this.suggestions[this.activeSuggestionIndex];\n if (suggestion) {\n this.onSuggestionSelect(suggestion);\n }\n }\n }\n\n private fetchSuggestions(searchQuery: string): void {\n this.suggestionsAbort?.abort();\n const controller = new AbortController();\n this.suggestionsAbort = controller;\n const requestId = ++this.suggestionsRequestId;\n\n this.isLoadingSuggestions = true;\n this.suggestionsError = null;\n this.suggestionsReady = false;\n\n void this.api\n .suggest(this.apiBaseUrl, searchQuery, this.localeFields, controller.signal)\n .then((data) => {\n if (requestId !== this.suggestionsRequestId) return;\n this.suggestions = data.suggestions;\n this.isLoadingSuggestions = false;\n this.suggestionsReady = true;\n this.cdr.detectChanges();\n })\n .catch((err: Error) => {\n if (err.name === \"AbortError\") return;\n if (requestId !== this.suggestionsRequestId) return;\n this.suggestions = [];\n this.suggestionsError = err.message || \"Suggestions failed\";\n this.isLoadingSuggestions = false;\n this.suggestionsReady = true;\n this.cdr.detectChanges();\n });\n }\n\n private clearResults(): void {\n this.searchAbort?.abort();\n this.searchAbort = null;\n this.suggestionsAbort?.abort();\n this.suggestionsAbort = null;\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n if (this.suggestionsTimer) {\n clearTimeout(this.suggestionsTimer);\n this.suggestionsTimer = null;\n }\n this.results = [];\n this.mission = null;\n this.hasFacetSession = false;\n this.meta = null;\n this.error = null;\n this.isLoading = false;\n this.hasSearched = false;\n this.suggestions = [];\n this.isLoadingSuggestions = false;\n this.suggestionsError = null;\n this.suggestionsReady = false;\n this.suggestionsDismissed = false;\n this.activeSuggestionIndex = -1;\n }\n\n private get localeFields() {\n return {\n catalogLocale: this.catalogLocale,\n queryLocale: this.queryLocale,\n locale: this.locale,\n };\n }\n\n isFacetSelected(facetId: string, key: string): boolean {\n return isFacetFilterSelected(this.meta?.appliedFilters ?? {}, facetId, key);\n }\n\n facetSwatch(facetId: string, key: string): string | undefined {\n return isColorLikeFacetName(facetId) ? hexColorSwatchValue(key) : undefined;\n }\n\n get hasAppliedFilters(): boolean {\n return Object.keys(this.meta?.appliedFilters ?? {}).length > 0;\n }\n\n toggleFacet(facetId: string, key: string): void {\n if (!this.meta?.searchTerms) return;\n const filters = toggleFacetFilter(this.meta.appliedFilters ?? {}, facetId, key);\n this.searchWithFacets(filters);\n }\n\n clearFacetFilters(): void {\n if (!this.meta?.searchTerms) return;\n this.searchWithFacets({});\n }\n\n startNewSearch(): void {\n const q = this.query.trim();\n if (!q) return;\n this.suggestedFacets = [];\n this.meta = this.meta\n ? { ...this.meta, searchTerms: undefined, appliedFilters: undefined }\n : null;\n this.lastSearchMode = \"text\";\n this.clearVoiceAudio();\n this.suggestions = [];\n this.activeSuggestionIndex = -1;\n\n this.searchAbort?.abort();\n const controller = new AbortController();\n this.searchAbort = controller;\n const requestId = ++this.searchRequestId;\n\n this.isLoading = true;\n this.error = null;\n this.results = [];\n this.mission = null;\n\n void this.api\n .search(this.apiBaseUrl, q, this.localeFields, controller.signal, {\n includeFacets: this.enableFacets,\n ...(this.enableMissions ? { enableMissions: true } : {}),\n })\n .then((data) => {\n if (requestId !== this.searchRequestId) return;\n this.applySearchResult(data);\n this.isLoading = false;\n this.hasSearched = true;\n this.cdr.detectChanges();\n })\n .catch((err: Error) => {\n if (err.name === \"AbortError\") return;\n if (requestId !== this.searchRequestId) return;\n this.error = err.message;\n this.results = [];\n this.mission = null;\n this.hasFacetSession = false;\n this.meta = null;\n this.isLoading = false;\n this.hasSearched = true;\n this.cdr.detectChanges();\n });\n }\n\n private searchWithFacets(filters: InterpretedSearchFilters, refineQuery?: string): void {\n if (!this.meta?.searchTerms) return;\n this.isLoading = true;\n this.error = null;\n const controller = new AbortController();\n this.searchAbort?.abort();\n this.searchAbort = controller;\n const requestId = ++this.searchRequestId;\n\n void this.api\n .search(this.apiBaseUrl, this.meta.queryInterpretation ?? this.query, this.localeFields, controller.signal, {\n filters,\n searchTerms: this.meta.searchTerms,\n refineQuery,\n suggestedFacets: this.suggestedFacets,\n includeFacets: true,\n })\n .then((data) => {\n if (requestId !== this.searchRequestId) return;\n this.applySearchResult(data);\n this.isLoading = false;\n this.hasSearched = true;\n this.cdr.detectChanges();\n })\n .catch((error: Error) => {\n if (requestId !== this.searchRequestId || error.name === \"AbortError\") return;\n this.error = error.message;\n this.isLoading = false;\n this.cdr.detectChanges();\n });\n }\n\n onSubmit(): void {\n const q = this.query.trim();\n if (!q) return;\n\n if (\n this.enableAutocomplete &&\n this.activeSuggestionIndex >= 0 &&\n this.suggestions[this.activeSuggestionIndex]\n ) {\n this.onSuggestionSelect(this.suggestions[this.activeSuggestionIndex]!);\n return;\n }\n\n this.lastSearchMode = \"text\";\n this.clearVoiceAudio();\n this.suggestions = [];\n this.activeSuggestionIndex = -1;\n\n this.searchAbort?.abort();\n const controller = new AbortController();\n this.searchAbort = controller;\n const requestId = ++this.searchRequestId;\n\n this.isLoading = true;\n this.error = null;\n this.results = [];\n this.mission = null;\n const previousMeta = this.meta;\n this.meta = null;\n\n const isRefinement =\n this.enableFacets &&\n this.hasFacetSession &&\n !(this.enableMissions && looksLikeCompoundShoppingList(q));\n void this.api.search(\n this.apiBaseUrl,\n isRefinement ? previousMeta?.queryInterpretation ?? q : q,\n this.localeFields,\n controller.signal,\n isRefinement\n ? {\n searchTerms: previousMeta?.searchTerms,\n filters: previousMeta?.appliedFilters,\n refineQuery: q,\n suggestedFacets: this.suggestedFacets,\n includeFacets: true,\n }\n : {\n includeFacets: this.enableFacets,\n ...(this.enableMissions ? { enableMissions: true } : {}),\n },\n ).then(\n (data) => {\n if (requestId !== this.searchRequestId) return;\n this.applySearchResult(data);\n this.isLoading = false;\n this.hasSearched = true;\n },\n (err: Error) => {\n if (err.name === \"AbortError\") return;\n if (requestId !== this.searchRequestId) return;\n this.error = err.message;\n this.results = [];\n this.mission = null;\n this.hasFacetSession = false;\n this.meta = null;\n this.isLoading = false;\n this.hasSearched = true;\n },\n );\n }\n\n onFileSelected(event: Event): void {\n const input = event.target as HTMLInputElement;\n const file = input.files?.[0];\n if (file) void this.searchByImage(file);\n }\n\n onCameraFileSelected(event: Event): void {\n const input = event.target as HTMLInputElement;\n const file = input.files?.[0];\n input.value = \"\";\n if (file) void this.searchByImage(file);\n }\n\n openCamera(): void {\n this.cameraError = null;\n\n if (prefersNativeCamera()) {\n this.cameraInputRef?.nativeElement.click();\n return;\n }\n\n void this.openCameraOverlay();\n }\n\n async openCameraOverlay(): Promise<void> {\n this.cameraError = null;\n\n try {\n const stream = await navigator.mediaDevices.getUserMedia(\n buildCameraConstraints(this.cameraFacingMode),\n );\n this.cameraStream = stream;\n this.isCameraOpen = true;\n this.cdr.detectChanges();\n\n const video = this.cameraVideoRef?.nativeElement;\n if (video) {\n video.srcObject = stream;\n await video.play();\n }\n } catch (err) {\n this.cameraError = getCameraErrorMessage(err);\n this.closeCamera();\n }\n }\n\n async capturePhoto(): Promise<void> {\n const video = this.cameraVideoRef?.nativeElement;\n if (!video) return;\n\n try {\n const file = await createJpegFileFromVideo(video);\n this.closeCamera();\n void this.searchByImage(file);\n } catch (err) {\n this.closeCamera();\n this.error = err instanceof Error ? err.message : this.resolvedMessages.couldNotCapturePhoto;\n }\n }\n\n closeCamera(): void {\n stopMediaStream(this.cameraStream);\n this.cameraStream = null;\n this.isCameraOpen = false;\n\n const video = this.cameraVideoRef?.nativeElement;\n if (video) {\n video.srcObject = null;\n }\n }\n\n clearCameraError(): void {\n this.cameraError = null;\n }\n\n onDragOver(event: DragEvent): void {\n if (!this.enableImageSearch) {\n return;\n }\n event.preventDefault();\n this.isDragging = true;\n }\n\n onDrop(event: DragEvent): void {\n event.preventDefault();\n this.isDragging = false;\n if (!this.enableImageSearch) {\n return;\n }\n const file = event.dataTransfer?.files[0];\n if (file) void this.searchByImage(file);\n }\n\n searchByImage(file: File): void {\n if (!file.type.startsWith(\"image/\")) return;\n\n this.lastSearchMode = \"image\";\n this.clearVoiceAudio();\n\n this.searchAbort?.abort();\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n\n this.isLoading = true;\n this.error = null;\n this.results = [];\n this.mission = null;\n this.meta = null;\n\n void this.api.searchByImage(this.apiBaseUrl, file, this.localeFields, this.enableMissions).then(\n (data) => {\n this.applySearchResult(data);\n if (data.interpretation) this.query = data.interpretation;\n this.isLoading = false;\n this.hasSearched = true;\n },\n (err: Error) => {\n this.error = err.message;\n this.isLoading = false;\n this.hasSearched = true;\n },\n );\n }\n\n async toggleRecording(): Promise<void> {\n if (this.isRecording) {\n this.mediaRecorder?.stop();\n this.isRecording = false;\n return;\n }\n\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n this.mediaRecorder = new MediaRecorder(stream);\n this.audioChunks = [];\n\n this.mediaRecorder.ondataavailable = (e) => {\n if (e.data.size > 0) this.audioChunks.push(e.data);\n };\n\n this.mediaRecorder.onstop = () => {\n stream.getTracks().forEach((t) => t.stop());\n const blob = new Blob(this.audioChunks, { type: \"audio/webm\" });\n this.isProcessing = true;\n this.clearVoiceAudio();\n\n void this.api\n .searchByVoice(this.apiBaseUrl, blob, this.localeFields, this.enableTts, this.enableMissions)\n .then(async (data) => {\n this.lastSearchMode = \"voice\";\n this.query = data.transcript;\n this.applySearchResult({\n products: data.products,\n meta: data.meta,\n mission: data.mission,\n });\n this.error = null;\n this.hasSearched = true;\n if (data.audioSummary) {\n this.audioSummary = data.audioSummary;\n const audio = new Audio(`data:audio/mpeg;base64,${data.audioSummary}`);\n void audio.play();\n } else if (this.enableTts && data.ttsText && data.ttsPending) {\n try {\n const blob = await this.api.synthesizeSpeech(this.apiBaseUrl, data.ttsText);\n const buffer = await blob.arrayBuffer();\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n this.audioSummary = btoa(binary);\n const audio = new Audio(`data:audio/mpeg;base64,${this.audioSummary}`);\n void audio.play();\n } catch {\n this.audioSummary = null;\n }\n }\n })\n .catch((err: Error) => {\n this.error = err.message;\n })\n .finally(() => {\n this.isProcessing = false;\n });\n };\n\n this.mediaRecorder.start();\n this.isRecording = true;\n } catch {\n this.error = \"Microphone access denied\";\n }\n }\n\n replayVoiceSummary(): void {\n if (!this.audioSummary) return;\n\n const audio = new Audio(`data:audio/mpeg;base64,${this.audioSummary}`);\n void audio.play();\n }\n\n addToCartLabel(product: ProductCard): string {\n if (this.addedProductIds[product.id]) {\n return this.resolvedMessages.itemAdded;\n }\n return product.sku || product.id\n ? this.resolvedMessages.addToCart\n : this.resolvedMessages.unableToAddToCart;\n }\n\n onAddItem(product: ProductCard): void {\n void this.handleAddToCart(product);\n }\n\n onCartRemove(lineItemId: string): void {\n void this.cart.removeFromCart(lineItemId);\n }\n\n onCartQuantityChange(event: { lineItemId: string; quantity: number }): void {\n void this.cart.updateQuantity(event.lineItemId, event.quantity);\n }\n\n onCartLogin(input: { email: string; password: string }): void {\n void this.cart.login(input);\n }\n\n onCartLogout(): void {\n void this.cart.logout();\n }\n\n private applySearchResult(data: Pick<SearchResult, \"products\" | \"meta\" | \"mission\" | \"facets\" | \"suggestedFacets\">): void {\n this.results = data.products;\n this.mission = data.mission ?? null;\n this.meta = data.meta;\n this.facets = data.facets ?? [];\n this.suggestedFacets = data.suggestedFacets ?? [];\n this.hasFacetSession = !data.mission && this.enableFacets && Boolean(data.meta.searchTerms);\n }\n\n private syncCart(): void {\n this.cart.configure({\n apiBaseUrl: this.apiBaseUrl,\n currency: this.currency,\n country: this.country,\n catalogLocale: this.catalogLocale,\n enabled: this.enableCart,\n onCartChange: (snapshot) => this.cartChange.emit(snapshot),\n });\n }\n\n private async handleAddToCart(product: ProductCard): Promise<void> {\n if (!product.sku && !product.id) {\n return;\n }\n const next = await this.cart.addToCart(toCartItem(product, 1));\n if (!next || this.destroyed) {\n return;\n }\n this.addedProductIds = { ...this.addedProductIds, [product.id]: true };\n const existing = this.addedTimeouts.get(product.id);\n if (existing) {\n clearTimeout(existing);\n }\n this.addedTimeouts.set(\n product.id,\n setTimeout(() => {\n if (this.destroyed) {\n return;\n }\n const { [product.id]: _removed, ...rest } = this.addedProductIds;\n this.addedProductIds = rest;\n this.addedTimeouts.delete(product.id);\n this.cdr.detectChanges();\n }, 1200),\n );\n this.cdr.detectChanges();\n }\n\n private clearVoiceAudio(): void {\n this.audioSummary = null;\n }\n}\n","import type { PaymentProvider } from \"../payments/types.js\";\n\nexport type { PaymentMethodOption } from \"../payments/types.js\";\n\nexport type ThemeMode = \"auto\" | \"light\" | \"dark\";\n\nexport type AIProviderName = \"openrouter\" | \"bedrock\";\n\nexport type VoiceMode = \"openrouter-audio\" | \"elevenlabs-stt\";\n\nexport interface SearchCacheConfig {\n ttlMs?: number;\n maxEntries?: number;\n}\n\nexport interface FacetConfig {\n enabled?: boolean;\n schemaTtlMs?: number;\n include?: string[];\n exclude?: string[];\n maxAttributes?: number;\n}\n\nexport interface SearchTimeoutsConfig {\n aiTextMs?: number;\n aiVoiceAudioMs?: number;\n aiImageMs?: number;\n commercetoolsMs?: number;\n commercetoolsSuggestMs?: number;\n elevenLabsTtsMs?: number;\n}\n\nexport const SUGGESTIONS_MIN_PREFIX_LENGTH = 2;\nexport const SUGGESTIONS_DEFAULT_LIMIT = 8;\nexport const SUGGESTIONS_MAX_LIMIT = 20;\n\nexport interface CommercetoolsConfig {\n projectKey: string;\n clientId: string;\n clientSecret: string;\n region: string;\n scopes?: string[];\n}\n\nexport interface OpenRouterConfig {\n apiKey: string;\n model?: string;\n visionModel?: string;\n /** Audio-capable model for direct voice search (OpenRouter input_audio). */\n voiceModel?: string;\n /**\n * Use OpenRouter `json_schema` (strict) for structured interpretation.\n * Falls back to `json_object` when the model rejects json_schema.\n * Default: true.\n */\n jsonSchema?: boolean;\n}\n\nexport interface BedrockConfig {\n region: string;\n modelId?: string;\n visionModelId?: string;\n}\n\nexport interface AIConfig {\n provider: AIProviderName;\n openrouter?: OpenRouterConfig;\n bedrock?: BedrockConfig;\n}\n\nexport interface ElevenLabsConfig {\n apiKey: string;\n sttModel?: string;\n ttsVoiceId?: string;\n ttsModel?: string;\n}\n\nexport interface CommerceAIDefaults {\n /** Language products are indexed in within commercetools */\n catalogLocale?: string;\n /** @deprecated Use catalogLocale */\n locale?: string;\n storeKey?: string;\n limit?: number;\n currency?: string;\n /** ISO country code for cart and product-card price selection (optional). */\n country?: string;\n}\n\nexport interface PaymentConfig {\n provider?: PaymentProvider;\n /** Block order creation without a successful authorization. Defaults to true when a provider is set. */\n requiredForOrder?: boolean;\n}\n\nexport interface LangfuseConfig {\n /** Derived from LANGFUSE_PUBLIC_KEY + LANGFUSE_SECRET_KEY when loading from env. */\n enabled?: boolean;\n /**\n * Fetch system prompts from Langfuse at runtime.\n * Applied via `configureLangfusePrompts` when creating the search orchestrator\n * (or set `LANGFUSE_PROMPTS=true` when config omits this field).\n * Local catalog remains the fallback and the source of truth for evals.\n */\n promptsEnabled?: boolean;\n /** Langfuse prompt label (default `production`). From LANGFUSE_PROMPT_LABEL or this field. */\n promptLabel?: string;\n /** Client-side prompt cache TTL in seconds (default 60). From LANGFUSE_PROMPT_CACHE_TTL_SECONDS or this field. */\n promptCacheTtlSeconds?: number;\n}\n\nexport interface MissionsConfig {\n /** Opt-in. When true, every fresh text search also runs mission decomposition. */\n enabled?: boolean;\n /** Maximum product intents kept from a decomposed mission. Default 5. */\n maxIntents?: number;\n /** Product Search limit per intent. Default 4. */\n perIntentLimit?: number;\n /** Minimum AI confidence (0–1) required to keep a mission. Default 0.6. */\n minConfidence?: number;\n}\n\nexport interface CommerceAIConfig {\n commercetools: CommercetoolsConfig;\n ai: AIConfig;\n elevenlabs?: ElevenLabsConfig;\n defaults?: CommerceAIDefaults;\n voiceMode?: VoiceMode;\n cache?: SearchCacheConfig;\n timeouts?: SearchTimeoutsConfig;\n facets?: FacetConfig;\n langfuse?: LangfuseConfig;\n payments?: PaymentConfig;\n missions?: MissionsConfig;\n}\n\nexport interface ProductCard {\n id: string;\n key?: string;\n name: string;\n description?: string;\n imageUrl?: string;\n /** SKU of the master variant — used for add-to-cart. */\n sku?: string;\n /** Master variant id — fallback when `sku` is missing. */\n variantId?: number;\n price?: {\n amount: number;\n currency: string;\n formatted: string;\n };\n slug?: string;\n}\n\nexport interface MoneyAmount {\n amount: number;\n currency: string;\n formatted: string;\n}\n\nexport interface CartLineItemSnapshot {\n id: string;\n name: string;\n sku?: string;\n productId: string;\n quantity: number;\n imageUrl?: string;\n price?: MoneyAmount;\n totalPrice?: MoneyAmount;\n}\n\nexport interface CheckoutAddress {\n firstName: string;\n lastName: string;\n streetName: string;\n additionalStreetInfo?: string;\n postalCode: string;\n city: string;\n region?: string;\n country: string;\n email?: string;\n phone?: string;\n}\n\nexport interface ShippingMethodSnapshot {\n id: string;\n name: string;\n description?: string;\n price?: MoneyAmount;\n}\n\nexport interface PaymentSnapshot {\n id: string;\n key?: string;\n interfaceId?: string;\n paymentInterface: string;\n method: string;\n status: \"authorized\" | \"pending\" | \"failed\";\n amount: MoneyAmount;\n clientData?: Record<string, string>;\n}\n\nexport interface DeliverySnapshot {\n id: string;\n trackingId?: string;\n carrier?: string;\n provider?: string;\n}\n\n/** Client-safe cart shape — never the raw commercetools Cart object. */\nexport interface CartSnapshot {\n id: string;\n version: number;\n anonymousId?: string;\n customerId?: string;\n lineItems: CartLineItemSnapshot[];\n totalPrice: MoneyAmount;\n totalQuantity: number;\n shippingAddress?: CheckoutAddress;\n billingAddress?: CheckoutAddress;\n shippingMethod?: ShippingMethodSnapshot;\n payments?: PaymentSnapshot[];\n}\n\nexport interface OrderSnapshot {\n id: string;\n orderNumber?: string;\n orderState: string;\n createdAt?: string;\n paymentState?: string;\n shipmentState?: string;\n customerEmail?: string;\n totalPrice: MoneyAmount;\n lineItems: CartLineItemSnapshot[];\n shippingAddress?: CheckoutAddress;\n billingAddress?: CheckoutAddress;\n shippingMethod?: ShippingMethodSnapshot;\n payments?: PaymentSnapshot[];\n deliveries?: DeliverySnapshot[];\n}\n\n/** Public customer identity — never includes credentials. */\nexport interface CustomerSnapshot {\n id: string;\n email: string;\n}\n\n/** HMAC cart session header for GET /cart — never send the token in the query string. */\nexport const CART_SESSION_HEADER = \"x-commerce-ai-cart-session\";\n\n/** Upper bound for a single add-to-cart line quantity (mission add-all and batch add). */\nexport const MAX_LINE_ITEM_QUANTITY = 99;\n\nexport interface CartLoginRequest {\n email: string;\n password: string;\n anonymousId?: string;\n cartId?: string;\n catalogLocale?: string;\n}\n\nexport interface CartLoginResult {\n cart: CartSnapshot | null;\n customer: CustomerSnapshot;\n}\n\nexport interface AddToCartLineItem {\n sku?: string;\n productId?: string;\n variantId?: number;\n quantity?: number;\n}\n\nexport interface AddToCartRequest {\n anonymousId?: string;\n customerId?: string;\n sku?: string;\n productId?: string;\n variantId?: number;\n quantity?: number;\n currency?: string;\n country?: string;\n catalogLocale?: string;\n /** When set, add to this cart instead of looking up by anonymousId / customerId. */\n cartId?: string;\n sessionToken?: string;\n}\n\nexport interface AddItemsToCartRequest {\n anonymousId?: string;\n customerId?: string;\n items: AddToCartLineItem[];\n currency?: string;\n country?: string;\n catalogLocale?: string;\n cartId?: string;\n sessionToken?: string;\n}\n\nexport interface CartMutationRequest {\n anonymousId?: string;\n customerId?: string;\n lineItemId: string;\n cartId?: string;\n cartVersion?: number;\n catalogLocale?: string;\n sessionToken?: string;\n}\n\nexport interface UpdateCartQuantityRequest extends CartMutationRequest {\n quantity: number;\n}\n\nexport interface GetCartRequest {\n anonymousId?: string;\n customerId?: string;\n catalogLocale?: string;\n sessionToken?: string;\n}\n\nexport interface CheckoutRequest {\n anonymousId?: string;\n customerId?: string;\n cartId?: string;\n catalogLocale?: string;\n sessionToken?: string;\n}\n\nexport interface SetCartAddressesRequest extends CheckoutRequest {\n shippingAddress: CheckoutAddress;\n billingAddress?: CheckoutAddress;\n}\n\nexport interface SetShippingMethodRequest extends CheckoutRequest {\n shippingMethodId: string;\n}\n\nexport interface CreateOrderRequest extends CheckoutRequest {\n orderNumber?: string;\n}\n\nexport interface AuthorizePaymentRequest extends CheckoutRequest {\n method: string;\n orderNumber?: string;\n}\n\nexport interface GetOrderRequest {\n orderNumber?: string;\n anonymousId?: string;\n customerId?: string;\n catalogLocale?: string;\n sessionToken?: string;\n}\n\nexport interface ListOrdersRequest {\n anonymousId?: string;\n customerId?: string;\n catalogLocale?: string;\n sessionToken?: string;\n limit?: number;\n}\n\nexport type SearchRelaxationLevel = \"none\" | \"drop_soft_filters\" | \"primary_any_match\";\n\nexport interface SearchMeta {\n total: number;\n limit: number;\n offset: number;\n /** @deprecated Use catalogLocale */\n locale: string;\n catalogLocale: string;\n queryLocale: string;\n queryInterpretation?: string;\n searchTerms?: string[];\n /** Catalog-language phrase closest to the user's intent, when the model supplied one. */\n primaryTerm?: string;\n appliedFilters?: InterpretedSearchFilters;\n /**\n * How far Product Search was relaxed after an empty first result.\n * Omitted when no relaxation ran (`none`).\n */\n relaxation?: SearchRelaxationLevel;\n sort?: \"relevance\" | \"price_asc\" | \"price_desc\";\n schemaEtag?: string;\n /** Per-step durations in milliseconds (dev / CAT_DEBUG only) */\n timings?: Record<string, number>;\n /** Total pipeline duration in milliseconds (dev / CAT_DEBUG only) */\n totalMs?: number;\n /**\n * Active OpenTelemetry / Langfuse trace id when Langfuse is enabled or CAT_DEBUG=true.\n * Non-stable for client contracts — intended for local/dev linking.\n */\n traceId?: string;\n}\n\nexport interface SearchResult {\n products: ProductCard[];\n meta: SearchMeta;\n facets?: SearchFacetGroup[];\n suggestedFacets?: SuggestedFacet[];\n facetSchema?: FacetAttributeDefinition[];\n /** Present when a compound shopping mission was executed. */\n mission?: MissionSearchResult;\n}\n\nexport interface SuggestionsRequest extends SearchLocaleOptions {\n query: string;\n limit?: number;\n}\n\nexport interface SuggestionsResult {\n suggestions: string[];\n /**\n * True when this request invoked AI `suggestSearchTerms` (not a cache hit).\n * Used by the server to flush Langfuse on serverless hosts.\n */\n aiFallbackUsed?: boolean;\n}\n\nexport interface TextSearchRequest {\n query: string;\n /** Language of the user query (AI input) */\n queryLocale?: string;\n /** Language products are indexed in commercetools */\n catalogLocale?: string;\n /** @deprecated Use queryLocale */\n locale?: string;\n limit?: number;\n offset?: number;\n filters?: InterpretedSearchFilters;\n searchTerms?: string[];\n sort?: \"relevance\" | \"price_asc\" | \"price_desc\";\n refineQuery?: string;\n includeFacets?: boolean;\n /** AI-suggested facets from the active search session (chip refine). */\n suggestedFacets?: SuggestedFacet[];\n /** Per-request override of `CommerceAIConfig.missions.enabled`. */\n enableMissions?: boolean;\n}\n\nexport interface SearchLocaleOptions {\n queryLocale?: string;\n catalogLocale?: string;\n /** @deprecated Use queryLocale */\n locale?: string;\n}\n\nexport interface SearchLocaleContext {\n queryLocale: string;\n catalogLocale: string;\n}\n\nexport interface VoiceSearchResult extends SearchResult {\n transcript: string;\n enhancedQuery?: string;\n}\n\nexport interface ImageSearchResult extends SearchResult {\n interpretation: string;\n}\n\n/** Standard filter keys returned by AI interpretation (all optional). */\nexport interface InterpretedSearchFilters {\n color?: string;\n brand?: string;\n category?: string;\n priceMin?: string;\n priceMax?: string;\n [key: string]: string | undefined;\n}\n\nexport interface InterpretedSearchQuery {\n /**\n * Catalog-language phrase closest to the user's intent.\n * Product Search boosts this phrase above the other `searchTerms`.\n * Kept separate from `searchTerms[0]` because same-language query passthrough\n * prepends the typed query without changing intent priority.\n */\n primaryTerm?: string;\n /**\n * Catalog-language search phrases. Product Search matches any phrase (OR).\n * Each element is a complete phrase (e.g. `\"red shoes\"`), not a split word.\n * Broad intents may include 3–5 synonym or hyponym phrases.\n */\n searchTerms: string[];\n filters?: InterpretedSearchFilters;\n suggestedFacets?: SuggestedFacet[];\n sort?: \"relevance\" | \"price_asc\" | \"price_desc\";\n interpretation: string;\n}\n\nexport interface ShoppingIntent {\n /** Stable id for UI keys and selection (`intent-0`, `intent-1`, …). */\n id: string;\n /** Short catalog-language label, e.g. \"tennis racket\". */\n label: string;\n /** Requested quantity, always >= 1. */\n quantity: number;\n /** Catalog-language phrase closest to this intent, boosted above alternates. */\n primaryTerm?: string;\n searchTerms: string[];\n filters?: InterpretedSearchFilters;\n sort?: \"relevance\" | \"price_asc\" | \"price_desc\";\n}\n\nexport interface DecomposedShoppingMission {\n isMission: boolean;\n /** 0–1. Missions below the configured minConfidence fall back to standard search. */\n confidence: number;\n intents: ShoppingIntent[];\n interpretation: string;\n}\n\nexport interface MissionIntentGroup {\n intent: ShoppingIntent;\n products: ProductCard[];\n total: number;\n /** True when this intent's Product Search failed; other groups still render. */\n failed?: boolean;\n}\n\nexport interface MissionSearchResult {\n intents: MissionIntentGroup[];\n interpretation: string;\n}\n\nexport type FacetAttributeType = \"enum\" | \"lenum\" | \"boolean\" | \"text\" | \"number\";\n\nexport interface FacetAttributeDefinition {\n name: string;\n label: string;\n kind: \"distinct\" | \"range\";\n attributeType: FacetAttributeType;\n field: string;\n fieldType?: string;\n}\n\nexport interface ResolvedFacetSchema {\n attributes: FacetAttributeDefinition[];\n systemFacets: Array<\"categories\" | \"price\">;\n etag: string;\n resolvedAt: string;\n}\n\nexport interface SuggestedFacet {\n name: string;\n reason?: string;\n}\n\nexport interface SearchFacetBucket {\n key: string;\n label: string;\n count: number;\n}\n\nexport interface SearchFacetGroup {\n id: string;\n label: string;\n type: \"distinct\" | \"range\";\n buckets: SearchFacetBucket[];\n selectedKey?: string;\n}\n\nexport interface SearchSessionState {\n query: string;\n searchTerms: string[];\n appliedFilters: InterpretedSearchFilters;\n suggestedFacets: SuggestedFacet[];\n facetSchema?: FacetAttributeDefinition[];\n schemaEtag?: string;\n}\n\nexport interface VoiceAudioInterpretation extends InterpretedSearchQuery {\n transcript: string;\n enhancedQuery: string;\n}\n\n/** @deprecated Use ProductSearchRequest from query-builder / platform-sdk */\nexport type { ProductSearchRequest as ProductSearchQueryBody } from \"@commercetools/platform-sdk\";\n","export interface CommerceAISearchMessages {\n placeholder: string;\n searchAriaLabel: string;\n productSearchAriaLabel: string;\n searchResultsAriaLabel: string;\n searching: string;\n noProductsFound: string;\n searchedFor: string;\n dropImageToSearch: string;\n voiceSearch: string;\n stopRecording: string;\n searchByCamera: string;\n searchByImage: string;\n replayVoiceSummary: string;\n listening: string;\n tapMicToStop: string;\n understandingQuery: string;\n preparingAudioSummary: string;\n cameraCapture: string;\n cameraPreview: string;\n capturePhoto: string;\n cancel: string;\n dismiss: string;\n searchFailed: string;\n imageSearchFailed: string;\n couldNotCapturePhoto: string;\n suggestionsAriaLabel: string;\n loadingSuggestions: string;\n noSuggestions: string;\n filtersAriaLabel: string;\n clearFilters: string;\n narrowResults: string;\n newSearch: string;\n addToCart: string;\n cart: string;\n emptyCart: string;\n removeItem: string;\n total: string;\n each: string;\n cartAriaLabel: string;\n itemAdded: string;\n increaseQuantity: string;\n decreaseQuantity: string;\n closeCart: string;\n unableToAddToCart: string;\n missionTitle: string;\n missionQuantity: string;\n /** Adds the first (recommended) product from each filled lane. Use `{count}` for the number of picks. */\n missionAddAll: string;\n missionItemsAdded: string;\n missionSelectProduct: string;\n missionIntentEmpty: string;\n missionIntentFailed: string;\n signIn: string;\n signInToSyncCart: string;\n signOut: string;\n email: string;\n password: string;\n signedInAs: string;\n invalidCredentials: string;\n signInFailed: string;\n checkout: string;\n checkoutTitle: string;\n orderSummary: string;\n shippingAddress: string;\n billingAddress: string;\n billingSameAsShipping: string;\n continueToDelivery: string;\n shippingMethod: string;\n selectShippingMethod: string;\n noShippingMethods: string;\n completeAddressToContinue: string;\n selectDeliveryToContinue: string;\n selectPaymentToContinue: string;\n checkoutStepAddress: string;\n checkoutStepDelivery: string;\n checkoutStepPayment: string;\n paymentMethod: string;\n selectPaymentMethod: string;\n paymentAuthorized: string;\n paymentDeclined: string;\n continueToPayment: string;\n placeOrder: string;\n placingOrder: string;\n orderPlaced: string;\n viewOrderStatus: string;\n continueShopping: string;\n checkoutFailed: string;\n orderStatus: string;\n orderPlacedAt: string;\n paymentStateLabel: string;\n shipmentStateLabel: string;\n trackingNumber: string;\n carrier: string;\n noTrackingYet: string;\n orderNotFound: string;\n noOrdersYet: string;\n yourOrders: string;\n firstName: string;\n lastName: string;\n streetName: string;\n additionalAddress: string;\n postalCode: string;\n city: string;\n region: string;\n country: string;\n}\n\nexport const DEFAULT_COMMERCE_AI_SEARCH_MESSAGES: CommerceAISearchMessages = {\n placeholder: \"What are you looking for?\",\n searchAriaLabel: \"Search query\",\n productSearchAriaLabel: \"Product search\",\n searchResultsAriaLabel: \"Search results\",\n searching: \"Searching...\",\n noProductsFound: \"No products found\",\n searchedFor: \"Searched for:\",\n dropImageToSearch: \"Drop image to search\",\n voiceSearch: \"Voice search\",\n stopRecording: \"Stop recording\",\n searchByCamera: \"Search by camera\",\n searchByImage: \"Search by image\",\n replayVoiceSummary: \"Replay voice result summary\",\n listening: \"Listening…\",\n tapMicToStop: \"Tap mic to stop\",\n understandingQuery: \"Understanding your query…\",\n preparingAudioSummary: \"Preparing audio summary…\",\n cameraCapture: \"Camera capture\",\n cameraPreview: \"Camera preview\",\n capturePhoto: \"Capture\",\n cancel: \"Cancel\",\n dismiss: \"Dismiss\",\n searchFailed: \"Search failed\",\n imageSearchFailed: \"Image search failed\",\n couldNotCapturePhoto: \"Could not capture photo\",\n suggestionsAriaLabel: \"Search suggestions\",\n loadingSuggestions: \"Loading suggestions...\",\n noSuggestions: \"No suggestions\",\n filtersAriaLabel: \"Search filters\",\n clearFilters: \"Clear filters\",\n narrowResults: \"Narrow your results\",\n newSearch: \"New search\",\n addToCart: \"Add to cart\",\n cart: \"Cart\",\n emptyCart: \"Your cart is empty\",\n removeItem: \"Remove item\",\n total: \"Total\",\n each: \"each\",\n cartAriaLabel: \"Shopping cart\",\n itemAdded: \"Added to cart\",\n increaseQuantity: \"Increase quantity\",\n decreaseQuantity: \"Decrease quantity\",\n closeCart: \"Close cart\",\n unableToAddToCart: \"This product cannot be added to the cart\",\n missionTitle: \"Shopping list\",\n missionQuantity: \"Looking for\",\n missionAddAll: \"Add {count} top picks to cart\",\n missionItemsAdded: \"Added to cart\",\n missionSelectProduct: \"Select this product\",\n missionIntentEmpty: \"No matching products\",\n missionIntentFailed: \"Couldn't search this item\",\n signIn: \"Sign in\",\n signInToSyncCart: \"Sign in to sync your cart\",\n signOut: \"Sign out\",\n email: \"Email\",\n password: \"Password\",\n signedInAs: \"Signed in as\",\n invalidCredentials: \"Invalid email or password\",\n signInFailed: \"Sign in failed\",\n checkout: \"Checkout\",\n checkoutTitle: \"Complete your order\",\n orderSummary: \"Order summary\",\n shippingAddress: \"Shipping address\",\n billingAddress: \"Billing address\",\n billingSameAsShipping: \"Billing address is the same as shipping\",\n continueToDelivery: \"Continue to delivery\",\n shippingMethod: \"Delivery method\",\n selectShippingMethod: \"Select a delivery method\",\n noShippingMethods: \"No delivery methods are available for this address\",\n completeAddressToContinue: \"Add a shipping address to continue\",\n selectDeliveryToContinue: \"Select a delivery method to continue\",\n selectPaymentToContinue: \"Select a payment method to continue\",\n checkoutStepAddress: \"Address\",\n checkoutStepDelivery: \"Delivery\",\n checkoutStepPayment: \"Payment\",\n paymentMethod: \"Payment method\",\n selectPaymentMethod: \"Select a payment method\",\n paymentAuthorized: \"Payment authorized\",\n paymentDeclined: \"Payment was declined\",\n continueToPayment: \"Continue to payment\",\n placeOrder: \"Place order\",\n placingOrder: \"Placing order…\",\n orderPlaced: \"Order placed\",\n viewOrderStatus: \"View order status\",\n continueShopping: \"Continue shopping\",\n checkoutFailed: \"Checkout failed\",\n orderStatus: \"Order status\",\n orderPlacedAt: \"Placed\",\n paymentStateLabel: \"Payment\",\n shipmentStateLabel: \"Shipment\",\n trackingNumber: \"Tracking number\",\n carrier: \"Carrier\",\n noTrackingYet: \"Tracking details will appear when the order ships\",\n orderNotFound: \"Order not found\",\n noOrdersYet: \"No orders yet\",\n yourOrders: \"Your orders\",\n firstName: \"First name\",\n lastName: \"Last name\",\n streetName: \"Street address\",\n additionalAddress: \"Apartment, suite, etc. (optional)\",\n postalCode: \"Postal code\",\n city: \"City\",\n region: \"State or region (optional)\",\n country: \"Country\",\n};\n\nexport function resolveCommerceAISearchMessages(\n overrides?: Partial<CommerceAISearchMessages>,\n): CommerceAISearchMessages {\n return {\n ...DEFAULT_COMMERCE_AI_SEARCH_MESSAGES,\n ...overrides,\n };\n}\n","const COMPOUND_SHOPPING_LIST_PATTERN = /\\band\\b|,|\\bplus\\b|\\boraz\\b/i;\n\n/**\n * Heuristic for the search box: a new multi-item query should not be treated as\n * a facet NL refine of the previous session.\n */\nexport function looksLikeCompoundShoppingList(query: string): boolean {\n return COMPOUND_SHOPPING_LIST_PATTERN.test(query.trim());\n}\n","/** CSS Color Module named colors used to label hex facet values. */\nconst CSS_NAMED_COLOR_TABLE = `\nAlice Blue:f0f8ff\nAntique White:faebd7\nAqua:00ffff\nAquamarine:7fffd4\nAzure:f0ffff\nBeige:f5f5dc\nBisque:ffe4c4\nBlack:000000\nBlanched Almond:ffebcd\nBlue:0000ff\nBlue Violet:8a2be2\nBrown:a52a2a\nBurlywood:deb887\nCadet Blue:5f9ea0\nChartreuse:7fff00\nChocolate:d2691e\nCoral:ff7f50\nCornflower Blue:6495ed\nCornsilk:fff8dc\nCrimson:dc143c\nDark Blue:00008b\nDark Cyan:008b8b\nDark Goldenrod:b8860b\nDark Gray:a9a9a9\nDark Green:006400\nDark Khaki:bdb76b\nDark Magenta:8b008b\nDark Olive Green:556b2f\nDark Orange:ff8c00\nDark Orchid:9932cc\nDark Red:8b0000\nDark Salmon:e9967a\nDark Sea Green:8fbc8f\nDark Slate Blue:483d8b\nDark Slate Gray:2f4f4f\nDark Turquoise:00ced1\nDark Violet:9400d3\nDeep Pink:ff1493\nDeep Sky Blue:00bfff\nDim Gray:696969\nDodger Blue:1e90ff\nFirebrick:b22222\nFloral White:fffaf0\nForest Green:228b22\nGainsboro:dcdcdc\nGhost White:f8f8ff\nGold:ffd700\nGoldenrod:daa520\nGray:808080\nGreen:008000\nGreen Yellow:adff2f\nHoneydew:f0fff0\nHot Pink:ff69b4\nIndian Red:cd5c5c\nIndigo:4b0082\nIvory:fffff0\nKhaki:f0e68c\nLavender:e6e6fa\nLavender Blush:fff0f5\nLawn Green:7cfc00\nLemon Chiffon:fffacd\nLight Blue:add8e6\nLight Coral:f08080\nLight Cyan:e0ffff\nLight Goldenrod Yellow:fafad2\nLight Gray:d3d3d3\nLight Green:90ee90\nLight Pink:ffb6c1\nLight Salmon:ffa07a\nLight Sea Green:20b2aa\nLight Sky Blue:87cefa\nLight Slate Gray:778899\nLight Steel Blue:b0c4de\nLight Yellow:ffffe0\nLime:00ff00\nLime Green:32cd32\nLinen:faf0e6\nMagenta:ff00ff\nMaroon:800000\nMedium Aquamarine:66cdaa\nMedium Blue:0000cd\nMedium Orchid:ba55d3\nMedium Purple:9370db\nMedium Sea Green:3cb371\nMedium Slate Blue:7b68ee\nMedium Spring Green:00fa9a\nMedium Turquoise:48d1cc\nMedium Violet Red:c71585\nMidnight Blue:191970\nMint Cream:f5fffa\nMisty Rose:ffe4e1\nMoccasin:ffe4b5\nNavajo White:ffdead\nNavy:000080\nOld Lace:fdf5e6\nOlive:808000\nOlive Drab:6b8e23\nOrange:ffa500\nOrange Red:ff4500\nOrchid:da70d6\nPale Goldenrod:eee8aa\nPale Green:98fb98\nPale Turquoise:afeeee\nPale Violet Red:db7093\nPapaya Whip:ffefd5\nPeach Puff:ffdab9\nPeru:cd853f\nPink:ffc0cb\nPlum:dda0dd\nPowder Blue:b0e0e6\nPurple:800080\nRebecca Purple:663399\nRed:ff0000\nRosy Brown:bc8f8f\nRoyal Blue:4169e1\nSaddle Brown:8b4513\nSalmon:fa8072\nSandy Brown:f4a460\nSea Green:2e8b57\nSeashell:fff5ee\nSienna:a0522d\nSilver:c0c0c0\nSky Blue:87ceeb\nSlate Blue:6a5acd\nSlate Gray:708090\nSnow:fffafa\nSpring Green:00ff7f\nSteel Blue:4682b4\nTan:d2b48c\nTeal:008080\nThistle:d8bfd8\nTomato:ff6347\nTurquoise:40e0d0\nViolet:ee82ee\nWheat:f5deb3\nWhite:ffffff\nWhite Smoke:f5f5f5\nYellow:ffff00\nYellow Green:9acd32\n`;\n\nconst HEX_COLOR_RE = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;\n\ntype NamedColor = { name: string; r: number; g: number; b: number };\n\nconst NAMED_COLORS: NamedColor[] = CSS_NAMED_COLOR_TABLE.trim()\n .split(\"\\n\")\n .flatMap((line) => {\n const separator = line.indexOf(\":\");\n if (separator <= 0) {\n return [];\n }\n const name = line.slice(0, separator);\n const hex = line.slice(separator + 1);\n const n = Number.parseInt(hex, 16);\n if (!name || !Number.isFinite(n)) {\n return [];\n }\n return [{ name, r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 }];\n });\n\nfunction parseHexRgb(value: string): [number, number, number] | null {\n const match = HEX_COLOR_RE.exec(value.trim());\n const captured = match?.[1];\n if (!captured) {\n return null;\n }\n const hex =\n captured.length === 3\n ? `${captured[0]}${captured[0]}${captured[1]}${captured[1]}${captured[2]}${captured[2]}`\n : captured;\n const n = Number.parseInt(hex, 16);\n return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n}\n\nfunction nearestCssColor(rgb: [number, number, number]): { name: string; dist: number } | null {\n const first = NAMED_COLORS[0];\n if (!first) {\n return null;\n }\n let bestName = first.name;\n let bestDist = Number.POSITIVE_INFINITY;\n for (const color of NAMED_COLORS) {\n const dist =\n (rgb[0] - color.r) ** 2 + (rgb[1] - color.g) ** 2 + (rgb[2] - color.b) ** 2;\n if (dist < bestDist) {\n bestDist = dist;\n bestName = color.name;\n if (dist === 0) {\n return { name: bestName, dist };\n }\n }\n }\n return { name: bestName, dist: bestDist };\n}\n\n/** Max squared RGB distance to accept a CSS name instead of the raw hex. */\nconst MAX_NAMED_COLOR_DISTANCE_SQ = 40 * 40;\n\nconst COLOR_LIKE_FACET_NAME = /(?:^|[-_])colou?r(?:[-_]|$)|(?:^|[-_])finish(?:[-_]|$)/i;\n\n/** True when a facet id looks like a color/finish attribute (`color-code`, `finish-code`). */\nexport function isColorLikeFacetName(name: string): boolean {\n return COLOR_LIKE_FACET_NAME.test(name.trim());\n}\n\n/** True when a facet bucket key is a CSS hex color (`#RGB` or `#RRGGBB`). */\nexport function isHexColorFacetKey(value: string): boolean {\n return HEX_COLOR_RE.test(value.trim());\n}\n\n/** Normalized `#rrggbb` for a hex facet key, or `undefined` when not a color. */\nexport function hexColorSwatchValue(value: string): string | undefined {\n const rgb = parseHexRgb(value);\n if (!rgb) {\n return undefined;\n }\n return `#${rgb.map((channel) => channel.toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n\n/**\n * Human-readable facet chip label. Hex keys on color/finish facets become the\n * nearest CSS color name when close enough; other keys are unchanged.\n */\nexport function formatFacetBucketLabel(key: string, facetName?: string): string {\n if (facetName !== undefined && !isColorLikeFacetName(facetName)) {\n return key;\n }\n\n const rgb = parseHexRgb(key);\n if (!rgb) {\n return key;\n }\n\n const nearest = nearestCssColor(rgb);\n if (!nearest || nearest.dist > MAX_NAMED_COLOR_DISTANCE_SQ) {\n return key;\n }\n\n return nearest.name;\n}\n","import type {\n FacetAttributeDefinition,\n InterpretedSearchFilters,\n ResolvedFacetSchema,\n SearchFacetGroup,\n SuggestedFacet,\n} from \"../types/index.js\";\nimport { formatFacetBucketLabel } from \"./facet-color.js\";\n\nexport const PRICE_FACET_ID = \"price\";\nexport const CATEGORIES_FACET_ID = \"categories\";\n\nexport const DEFAULT_PRICE_RANGES = [\n { key: \"under-50\", to: 5_000 },\n { key: \"50-to-100\", from: 5_000, to: 10_000 },\n { key: \"100-plus\", from: 10_000 },\n] as const;\n\nexport type PriceRangeDefinition = (typeof DEFAULT_PRICE_RANGES)[number];\n\n/** Major currency units (e.g. EUR) corresponding to a price range bucket. */\nexport function priceRangeToFilterValues(\n range: PriceRangeDefinition,\n): Pick<InterpretedSearchFilters, \"priceMin\" | \"priceMax\"> {\n const filters: Pick<InterpretedSearchFilters, \"priceMin\" | \"priceMax\"> = {};\n if (\"from\" in range && range.from !== undefined) {\n filters.priceMin = String(range.from / 100);\n }\n if (\"to\" in range && range.to !== undefined) {\n filters.priceMax = String(range.to / 100);\n }\n return filters;\n}\n\nexport function priceBucketKeyFromFilters(\n filters: InterpretedSearchFilters,\n): string | undefined {\n const minCents = parseMajorToCents(filters.priceMin);\n const maxCents = parseMajorToCents(filters.priceMax);\n\n for (const range of DEFAULT_PRICE_RANGES) {\n const from = \"from\" in range ? range.from : undefined;\n const to = \"to\" in range ? range.to : undefined;\n if (minCents === from && maxCents === to) {\n return range.key;\n }\n }\n return undefined;\n}\n\nexport function isFacetFilterSelected(\n filters: InterpretedSearchFilters,\n facetId: string,\n bucketKey: string,\n): boolean {\n if (facetId === CATEGORIES_FACET_ID) {\n return filters.category === bucketKey;\n }\n if (facetId === PRICE_FACET_ID) {\n return priceBucketKeyFromFilters(filters) === bucketKey;\n }\n return filters[facetId] === bucketKey;\n}\n\n/**\n * Toggle a facet chip into filter state. Price buckets map to priceMin/priceMax;\n * categories map to `category`.\n */\nexport function toggleFacetFilter(\n filters: InterpretedSearchFilters,\n facetId: string,\n bucketKey: string,\n): InterpretedSearchFilters {\n const next = { ...filters };\n\n if (facetId === PRICE_FACET_ID) {\n if (priceBucketKeyFromFilters(next) === bucketKey) {\n delete next.priceMin;\n delete next.priceMax;\n return next;\n }\n delete next.priceMin;\n delete next.priceMax;\n const range = DEFAULT_PRICE_RANGES.find((candidate) => candidate.key === bucketKey);\n if (!range) {\n return next;\n }\n return { ...next, ...priceRangeToFilterValues(range) };\n }\n\n const filterKey = facetId === CATEGORIES_FACET_ID ? \"category\" : facetId;\n if (next[filterKey] === bucketKey) {\n delete next[filterKey];\n } else {\n next[filterKey] = bucketKey;\n }\n return next;\n}\n\nexport function buildProductSearchFacets(\n schema: ResolvedFacetSchema,\n suggestedFacets: SuggestedFacet[] = [],\n): unknown[] {\n const names = requestedFacetNames(schema, suggestedFacets);\n const facets: unknown[] = [];\n for (const name of names) {\n if (name === CATEGORIES_FACET_ID) {\n facets.push({ distinct: { name, field: \"categories\", limit: 12, level: \"products\" } });\n continue;\n }\n if (name === PRICE_FACET_ID) {\n facets.push({\n ranges: {\n name,\n field: \"variants.prices.centAmount\",\n ranges: DEFAULT_PRICE_RANGES.map((range) => ({ ...range })),\n level: \"products\",\n },\n });\n continue;\n }\n\n const attribute = schema.attributes.find((candidate) => candidate.name === name);\n if (!attribute || attribute.kind !== \"distinct\") {\n continue;\n }\n facets.push({\n distinct: {\n name: attribute.name,\n field: attribute.field,\n fieldType: attribute.fieldType,\n limit: 12,\n level: \"products\",\n },\n });\n }\n return facets;\n}\n\n/** Product Projection Search `facet` query parameter expressions. */\nexport function buildProjectionFacetParams(\n schema: ResolvedFacetSchema,\n suggestedFacets: SuggestedFacet[] = [],\n): string[] {\n const names = requestedFacetNames(schema, suggestedFacets);\n const facets: string[] = [];\n\n for (const name of names) {\n if (name === CATEGORIES_FACET_ID) {\n facets.push(`categories.id as ${CATEGORIES_FACET_ID}`);\n continue;\n }\n if (name === PRICE_FACET_ID) {\n const ranges = DEFAULT_PRICE_RANGES.map((range) => {\n const from = \"from\" in range && range.from !== undefined ? String(range.from) : \"*\";\n const to = \"to\" in range && range.to !== undefined ? String(range.to) : \"*\";\n return `(${from} to ${to})`;\n }).join(\", \");\n facets.push(`variants.prices.centAmount:range ${ranges} as ${PRICE_FACET_ID}`);\n continue;\n }\n\n const attribute = schema.attributes.find((candidate) => candidate.name === name);\n if (!attribute || attribute.kind !== \"distinct\") {\n continue;\n }\n facets.push(`${attribute.field} as ${attribute.name}`);\n }\n\n return facets;\n}\n\nexport function normalizeProductSearchFacets(\n rawFacets: unknown,\n schema: ResolvedFacetSchema,\n suggestedFacets: SuggestedFacet[] = [],\n filters: InterpretedSearchFilters = {},\n): SearchFacetGroup[] {\n const raw = Array.isArray(rawFacets)\n ? rawFacets\n : rawFacets && typeof rawFacets === \"object\"\n ? Object.entries(rawFacets as Record<string, unknown>).map(([name, facet]) => ({\n name,\n ...(facet && typeof facet === \"object\" ? facet : {}),\n }))\n : [];\n const labels = new Map(schema.attributes.map((attribute) => [attribute.name, attribute.label]));\n\n const facets: SearchFacetGroup[] = [];\n for (const facet of raw) {\n if (!facet || typeof facet !== \"object\") {\n continue;\n }\n const record = facet as {\n name?: unknown;\n buckets?: unknown;\n terms?: unknown;\n ranges?: unknown;\n };\n if (typeof record.name !== \"string\") {\n continue;\n }\n\n const buckets = normalizeFacetBuckets({\n name: record.name,\n buckets: record.buckets,\n terms: record.terms,\n ranges: record.ranges,\n });\n if (!buckets.length) {\n continue;\n }\n\n facets.push({\n id: record.name,\n label:\n labels.get(record.name) ??\n (record.name === CATEGORIES_FACET_ID\n ? \"Categories\"\n : record.name === PRICE_FACET_ID\n ? \"Price\"\n : record.name),\n type: record.name === PRICE_FACET_ID ? \"range\" : \"distinct\",\n buckets,\n ...(selectedFacetValue(record.name, filters)\n ? { selectedKey: selectedFacetValue(record.name, filters) }\n : {}),\n });\n }\n const requested = requestedFacetNames(schema, suggestedFacets);\n return facets.sort((left, right) => requested.indexOf(left.id) - requested.indexOf(right.id));\n}\n\nexport function filterFacetSuggestions(\n suggestedFacets: SuggestedFacet[] | undefined,\n schema: ResolvedFacetSchema,\n): SuggestedFacet[] {\n const known = new Set([...schema.attributes.map((attribute) => attribute.name), ...schema.systemFacets]);\n return (suggestedFacets ?? []).filter((facet) => known.has(facet.name)).slice(0, 5);\n}\n\nexport function getFacetAttribute(\n schema: ResolvedFacetSchema,\n name: string,\n): FacetAttributeDefinition | undefined {\n return schema.attributes.find((attribute) => attribute.name === name);\n}\n\nfunction requestedFacetNames(\n schema: ResolvedFacetSchema,\n suggestedFacets: SuggestedFacet[],\n): string[] {\n const suggested = filterFacetSuggestions(suggestedFacets, schema).map((facet) => facet.name);\n return suggested.length ? suggested : schema.attributes.slice(0, 3).map((attribute) => attribute.name);\n}\n\nfunction normalizeFacetBuckets(record: {\n buckets?: unknown;\n terms?: unknown;\n ranges?: unknown;\n name?: string;\n}): Array<{ key: string; label: string; count: number }> {\n if (Array.isArray(record.buckets)) {\n return record.buckets.flatMap((bucket) => normalizeBucket(bucket, record.name));\n }\n\n if (Array.isArray(record.terms)) {\n return record.terms.flatMap((term) => normalizeTermBucket(term, record.name));\n }\n\n if (Array.isArray(record.ranges)) {\n return record.ranges.flatMap((range, index) => normalizeRangeBucket(range, index));\n }\n\n return [];\n}\n\nfunction normalizeBucket(bucket: unknown, facetName?: string) {\n if (!bucket || typeof bucket !== \"object\") {\n return [];\n }\n const record = bucket as { key?: unknown; count?: unknown };\n if (typeof record.key !== \"string\" || typeof record.count !== \"number\") {\n return [];\n }\n return [{ key: record.key, label: formatFacetBucketLabel(record.key, facetName), count: record.count }];\n}\n\nfunction normalizeTermBucket(bucket: unknown, facetName?: string) {\n if (!bucket || typeof bucket !== \"object\") {\n return [];\n }\n const record = bucket as { term?: unknown; count?: unknown };\n if (typeof record.term !== \"string\" || typeof record.count !== \"number\") {\n return [];\n }\n return [{ key: record.term, label: formatFacetBucketLabel(record.term, facetName), count: record.count }];\n}\n\nfunction normalizeRangeBucket(bucket: unknown, index: number) {\n if (!bucket || typeof bucket !== \"object\") {\n return [];\n }\n const record = bucket as { from?: unknown; to?: unknown; count?: unknown; key?: unknown };\n if (typeof record.count !== \"number\") {\n return [];\n }\n\n if (typeof record.key === \"string\") {\n return [{ key: record.key, label: record.key, count: record.count }];\n }\n\n const from = typeof record.from === \"number\" ? record.from : undefined;\n const to = typeof record.to === \"number\" ? record.to : undefined;\n const matched = DEFAULT_PRICE_RANGES.find((range) => {\n const rangeFrom = \"from\" in range ? range.from : undefined;\n const rangeTo = \"to\" in range ? range.to : undefined;\n return rangeFrom === from && rangeTo === to;\n });\n const key = matched?.key ?? `${from ?? \"*\"}-${to ?? \"*\"}-${index}`;\n return [{ key, label: key, count: record.count }];\n}\n\nfunction selectedFacetValue(name: string, filters: InterpretedSearchFilters): string | undefined {\n if (name === CATEGORIES_FACET_ID) {\n return filters.category;\n }\n if (name === PRICE_FACET_ID) {\n return priceBucketKeyFromFilters(filters);\n }\n return filters[name];\n}\n\nfunction parseMajorToCents(value?: string): number | undefined {\n if (!value) {\n return undefined;\n }\n const parsed = Number(value);\n if (!Number.isFinite(parsed) || parsed < 0) {\n return undefined;\n }\n return Math.round(parsed * 100);\n}\n","import { Injectable } from \"@angular/core\";\nimport type {\n InterpretedSearchFilters,\n MissionSearchResult,\n ProductCard,\n SearchResult,\n SuggestedFacet,\n SuggestionsResult,\n} from \"@commerce-ai-tool/core/client\";\n\nexport interface SearchLocaleFields {\n queryLocale?: string;\n catalogLocale?: string;\n /** @deprecated Use queryLocale */\n locale?: string;\n}\n\nexport interface SearchRequestOptions {\n filters?: InterpretedSearchFilters;\n searchTerms?: string[];\n refineQuery?: string;\n includeFacets?: boolean;\n suggestedFacets?: SuggestedFacet[];\n enableMissions?: boolean;\n}\n\nexport interface VoiceSearchResult {\n transcript: string;\n enhancedQuery?: string;\n products: ProductCard[];\n meta: SearchResult[\"meta\"];\n mission?: MissionSearchResult;\n ttsText?: string;\n audioSummary?: string;\n ttsPending?: boolean;\n}\n\nfunction buildLocalePayload(options: SearchLocaleFields): Record<string, string> {\n const payload: Record<string, string> = {};\n const queryLocale = options.queryLocale ?? options.locale;\n\n if (options.catalogLocale) {\n payload.catalogLocale = options.catalogLocale;\n }\n\n if (queryLocale) {\n payload.queryLocale = queryLocale;\n }\n\n return payload;\n}\n\n@Injectable({ providedIn: \"root\" })\nexport class CommerceAiApiService {\n search(\n apiBaseUrl: string,\n query: string,\n locales: SearchLocaleFields = {},\n signal?: AbortSignal,\n options: SearchRequestOptions = {},\n ): Promise<SearchResult> {\n const baseUrl = apiBaseUrl.replace(/\\/$/, \"\");\n return fetch(`${baseUrl}/search`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ query, ...options, ...buildLocalePayload(locales) }),\n signal,\n }).then(async (response) => {\n if (!response.ok) {\n const data = (await response.json()) as { error?: string };\n throw new Error(data.error ?? \"Search failed\");\n }\n return response.json() as Promise<SearchResult>;\n });\n }\n\n suggest(\n apiBaseUrl: string,\n query: string,\n locales: SearchLocaleFields = {},\n signal?: AbortSignal,\n ): Promise<SuggestionsResult> {\n const baseUrl = apiBaseUrl.replace(/\\/$/, \"\");\n return fetch(`${baseUrl}/search/suggestions`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ query, ...buildLocalePayload(locales) }),\n signal,\n }).then(async (response) => {\n if (!response.ok) {\n const data = (await response.json()) as { error?: string };\n throw new Error(data.error ?? \"Suggestions failed\");\n }\n return response.json() as Promise<SuggestionsResult>;\n });\n }\n\n searchByImage(\n apiBaseUrl: string,\n file: File,\n locales: SearchLocaleFields = {},\n enableMissions = false,\n ): Promise<SearchResult & { interpretation?: string }> {\n const baseUrl = apiBaseUrl.replace(/\\/$/, \"\");\n const formData = new FormData();\n formData.append(\"image\", file);\n for (const [key, value] of Object.entries(buildLocalePayload(locales))) {\n formData.append(key, value);\n }\n if (enableMissions) {\n formData.append(\"enableMissions\", \"true\");\n }\n\n return fetch(`${baseUrl}/search/image`, {\n method: \"POST\",\n body: formData,\n }).then(async (response) => {\n if (!response.ok) {\n const data = (await response.json()) as { error?: string };\n throw new Error(data.error ?? \"Image search failed\");\n }\n return response.json() as Promise<SearchResult & { interpretation?: string }>;\n });\n }\n\n searchByVoice(\n apiBaseUrl: string,\n audio: Blob,\n locales: SearchLocaleFields = {},\n enableTts = true,\n enableMissions = false,\n ): Promise<VoiceSearchResult> {\n const baseUrl = apiBaseUrl.replace(/\\/$/, \"\");\n const formData = new FormData();\n formData.append(\"audio\", audio, \"recording.webm\");\n for (const [key, value] of Object.entries(buildLocalePayload(locales))) {\n formData.append(key, value);\n }\n formData.append(\"enableTts\", String(enableTts));\n if (enableMissions) {\n formData.append(\"enableMissions\", \"true\");\n }\n\n return fetch(`${baseUrl}/search/voice`, {\n method: \"POST\",\n body: formData,\n }).then(async (response) => {\n if (!response.ok) {\n const data = (await response.json()) as { error?: string };\n throw new Error(data.error ?? \"Voice search failed\");\n }\n return response.json() as Promise<VoiceSearchResult>;\n });\n }\n\n synthesizeSpeech(apiBaseUrl: string, text: string): Promise<Blob> {\n const baseUrl = apiBaseUrl.replace(/\\/$/, \"\");\n return fetch(`${baseUrl}/tts`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ text }),\n }).then(async (response) => {\n if (!response.ok) {\n throw new Error(\"TTS failed\");\n }\n return response.blob();\n });\n }\n}\n","import { Component, EventEmitter, Input, Output } from \"@angular/core\";\nimport { FormsModule } from \"@angular/forms\";\nimport type {\n CartSnapshot,\n CommerceAISearchMessages,\n CustomerSnapshot,\n} from \"@commerce-ai-tool/core/client\";\n\nexport function displayCartError(\n error: string | null,\n messages: CommerceAISearchMessages,\n): string | null {\n if (!error) {\n return null;\n }\n if (error === \"Invalid credentials\") {\n return messages.invalidCredentials;\n }\n if (error === \"Sign in failed\" || error === \"Login failed\") {\n return messages.signInFailed;\n }\n return error;\n}\n\nexport function formatLineTotal(\n price: CartSnapshot[\"lineItems\"][number][\"price\"],\n quantity: number,\n locale?: string,\n): string | null {\n if (!price) {\n return null;\n }\n return new Intl.NumberFormat(locale || undefined, {\n style: \"currency\",\n currency: price.currency,\n }).format(price.amount * quantity);\n}\n\n@Component({\n selector: \"commerce-ai-cart-panel\",\n standalone: true,\n imports: [FormsModule],\n template: `\n <section\n class=\"cat-cart-panel\"\n [class.cat-cart-panel--with-items]=\"!isEmpty\"\n [attr.aria-label]=\"messages.cartAriaLabel\"\n >\n <header class=\"cat-cart-panel__header\">\n <h2 class=\"cat-cart-panel__title\">{{ messages.cart }}</h2>\n <button type=\"button\" class=\"cat-icon-btn\" (click)=\"close.emit()\" [attr.aria-label]=\"messages.closeCart\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </header>\n\n @if (visibleError) {\n <div class=\"cat-status cat-status--error\" role=\"alert\">{{ visibleError }}</div>\n }\n\n @if (isEmpty) {\n <div class=\"cat-cart-panel__empty\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n <path d=\"M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4Z\" />\n <path d=\"M3 6h18\" />\n <path d=\"M16 10a4 4 0 0 1-8 0\" />\n </svg>\n <span>{{ messages.emptyCart }}</span>\n </div>\n } @else {\n <ul class=\"cat-cart-panel__items\">\n @for (item of cart!.lineItems; track item.id) {\n <li class=\"cat-cart-item\">\n @if (item.imageUrl) {\n <img [src]=\"item.imageUrl\" alt=\"\" class=\"cat-cart-item__image\" />\n } @else {\n <div class=\"cat-cart-item__image cat-cart-item__image--placeholder\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n <path d=\"M16.5 9.4 7.55 4.24\" />\n <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\" />\n <polyline points=\"3.29 7 12 12 20.71 7\" />\n <line x1=\"12\" x2=\"12\" y1=\"22\" y2=\"12\" />\n </svg>\n </div>\n }\n <div class=\"cat-cart-item__info\">\n <div class=\"cat-cart-item__name\">{{ item.name }}</div>\n @if (item.price) {\n <div class=\"cat-cart-item__price\">\n <span>{{ item.price.formatted }} {{ messages.each }}</span>\n <strong>{{ item.totalPrice?.formatted ?? lineTotal(item) }}</strong>\n </div>\n }\n <div class=\"cat-cart-item__qty\">\n <button\n type=\"button\"\n class=\"cat-cart-item__qty-btn\"\n [attr.aria-label]=\"messages.decreaseQuantity\"\n [disabled]=\"isLoading || item.quantity <= 1\"\n (click)=\"quantityChange.emit({ lineItemId: item.id, quantity: item.quantity - 1 })\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n <path d=\"M5 12h14\" />\n </svg>\n </button>\n <span class=\"cat-cart-item__qty-value\">{{ item.quantity }}</span>\n <button\n type=\"button\"\n class=\"cat-cart-item__qty-btn\"\n [attr.aria-label]=\"messages.increaseQuantity\"\n [disabled]=\"isLoading\"\n (click)=\"quantityChange.emit({ lineItemId: item.id, quantity: item.quantity + 1 })\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n <path d=\"M5 12h14\" />\n <path d=\"M12 5v14\" />\n </svg>\n </button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"cat-icon-btn\"\n [attr.aria-label]=\"messages.removeItem\"\n [disabled]=\"isLoading\"\n (click)=\"remove.emit(item.id)\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n <path d=\"M3 6h18\" />\n <path d=\"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6\" />\n <path d=\"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2\" />\n </svg>\n </button>\n </li>\n }\n </ul>\n }\n\n <div class=\"cat-cart-panel__auth\">\n @if (customer) {\n <div class=\"cat-cart-panel__signed-in\">\n <span>{{ messages.signedInAs }} {{ customer.email }}</span>\n <button type=\"button\" class=\"cat-cart-panel__sign-out\" (click)=\"logout.emit()\">\n {{ messages.signOut }}\n </button>\n </div>\n } @else {\n <button\n type=\"button\"\n class=\"cat-cart-panel__auth-toggle\"\n [disabled]=\"isLoggingIn\"\n [attr.aria-expanded]=\"isAuthExpanded\"\n (click)=\"isAuthExpanded = !isAuthExpanded\"\n >\n <span class=\"cat-cart-panel__auth-toggle-copy\">\n <span class=\"cat-cart-panel__auth-icon\" aria-hidden=\"true\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"8\" r=\"5\" />\n <path d=\"M20 21a8 8 0 0 0-16 0\" />\n </svg>\n </span>\n {{ messages.signInToSyncCart }}\n </span>\n <svg\n width=\"15\"\n height=\"15\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n aria-hidden=\"true\"\n [class.cat-cart-panel__auth-chevron--open]=\"isAuthExpanded\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n @if (isAuthExpanded) {\n <form class=\"cat-cart-panel__auth-form\" (ngSubmit)=\"onLoginSubmit()\">\n <label class=\"cat-cart-panel__field\">\n <span class=\"cat-cart-panel__label\">{{ messages.email }}</span>\n <input\n class=\"cat-cart-panel__input\"\n type=\"email\"\n name=\"email\"\n autocomplete=\"email\"\n required\n [disabled]=\"isLoggingIn\"\n [(ngModel)]=\"email\"\n />\n </label>\n <label class=\"cat-cart-panel__field\">\n <span class=\"cat-cart-panel__label\">{{ messages.password }}</span>\n <input\n class=\"cat-cart-panel__input\"\n type=\"password\"\n name=\"password\"\n autocomplete=\"current-password\"\n required\n [disabled]=\"isLoggingIn\"\n [(ngModel)]=\"password\"\n />\n </label>\n <button type=\"submit\" class=\"cat-cart-panel__submit\" [disabled]=\"isLoggingIn\">\n {{ messages.signIn }}\n </button>\n </form>\n }\n }\n </div>\n\n @if (!isEmpty) {\n <footer class=\"cat-cart-panel__footer\">\n <div class=\"cat-cart-panel__total\">\n <span>{{ messages.total }}</span>\n <strong>{{ cart!.totalPrice.formatted }}</strong>\n </div>\n @if (showCheckout && cart) {\n <button\n type=\"button\"\n class=\"cat-checkout-cta\"\n [disabled]=\"isLoading\"\n (click)=\"checkout.emit(cart)\"\n >\n <span>{{ messages.checkout }}</span>\n <span class=\"cat-checkout-cta__icon\" aria-hidden=\"true\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M5 12h14\" />\n <path d=\"m12 5 7 7-7 7\" />\n </svg>\n </span>\n </button>\n }\n </footer>\n }\n </section>\n `,\n})\nexport class CommerceAiCartPanelComponent {\n @Input() cart: CartSnapshot | null = null;\n @Input() customer: CustomerSnapshot | null = null;\n @Input() isLoading = false;\n @Input() isLoggingIn = false;\n @Input() error: string | null = null;\n @Input({ required: true }) messages!: CommerceAISearchMessages;\n @Input() catalogLocale?: string;\n @Input() showCheckout = false;\n\n @Output() close = new EventEmitter<void>();\n @Output() remove = new EventEmitter<string>();\n @Output() quantityChange = new EventEmitter<{ lineItemId: string; quantity: number }>();\n @Output() login = new EventEmitter<{ email: string; password: string }>();\n @Output() logout = new EventEmitter<void>();\n @Output() checkout = new EventEmitter<CartSnapshot>();\n\n email = \"\";\n password = \"\";\n isAuthExpanded = false;\n\n get isEmpty(): boolean {\n return !this.cart || this.cart.lineItems.length === 0;\n }\n\n get visibleError(): string | null {\n return displayCartError(this.error, this.messages);\n }\n\n lineTotal(item: CartSnapshot[\"lineItems\"][number]): string | null {\n return formatLineTotal(item.price, item.quantity, this.catalogLocale);\n }\n\n onLoginSubmit(): void {\n this.login.emit({ email: this.email, password: this.password });\n this.password = \"\";\n }\n}\n","import { Injectable, computed, signal } from \"@angular/core\";\nimport {\n CART_SESSION_HEADER,\n type CartSnapshot,\n type CustomerSnapshot,\n} from \"@commerce-ai-tool/core/client\";\n\nexport const ANONYMOUS_ID_STORAGE_KEY = \"commerce-ai-tool:anonymousId\";\nexport const CUSTOMER_SESSION_STORAGE_KEY = \"commerce-ai-tool:customerSession\";\nexport const CUSTOMER_STORAGE_KEY = \"commerce-ai-tool:customer\";\n\nexport interface AddToCartItem {\n sku?: string;\n productId?: string;\n variantId?: number;\n quantity?: number;\n}\n\nexport interface CommerceAiCartOptions {\n apiBaseUrl: string;\n currency?: string;\n country?: string;\n catalogLocale?: string;\n enabled?: boolean;\n onCartChange?: (cart: CartSnapshot | null) => void;\n}\n\ninterface CartApiBody {\n cart?: CartSnapshot | null;\n customer?: CustomerSnapshot | null;\n sessionToken?: string;\n error?: string;\n}\n\nfunction readStorage(key: string): string | null {\n if (typeof window === \"undefined\") {\n return null;\n }\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeStorage(key: string, value: string): void {\n if (typeof window === \"undefined\") {\n return;\n }\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // Ignore quota / privacy mode failures — session still works in memory.\n }\n}\n\nfunction removeStorage(key: string): void {\n if (typeof window === \"undefined\") {\n return;\n }\n try {\n window.localStorage.removeItem(key);\n } catch {\n // Ignore privacy mode failures.\n }\n}\n\nfunction readStoredCustomer(): CustomerSnapshot | null {\n const raw = readStorage(CUSTOMER_STORAGE_KEY);\n if (!raw) {\n return null;\n }\n try {\n const parsed = JSON.parse(raw) as CustomerSnapshot;\n if (typeof parsed.id === \"string\" && typeof parsed.email === \"string\") {\n return parsed;\n }\n } catch {\n return null;\n }\n return null;\n}\n\nexport function createAnonymousId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n return `anon-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nexport function getOrCreateAnonymousId(): string {\n const existing = readStorage(ANONYMOUS_ID_STORAGE_KEY);\n if (existing) {\n return existing;\n }\n const created = createAnonymousId();\n writeStorage(ANONYMOUS_ID_STORAGE_KEY, created);\n return created;\n}\n\nfunction rotateAnonymousId(): string {\n const created = createAnonymousId();\n writeStorage(ANONYMOUS_ID_STORAGE_KEY, created);\n return created;\n}\n\nclass CartRequestError extends Error {\n status: number;\n\n constructor(message: string, status: number) {\n super(message);\n this.name = \"CartRequestError\";\n this.status = status;\n }\n}\n\nasync function parseCartApi(response: Response): Promise<CartApiBody> {\n const body = (await response.json()) as CartApiBody;\n if (!response.ok) {\n throw new CartRequestError(body.error ?? \"Cart request failed\", response.status);\n }\n return body;\n}\n\n@Injectable()\nexport class CommerceAiCartService {\n readonly cart = signal<CartSnapshot | null>(null);\n readonly anonymousId = signal(\"\");\n readonly customer = signal<CustomerSnapshot | null>(null);\n readonly isLoading = signal(false);\n readonly isMutating = signal(false);\n readonly isLoggingIn = signal(false);\n readonly error = signal<string | null>(null);\n readonly isCartOpen = signal(false);\n readonly isAuthenticated = computed(() => Boolean(this.customer() && this.sessionToken()));\n\n private apiBaseUrl = \"\";\n private currency?: string;\n private country?: string;\n private catalogLocale?: string;\n private enabled = false;\n private onCartChange?: (cart: CartSnapshot | null) => void;\n private sessionTokenValue: string | null = null;\n private cartValue: CartSnapshot | null = null;\n private mutationChain: Promise<unknown> = Promise.resolve();\n private started = false;\n\n sessionToken(): string | null {\n return this.sessionTokenValue;\n }\n\n configure(options: CommerceAiCartOptions): void {\n this.apiBaseUrl = options.apiBaseUrl.replace(/\\/$/, \"\");\n this.currency = options.currency;\n this.country = options.country;\n this.catalogLocale = options.catalogLocale;\n this.enabled = options.enabled !== false;\n this.onCartChange = options.onCartChange;\n\n if (!this.enabled) {\n return;\n }\n\n if (!this.started) {\n this.started = true;\n this.anonymousId.set(getOrCreateAnonymousId());\n const storedToken = readStorage(CUSTOMER_SESSION_STORAGE_KEY);\n const storedCustomer = readStoredCustomer();\n if (storedToken && storedCustomer) {\n this.sessionTokenValue = storedToken;\n this.customer.set(storedCustomer);\n }\n void this.refresh();\n }\n }\n\n openCart(): void {\n this.isCartOpen.set(true);\n }\n\n closeCart(): void {\n this.isCartOpen.set(false);\n }\n\n toggleCart(): void {\n this.isCartOpen.update((open) => !open);\n }\n\n async refresh(): Promise<void> {\n const anonymousId = this.anonymousId();\n if (!this.enabled || !anonymousId) {\n return;\n }\n\n const params = new URLSearchParams();\n const headers: Record<string, string> = {};\n const token = this.sessionTokenValue;\n if (token) {\n headers[CART_SESSION_HEADER] = token;\n } else {\n params.set(\"anonymousId\", anonymousId);\n }\n if (this.catalogLocale) {\n params.set(\"catalogLocale\", this.catalogLocale);\n }\n\n this.isLoading.set(true);\n this.error.set(null);\n try {\n const query = params.toString();\n const url = `${this.apiBaseUrl}/cart${query ? `?${query}` : \"\"}`;\n const response = await (token ? fetch(url, { headers }) : fetch(url));\n const body = await parseCartApi(response);\n this.applyCart(body.cart ?? null);\n } catch (err) {\n if (err instanceof CartRequestError && err.status === 401 && this.sessionTokenValue) {\n this.clearSession();\n this.anonymousId.set(rotateAnonymousId());\n this.applyCart(null);\n this.error.set(err.message);\n this.isLoading.set(false);\n await this.refresh();\n return;\n }\n this.error.set(err instanceof Error ? err.message : \"Cart request failed\");\n } finally {\n this.isLoading.set(false);\n }\n }\n\n addToCart(item: AddToCartItem): Promise<CartSnapshot | null> {\n return this.mutate(\"/cart/add\", {\n sku: item.sku,\n productId: item.productId,\n variantId: item.variantId,\n quantity: item.quantity,\n });\n }\n\n addItems(items: AddToCartItem[]): Promise<CartSnapshot | null> {\n return this.mutate(\"/cart/add-items\", { items });\n }\n\n removeFromCart(lineItemId: string): Promise<CartSnapshot | null> {\n return this.mutate(\"/cart/remove\", { lineItemId });\n }\n\n updateQuantity(lineItemId: string, quantity: number): Promise<CartSnapshot | null> {\n return this.mutate(\"/cart/update-quantity\", { lineItemId, quantity });\n }\n\n async login(input: { email: string; password: string }): Promise<CartSnapshot | null> {\n if (!this.enabled) {\n return null;\n }\n\n this.isLoggingIn.set(true);\n this.error.set(null);\n try {\n const response = await fetch(`${this.apiBaseUrl}/cart/login`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n email: input.email,\n password: input.password,\n anonymousId: this.anonymousId(),\n catalogLocale: this.catalogLocale,\n cartId: this.cartValue?.id,\n }),\n });\n const body = await parseCartApi(response);\n if (!body.sessionToken || !body.customer) {\n throw new CartRequestError(\"Sign in failed\", response.status);\n }\n this.persistSession(body.sessionToken, body.customer);\n this.applyCart(body.cart ?? null);\n return body.cart ?? null;\n } catch (err) {\n this.error.set(err instanceof Error ? err.message : \"Sign in failed\");\n return null;\n } finally {\n this.isLoggingIn.set(false);\n }\n }\n\n async logout(): Promise<void> {\n try {\n await fetch(`${this.apiBaseUrl}/cart/logout`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({}),\n });\n } catch {\n // Client-side logout still proceeds if the network call fails.\n }\n this.clearSession();\n this.applyCart(null);\n this.anonymousId.set(rotateAnonymousId());\n }\n\n private mutate(path: string, body: Record<string, unknown>): Promise<CartSnapshot | null> {\n const run = async (): Promise<CartSnapshot | null> => {\n const anonymousId = this.anonymousId();\n if (!this.enabled || !anonymousId) {\n return null;\n }\n\n this.isMutating.set(true);\n this.error.set(null);\n try {\n const response = await fetch(`${this.apiBaseUrl}${path}`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n anonymousId,\n sessionToken: this.sessionTokenValue ?? undefined,\n currency: this.currency,\n country: this.country,\n catalogLocale: this.catalogLocale,\n cartId: this.cartValue?.id,\n cartVersion: this.cartValue?.version,\n ...body,\n }),\n });\n const next = await parseCartApi(response);\n this.applyCart(next.cart ?? null);\n return next.cart ?? null;\n } catch (err) {\n if (err instanceof CartRequestError && err.status === 401 && this.sessionTokenValue) {\n this.clearSession();\n this.anonymousId.set(rotateAnonymousId());\n this.applyCart(null);\n }\n this.error.set(err instanceof Error ? err.message : \"Cart request failed\");\n return null;\n } finally {\n this.isMutating.set(false);\n }\n };\n\n const pending = this.mutationChain.then(run, run);\n this.mutationChain = pending.then(\n () => undefined,\n () => undefined,\n );\n return pending;\n }\n\n private applyCart(next: CartSnapshot | null): void {\n this.cartValue = next;\n this.cart.set(next);\n this.onCartChange?.(next);\n }\n\n private persistSession(token: string, nextCustomer: CustomerSnapshot): void {\n this.sessionTokenValue = token;\n this.customer.set(nextCustomer);\n writeStorage(CUSTOMER_SESSION_STORAGE_KEY, token);\n writeStorage(CUSTOMER_STORAGE_KEY, JSON.stringify(nextCustomer));\n }\n\n private clearSession(): void {\n this.sessionTokenValue = null;\n this.customer.set(null);\n removeStorage(CUSTOMER_SESSION_STORAGE_KEY);\n removeStorage(CUSTOMER_STORAGE_KEY);\n }\n}\n","import { NgTemplateOutlet } from \"@angular/common\";\nimport { Component, EventEmitter, Input, OnChanges, OnDestroy, Output } from \"@angular/core\";\nimport type {\n AddToCartLineItem,\n CommerceAISearchMessages,\n MissionSearchResult,\n ProductCard,\n} from \"@commerce-ai-tool/core/client\";\nimport { formatMissionAddAll, starterBundleItems } from \"./mission.util.js\";\n\nlet missionTitleCounter = 0;\n\n@Component({\n selector: \"commerce-ai-mission-results\",\n standalone: true,\n imports: [NgTemplateOutlet],\n template: `\n <div class=\"cat-mission\">\n <div class=\"cat-mission__header\">\n <h2 [id]=\"titleId\" class=\"cat-mission__title\">{{ messages.missionTitle }}</h2>\n @if (mission.interpretation) {\n <p class=\"cat-mission__interpretation\">{{ mission.interpretation }}</p>\n }\n </div>\n\n <div class=\"cat-mission__lanes\" role=\"list\" [attr.aria-labelledby]=\"titleId\">\n @for (group of mission.intents; track group.intent.id) {\n <section class=\"cat-mission-group\" role=\"listitem\">\n <div class=\"cat-mission-group__header\">\n <h3 class=\"cat-mission-group__label\">{{ group.intent.label }}</h3>\n @if (group.intent.quantity > 1) {\n <span class=\"cat-mission-group__qty\">\n {{ messages.missionQuantity }} {{ group.intent.quantity }}\n </span>\n }\n </div>\n\n @if (group.failed) {\n <div class=\"cat-status cat-status--empty\" role=\"status\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.3-4.3\" />\n <path d=\"m13 9-4 4\" />\n <path d=\"m9 9 4 4\" />\n </svg>\n <div class=\"cat-status__content\">\n <div class=\"cat-status__title\">{{ messages.missionIntentFailed }}</div>\n </div>\n </div>\n } @else if (group.products.length === 0) {\n <div class=\"cat-status cat-status--empty\" role=\"status\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.3-4.3\" />\n <path d=\"m13 9-4 4\" />\n <path d=\"m9 9 4 4\" />\n </svg>\n <div class=\"cat-status__content\">\n <div class=\"cat-status__title\">{{ messages.missionIntentEmpty }}</div>\n </div>\n </div>\n } @else {\n <div class=\"cat-mission-group__products\">\n @for (product of group.products; track product.id; let index = $index) {\n <article\n class=\"cat-result-card\"\n [class.cat-result-card--primary]=\"index === 0\"\n [class.cat-result-card--compact]=\"index !== 0\"\n >\n <div class=\"cat-result-card__core\">\n @if (productSelect.observed) {\n <button type=\"button\" class=\"cat-result-card__select\" (click)=\"productSelect.emit(product)\">\n <ng-container [ngTemplateOutlet]=\"cardBody\" [ngTemplateOutletContext]=\"{ $implicit: product }\" />\n </button>\n } @else {\n <div class=\"cat-result-card__select\">\n <ng-container [ngTemplateOutlet]=\"cardBody\" [ngTemplateOutletContext]=\"{ $implicit: product }\" />\n </div>\n }\n @if (enableCart) {\n <button\n type=\"button\"\n class=\"cat-icon-btn cat-result-card__add\"\n [class.cat-result-card__add--added]=\"addedProductIds[product.id]\"\n [attr.aria-label]=\"addLabel(product)\"\n [disabled]=\"!(product.sku || product.id) || isMutating\"\n (click)=\"addItem.emit(product)\"\n >\n @if (addedProductIds[product.id]) {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n } @else {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n <circle cx=\"8\" cy=\"21\" r=\"1\" />\n <circle cx=\"19\" cy=\"21\" r=\"1\" />\n <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\" />\n </svg>\n }\n </button>\n }\n </div>\n </article>\n }\n </div>\n }\n </section>\n }\n </div>\n\n @if (enableCart) {\n <div class=\"cat-mission__footer\">\n <button\n type=\"button\"\n class=\"cat-checkout-cta cat-mission__add-all\"\n [class.cat-mission__add-all--added]=\"justAddedAll\"\n [disabled]=\"isMutating || bundleItems.length === 0\"\n (click)=\"onAddAllClick()\"\n >\n {{ justAddedAll ? messages.missionItemsAdded : addAllLabel }}\n </button>\n </div>\n }\n </div>\n\n <ng-template #cardBody let-product>\n @if (product.imageUrl) {\n <img [src]=\"product.imageUrl\" alt=\"\" class=\"cat-result-image\" loading=\"lazy\" />\n } @else {\n <div class=\"cat-result-image cat-result-image--placeholder\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n <path d=\"M16.5 9.4 7.55 4.24\" />\n <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\" />\n <polyline points=\"3.29 7 12 12 20.71 7\" />\n <line x1=\"12\" x2=\"12\" y1=\"22\" y2=\"12\" />\n </svg>\n </div>\n }\n <div class=\"cat-result-info\">\n <div class=\"cat-result-name\">{{ product.name }}</div>\n @if (product.price) {\n <div class=\"cat-result-price\">{{ product.price.formatted }}</div>\n }\n </div>\n </ng-template>\n `,\n})\nexport class CommerceAiMissionResultsComponent implements OnChanges, OnDestroy {\n @Input({ required: true }) mission!: MissionSearchResult;\n @Input({ required: true }) messages!: CommerceAISearchMessages;\n @Input() enableCart = false;\n @Input() isMutating = false;\n @Input() addedProductIds: Record<string, true> = {};\n @Input() addAllHandler?: (items: AddToCartLineItem[]) => Promise<unknown>;\n\n @Output() productSelect = new EventEmitter<ProductCard>();\n @Output() addItem = new EventEmitter<ProductCard>();\n @Output() addAll = new EventEmitter<AddToCartLineItem[]>();\n\n readonly titleId = `cat-mission-title-${(missionTitleCounter += 1)}`;\n justAddedAll = false;\n private addedAllTimeout: ReturnType<typeof setTimeout> | null = null;\n private lastMission: MissionSearchResult | null = null;\n private cachedBundleMission: MissionSearchResult | null = null;\n private cachedBundleItems: AddToCartLineItem[] = [];\n\n get bundleItems(): AddToCartLineItem[] {\n if (this.cachedBundleMission !== this.mission) {\n this.cachedBundleMission = this.mission;\n this.cachedBundleItems = starterBundleItems(this.mission);\n }\n return this.cachedBundleItems;\n }\n\n get addAllLabel(): string {\n const totalQuantity = this.bundleItems.reduce((sum, item) => sum + (item.quantity ?? 1), 0);\n return formatMissionAddAll(this.messages.missionAddAll, totalQuantity);\n }\n\n ngOnChanges(): void {\n if (this.mission !== this.lastMission) {\n this.lastMission = this.mission;\n this.justAddedAll = false;\n }\n }\n\n ngOnDestroy(): void {\n if (this.addedAllTimeout) {\n clearTimeout(this.addedAllTimeout);\n this.addedAllTimeout = null;\n }\n }\n\n addLabel(product: ProductCard): string {\n if (this.addedProductIds[product.id]) {\n return this.messages.itemAdded;\n }\n return product.sku || product.id ? this.messages.addToCart : this.messages.unableToAddToCart;\n }\n\n onAddAllClick(): void {\n const items = this.bundleItems;\n this.addAll.emit(items);\n void this.addAllHandler?.(items).then((result) => {\n if (!result) {\n return;\n }\n this.justAddedAll = true;\n if (this.addedAllTimeout) {\n clearTimeout(this.addedAllTimeout);\n }\n this.addedAllTimeout = setTimeout(() => {\n this.justAddedAll = false;\n this.addedAllTimeout = null;\n }, 1200);\n });\n }\n}\n","import type { AddToCartLineItem, MissionSearchResult, ProductCard } from \"@commerce-ai-tool/core/client\";\n\nexport function formatMissionAddAll(template: string, count: number): string {\n const withPlural = count === 1 ? template.replaceAll(\"top picks\", \"top pick\") : template;\n if (withPlural.includes(\"{count}\")) {\n return withPlural.replaceAll(\"{count}\", String(count));\n }\n return `${withPlural} (${count})`;\n}\n\nexport function toCartItem(product: ProductCard, quantity: number): AddToCartLineItem {\n return product.sku\n ? { sku: product.sku, quantity }\n : { productId: product.id, variantId: product.variantId, quantity };\n}\n\nexport function lineItemKey(item: AddToCartLineItem): string {\n if (item.sku) {\n return `sku:${item.sku}`;\n }\n return `id:${item.productId ?? \"\"}:${item.variantId ?? \"\"}`;\n}\n\nexport function starterBundleItems(mission: MissionSearchResult): AddToCartLineItem[] {\n const items: AddToCartLineItem[] = [];\n const seen = new Set<string>();\n for (const group of mission.intents) {\n if (group.failed) {\n continue;\n }\n const product = group.products[0];\n if (!product || (!product.sku && !product.id)) {\n continue;\n }\n const item = toCartItem(product, 1);\n const key = lineItemKey(item);\n if (seen.has(key)) {\n continue;\n }\n seen.add(key);\n items.push(item);\n }\n return items;\n}\n","export type CameraFacingMode = \"environment\" | \"user\";\n\nexport function prefersNativeCamera(): boolean {\n if (typeof navigator === \"undefined\") return false;\n\n return (\n /Android|iPhone|iPad|iPod/i.test(navigator.userAgent) ||\n (navigator.maxTouchPoints > 1 && matchMedia(\"(pointer: coarse)\").matches)\n );\n}\n\nexport function stopMediaStream(stream: MediaStream | null): void {\n stream?.getTracks().forEach((track) => track.stop());\n}\n\nexport function getCameraErrorMessage(error: unknown): string {\n if (error instanceof DOMException) {\n if (error.name === \"NotAllowedError\" || error.name === \"PermissionDeniedError\") {\n return \"Camera access denied\";\n }\n if (error.name === \"NotFoundError\" || error.name === \"DevicesNotFoundError\") {\n return \"No camera found\";\n }\n if (error.name === \"NotReadableError\" || error.name === \"TrackStartError\") {\n return \"Camera is already in use\";\n }\n }\n\n return \"Camera unavailable\";\n}\n\nexport function createJpegFileFromVideo(\n video: HTMLVideoElement,\n fileName = \"camera-capture.jpg\",\n): Promise<File> {\n const width = video.videoWidth;\n const height = video.videoHeight;\n\n if (!width || !height) {\n return Promise.reject(new Error(\"Camera preview is not ready\"));\n }\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n\n const context = canvas.getContext(\"2d\");\n if (!context) {\n return Promise.reject(new Error(\"Could not capture photo\"));\n }\n\n context.drawImage(video, 0, 0, width, height);\n\n return new Promise((resolve, reject) => {\n canvas.toBlob(\n (blob) => {\n if (!blob) {\n reject(new Error(\"Could not capture photo\"));\n return;\n }\n\n resolve(new File([blob], fileName, { type: \"image/jpeg\" }));\n },\n \"image/jpeg\",\n 0.92,\n );\n });\n}\n\nexport function buildCameraConstraints(facingMode: CameraFacingMode): MediaStreamConstraints {\n return {\n video: {\n facingMode: { ideal: facingMode },\n width: { ideal: 1920 },\n height: { ideal: 1080 },\n },\n audio: false,\n };\n}\n"],"mappings":"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","names":["ChangeDetectorRef","Component","ElementRef","EventEmitter","Input","Output","ViewChild","ViewEncapsulation","inject","FormsModule","Injectable","buildLocalePayload","options","payload","queryLocale","locale","catalogLocale","CommerceAiApiService","search","apiBaseUrl","query","locales","signal","baseUrl","replace","fetch","method","headers","body","JSON","stringify","then","response","ok","data","json","Error","error","suggest","searchByImage","file","enableMissions","formData","FormData","append","key","value","Object","entries","searchByVoice","audio","enableTts","String","synthesizeSpeech","text","blob","providedIn","Component","EventEmitter","Input","Output","FormsModule","displayCartError","error","messages","invalidCredentials","signInFailed","formatLineTotal","price","quantity","locale","Intl","NumberFormat","undefined","style","currency","format","amount","CommerceAiCartPanelComponent","cart","customer","isLoading","isLoggingIn","catalogLocale","showCheckout","close","EventEmitter","remove","quantityChange","login","logout","checkout","email","password","isAuthExpanded","isEmpty","lineItems","length","visibleError","lineTotal","item","onLoginSubmit","emit","required","selector","standalone","imports","FormsModule","template","Injectable","computed","signal","ANONYMOUS_ID_STORAGE_KEY","CUSTOMER_SESSION_STORAGE_KEY","CUSTOMER_STORAGE_KEY","readStorage","key","window","localStorage","getItem","writeStorage","value","setItem","removeStorage","removeItem","readStoredCustomer","raw","parsed","JSON","parse","id","email","createAnonymousId","crypto","randomUUID","Date","now","Math","random","toString","slice","getOrCreateAnonymousId","existing","created","rotateAnonymousId","CartRequestError","Error","status","message","name","parseCartApi","response","body","json","ok","error","CommerceAiCartService","cart","signal","anonymousId","customer","isLoading","isMutating","isLoggingIn","isCartOpen","isAuthenticated","computed","Boolean","sessionToken","apiBaseUrl","currency","country","catalogLocale","enabled","onCartChange","sessionTokenValue","cartValue","mutationChain","Promise","resolve","started","configure","options","replace","set","storedToken","storedCustomer","refresh","openCart","closeCart","toggleCart","update","open","params","URLSearchParams","headers","token","CART_SESSION_HEADER","query","url","fetch","applyCart","err","clearSession","addToCart","item","mutate","sku","productId","variantId","quantity","addItems","items","removeFromCart","lineItemId","updateQuantity","login","input","method","stringify","password","cartId","persistSession","logout","path","run","undefined","cartVersion","version","next","pending","then","nextCustomer","NgTemplateOutlet","Component","EventEmitter","Input","Output","formatMissionAddAll","template","count","withPlural","replaceAll","includes","String","toCartItem","product","quantity","sku","productId","id","variantId","lineItemKey","item","starterBundleItems","mission","items","seen","Set","group","intents","failed","products","key","has","add","push","missionTitleCounter","CommerceAiMissionResultsComponent","mission","messages","enableCart","isMutating","addedProductIds","addAllHandler","productSelect","EventEmitter","addItem","addAll","titleId","justAddedAll","addedAllTimeout","lastMission","cachedBundleMission","cachedBundleItems","bundleItems","starterBundleItems","addAllLabel","totalQuantity","reduce","sum","item","quantity","formatMissionAddAll","missionAddAll","ngOnChanges","ngOnDestroy","clearTimeout","addLabel","product","id","itemAdded","sku","addToCart","unableToAddToCart","onAddAllClick","items","emit","then","result","setTimeout","required","selector","standalone","imports","NgTemplateOutlet","template","prefersNativeCamera","navigator","test","userAgent","maxTouchPoints","matchMedia","matches","stopMediaStream","stream","getTracks","forEach","track","stop","getCameraErrorMessage","error","DOMException","name","createJpegFileFromVideo","video","fileName","width","videoWidth","height","videoHeight","Promise","reject","Error","canvas","document","createElement","context","getContext","drawImage","resolve","toBlob","blob","File","type","buildCameraConstraints","facingMode","ideal","audio","CommerceAiSearchComponent","api","inject","CommerceAiApiService","cdr","ChangeDetectorRef","cart","CommerceAiCartService","apiBaseUrl","theme","catalogLocale","queryLocale","locale","placeholder","messages","enableAutocomplete","enableFacets","enableVoice","enableImageSearch","enableCameraSearch","cameraFacingMode","enableTts","enableCart","enableMissions","currency","country","productSelect","EventEmitter","cartChange","checkout","cameraInputRef","cameraVideoRef","query","suggestions","activeSuggestionIndex","isLoadingSuggestions","suggestionsError","suggestionsReady","suggestionsDismissed","results","mission","hasFacetSession","addedProductIds","meta","facets","suggestedFacets","isLoading","hasSearched","error","isDragging","isRecording","isProcessing","isCameraOpen","cameraError","lastSearchMode","audioSummary","mediaRecorder","audioChunks","cameraStream","debounceTimer","suggestionsTimer","searchAbort","suggestionsAbort","searchRequestId","suggestionsRequestId","addedTimeouts","Map","destroyed","addMissionItems","items","addItems","ngOnInit","syncCart","ngOnChanges","ngOnDestroy","timeout","values","clearTimeout","closeCamera","abort","resolvedMessages","resolveCommerceAISearchMessages","showSuggestions","showResults","trim","length","activeSuggestionId","showMission","Boolean","showMissionVisible","missionLaneCount","Math","min","max","intents","cartQuantity","totalQuantity","cartBadgeLabel","String","cartAriaLabel","hasQuery","showEmptyResults","showVoiceReplay","onQueryChange","value","trimmed","clearVoiceAudio","clearResults","setTimeout","fetchSuggestions","onSubmit","onSuggestionSelect","suggestion","onInputKeyDown","event","key","preventDefault","searchQuery","controller","AbortController","requestId","suggest","localeFields","signal","then","data","detectChanges","catch","err","name","message","isFacetSelected","facetId","isFacetFilterSelected","appliedFilters","facetSwatch","isColorLikeFacetName","hexColorSwatchValue","undefined","hasAppliedFilters","Object","keys","toggleFacet","searchTerms","filters","toggleFacetFilter","searchWithFacets","clearFacetFilters","startNewSearch","q","search","includeFacets","applySearchResult","refineQuery","queryInterpretation","previousMeta","isRefinement","looksLikeCompoundShoppingList","onFileSelected","input","target","file","files","searchByImage","onCameraFileSelected","openCamera","prefersNativeCamera","nativeElement","click","openCameraOverlay","stream","navigator","mediaDevices","getUserMedia","buildCameraConstraints","video","srcObject","play","getCameraErrorMessage","capturePhoto","createJpegFileFromVideo","Error","couldNotCapturePhoto","stopMediaStream","clearCameraError","onDragOver","onDrop","dataTransfer","type","startsWith","interpretation","toggleRecording","stop","audio","MediaRecorder","ondataavailable","e","size","push","onstop","getTracks","forEach","t","blob","Blob","searchByVoice","transcript","products","Audio","ttsText","ttsPending","synthesizeSpeech","buffer","arrayBuffer","bytes","Uint8Array","binary","i","fromCharCode","btoa","finally","start","replayVoiceSummary","addToCartLabel","product","id","itemAdded","sku","addToCart","unableToAddToCart","onAddItem","handleAddToCart","onCartRemove","lineItemId","removeFromCart","onCartQuantityChange","updateQuantity","quantity","onCartLogin","login","onCartLogout","logout","configure","enabled","onCartChange","snapshot","emit","next","toCartItem","existing","get","set","_removed","rest","delete","selector","standalone","imports","FormsModule","CommerceAiMissionResultsComponent","CommerceAiCartPanelComponent","providers","template","encapsulation","ViewEncapsulation","None"]}
@@ -0,0 +1,7 @@
1
+ export type CameraFacingMode = "environment" | "user";
2
+ export declare function prefersNativeCamera(): boolean;
3
+ export declare function stopMediaStream(stream: MediaStream | null): void;
4
+ export declare function getCameraErrorMessage(error: unknown): string;
5
+ export declare function createJpegFileFromVideo(video: HTMLVideoElement, fileName?: string): Promise<File>;
6
+ export declare function buildCameraConstraints(facingMode: CameraFacingMode): MediaStreamConstraints;
7
+ //# sourceMappingURL=camera.util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"camera.util.d.ts","sourceRoot":"","sources":["../../src/lib/camera.util.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG,aAAa,GAAG,MAAM,CAAC;AAEtD,wBAAgB,mBAAmB,IAAI,OAAO,CAO7C;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAEhE;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAc5D;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,gBAAgB,EACvB,QAAQ,SAAuB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAiCf;AAED,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,gBAAgB,GAAG,sBAAsB,CAS3F"}
@@ -0,0 +1,35 @@
1
+ import type { InterpretedSearchFilters, MissionSearchResult, ProductCard, SearchResult, SuggestedFacet, SuggestionsResult } from "../vendor-core/client.js";
2
+ export interface SearchLocaleFields {
3
+ queryLocale?: string;
4
+ catalogLocale?: string;
5
+ /** @deprecated Use queryLocale */
6
+ locale?: string;
7
+ }
8
+ export interface SearchRequestOptions {
9
+ filters?: InterpretedSearchFilters;
10
+ searchTerms?: string[];
11
+ refineQuery?: string;
12
+ includeFacets?: boolean;
13
+ suggestedFacets?: SuggestedFacet[];
14
+ enableMissions?: boolean;
15
+ }
16
+ export interface VoiceSearchResult {
17
+ transcript: string;
18
+ enhancedQuery?: string;
19
+ products: ProductCard[];
20
+ meta: SearchResult["meta"];
21
+ mission?: MissionSearchResult;
22
+ ttsText?: string;
23
+ audioSummary?: string;
24
+ ttsPending?: boolean;
25
+ }
26
+ export declare class CommerceAiApiService {
27
+ search(apiBaseUrl: string, query: string, locales?: SearchLocaleFields, signal?: AbortSignal, options?: SearchRequestOptions): Promise<SearchResult>;
28
+ suggest(apiBaseUrl: string, query: string, locales?: SearchLocaleFields, signal?: AbortSignal): Promise<SuggestionsResult>;
29
+ searchByImage(apiBaseUrl: string, file: File, locales?: SearchLocaleFields, enableMissions?: boolean): Promise<SearchResult & {
30
+ interpretation?: string;
31
+ }>;
32
+ searchByVoice(apiBaseUrl: string, audio: Blob, locales?: SearchLocaleFields, enableTts?: boolean, enableMissions?: boolean): Promise<VoiceSearchResult>;
33
+ synthesizeSpeech(apiBaseUrl: string, text: string): Promise<Blob>;
34
+ }
35
+ //# sourceMappingURL=commerce-ai-api.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commerce-ai-api.service.d.ts","sourceRoot":"","sources":["../../src/lib/commerce-ai-api.service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,wBAAwB,EACxB,mBAAmB,EACnB,WAAW,EACX,YAAY,EACZ,cAAc,EACd,iBAAiB,EAClB,MAAM,+BAA+B,CAAC;AAEvC,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kCAAkC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,wBAAwB,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IAC3B,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAiBD,qBACa,oBAAoB;IAC/B,MAAM,CACJ,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,kBAAuB,EAChC,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,YAAY,CAAC,CAcvB;IAED,OAAO,CACL,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,kBAAuB,EAChC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,iBAAiB,CAAC,CAc5B;IAED,aAAa,CACX,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,kBAAuB,EAChC,cAAc,UAAQ,GACrB,OAAO,CAAC,YAAY,GAAG;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAqBrD;IAED,aAAa,CACX,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,IAAI,EACX,OAAO,GAAE,kBAAuB,EAChC,SAAS,UAAO,EAChB,cAAc,UAAQ,GACrB,OAAO,CAAC,iBAAiB,CAAC,CAsB5B;IAED,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYhE;CACF"}
@@ -0,0 +1,34 @@
1
+ import { EventEmitter } from "@angular/core";
2
+ import type { CartSnapshot, CommerceAISearchMessages, CustomerSnapshot } from "../vendor-core/client.js";
3
+ export declare function displayCartError(error: string | null, messages: CommerceAISearchMessages): string | null;
4
+ export declare function formatLineTotal(price: CartSnapshot["lineItems"][number]["price"], quantity: number, locale?: string): string | null;
5
+ export declare class CommerceAiCartPanelComponent {
6
+ cart: CartSnapshot | null;
7
+ customer: CustomerSnapshot | null;
8
+ isLoading: boolean;
9
+ isLoggingIn: boolean;
10
+ error: string | null;
11
+ messages: CommerceAISearchMessages;
12
+ catalogLocale?: string;
13
+ showCheckout: boolean;
14
+ close: EventEmitter<void>;
15
+ remove: EventEmitter<string>;
16
+ quantityChange: EventEmitter<{
17
+ lineItemId: string;
18
+ quantity: number;
19
+ }>;
20
+ login: EventEmitter<{
21
+ email: string;
22
+ password: string;
23
+ }>;
24
+ logout: EventEmitter<void>;
25
+ checkout: EventEmitter<CartSnapshot>;
26
+ email: string;
27
+ password: string;
28
+ isAuthExpanded: boolean;
29
+ get isEmpty(): boolean;
30
+ get visibleError(): string | null;
31
+ lineTotal(item: CartSnapshot["lineItems"][number]): string | null;
32
+ onLoginSubmit(): void;
33
+ }
34
+ //# sourceMappingURL=commerce-ai-cart-panel.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commerce-ai-cart-panel.component.d.ts","sourceRoot":"","sources":["../../src/lib/commerce-ai-cart-panel.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,YAAY,EAAiB,MAAM,eAAe,CAAC;AAEvE,OAAO,KAAK,EACV,YAAY,EACZ,wBAAwB,EACxB,gBAAgB,EACjB,MAAM,+BAA+B,CAAC;AAEvC,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,QAAQ,EAAE,wBAAwB,GACjC,MAAM,GAAG,IAAI,CAWf;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,YAAY,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EACjD,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,GAAG,IAAI,CAQf;AAED,qBAyMa,4BAA4B;IAC9B,IAAI,EAAE,YAAY,GAAG,IAAI,CAAQ;IACjC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAQ;IACzC,SAAS,UAAS;IAClB,WAAW,UAAS;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAQ;IACV,QAAQ,EAAG,wBAAwB,CAAC;IACtD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,UAAS;IAEpB,KAAK,qBAA4B;IACjC,MAAM,uBAA8B;IACpC,cAAc;oBAAkC,MAAM;kBAAY,MAAM;OAAM;IAC9E,KAAK;eAA6B,MAAM;kBAAY,MAAM;OAAM;IAChE,MAAM,qBAA4B;IAClC,QAAQ,6BAAoC;IAEtD,KAAK,SAAM;IACX,QAAQ,SAAM;IACd,cAAc,UAAS;IAEvB,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,YAAY,IAAI,MAAM,GAAG,IAAI,CAEhC;IAED,SAAS,CAAC,IAAI,EAAE,YAAY,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAEhE;IAED,aAAa,IAAI,IAAI,CAGpB;CACF"}
@@ -0,0 +1,61 @@
1
+ import { type CartSnapshot, type CustomerSnapshot } from "../vendor-core/client.js";
2
+ export declare const ANONYMOUS_ID_STORAGE_KEY = "commerce-ai-tool:anonymousId";
3
+ export declare const CUSTOMER_SESSION_STORAGE_KEY = "commerce-ai-tool:customerSession";
4
+ export declare const CUSTOMER_STORAGE_KEY = "commerce-ai-tool:customer";
5
+ export interface AddToCartItem {
6
+ sku?: string;
7
+ productId?: string;
8
+ variantId?: number;
9
+ quantity?: number;
10
+ }
11
+ export interface CommerceAiCartOptions {
12
+ apiBaseUrl: string;
13
+ currency?: string;
14
+ country?: string;
15
+ catalogLocale?: string;
16
+ enabled?: boolean;
17
+ onCartChange?: (cart: CartSnapshot | null) => void;
18
+ }
19
+ export declare function createAnonymousId(): string;
20
+ export declare function getOrCreateAnonymousId(): string;
21
+ export declare class CommerceAiCartService {
22
+ readonly cart: import("@angular/core").WritableSignal<CartSnapshot | null>;
23
+ readonly anonymousId: import("@angular/core").WritableSignal<string>;
24
+ readonly customer: import("@angular/core").WritableSignal<CustomerSnapshot | null>;
25
+ readonly isLoading: import("@angular/core").WritableSignal<boolean>;
26
+ readonly isMutating: import("@angular/core").WritableSignal<boolean>;
27
+ readonly isLoggingIn: import("@angular/core").WritableSignal<boolean>;
28
+ readonly error: import("@angular/core").WritableSignal<string | null>;
29
+ readonly isCartOpen: import("@angular/core").WritableSignal<boolean>;
30
+ readonly isAuthenticated: import("@angular/core").Signal<boolean>;
31
+ private apiBaseUrl;
32
+ private currency?;
33
+ private country?;
34
+ private catalogLocale?;
35
+ private enabled;
36
+ private onCartChange?;
37
+ private sessionTokenValue;
38
+ private cartValue;
39
+ private mutationChain;
40
+ private started;
41
+ sessionToken(): string | null;
42
+ configure(options: CommerceAiCartOptions): void;
43
+ openCart(): void;
44
+ closeCart(): void;
45
+ toggleCart(): void;
46
+ refresh(): Promise<void>;
47
+ addToCart(item: AddToCartItem): Promise<CartSnapshot | null>;
48
+ addItems(items: AddToCartItem[]): Promise<CartSnapshot | null>;
49
+ removeFromCart(lineItemId: string): Promise<CartSnapshot | null>;
50
+ updateQuantity(lineItemId: string, quantity: number): Promise<CartSnapshot | null>;
51
+ login(input: {
52
+ email: string;
53
+ password: string;
54
+ }): Promise<CartSnapshot | null>;
55
+ logout(): Promise<void>;
56
+ private mutate;
57
+ private applyCart;
58
+ private persistSession;
59
+ private clearSession;
60
+ }
61
+ //# sourceMappingURL=commerce-ai-cart.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commerce-ai-cart.service.d.ts","sourceRoot":"","sources":["../../src/lib/commerce-ai-cart.service.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACtB,MAAM,+BAA+B,CAAC;AAEvC,eAAO,MAAM,wBAAwB,iCAAiC,CAAC;AACvE,eAAO,MAAM,4BAA4B,qCAAqC,CAAC;AAC/E,eAAO,MAAM,oBAAoB,8BAA8B,CAAC;AAEhE,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,KAAK,IAAI,CAAC;CACpD;AA0DD,wBAAgB,iBAAiB,IAAI,MAAM,CAK1C;AAED,wBAAgB,sBAAsB,IAAI,MAAM,CAQ/C;AA0BD,qBACa,qBAAqB;IAChC,QAAQ,CAAC,IAAI,8DAAqC;IAClD,QAAQ,CAAC,WAAW,iDAAc;IAClC,QAAQ,CAAC,QAAQ,kEAAyC;IAC1D,QAAQ,CAAC,SAAS,kDAAiB;IACnC,QAAQ,CAAC,UAAU,kDAAiB;IACpC,QAAQ,CAAC,WAAW,kDAAiB;IACrC,QAAQ,CAAC,KAAK,wDAA+B;IAC7C,QAAQ,CAAC,UAAU,kDAAiB;IACpC,QAAQ,CAAC,eAAe,0CAAmE;IAE3F,OAAO,CAAC,UAAU,CAAM;IACxB,OAAO,CAAC,QAAQ,CAAC,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,aAAa,CAAC,CAAS;IAC/B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAC,CAAsC;IAC3D,OAAO,CAAC,iBAAiB,CAAuB;IAChD,OAAO,CAAC,SAAS,CAA6B;IAC9C,OAAO,CAAC,aAAa,CAAuC;IAC5D,OAAO,CAAC,OAAO,CAAS;IAExB,YAAY,IAAI,MAAM,GAAG,IAAI,CAE5B;IAED,SAAS,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAuB9C;IAED,QAAQ,IAAI,IAAI,CAEf;IAED,SAAS,IAAI,IAAI,CAEhB;IAED,UAAU,IAAI,IAAI,CAEjB;IAEK,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAwC7B;IAED,SAAS,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAO3D;IAED,QAAQ,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAE7D;IAED,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAE/D;IAED,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAEjF;IAEK,KAAK,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAgCpF;IAEK,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAa5B;IAED,OAAO,CAAC,MAAM;IAgDd,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,YAAY;CAMrB"}