@commerce-ai-tool/core 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/dist/ai/factory.d.ts +5 -0
- package/dist/ai/factory.d.ts.map +1 -0
- package/dist/ai/index.d.ts +3 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/json-schema-errors.d.ts +4 -0
- package/dist/ai/json-schema-errors.d.ts.map +1 -0
- package/dist/ai/providers/bedrock.d.ts +19 -0
- package/dist/ai/providers/bedrock.d.ts.map +1 -0
- package/dist/ai/providers/openrouter.d.ts +27 -0
- package/dist/ai/providers/openrouter.d.ts.map +1 -0
- package/dist/ai/types.d.ts +23 -0
- package/dist/ai/types.d.ts.map +1 -0
- package/dist/cache/search-cache.d.ts +19 -0
- package/dist/cache/search-cache.d.ts.map +1 -0
- package/dist/client.cjs +423 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +12 -0
- package/dist/client.d.ts +12 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +388 -0
- package/dist/client.js.map +1 -0
- package/dist/commercetools/build-search-keywords.d.ts +37 -0
- package/dist/commercetools/build-search-keywords.d.ts.map +1 -0
- package/dist/commercetools/cart.d.ts +70 -0
- package/dist/commercetools/cart.d.ts.map +1 -0
- package/dist/commercetools/checkout.d.ts +33 -0
- package/dist/commercetools/checkout.d.ts.map +1 -0
- package/dist/commercetools/client.d.ts +43 -0
- package/dist/commercetools/client.d.ts.map +1 -0
- package/dist/commercetools/facet-color.d.ts +12 -0
- package/dist/commercetools/facet-color.d.ts.map +1 -0
- package/dist/commercetools/facets.d.ts +31 -0
- package/dist/commercetools/facets.d.ts.map +1 -0
- package/dist/commercetools/graphql-product-cards.d.ts +94 -0
- package/dist/commercetools/graphql-product-cards.d.ts.map +1 -0
- package/dist/commercetools/order.d.ts +27 -0
- package/dist/commercetools/order.d.ts.map +1 -0
- package/dist/commercetools/payment-map.d.ts +18 -0
- package/dist/commercetools/payment-map.d.ts.map +1 -0
- package/dist/commercetools/payment.d.ts +28 -0
- package/dist/commercetools/payment.d.ts.map +1 -0
- package/dist/commercetools/product-card-hydrate.d.ts +25 -0
- package/dist/commercetools/product-card-hydrate.d.ts.map +1 -0
- package/dist/commercetools/product-types.d.ts +34 -0
- package/dist/commercetools/product-types.d.ts.map +1 -0
- package/dist/commercetools/query-builder.d.ts +41 -0
- package/dist/commercetools/query-builder.d.ts.map +1 -0
- package/dist/commercetools/search-helpers.d.ts +16 -0
- package/dist/commercetools/search-helpers.d.ts.map +1 -0
- package/dist/commercetools/suggestion-quality.d.ts +13 -0
- package/dist/commercetools/suggestion-quality.d.ts.map +1 -0
- package/dist/commercetools/suggestions.d.ts +3 -0
- package/dist/commercetools/suggestions.d.ts.map +1 -0
- package/dist/index.cjs +6066 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +42 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5939 -0
- package/dist/index.js.map +1 -0
- package/dist/locale/resolve.d.ts +16 -0
- package/dist/locale/resolve.d.ts.map +1 -0
- package/dist/messages/index.d.ts +110 -0
- package/dist/messages/index.d.ts.map +1 -0
- package/dist/observability/enabled.d.ts +15 -0
- package/dist/observability/enabled.d.ts.map +1 -0
- package/dist/observability/index.d.ts +8 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/redact.d.ts +9 -0
- package/dist/observability/redact.d.ts.map +1 -0
- package/dist/observability/spans.d.ts +20 -0
- package/dist/observability/spans.d.ts.map +1 -0
- package/dist/observability/wrap-ai-provider.d.ts +14 -0
- package/dist/observability/wrap-ai-provider.d.ts.map +1 -0
- package/dist/payments/types.d.ts +35 -0
- package/dist/payments/types.d.ts.map +1 -0
- package/dist/prompts/catalog.d.ts +20 -0
- package/dist/prompts/catalog.d.ts.map +1 -0
- package/dist/prompts/fragments.d.ts +9 -0
- package/dist/prompts/fragments.d.ts.map +1 -0
- package/dist/prompts/index.d.ts +35 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/resolve.d.ts +35 -0
- package/dist/prompts/resolve.d.ts.map +1 -0
- package/dist/prompts/schemas.d.ts +206 -0
- package/dist/prompts/schemas.d.ts.map +1 -0
- package/dist/search/compound-shopping-list.d.ts +6 -0
- package/dist/search/compound-shopping-list.d.ts.map +1 -0
- package/dist/search/mission.d.ts +14 -0
- package/dist/search/mission.d.ts.map +1 -0
- package/dist/search/orchestrator.d.ts +29 -0
- package/dist/search/orchestrator.d.ts.map +1 -0
- package/dist/search/query-passthrough.d.ts +12 -0
- package/dist/search/query-passthrough.d.ts.map +1 -0
- package/dist/search/relax-search.d.ts +8 -0
- package/dist/search/relax-search.d.ts.map +1 -0
- package/dist/search/suggestions-input.d.ts +35 -0
- package/dist/search/suggestions-input.d.ts.map +1 -0
- package/dist/search/voice-tts.d.ts +6 -0
- package/dist/search/voice-tts.d.ts.map +1 -0
- package/dist/types/index.d.ts +510 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/utils/audio.d.ts +4 -0
- package/dist/utils/audio.d.ts.map +1 -0
- package/dist/utils/dev-trace.d.ts +2 -0
- package/dist/utils/dev-trace.d.ts.map +1 -0
- package/dist/utils/hash.d.ts +2 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/model-json.d.ts +4 -0
- package/dist/utils/model-json.d.ts.map +1 -0
- package/dist/utils/search-timer.d.ts +11 -0
- package/dist/utils/search-timer.d.ts.map +1 -0
- package/dist/utils/with-timeout.d.ts +6 -0
- package/dist/utils/with-timeout.d.ts.map +1 -0
- package/package.json +86 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/types/index.ts","../src/messages/index.ts","../src/search/compound-shopping-list.ts","../src/commercetools/facet-color.ts","../src/commercetools/facets.ts"],"sourcesContent":["/**\n * Browser-safe core surface for the React and Angular widgets.\n * Do not re-export AI, commercetools, or observability from this entry.\n */\nexport type * from \"./types/index.js\";\nexport { CART_SESSION_HEADER, MAX_LINE_ITEM_QUANTITY } from \"./types/index.js\";\nexport {\n DEFAULT_COMMERCE_AI_SEARCH_MESSAGES,\n resolveCommerceAISearchMessages,\n} from \"./messages/index.js\";\nexport type { CommerceAISearchMessages } from \"./messages/index.js\";\nexport { looksLikeCompoundShoppingList } from \"./search/compound-shopping-list.js\";\nexport {\n isFacetFilterSelected,\n toggleFacetFilter,\n} from \"./commercetools/facets.js\";\nexport {\n hexColorSwatchValue,\n isColorLikeFacetName,\n} from \"./commercetools/facet-color.js\";\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"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe core surface for the React and Angular widgets.
|
|
3
|
+
* Do not re-export AI, commercetools, or observability from this entry.
|
|
4
|
+
*/
|
|
5
|
+
export type * from "./types/index.js";
|
|
6
|
+
export { CART_SESSION_HEADER, MAX_LINE_ITEM_QUANTITY } from "./types/index.js";
|
|
7
|
+
export { DEFAULT_COMMERCE_AI_SEARCH_MESSAGES, resolveCommerceAISearchMessages, } from "./messages/index.js";
|
|
8
|
+
export type { CommerceAISearchMessages } from "./messages/index.js";
|
|
9
|
+
export { looksLikeCompoundShoppingList } from "./search/compound-shopping-list.js";
|
|
10
|
+
export { isFacetFilterSelected, toggleFacetFilter, } from "./commercetools/facets.js";
|
|
11
|
+
export { hexColorSwatchValue, isColorLikeFacetName, } from "./commercetools/facet-color.js";
|
|
12
|
+
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe core surface for the React and Angular widgets.
|
|
3
|
+
* Do not re-export AI, commercetools, or observability from this entry.
|
|
4
|
+
*/
|
|
5
|
+
export type * from "./types/index.js";
|
|
6
|
+
export { CART_SESSION_HEADER, MAX_LINE_ITEM_QUANTITY } from "./types/index.js";
|
|
7
|
+
export { DEFAULT_COMMERCE_AI_SEARCH_MESSAGES, resolveCommerceAISearchMessages, } from "./messages/index.js";
|
|
8
|
+
export type { CommerceAISearchMessages } from "./messages/index.js";
|
|
9
|
+
export { looksLikeCompoundShoppingList } from "./search/compound-shopping-list.js";
|
|
10
|
+
export { isFacetFilterSelected, toggleFacetFilter, } from "./commercetools/facets.js";
|
|
11
|
+
export { hexColorSwatchValue, isColorLikeFacetName, } from "./commercetools/facet-color.js";
|
|
12
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,mBAAmB,kBAAkB,CAAC;AACtC,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC/E,OAAO,EACL,mCAAmC,EACnC,+BAA+B,GAChC,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AACnF,OAAO,EACL,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,gCAAgC,CAAC"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
// src/types/index.ts
|
|
2
|
+
var CART_SESSION_HEADER = "x-commerce-ai-cart-session";
|
|
3
|
+
var MAX_LINE_ITEM_QUANTITY = 99;
|
|
4
|
+
|
|
5
|
+
// src/messages/index.ts
|
|
6
|
+
var DEFAULT_COMMERCE_AI_SEARCH_MESSAGES = {
|
|
7
|
+
placeholder: "What are you looking for?",
|
|
8
|
+
searchAriaLabel: "Search query",
|
|
9
|
+
productSearchAriaLabel: "Product search",
|
|
10
|
+
searchResultsAriaLabel: "Search results",
|
|
11
|
+
searching: "Searching...",
|
|
12
|
+
noProductsFound: "No products found",
|
|
13
|
+
searchedFor: "Searched for:",
|
|
14
|
+
dropImageToSearch: "Drop image to search",
|
|
15
|
+
voiceSearch: "Voice search",
|
|
16
|
+
stopRecording: "Stop recording",
|
|
17
|
+
searchByCamera: "Search by camera",
|
|
18
|
+
searchByImage: "Search by image",
|
|
19
|
+
replayVoiceSummary: "Replay voice result summary",
|
|
20
|
+
listening: "Listening\u2026",
|
|
21
|
+
tapMicToStop: "Tap mic to stop",
|
|
22
|
+
understandingQuery: "Understanding your query\u2026",
|
|
23
|
+
preparingAudioSummary: "Preparing audio summary\u2026",
|
|
24
|
+
cameraCapture: "Camera capture",
|
|
25
|
+
cameraPreview: "Camera preview",
|
|
26
|
+
capturePhoto: "Capture",
|
|
27
|
+
cancel: "Cancel",
|
|
28
|
+
dismiss: "Dismiss",
|
|
29
|
+
searchFailed: "Search failed",
|
|
30
|
+
imageSearchFailed: "Image search failed",
|
|
31
|
+
couldNotCapturePhoto: "Could not capture photo",
|
|
32
|
+
suggestionsAriaLabel: "Search suggestions",
|
|
33
|
+
loadingSuggestions: "Loading suggestions...",
|
|
34
|
+
noSuggestions: "No suggestions",
|
|
35
|
+
filtersAriaLabel: "Search filters",
|
|
36
|
+
clearFilters: "Clear filters",
|
|
37
|
+
narrowResults: "Narrow your results",
|
|
38
|
+
newSearch: "New search",
|
|
39
|
+
addToCart: "Add to cart",
|
|
40
|
+
cart: "Cart",
|
|
41
|
+
emptyCart: "Your cart is empty",
|
|
42
|
+
removeItem: "Remove item",
|
|
43
|
+
total: "Total",
|
|
44
|
+
each: "each",
|
|
45
|
+
cartAriaLabel: "Shopping cart",
|
|
46
|
+
itemAdded: "Added to cart",
|
|
47
|
+
increaseQuantity: "Increase quantity",
|
|
48
|
+
decreaseQuantity: "Decrease quantity",
|
|
49
|
+
closeCart: "Close cart",
|
|
50
|
+
unableToAddToCart: "This product cannot be added to the cart",
|
|
51
|
+
missionTitle: "Shopping list",
|
|
52
|
+
missionQuantity: "Looking for",
|
|
53
|
+
missionAddAll: "Add {count} top picks to cart",
|
|
54
|
+
missionItemsAdded: "Added to cart",
|
|
55
|
+
missionSelectProduct: "Select this product",
|
|
56
|
+
missionIntentEmpty: "No matching products",
|
|
57
|
+
missionIntentFailed: "Couldn't search this item",
|
|
58
|
+
signIn: "Sign in",
|
|
59
|
+
signInToSyncCart: "Sign in to sync your cart",
|
|
60
|
+
signOut: "Sign out",
|
|
61
|
+
email: "Email",
|
|
62
|
+
password: "Password",
|
|
63
|
+
signedInAs: "Signed in as",
|
|
64
|
+
invalidCredentials: "Invalid email or password",
|
|
65
|
+
signInFailed: "Sign in failed",
|
|
66
|
+
checkout: "Checkout",
|
|
67
|
+
checkoutTitle: "Complete your order",
|
|
68
|
+
orderSummary: "Order summary",
|
|
69
|
+
shippingAddress: "Shipping address",
|
|
70
|
+
billingAddress: "Billing address",
|
|
71
|
+
billingSameAsShipping: "Billing address is the same as shipping",
|
|
72
|
+
continueToDelivery: "Continue to delivery",
|
|
73
|
+
shippingMethod: "Delivery method",
|
|
74
|
+
selectShippingMethod: "Select a delivery method",
|
|
75
|
+
noShippingMethods: "No delivery methods are available for this address",
|
|
76
|
+
completeAddressToContinue: "Add a shipping address to continue",
|
|
77
|
+
selectDeliveryToContinue: "Select a delivery method to continue",
|
|
78
|
+
selectPaymentToContinue: "Select a payment method to continue",
|
|
79
|
+
checkoutStepAddress: "Address",
|
|
80
|
+
checkoutStepDelivery: "Delivery",
|
|
81
|
+
checkoutStepPayment: "Payment",
|
|
82
|
+
paymentMethod: "Payment method",
|
|
83
|
+
selectPaymentMethod: "Select a payment method",
|
|
84
|
+
paymentAuthorized: "Payment authorized",
|
|
85
|
+
paymentDeclined: "Payment was declined",
|
|
86
|
+
continueToPayment: "Continue to payment",
|
|
87
|
+
placeOrder: "Place order",
|
|
88
|
+
placingOrder: "Placing order\u2026",
|
|
89
|
+
orderPlaced: "Order placed",
|
|
90
|
+
viewOrderStatus: "View order status",
|
|
91
|
+
continueShopping: "Continue shopping",
|
|
92
|
+
checkoutFailed: "Checkout failed",
|
|
93
|
+
orderStatus: "Order status",
|
|
94
|
+
orderPlacedAt: "Placed",
|
|
95
|
+
paymentStateLabel: "Payment",
|
|
96
|
+
shipmentStateLabel: "Shipment",
|
|
97
|
+
trackingNumber: "Tracking number",
|
|
98
|
+
carrier: "Carrier",
|
|
99
|
+
noTrackingYet: "Tracking details will appear when the order ships",
|
|
100
|
+
orderNotFound: "Order not found",
|
|
101
|
+
noOrdersYet: "No orders yet",
|
|
102
|
+
yourOrders: "Your orders",
|
|
103
|
+
firstName: "First name",
|
|
104
|
+
lastName: "Last name",
|
|
105
|
+
streetName: "Street address",
|
|
106
|
+
additionalAddress: "Apartment, suite, etc. (optional)",
|
|
107
|
+
postalCode: "Postal code",
|
|
108
|
+
city: "City",
|
|
109
|
+
region: "State or region (optional)",
|
|
110
|
+
country: "Country"
|
|
111
|
+
};
|
|
112
|
+
function resolveCommerceAISearchMessages(overrides) {
|
|
113
|
+
return {
|
|
114
|
+
...DEFAULT_COMMERCE_AI_SEARCH_MESSAGES,
|
|
115
|
+
...overrides
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/search/compound-shopping-list.ts
|
|
120
|
+
var COMPOUND_SHOPPING_LIST_PATTERN = /\band\b|,|\bplus\b|\boraz\b/i;
|
|
121
|
+
function looksLikeCompoundShoppingList(query) {
|
|
122
|
+
return COMPOUND_SHOPPING_LIST_PATTERN.test(query.trim());
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/commercetools/facet-color.ts
|
|
126
|
+
var CSS_NAMED_COLOR_TABLE = `
|
|
127
|
+
Alice Blue:f0f8ff
|
|
128
|
+
Antique White:faebd7
|
|
129
|
+
Aqua:00ffff
|
|
130
|
+
Aquamarine:7fffd4
|
|
131
|
+
Azure:f0ffff
|
|
132
|
+
Beige:f5f5dc
|
|
133
|
+
Bisque:ffe4c4
|
|
134
|
+
Black:000000
|
|
135
|
+
Blanched Almond:ffebcd
|
|
136
|
+
Blue:0000ff
|
|
137
|
+
Blue Violet:8a2be2
|
|
138
|
+
Brown:a52a2a
|
|
139
|
+
Burlywood:deb887
|
|
140
|
+
Cadet Blue:5f9ea0
|
|
141
|
+
Chartreuse:7fff00
|
|
142
|
+
Chocolate:d2691e
|
|
143
|
+
Coral:ff7f50
|
|
144
|
+
Cornflower Blue:6495ed
|
|
145
|
+
Cornsilk:fff8dc
|
|
146
|
+
Crimson:dc143c
|
|
147
|
+
Dark Blue:00008b
|
|
148
|
+
Dark Cyan:008b8b
|
|
149
|
+
Dark Goldenrod:b8860b
|
|
150
|
+
Dark Gray:a9a9a9
|
|
151
|
+
Dark Green:006400
|
|
152
|
+
Dark Khaki:bdb76b
|
|
153
|
+
Dark Magenta:8b008b
|
|
154
|
+
Dark Olive Green:556b2f
|
|
155
|
+
Dark Orange:ff8c00
|
|
156
|
+
Dark Orchid:9932cc
|
|
157
|
+
Dark Red:8b0000
|
|
158
|
+
Dark Salmon:e9967a
|
|
159
|
+
Dark Sea Green:8fbc8f
|
|
160
|
+
Dark Slate Blue:483d8b
|
|
161
|
+
Dark Slate Gray:2f4f4f
|
|
162
|
+
Dark Turquoise:00ced1
|
|
163
|
+
Dark Violet:9400d3
|
|
164
|
+
Deep Pink:ff1493
|
|
165
|
+
Deep Sky Blue:00bfff
|
|
166
|
+
Dim Gray:696969
|
|
167
|
+
Dodger Blue:1e90ff
|
|
168
|
+
Firebrick:b22222
|
|
169
|
+
Floral White:fffaf0
|
|
170
|
+
Forest Green:228b22
|
|
171
|
+
Gainsboro:dcdcdc
|
|
172
|
+
Ghost White:f8f8ff
|
|
173
|
+
Gold:ffd700
|
|
174
|
+
Goldenrod:daa520
|
|
175
|
+
Gray:808080
|
|
176
|
+
Green:008000
|
|
177
|
+
Green Yellow:adff2f
|
|
178
|
+
Honeydew:f0fff0
|
|
179
|
+
Hot Pink:ff69b4
|
|
180
|
+
Indian Red:cd5c5c
|
|
181
|
+
Indigo:4b0082
|
|
182
|
+
Ivory:fffff0
|
|
183
|
+
Khaki:f0e68c
|
|
184
|
+
Lavender:e6e6fa
|
|
185
|
+
Lavender Blush:fff0f5
|
|
186
|
+
Lawn Green:7cfc00
|
|
187
|
+
Lemon Chiffon:fffacd
|
|
188
|
+
Light Blue:add8e6
|
|
189
|
+
Light Coral:f08080
|
|
190
|
+
Light Cyan:e0ffff
|
|
191
|
+
Light Goldenrod Yellow:fafad2
|
|
192
|
+
Light Gray:d3d3d3
|
|
193
|
+
Light Green:90ee90
|
|
194
|
+
Light Pink:ffb6c1
|
|
195
|
+
Light Salmon:ffa07a
|
|
196
|
+
Light Sea Green:20b2aa
|
|
197
|
+
Light Sky Blue:87cefa
|
|
198
|
+
Light Slate Gray:778899
|
|
199
|
+
Light Steel Blue:b0c4de
|
|
200
|
+
Light Yellow:ffffe0
|
|
201
|
+
Lime:00ff00
|
|
202
|
+
Lime Green:32cd32
|
|
203
|
+
Linen:faf0e6
|
|
204
|
+
Magenta:ff00ff
|
|
205
|
+
Maroon:800000
|
|
206
|
+
Medium Aquamarine:66cdaa
|
|
207
|
+
Medium Blue:0000cd
|
|
208
|
+
Medium Orchid:ba55d3
|
|
209
|
+
Medium Purple:9370db
|
|
210
|
+
Medium Sea Green:3cb371
|
|
211
|
+
Medium Slate Blue:7b68ee
|
|
212
|
+
Medium Spring Green:00fa9a
|
|
213
|
+
Medium Turquoise:48d1cc
|
|
214
|
+
Medium Violet Red:c71585
|
|
215
|
+
Midnight Blue:191970
|
|
216
|
+
Mint Cream:f5fffa
|
|
217
|
+
Misty Rose:ffe4e1
|
|
218
|
+
Moccasin:ffe4b5
|
|
219
|
+
Navajo White:ffdead
|
|
220
|
+
Navy:000080
|
|
221
|
+
Old Lace:fdf5e6
|
|
222
|
+
Olive:808000
|
|
223
|
+
Olive Drab:6b8e23
|
|
224
|
+
Orange:ffa500
|
|
225
|
+
Orange Red:ff4500
|
|
226
|
+
Orchid:da70d6
|
|
227
|
+
Pale Goldenrod:eee8aa
|
|
228
|
+
Pale Green:98fb98
|
|
229
|
+
Pale Turquoise:afeeee
|
|
230
|
+
Pale Violet Red:db7093
|
|
231
|
+
Papaya Whip:ffefd5
|
|
232
|
+
Peach Puff:ffdab9
|
|
233
|
+
Peru:cd853f
|
|
234
|
+
Pink:ffc0cb
|
|
235
|
+
Plum:dda0dd
|
|
236
|
+
Powder Blue:b0e0e6
|
|
237
|
+
Purple:800080
|
|
238
|
+
Rebecca Purple:663399
|
|
239
|
+
Red:ff0000
|
|
240
|
+
Rosy Brown:bc8f8f
|
|
241
|
+
Royal Blue:4169e1
|
|
242
|
+
Saddle Brown:8b4513
|
|
243
|
+
Salmon:fa8072
|
|
244
|
+
Sandy Brown:f4a460
|
|
245
|
+
Sea Green:2e8b57
|
|
246
|
+
Seashell:fff5ee
|
|
247
|
+
Sienna:a0522d
|
|
248
|
+
Silver:c0c0c0
|
|
249
|
+
Sky Blue:87ceeb
|
|
250
|
+
Slate Blue:6a5acd
|
|
251
|
+
Slate Gray:708090
|
|
252
|
+
Snow:fffafa
|
|
253
|
+
Spring Green:00ff7f
|
|
254
|
+
Steel Blue:4682b4
|
|
255
|
+
Tan:d2b48c
|
|
256
|
+
Teal:008080
|
|
257
|
+
Thistle:d8bfd8
|
|
258
|
+
Tomato:ff6347
|
|
259
|
+
Turquoise:40e0d0
|
|
260
|
+
Violet:ee82ee
|
|
261
|
+
Wheat:f5deb3
|
|
262
|
+
White:ffffff
|
|
263
|
+
White Smoke:f5f5f5
|
|
264
|
+
Yellow:ffff00
|
|
265
|
+
Yellow Green:9acd32
|
|
266
|
+
`;
|
|
267
|
+
var HEX_COLOR_RE = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
268
|
+
var NAMED_COLORS = CSS_NAMED_COLOR_TABLE.trim().split("\n").flatMap((line) => {
|
|
269
|
+
const separator = line.indexOf(":");
|
|
270
|
+
if (separator <= 0) {
|
|
271
|
+
return [];
|
|
272
|
+
}
|
|
273
|
+
const name = line.slice(0, separator);
|
|
274
|
+
const hex = line.slice(separator + 1);
|
|
275
|
+
const n = Number.parseInt(hex, 16);
|
|
276
|
+
if (!name || !Number.isFinite(n)) {
|
|
277
|
+
return [];
|
|
278
|
+
}
|
|
279
|
+
return [{ name, r: n >> 16 & 255, g: n >> 8 & 255, b: n & 255 }];
|
|
280
|
+
});
|
|
281
|
+
function parseHexRgb(value) {
|
|
282
|
+
const match = HEX_COLOR_RE.exec(value.trim());
|
|
283
|
+
const captured = match?.[1];
|
|
284
|
+
if (!captured) {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
const hex = captured.length === 3 ? `${captured[0]}${captured[0]}${captured[1]}${captured[1]}${captured[2]}${captured[2]}` : captured;
|
|
288
|
+
const n = Number.parseInt(hex, 16);
|
|
289
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
290
|
+
}
|
|
291
|
+
var MAX_NAMED_COLOR_DISTANCE_SQ = 40 * 40;
|
|
292
|
+
var COLOR_LIKE_FACET_NAME = /(?:^|[-_])colou?r(?:[-_]|$)|(?:^|[-_])finish(?:[-_]|$)/i;
|
|
293
|
+
function isColorLikeFacetName(name) {
|
|
294
|
+
return COLOR_LIKE_FACET_NAME.test(name.trim());
|
|
295
|
+
}
|
|
296
|
+
function hexColorSwatchValue(value) {
|
|
297
|
+
const rgb = parseHexRgb(value);
|
|
298
|
+
if (!rgb) {
|
|
299
|
+
return void 0;
|
|
300
|
+
}
|
|
301
|
+
return `#${rgb.map((channel) => channel.toString(16).padStart(2, "0")).join("")}`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/commercetools/facets.ts
|
|
305
|
+
var PRICE_FACET_ID = "price";
|
|
306
|
+
var CATEGORIES_FACET_ID = "categories";
|
|
307
|
+
var DEFAULT_PRICE_RANGES = [
|
|
308
|
+
{ key: "under-50", to: 5e3 },
|
|
309
|
+
{ key: "50-to-100", from: 5e3, to: 1e4 },
|
|
310
|
+
{ key: "100-plus", from: 1e4 }
|
|
311
|
+
];
|
|
312
|
+
function priceRangeToFilterValues(range) {
|
|
313
|
+
const filters = {};
|
|
314
|
+
if ("from" in range && range.from !== void 0) {
|
|
315
|
+
filters.priceMin = String(range.from / 100);
|
|
316
|
+
}
|
|
317
|
+
if ("to" in range && range.to !== void 0) {
|
|
318
|
+
filters.priceMax = String(range.to / 100);
|
|
319
|
+
}
|
|
320
|
+
return filters;
|
|
321
|
+
}
|
|
322
|
+
function priceBucketKeyFromFilters(filters) {
|
|
323
|
+
const minCents = parseMajorToCents(filters.priceMin);
|
|
324
|
+
const maxCents = parseMajorToCents(filters.priceMax);
|
|
325
|
+
for (const range of DEFAULT_PRICE_RANGES) {
|
|
326
|
+
const from = "from" in range ? range.from : void 0;
|
|
327
|
+
const to = "to" in range ? range.to : void 0;
|
|
328
|
+
if (minCents === from && maxCents === to) {
|
|
329
|
+
return range.key;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return void 0;
|
|
333
|
+
}
|
|
334
|
+
function isFacetFilterSelected(filters, facetId, bucketKey) {
|
|
335
|
+
if (facetId === CATEGORIES_FACET_ID) {
|
|
336
|
+
return filters.category === bucketKey;
|
|
337
|
+
}
|
|
338
|
+
if (facetId === PRICE_FACET_ID) {
|
|
339
|
+
return priceBucketKeyFromFilters(filters) === bucketKey;
|
|
340
|
+
}
|
|
341
|
+
return filters[facetId] === bucketKey;
|
|
342
|
+
}
|
|
343
|
+
function toggleFacetFilter(filters, facetId, bucketKey) {
|
|
344
|
+
const next = { ...filters };
|
|
345
|
+
if (facetId === PRICE_FACET_ID) {
|
|
346
|
+
if (priceBucketKeyFromFilters(next) === bucketKey) {
|
|
347
|
+
delete next.priceMin;
|
|
348
|
+
delete next.priceMax;
|
|
349
|
+
return next;
|
|
350
|
+
}
|
|
351
|
+
delete next.priceMin;
|
|
352
|
+
delete next.priceMax;
|
|
353
|
+
const range = DEFAULT_PRICE_RANGES.find((candidate) => candidate.key === bucketKey);
|
|
354
|
+
if (!range) {
|
|
355
|
+
return next;
|
|
356
|
+
}
|
|
357
|
+
return { ...next, ...priceRangeToFilterValues(range) };
|
|
358
|
+
}
|
|
359
|
+
const filterKey = facetId === CATEGORIES_FACET_ID ? "category" : facetId;
|
|
360
|
+
if (next[filterKey] === bucketKey) {
|
|
361
|
+
delete next[filterKey];
|
|
362
|
+
} else {
|
|
363
|
+
next[filterKey] = bucketKey;
|
|
364
|
+
}
|
|
365
|
+
return next;
|
|
366
|
+
}
|
|
367
|
+
function parseMajorToCents(value) {
|
|
368
|
+
if (!value) {
|
|
369
|
+
return void 0;
|
|
370
|
+
}
|
|
371
|
+
const parsed = Number(value);
|
|
372
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
373
|
+
return void 0;
|
|
374
|
+
}
|
|
375
|
+
return Math.round(parsed * 100);
|
|
376
|
+
}
|
|
377
|
+
export {
|
|
378
|
+
CART_SESSION_HEADER,
|
|
379
|
+
DEFAULT_COMMERCE_AI_SEARCH_MESSAGES,
|
|
380
|
+
MAX_LINE_ITEM_QUANTITY,
|
|
381
|
+
hexColorSwatchValue,
|
|
382
|
+
isColorLikeFacetName,
|
|
383
|
+
isFacetFilterSelected,
|
|
384
|
+
looksLikeCompoundShoppingList,
|
|
385
|
+
resolveCommerceAISearchMessages,
|
|
386
|
+
toggleFacetFilter
|
|
387
|
+
};
|
|
388
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types/index.ts","../src/messages/index.ts","../src/search/compound-shopping-list.ts","../src/commercetools/facet-color.ts","../src/commercetools/facets.ts"],"sourcesContent":["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"],"mappings":"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","names":[]}
|