@anker-in/campaign-ui 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js +1 -1
  2. package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
  3. package/dist/cjs/components/LiveChatWidget/api/chat.d.ts +1 -1
  4. package/dist/cjs/components/LiveChatWidget/api/chat.js +2 -2
  5. package/dist/cjs/components/LiveChatWidget/api/chat.js.map +3 -3
  6. package/dist/cjs/components/LiveChatWidget/components/MessageContent/CartCard.js +1 -1
  7. package/dist/cjs/components/LiveChatWidget/components/MessageContent/CartCard.js.map +3 -3
  8. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.js +1 -1
  9. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.js.map +2 -2
  10. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductComparison.js +1 -1
  11. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductComparison.js.map +2 -2
  12. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductList.js +1 -1
  13. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductList.js.map +2 -2
  14. package/dist/cjs/components/LiveChatWidget/components/MessageList.js +2 -2
  15. package/dist/cjs/components/LiveChatWidget/components/MessageList.js.map +3 -3
  16. package/dist/cjs/components/LiveChatWidget/constants.d.ts +1 -0
  17. package/dist/cjs/components/LiveChatWidget/constants.js +1 -1
  18. package/dist/cjs/components/LiveChatWidget/constants.js.map +3 -3
  19. package/dist/cjs/components/LiveChatWidget/hooks/useChatAPI.js +1 -1
  20. package/dist/cjs/components/LiveChatWidget/hooks/useChatAPI.js.map +3 -3
  21. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.d.ts +5 -0
  22. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js +1 -1
  23. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
  24. package/dist/cjs/components/LiveChatWidget/utils/fetcher.d.ts +3 -1
  25. package/dist/cjs/components/LiveChatWidget/utils/fetcher.js +1 -1
  26. package/dist/cjs/components/LiveChatWidget/utils/fetcher.js.map +3 -3
  27. package/dist/cjs/stories/CartCard.stories.d.ts +33 -0
  28. package/dist/cjs/stories/CartCard.stories.js +21 -0
  29. package/dist/cjs/stories/CartCard.stories.js.map +7 -0
  30. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js +1 -1
  31. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
  32. package/dist/esm/components/LiveChatWidget/api/chat.d.ts +1 -1
  33. package/dist/esm/components/LiveChatWidget/api/chat.js +2 -2
  34. package/dist/esm/components/LiveChatWidget/api/chat.js.map +3 -3
  35. package/dist/esm/components/LiveChatWidget/components/MessageContent/CartCard.js +1 -1
  36. package/dist/esm/components/LiveChatWidget/components/MessageContent/CartCard.js.map +3 -3
  37. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.js +1 -1
  38. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.js.map +3 -3
  39. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.js +1 -1
  40. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.js.map +3 -3
  41. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductList.js +1 -1
  42. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductList.js.map +3 -3
  43. package/dist/esm/components/LiveChatWidget/components/MessageList.js +2 -2
  44. package/dist/esm/components/LiveChatWidget/components/MessageList.js.map +3 -3
  45. package/dist/esm/components/LiveChatWidget/constants.d.ts +1 -0
  46. package/dist/esm/components/LiveChatWidget/constants.js +1 -1
  47. package/dist/esm/components/LiveChatWidget/constants.js.map +3 -3
  48. package/dist/esm/components/LiveChatWidget/hooks/useChatAPI.js +1 -1
  49. package/dist/esm/components/LiveChatWidget/hooks/useChatAPI.js.map +3 -3
  50. package/dist/esm/components/LiveChatWidget/hooks/useChatState.d.ts +5 -0
  51. package/dist/esm/components/LiveChatWidget/hooks/useChatState.js +1 -1
  52. package/dist/esm/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
  53. package/dist/esm/components/LiveChatWidget/utils/fetcher.d.ts +3 -1
  54. package/dist/esm/components/LiveChatWidget/utils/fetcher.js +1 -1
  55. package/dist/esm/components/LiveChatWidget/utils/fetcher.js.map +3 -3
  56. package/dist/esm/stories/CartCard.stories.d.ts +33 -0
  57. package/dist/esm/stories/CartCard.stories.js +21 -0
  58. package/dist/esm/stories/CartCard.stories.js.map +7 -0
  59. package/package.json +1 -1
  60. package/src/components/LiveChatWidget/LiveChatWidget.tsx +6 -4
  61. package/src/components/LiveChatWidget/api/chat.ts +3 -2
  62. package/src/components/LiveChatWidget/components/MessageContent/CartCard.tsx +2 -6
  63. package/src/components/LiveChatWidget/components/MessageContent/ProductCard.tsx +3 -6
  64. package/src/components/LiveChatWidget/components/MessageContent/ProductComparison.tsx +5 -5
  65. package/src/components/LiveChatWidget/components/MessageContent/ProductList.tsx +3 -6
  66. package/src/components/LiveChatWidget/components/MessageList.tsx +20 -4
  67. package/src/components/LiveChatWidget/constants.ts +9 -0
  68. package/src/components/LiveChatWidget/hooks/useChatAPI.ts +2 -3
  69. package/src/components/LiveChatWidget/hooks/useChatState.ts +27 -7
  70. package/src/components/LiveChatWidget/utils/fetcher.ts +15 -2
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/components/LiveChatWidget/components/MessageContent/ProductComparison.tsx"],
4
- "sourcesContent": ["/**\n * \u4EA7\u54C1\u5BF9\u6BD4\u7EC4\u4EF6\n * \u663E\u793A\u591A\u4E2A\u4EA7\u54C1\u7684\u5BF9\u6BD4\u4FE1\u606F\uFF0C\u91C7\u7528\u8868\u683C\u5E03\u5C40\u5C55\u793A\u5404\u7EF4\u5EA6\u5DEE\u5F02\n * \u57FA\u4E8E\u53C2\u8003\u8BBE\u8BA1\uFF1A\u9876\u90E8\u4EA7\u54C1\u5C55\u793A + \u5E95\u90E8\u5BF9\u6BD4\u8868\u683C\n */\n\nimport React, { useState } from 'react'\nimport type { Product, MessageRenderer, CommonText } from '../../types'\nimport { DEFAULT_COMMON_TEXT, CURRENCY_SYMBOLS } from '../../constants'\n\n/**\n * \u5BF9\u6BD4\u7EF4\u5EA6\u6570\u636E\u7ED3\u6784\n */\ninterface ComparisonDimension {\n label: string\n values: Array<{\n product_id: string\n [key: string]: any\n }>\n}\n\n/**\n * \u4EA7\u54C1\u5BF9\u6BD4\u6570\u636E\u7ED3\u6784\n */\nexport interface ProductComparisonData {\n products: Product[]\n dimensions: {\n price?: ComparisonDimension\n variants?: ComparisonDimension\n member_price?: ComparisonDimension\n discount?: ComparisonDimension\n reviews?: ComparisonDimension\n [key: string]: ComparisonDimension | undefined\n }\n commonText?: CommonText\n productComparisonRender?: (data: ProductComparisonData) => React.ReactNode\n}\n\nexport interface ProductComparisonProps {\n /**\n * \u4EA7\u54C1\u5BF9\u6BD4\u6570\u636E\n */\n data: ProductComparisonData\n\n /**\n * \u662F\u5426\u4E3A\u7528\u6237\u6D88\u606F\n */\n isUser?: boolean\n\n /**\n * \u662F\u5426\u4E3A\u7CFB\u7EDF\u6D88\u606F\n */\n isSystem?: boolean\n\n /**\n * \u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n */\n onAddToCart?: (product: Product) => void\n\n /**\n * \u901A\u7528\u6587\u6848\u914D\u7F6E\n */\n commonText?: CommonText\n}\n\n/**\n * \u683C\u5F0F\u5316\u4EF7\u683C\u663E\u793A\n */\nconst formatPrice = (amount: number, currency: string = 'USD'): string => {\n const symbol = CURRENCY_SYMBOLS[currency] || currency\n return `${symbol}${amount}`\n}\n\n/**\n * \u4EA7\u54C1\u5BF9\u6BD4\u7EC4\u4EF6\n *\n * \u5E03\u5C40\uFF1A\n * ```\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n * \u2502 [\u4EA7\u54C11\u56FE\u7247] [\u4EA7\u54C12\u56FE\u7247] \u2502\n * \u2502 \u4EF7\u683C1 \u4EF7\u683C2 \u2502\n * \u2502 \u989C\u8272\u9009\u9879 \u989C\u8272\u9009\u9879 \u2502\n * \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n * \u2502 \u7EF4\u5EA61 \u2502 \u503C1-1 \u2502 \u503C1-2 \u2502\n * \u2502 \u7EF4\u5EA62 \u2502 \u503C2-1 \u2502 \u503C2-2 \u2502\n * \u2502 \u7EF4\u5EA63 \u2502 \u503C3-1 \u2502 \u503C3-2 \u2502\n * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n * ```\n */\nexport const ProductComparison: React.FC<ProductComparisonProps> = ({ data, onAddToCart, commonText }) => {\n const { products: rawProducts, dimensions } = data\n\n // \u5408\u5E76\u9ED8\u8BA4\u6587\u6848\u548C\u81EA\u5B9A\u4E49\u6587\u6848\n const mergedText = { ...DEFAULT_COMMON_TEXT, ...commonText }\n\n // \u8FC7\u6EE4\u6389 null \u6216\u65E0\u6548\u7684\u4EA7\u54C1\n const allProducts = rawProducts?.filter(p => p && p.shopifyId) || []\n\n // \u5BF9\u6BD4\u5217\u6570\uFF08\u56FA\u5B9A\u4E3A2\u5217\uFF09\n const COMPARISON_COLUMNS = 2\n\n // \u521D\u59CB\u5316\u6BCF\u4E2A\u5BF9\u6BD4\u4F4D\u7F6E\u7684\u9009\u4E2D\u4EA7\u54C1\uFF08\u9ED8\u8BA4\u53D6\u524D\u4E24\u4E2A\u4EA7\u54C1\uFF09\n const initialSelectedProducts = allProducts.slice(0, COMPARISON_COLUMNS)\n const [selectedProducts, setSelectedProducts] = useState<Product[]>(initialSelectedProducts)\n\n // Early return \u5FC5\u987B\u5728\u6240\u6709 hooks \u4E4B\u540E\n if (allProducts.length === 0) {\n return null\n }\n\n // \u5904\u7406\u4EA7\u54C1\u9009\u62E9\u53D8\u66F4\n const handleProductChange = (index: number, productId: string) => {\n const newProduct = allProducts.find(p => p.shopifyId === productId)\n if (newProduct) {\n const newSelectedProducts = [...selectedProducts]\n newSelectedProducts[index] = newProduct\n setSelectedProducts(newSelectedProducts)\n }\n }\n\n // \u5F53\u524D\u663E\u793A\u7684\u4EA7\u54C1\uFF08\u786E\u4FDD\u53EA\u663E\u793A\u6307\u5B9A\u5217\u6570\uFF09\n const products = selectedProducts.slice(0, COMPARISON_COLUMNS)\n\n /**\n * \u83B7\u53D6\u6307\u5B9A\u4EA7\u54C1\u5728\u67D0\u4E2A\u7EF4\u5EA6\u7684\u503C\n */\n const getDimensionValue = (dimension: ComparisonDimension, productId: string): any => {\n if (!dimension || !dimension.values || !Array.isArray(dimension.values)) {\n return null\n }\n return dimension.values.find(v => v && v.product_id === productId)\n }\n\n /**\n * \u6E32\u67D3\u901A\u7528\u5BF9\u6BD4\u884C\n */\n const renderComparisonRow = (label: string, dimension: ComparisonDimension | undefined) => {\n if (!dimension) return null\n\n return (\n <div className=\"border-b border-[#DADCE0] pb-2\">\n {/* \u7EF4\u5EA6\u6807\u7B7E\uFF08\u6807\u9898\uFF09 */}\n <div className=\"mb-1\">\n <span className=\"text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#86868C]\">{label==='price'?'has member price':label}</span>\n </div>\n\n {/* \u4EA7\u54C1\u503C\u5217\u8868\uFF08\u6A2A\u5411\u6392\u5217\uFF09 */}\n <div className=\"flex gap-4\" style={{ gap: '36px' }}>\n {products.map((product, index) => {\n if (!product || !product.shopifyId) return null\n const value = getDimensionValue(dimension, product.shopifyId)\n\n return (\n <div key={`comparison-${index}`} className=\"flex-1\">\n {renderDimensionValue(value, dimension.label)}\n </div>\n )\n })}\n </div>\n </div>\n )\n }\n\n /**\n * \u6E32\u67D3\u7EF4\u5EA6\u503C\n */\n const renderDimensionValue = (value: any, dimensionLabel: string) => {\n if (!value) {\n return <span className=\"text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\">-</span>\n }\n\n // \u6839\u636E\u4E0D\u540C\u7EF4\u5EA6\u7C7B\u578B\u6E32\u67D3\u4E0D\u540C\u683C\u5F0F\n if (dimensionLabel.toLowerCase().includes('price')) {\n const hasMemberPrice = value?.has_member_price\n // \u4EF7\u683C\u7EF4\u5EA6\n const priceDisplay = value?.available?\n value.min === value.max\n ? formatPrice(value.min, value.currency)\n : `${formatPrice(value.min, value.currency)} - ${formatPrice(value.max, value.currency)}`:'-'\n return <span className=\"text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\">{hasMemberPrice?'Yes':'No'}</span>\n }\n\n if (dimensionLabel.toLowerCase().includes('variant')) {\n // \u53D8\u4F53\u6570\u91CF\n return (\n <span className=\"text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\">\n {value.count || 0} {value.count === 1 ? 'variant' : 'variants'}\n </span>\n )\n }\n\n if (dimensionLabel.toLowerCase().includes('review')) {\n // \u8BC4\u8BBA\u7EF4\u5EA6\uFF1A\u663E\u793A\u8BC4\u5206\u548C\u6570\u91CF\n const rating = value.rating || 0\n const count = value.count || 0\n return (\n <div className=\"flex items-center gap-1\">\n <span className=\"text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\">\n \u2B50 {rating.toFixed(1)}\n </span>\n <span className=\"text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#86868C]\">\n ({count})\n </span>\n </div>\n )\n }\n\n // \u9ED8\u8BA4\u663E\u793A\n return (\n <span className=\"text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\">\n {value.display || value.value || '-'}\n </span>\n )\n }\n\n return (\n <div className=\"w-full overflow-hidden rounded-2xl bg-[#F5F6F7]\">\n {/* \u9876\u90E8\u4EA7\u54C1\u5C55\u793A\u533A\u57DF */}\n <div className=\"flex p-4\" style={{ gap: '36px', paddingBottom: '0px' }}>\n {products.map((product, index) => {\n if (!product || !product.shopifyId) return null\n\n // \u83B7\u53D6\u4EF7\u683C\u4FE1\u606F\n const priceInfo = dimensions.price ? getDimensionValue(dimensions.price, product.shopifyId) : null\n\n // \u83B7\u53D6\u6298\u6263\u4FE1\u606F\n const firstVariant = product.variants?.[0]\n const hasDiscount = firstVariant?.discount?.has_discount\n const discountPrice = hasDiscount ? firstVariant?.discount?.discount_price : null\n\n // \u5F53\u524D\u663E\u793A\u4EF7\u683C\uFF1A\u6709\u6298\u6263\u65F6\u663E\u793A\u6298\u6263\u4EF7\uFF0C\u5426\u5219\u663E\u793A\u539F\u4EF7\n const currentPrice = discountPrice || priceInfo?.min || product.price.amount\n const originalPrice = product.price.amount\n\n // Add to Cart \u6309\u94AE\u70B9\u51FB\u5904\u7406\n const handleAddToCart = (e: React.MouseEvent) => {\n e.preventDefault()\n e.stopPropagation()\n if (onAddToCart) {\n onAddToCart(product)\n }\n }\n\n return (\n <div key={`product-column-${index}`} className=\"flex flex-1 flex-col items-center\">\n {/* \u4EA7\u54C1\u9009\u62E9\u4E0B\u62C9\u6846 */}\n <div className=\"mb-4 w-full\">\n <select\n value={product.shopifyId}\n onChange={e => handleProductChange(index, e.target.value)}\n className=\"w-full rounded-lg border border-[#DADCE0] bg-[#F5F6F7] px-3 py-2 text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\"\n style={{\n appearance: 'none',\n backgroundImage:\n \"url(\\\"data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%231D1D1F' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\\\")\",\n backgroundRepeat: 'no-repeat',\n backgroundPosition: 'right 12px center',\n paddingRight: '32px',\n }}\n >\n {allProducts.map(p => (\n <option key={p.shopifyId} value={p.shopifyId}>\n {p.title.length > 30 ? `${p.title.slice(0, 30)}...` : p.title}\n </option>\n ))}\n </select>\n </div>\n\n {/* \u4EA7\u54C1\u56FE\u7247 */}\n <a\n href={product.productUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"mb-4 block w-full max-w-[160px]\"\n >\n <div className=\"aspect-square w-full overflow-hidden rounded-lg\">\n {product.imageUrl ? (\n <img\n src={product.imageUrl}\n alt={product.title}\n className=\"size-full object-contain\"\n loading=\"lazy\"\n />\n ) : (\n <div className=\"flex size-full items-center justify-center text-gray-400\">\n <svg className=\"size-12\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z\"\n />\n </svg>\n </div>\n )}\n </div>\n </a>\n\n {/* \u4EF7\u683C\u5C55\u793A\uFF08\u5E26\u5212\u7EBF\u4EF7\uFF09 */}\n <div className=\"mb-4 flex flex-col items-center gap-1\">\n <div className=\"flex items-center gap-2\">\n {/* \u5F53\u524D\u4EF7\u683C\uFF08\u6298\u6263\u4EF7\u6216\u539F\u4EF7\uFF09 */}\n <span className=\"text-base font-bold leading-[1.2] tracking-[-0.02em] text-[#1D1D1F]\">\n {formatPrice(currentPrice, priceInfo?.currency || product.price.currency)}\n </span>\n {/* \u5212\u7EBF\u4EF7 - \u4EC5\u5728\u6709\u6298\u6263\u65F6\u663E\u793A */}\n {hasDiscount && discountPrice && (\n <span className=\"text-base font-bold leading-[1.2] tracking-[-0.02em] text-[#4A4C56] line-through\">\n {formatPrice(originalPrice, product.price.currency)}\n </span>\n )}\n </div>\n </div>\n\n {/* Add to Cart \u6309\u94AE */}\n {onAddToCart && (\n <button\n type=\"button\"\n onClick={handleAddToCart}\n className=\"livechat-btn-primary mb-3 w-fit rounded-full px-[20px] py-[10px] text-center text-sm font-bold leading-[1.2] tracking-[-0.04em] text-white\"\n style={{ backgroundColor: '#1D1D1F' }}\n >\n {mergedText.addToCart}\n </button>\n )}\n\n </div>\n )\n })}\n </div>\n\n {/* \u5BF9\u6BD4\u8868\u683C\u533A\u57DF */}\n <div className=\"flex flex-col gap-4 p-4\">\n {/* \u904D\u5386\u6240\u6709\u7EF4\u5EA6\u5E76\u6E32\u67D3 */}\n {Object.entries(dimensions).map(([key, dimension]) => {\n if (!dimension) return null // price \u5DF2\u5728\u9876\u90E8\u663E\u793A\n return <div key={key}>{renderComparisonRow(dimension.label, dimension)}</div>\n })}\n </div>\n </div>\n )\n}\n\n/**\n * \u521B\u5EFA\u4EA7\u54C1\u5BF9\u6BD4\u6E32\u67D3\u5668\n */\nexport const ProductComparisonRenderer: MessageRenderer = {\n render: (content, isUser, isSystem) => {\n if (content.type !== 'product_comparison' || !content.data) {\n return null\n }\n\n const comparisonData = content.data as ProductComparisonData & { onAddToCart?: (product: Product) => void }\n\n // \u5982\u679C\u63D0\u4F9B\u4E86\u81EA\u5B9A\u4E49\u6E32\u67D3\u51FD\u6570\uFF0C\u4F7F\u7528\u81EA\u5B9A\u4E49\u6E32\u67D3\n if (comparisonData.productComparisonRender) {\n return <>{comparisonData.productComparisonRender(comparisonData)}</>\n }\n\n return (\n <ProductComparison\n data={comparisonData}\n isUser={isUser}\n isSystem={isSystem}\n onAddToCart={comparisonData.onAddToCart}\n commonText={comparisonData.commonText}\n />\n )\n },\n}\n"],
5
- "mappings": "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",
6
- "names": ["Fragment", "jsx", "jsxs", "useState", "DEFAULT_COMMON_TEXT", "CURRENCY_SYMBOLS", "formatPrice", "amount", "currency", "ProductComparison", "data", "onAddToCart", "commonText", "rawProducts", "dimensions", "mergedText", "allProducts", "p", "COMPARISON_COLUMNS", "initialSelectedProducts", "selectedProducts", "setSelectedProducts", "handleProductChange", "index", "productId", "newProduct", "newSelectedProducts", "products", "getDimensionValue", "dimension", "v", "renderComparisonRow", "label", "product", "value", "renderDimensionValue", "dimensionLabel", "hasMemberPrice", "priceDisplay", "rating", "count", "priceInfo", "firstVariant", "hasDiscount", "discountPrice", "currentPrice", "originalPrice", "handleAddToCart", "e", "key", "ProductComparisonRenderer", "content", "isUser", "isSystem", "comparisonData"]
4
+ "sourcesContent": ["/**\n * \u4EA7\u54C1\u5BF9\u6BD4\u7EC4\u4EF6\n * \u663E\u793A\u591A\u4E2A\u4EA7\u54C1\u7684\u5BF9\u6BD4\u4FE1\u606F\uFF0C\u91C7\u7528\u8868\u683C\u5E03\u5C40\u5C55\u793A\u5404\u7EF4\u5EA6\u5DEE\u5F02\n * \u57FA\u4E8E\u53C2\u8003\u8BBE\u8BA1\uFF1A\u9876\u90E8\u4EA7\u54C1\u5C55\u793A + \u5E95\u90E8\u5BF9\u6BD4\u8868\u683C\n */\n\nimport React, { useState } from 'react'\nimport type { Product, MessageRenderer, CommonText } from '../../types'\nimport { DEFAULT_COMMON_TEXT, formatCurrency } from '../../constants'\n\n/**\n * \u5BF9\u6BD4\u7EF4\u5EA6\u6570\u636E\u7ED3\u6784\n */\ninterface ComparisonDimension {\n label: string\n values: Array<{\n product_id: string\n [key: string]: any\n }>\n}\n\n/**\n * \u4EA7\u54C1\u5BF9\u6BD4\u6570\u636E\u7ED3\u6784\n */\nexport interface ProductComparisonData {\n products: Product[]\n dimensions: {\n price?: ComparisonDimension\n variants?: ComparisonDimension\n member_price?: ComparisonDimension\n discount?: ComparisonDimension\n reviews?: ComparisonDimension\n [key: string]: ComparisonDimension | undefined\n }\n commonText?: CommonText\n productComparisonRender?: (data: ProductComparisonData) => React.ReactNode\n}\n\nexport interface ProductComparisonProps {\n /**\n * \u4EA7\u54C1\u5BF9\u6BD4\u6570\u636E\n */\n data: ProductComparisonData\n\n /**\n * \u662F\u5426\u4E3A\u7528\u6237\u6D88\u606F\n */\n isUser?: boolean\n\n /**\n * \u662F\u5426\u4E3A\u7CFB\u7EDF\u6D88\u606F\n */\n isSystem?: boolean\n\n /**\n * \u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n */\n onAddToCart?: (product: Product) => void\n\n /**\n * \u901A\u7528\u6587\u6848\u914D\u7F6E\n */\n commonText?: CommonText\n}\n\n/**\n * \u683C\u5F0F\u5316\u4EF7\u683C\u663E\u793A\n * \u6839\u636E\u8D27\u5E01\u7C7B\u578B\u81EA\u52A8\u51B3\u5B9A\u7B26\u53F7\u4F4D\u7F6E\uFF08\u524D\u7F6E\u6216\u540E\u7F6E\uFF09\n */\nconst formatPrice = (amount: number, currency: string = 'USD'): string => {\n return formatCurrency(amount, currency)\n}\n\n/**\n * \u4EA7\u54C1\u5BF9\u6BD4\u7EC4\u4EF6\n *\n * \u5E03\u5C40\uFF1A\n * ```\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n * \u2502 [\u4EA7\u54C11\u56FE\u7247] [\u4EA7\u54C12\u56FE\u7247] \u2502\n * \u2502 \u4EF7\u683C1 \u4EF7\u683C2 \u2502\n * \u2502 \u989C\u8272\u9009\u9879 \u989C\u8272\u9009\u9879 \u2502\n * \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n * \u2502 \u7EF4\u5EA61 \u2502 \u503C1-1 \u2502 \u503C1-2 \u2502\n * \u2502 \u7EF4\u5EA62 \u2502 \u503C2-1 \u2502 \u503C2-2 \u2502\n * \u2502 \u7EF4\u5EA63 \u2502 \u503C3-1 \u2502 \u503C3-2 \u2502\n * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n * ```\n */\nexport const ProductComparison: React.FC<ProductComparisonProps> = ({ data, onAddToCart, commonText }) => {\n const { products: rawProducts, dimensions } = data\n\n // \u5408\u5E76\u9ED8\u8BA4\u6587\u6848\u548C\u81EA\u5B9A\u4E49\u6587\u6848\n const mergedText = { ...DEFAULT_COMMON_TEXT, ...commonText }\n\n // \u8FC7\u6EE4\u6389 null \u6216\u65E0\u6548\u7684\u4EA7\u54C1\n const allProducts = rawProducts?.filter(p => p && p.shopifyId) || []\n\n // \u5BF9\u6BD4\u5217\u6570\uFF08\u56FA\u5B9A\u4E3A2\u5217\uFF09\n const COMPARISON_COLUMNS = 2\n\n // \u521D\u59CB\u5316\u6BCF\u4E2A\u5BF9\u6BD4\u4F4D\u7F6E\u7684\u9009\u4E2D\u4EA7\u54C1\uFF08\u9ED8\u8BA4\u53D6\u524D\u4E24\u4E2A\u4EA7\u54C1\uFF09\n const initialSelectedProducts = allProducts.slice(0, COMPARISON_COLUMNS)\n const [selectedProducts, setSelectedProducts] = useState<Product[]>(initialSelectedProducts)\n\n // Early return \u5FC5\u987B\u5728\u6240\u6709 hooks \u4E4B\u540E\n if (allProducts.length === 0) {\n return null\n }\n\n // \u5904\u7406\u4EA7\u54C1\u9009\u62E9\u53D8\u66F4\n const handleProductChange = (index: number, productId: string) => {\n const newProduct = allProducts.find(p => p.shopifyId === productId)\n if (newProduct) {\n const newSelectedProducts = [...selectedProducts]\n newSelectedProducts[index] = newProduct\n setSelectedProducts(newSelectedProducts)\n }\n }\n\n // \u5F53\u524D\u663E\u793A\u7684\u4EA7\u54C1\uFF08\u786E\u4FDD\u53EA\u663E\u793A\u6307\u5B9A\u5217\u6570\uFF09\n const products = selectedProducts.slice(0, COMPARISON_COLUMNS)\n\n /**\n * \u83B7\u53D6\u6307\u5B9A\u4EA7\u54C1\u5728\u67D0\u4E2A\u7EF4\u5EA6\u7684\u503C\n */\n const getDimensionValue = (dimension: ComparisonDimension, productId: string): any => {\n if (!dimension || !dimension.values || !Array.isArray(dimension.values)) {\n return null\n }\n return dimension.values.find(v => v && v.product_id === productId)\n }\n\n /**\n * \u6E32\u67D3\u901A\u7528\u5BF9\u6BD4\u884C\n */\n const renderComparisonRow = (label: string, dimension: ComparisonDimension | undefined) => {\n if (!dimension) return null\n\n return (\n <div className=\"border-b border-[#DADCE0] pb-2\">\n {/* \u7EF4\u5EA6\u6807\u7B7E\uFF08\u6807\u9898\uFF09 */}\n <div className=\"mb-1\">\n <span className=\"text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#86868C]\">{label==='price'?'has member price':label}</span>\n </div>\n\n {/* \u4EA7\u54C1\u503C\u5217\u8868\uFF08\u6A2A\u5411\u6392\u5217\uFF09 */}\n <div className=\"flex gap-4\" style={{ gap: '36px' }}>\n {products.map((product, index) => {\n if (!product || !product.shopifyId) return null\n const value = getDimensionValue(dimension, product.shopifyId)\n\n return (\n <div key={`comparison-${index}`} className=\"flex-1\">\n {renderDimensionValue(value, dimension.label)}\n </div>\n )\n })}\n </div>\n </div>\n )\n }\n\n /**\n * \u6E32\u67D3\u7EF4\u5EA6\u503C\n */\n const renderDimensionValue = (value: any, dimensionLabel: string) => {\n if (!value) {\n return <span className=\"text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\">-</span>\n }\n\n // \u6839\u636E\u4E0D\u540C\u7EF4\u5EA6\u7C7B\u578B\u6E32\u67D3\u4E0D\u540C\u683C\u5F0F\n if (dimensionLabel.toLowerCase().includes('price')) {\n const hasMemberPrice = value?.has_member_price\n // \u4EF7\u683C\u7EF4\u5EA6\n const priceDisplay = value?.available?\n value.min === value.max\n ? formatPrice(value.min, value.currency)\n : `${formatPrice(value.min, value.currency)} - ${formatPrice(value.max, value.currency)}`:'-'\n return <span className=\"text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\">{hasMemberPrice?'Yes':'No'}</span>\n }\n\n if (dimensionLabel.toLowerCase().includes('variant')) {\n // \u53D8\u4F53\u6570\u91CF\n return (\n <span className=\"text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\">\n {value.count || 0} {value.count === 1 ? 'variant' : 'variants'}\n </span>\n )\n }\n\n if (dimensionLabel.toLowerCase().includes('review')) {\n // \u8BC4\u8BBA\u7EF4\u5EA6\uFF1A\u663E\u793A\u8BC4\u5206\u548C\u6570\u91CF\n const rating = value.rating || 0\n const count = value.count || 0\n return (\n <div className=\"flex items-center gap-1\">\n <span className=\"text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\">\n \u2B50 {rating.toFixed(1)}\n </span>\n <span className=\"text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#86868C]\">\n ({count})\n </span>\n </div>\n )\n }\n\n // \u9ED8\u8BA4\u663E\u793A\n return (\n <span className=\"text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\">\n {value.display || value.value || '-'}\n </span>\n )\n }\n\n return (\n <div className=\"w-full overflow-hidden rounded-2xl bg-[#F5F6F7]\">\n {/* \u9876\u90E8\u4EA7\u54C1\u5C55\u793A\u533A\u57DF */}\n <div className=\"flex p-4\" style={{ gap: '36px', paddingBottom: '0px' }}>\n {products.map((product, index) => {\n if (!product || !product.shopifyId) return null\n\n // \u83B7\u53D6\u4EF7\u683C\u4FE1\u606F\n const priceInfo = dimensions.price ? getDimensionValue(dimensions.price, product.shopifyId) : null\n\n // \u83B7\u53D6\u6298\u6263\u4FE1\u606F\n const firstVariant = product.variants?.[0]\n const hasDiscount = firstVariant?.discount?.has_discount\n const discountPrice = hasDiscount ? firstVariant?.discount?.discount_price : null\n\n // \u5F53\u524D\u663E\u793A\u4EF7\u683C\uFF1A\u6709\u6298\u6263\u65F6\u663E\u793A\u6298\u6263\u4EF7\uFF0C\u5426\u5219\u663E\u793A\u539F\u4EF7\n const currentPrice = discountPrice || priceInfo?.min || product.price.amount\n const originalPrice = product.price.amount\n\n // Add to Cart \u6309\u94AE\u70B9\u51FB\u5904\u7406\n const handleAddToCart = (e: React.MouseEvent) => {\n e.preventDefault()\n e.stopPropagation()\n if (onAddToCart) {\n onAddToCart(product)\n }\n }\n\n return (\n <div key={`product-column-${index}`} className=\"flex flex-1 flex-col items-center\">\n {/* \u4EA7\u54C1\u9009\u62E9\u4E0B\u62C9\u6846 */}\n <div className=\"mb-4 w-full\">\n <select\n value={product.shopifyId}\n onChange={e => handleProductChange(index, e.target.value)}\n className=\"w-full rounded-lg border border-[#DADCE0] bg-[#F5F6F7] px-3 py-2 text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\"\n style={{\n appearance: 'none',\n backgroundImage:\n \"url(\\\"data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%231D1D1F' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\\\")\",\n backgroundRepeat: 'no-repeat',\n backgroundPosition: 'right 12px center',\n paddingRight: '32px',\n }}\n >\n {allProducts.map(p => (\n <option key={p.shopifyId} value={p.shopifyId}>\n {p.title.length > 30 ? `${p.title.slice(0, 30)}...` : p.title}\n </option>\n ))}\n </select>\n </div>\n\n {/* \u4EA7\u54C1\u56FE\u7247 */}\n <a\n href={product.productUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"mb-4 block w-full max-w-[160px]\"\n >\n <div className=\"aspect-square w-full overflow-hidden rounded-lg\">\n {product.imageUrl ? (\n <img\n src={product.imageUrl}\n alt={product.title}\n className=\"size-full object-contain\"\n loading=\"lazy\"\n />\n ) : (\n <div className=\"flex size-full items-center justify-center text-gray-400\">\n <svg className=\"size-12\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z\"\n />\n </svg>\n </div>\n )}\n </div>\n </a>\n\n {/* \u4EF7\u683C\u5C55\u793A\uFF08\u5E26\u5212\u7EBF\u4EF7\uFF09 */}\n <div className=\"mb-4 flex flex-col items-center gap-1\">\n <div className=\"flex items-center gap-2 justify-center flex-wrap\">\n {/* \u5F53\u524D\u4EF7\u683C\uFF08\u6298\u6263\u4EF7\u6216\u539F\u4EF7\uFF09 */}\n <span className=\"text-base font-bold leading-[1.2] tracking-[-0.02em] text-[#1D1D1F]\">\n {formatPrice(currentPrice, priceInfo?.currency || product.price.currency)}\n </span>\n {/* \u5212\u7EBF\u4EF7 - \u4EC5\u5728\u6709\u6298\u6263\u65F6\u663E\u793A */}\n {hasDiscount && discountPrice && (\n <span className=\"text-base font-bold leading-[1.2] tracking-[-0.02em] text-[#4A4C56] line-through\">\n {formatPrice(originalPrice, product.price.currency)}\n </span>\n )}\n </div>\n </div>\n\n {/* Add to Cart \u6309\u94AE */}\n {onAddToCart && (\n <button\n type=\"button\"\n onClick={handleAddToCart}\n className=\"livechat-btn-primary mb-3 mt-auto w-fit rounded-full px-[20px] py-[10px] text-center text-sm font-bold leading-[1.2] tracking-[-0.04em] text-white\"\n style={{ backgroundColor: '#1D1D1F' }}\n >\n {mergedText.addToCart}\n </button>\n )}\n\n </div>\n )\n })}\n </div>\n\n {/* \u5BF9\u6BD4\u8868\u683C\u533A\u57DF */}\n <div className=\"flex flex-col gap-4 p-4\">\n {/* \u904D\u5386\u6240\u6709\u7EF4\u5EA6\u5E76\u6E32\u67D3 */}\n {Object.entries(dimensions).map(([key, dimension]) => {\n if (!dimension) return null // price \u5DF2\u5728\u9876\u90E8\u663E\u793A\n return <div key={key}>{renderComparisonRow(dimension.label, dimension)}</div>\n })}\n </div>\n </div>\n )\n}\n\n/**\n * \u521B\u5EFA\u4EA7\u54C1\u5BF9\u6BD4\u6E32\u67D3\u5668\n */\nexport const ProductComparisonRenderer: MessageRenderer = {\n render: (content, isUser, isSystem) => {\n if (content.type !== 'product_comparison' || !content.data) {\n return null\n }\n\n const comparisonData = content.data as ProductComparisonData & { onAddToCart?: (product: Product) => void }\n\n // \u5982\u679C\u63D0\u4F9B\u4E86\u81EA\u5B9A\u4E49\u6E32\u67D3\u51FD\u6570\uFF0C\u4F7F\u7528\u81EA\u5B9A\u4E49\u6E32\u67D3\n if (comparisonData.productComparisonRender) {\n return <>{comparisonData.productComparisonRender(comparisonData)}</>\n }\n\n return (\n <ProductComparison\n data={comparisonData}\n isUser={isUser}\n isSystem={isSystem}\n onAddToCart={comparisonData.onAddToCart}\n commonText={comparisonData.commonText}\n />\n )\n },\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["Fragment", "jsx", "jsxs", "useState", "DEFAULT_COMMON_TEXT", "formatCurrency", "formatPrice", "amount", "currency", "ProductComparison", "data", "onAddToCart", "commonText", "rawProducts", "dimensions", "mergedText", "allProducts", "p", "COMPARISON_COLUMNS", "initialSelectedProducts", "selectedProducts", "setSelectedProducts", "handleProductChange", "index", "productId", "newProduct", "newSelectedProducts", "products", "getDimensionValue", "dimension", "v", "renderComparisonRow", "label", "product", "value", "renderDimensionValue", "dimensionLabel", "hasMemberPrice", "priceDisplay", "rating", "count", "priceInfo", "firstVariant", "hasDiscount", "discountPrice", "currentPrice", "originalPrice", "handleAddToCart", "e", "key", "ProductComparisonRenderer", "content", "isUser", "isSystem", "comparisonData"]
7
7
  }
@@ -1,2 +1,2 @@
1
- import{jsx as t,jsxs as c}from"react/jsx-runtime";import{useState as P}from"react";import{CURRENCY_SYMBOLS as C,DEFAULT_COMMON_TEXT as p}from"../../constants.js";function N(e){const{amount:o,currency:s}=e;return`${C[s]||s}${o.toFixed(2)}`}function _(e,o,s=p.off){if(!e.discount_type||e.discount_value===void 0)return"";const a=typeof e.discount_value=="string"?parseFloat(e.discount_value):e.discount_value;return isNaN(a)?"":e.discount_type==="percentage"?`${Math.round(a)}% ${s}`:e.discount_type==="fixed_amount"?`${C[o]||o}${Math.round(a)} ${s}`:""}const w=({product:e,onAddToCart:o,addToCartText:s=p.addToCart,offText:a=p.off})=>{const{title:r,description:l,price:n,imageUrl:d,stockStatus:u,averageRating:m,variants:f}=e,i=u==="out_of_stock",x=f?.[0],g=x?.discount?.has_discount,v=g?x?.discount?.discount_price:null,y=x?.discount,T=v?{amount:v,currency:n.currency}:n,h=y&&g?_(y,n.currency,a):"",k=b=>{b.preventDefault(),b.stopPropagation(),o&&o(e)};return t("div",{className:"block w-full overflow-hidden rounded-2xl bg-[#F5F6F7] transition-shadow",children:t("div",{className:"block",children:c("div",{className:"flex gap-2 p-4",children:[t("div",{className:" flex shrink-0 items-center overflow-hidden rounded-md ",style:{width:"40%"},children:t("img",{src:d,alt:r,className:`h-auto w-full object-cover ${i?"opacity-50":""}`,loading:"lazy"})}),c("div",{className:"flex flex-1 flex-col justify-center",children:[h&&t("div",{className:"livechat-tag-product mb-1 w-fit rounded-full px-2 text-sm font-bold leading-none tracking-[-0.04em] text-white",style:{backgroundColor:"#005D8E",paddingTop:"6px",paddingBottom:"4px"},children:h}),t("h4",{className:"line-clamp-2 text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#080A0F]",children:r}),l&&t("p",{className:"line-clamp-2 text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]",children:l}),c("div",{className:"mt-4 flex items-center gap-2",children:[c("div",{className:"flex items-center gap-1",children:[t("span",{className:"text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]",children:N(T)}),g&&t("span",{className:"text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#6D6D6F] line-through",children:N(n)})]}),m!==void 0&&c("div",{className:"flex items-center gap-0.5 text-xs text-gray-600",children:[t("span",{className:"text-yellow-500",children:"\u2B50"}),t("span",{children:m.toFixed(1)})]})]}),t("button",{type:"button",onClick:k,className:"livechat-btn-primary mt-2 w-fit rounded-full px-[20px] py-[10px] text-center text-sm font-bold leading-[1.2] tracking-[-0.04em] text-white",style:{backgroundColor:"#1D1D1F"},children:s})]})]})})})},F=({products:e,title:o,onAddToCart:s,commonText:a})=>{const[r,l]=P(!1),n={...p,...a},d=e.filter(i=>i&&i.shopifyId),u=3,m=d.length>u,f=r?d:d.slice(0,u);return c("div",{className:"flex w-full flex-col gap-2",children:[o&&t("h3",{className:"text-sm font-semibold text-gray-900",children:o}),t("div",{className:"flex flex-col gap-1.5",children:f.map(i=>!i||!i.shopifyId?null:t(w,{product:i,onAddToCart:s,addToCartText:n.addToCart,offText:n.off},i.shopifyId))}),m&&c("button",{type:"button",onClick:()=>l(!r),className:"flex items-center justify-center gap-1.5 px-3 py-2 text-[14px] font-bold leading-[1.2] tracking-tighter text-[#080A0F]",children:[t("span",{children:r?n.showLess:n.learnMore}),t("svg",{width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:`transition-transform ${r?"rotate-180":""}`,children:t("polyline",{points:"6 9 12 15 18 9"})})]})]})},M={render:e=>{const o=e,{products:s,title:a,onAddToCart:r,commonText:l}=o.data,n=s?.filter(d=>d&&d.shopifyId)||[];return n.length===0?null:t(F,{products:n,title:a,onAddToCart:r,commonText:l})}};export{M as ProductList};
1
+ import{jsx as t,jsxs as d}from"react/jsx-runtime";import{useState as P}from"react";import{formatCurrency as C,DEFAULT_COMMON_TEXT as m}from"../../constants.js";function N(e){const{amount:n,currency:s}=e;return C(n,s)}function _(e,n,s=m.off){if(!e.discount_type||e.discount_value===void 0)return"";const a=typeof e.discount_value=="string"?parseFloat(e.discount_value):e.discount_value;return isNaN(a)?"":e.discount_type==="percentage"?`${Math.round(a)}% ${s}`:e.discount_type==="fixed_amount"?`${C(Math.round(a),n)} ${s}`:""}const w=({product:e,onAddToCart:n,addToCartText:s=m.addToCart,offText:a=m.off})=>{const{title:r,description:l,price:o,imageUrl:c,stockStatus:u,averageRating:p,variants:f}=e,i=u==="out_of_stock",x=f?.[0],g=x?.discount?.has_discount,v=g?x?.discount?.discount_price:null,h=x?.discount,T=v?{amount:v,currency:o.currency}:o,y=h&&g?_(h,o.currency,a):"",k=b=>{b.preventDefault(),b.stopPropagation(),n&&n(e)};return t("div",{className:"block w-full overflow-hidden rounded-2xl bg-[#F5F6F7] transition-shadow",children:t("div",{className:"block",children:d("div",{className:"flex gap-2 p-4",children:[t("div",{className:" flex shrink-0 items-center overflow-hidden rounded-md ",style:{width:"40%"},children:t("img",{src:c,alt:r,className:`h-auto w-full object-cover ${i?"opacity-50":""}`,loading:"lazy"})}),d("div",{className:"flex flex-1 flex-col justify-center",children:[y&&t("div",{className:"livechat-tag-product mb-1 w-fit rounded-full px-2 text-sm font-bold leading-none tracking-[-0.04em] text-white",style:{backgroundColor:"#005D8E",paddingTop:"6px",paddingBottom:"4px"},children:y}),t("h4",{className:"line-clamp-2 text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#080A0F]",children:r}),l&&t("p",{className:"line-clamp-2 text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]",children:l}),d("div",{className:"mt-4 flex items-center gap-2",children:[d("div",{className:"flex items-center gap-1",children:[t("span",{className:"text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]",children:N(T)}),g&&t("span",{className:"text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#6D6D6F] line-through",children:N(o)})]}),p!==void 0&&d("div",{className:"flex items-center gap-0.5 text-xs text-gray-600",children:[t("span",{className:"text-yellow-500",children:"\u2B50"}),t("span",{children:p.toFixed(1)})]})]}),t("button",{type:"button",onClick:k,className:"livechat-btn-primary mt-2 w-fit rounded-full px-[20px] py-[10px] text-center text-sm font-bold leading-[1.2] tracking-[-0.04em] text-white",style:{backgroundColor:"#1D1D1F"},children:s})]})]})})})},D=({products:e,title:n,onAddToCart:s,commonText:a})=>{const[r,l]=P(!1),o={...m,...a},c=e.filter(i=>i&&i.shopifyId),u=3,p=c.length>u,f=r?c:c.slice(0,u);return d("div",{className:"flex w-full flex-col gap-2",children:[n&&t("h3",{className:"text-sm font-semibold text-gray-900",children:n}),t("div",{className:"flex flex-col gap-1.5",children:f.map(i=>!i||!i.shopifyId?null:t(w,{product:i,onAddToCart:s,addToCartText:o.addToCart,offText:o.off},i.shopifyId))}),p&&d("button",{type:"button",onClick:()=>l(!r),className:"flex items-center justify-center gap-1.5 px-3 py-2 text-[14px] font-bold leading-[1.2] tracking-tighter text-[#080A0F]",children:[t("span",{children:r?o.showLess:o.learnMore}),t("svg",{width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:`transition-transform ${r?"rotate-180":""}`,children:t("polyline",{points:"6 9 12 15 18 9"})})]})]})},I={render:e=>{const n=e,{products:s,title:a,onAddToCart:r,commonText:l}=n.data,o=s?.filter(c=>c&&c.shopifyId)||[];return o.length===0?null:t(D,{products:o,title:a,onAddToCart:r,commonText:l})}};export{I as ProductList};
2
2
  //# sourceMappingURL=ProductList.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/components/LiveChatWidget/components/MessageContent/ProductList.tsx"],
4
- "sourcesContent": ["/**\n * \u5546\u54C1\u5217\u8868\u6E32\u67D3\u5668\n * \u663E\u793A\u591A\u4E2A\u5546\u54C1\u7684\u7EB5\u5411\u5217\u8868\uFF0C\u652F\u6301\u5C55\u5F00/\u6536\u8D77\n * \u57FA\u4E8E specs/livechat-widget/data-model.md \u7684\u5546\u54C1\u6570\u636E\u6A21\u578B\n */\n\nimport React, { useState } from 'react'\nimport type { MessageRenderer, ProductListContent, Product, CommonText } from '../../types'\nimport { CURRENCY_SYMBOLS, DEFAULT_COMMON_TEXT } from '../../constants.js'\n\n/**\n * \u683C\u5F0F\u5316\u4EF7\u683C\n */\nfunction formatPrice(price: Product['price']): string {\n const { amount, currency } = price\n\n const symbol = CURRENCY_SYMBOLS[currency] || currency\n return `${symbol}${amount.toFixed(2)}`\n}\n\n/**\n * \u683C\u5F0F\u5316\u6298\u6263\u6807\u7B7E\u6587\u672C\n * @param discount \u6298\u6263\u5BF9\u8C61\n * @param currency \u8D27\u5E01\u4EE3\u7801\n * @param offText \"OFF\" \u6587\u6848\n * @returns \u683C\u5F0F\u5316\u540E\u7684\u6298\u6263\u6587\u672C\uFF08\u5982 \"$10 OFF\" \u6216 \"20% OFF\"\uFF09\n */\nfunction formatDiscountLabel(\n discount: { discount_type?: string; discount_value?: string | number },\n currency: string,\n offText: string = DEFAULT_COMMON_TEXT.off\n): string {\n if (!discount.discount_type || discount.discount_value === undefined) {\n return ''\n }\n\n // \u5C06 discount_value \u8F6C\u6362\u4E3A\u6570\u5B57\n const value =\n typeof discount.discount_value === 'string' ? parseFloat(discount.discount_value) : discount.discount_value\n\n if (isNaN(value)) {\n return ''\n }\n\n if (discount.discount_type === 'percentage') {\n return `${Math.round(value)}% ${offText}`\n }\n\n if (discount.discount_type === 'fixed_amount') {\n const symbol = CURRENCY_SYMBOLS[currency] || currency\n return `${symbol}${Math.round(value)} ${offText}`\n }\n\n return ''\n}\n\n/**\n * \u7D27\u51D1\u578B\u5546\u54C1\u5361\u7247\uFF08\u7528\u4E8E\u7EB5\u5411\u5217\u8868\uFF09\n */\nconst CompactProductCard: React.FC<{\n product: Product\n onAddToCart?: (product: Product) => void\n addToCartText?: string\n offText?: string\n}> = ({ product, onAddToCart, addToCartText = DEFAULT_COMMON_TEXT.addToCart, offText = DEFAULT_COMMON_TEXT.off }) => {\n const { title, description, price, imageUrl, stockStatus, averageRating, variants } = product\n\n const isOutOfStock = stockStatus === 'out_of_stock'\n\n // \u83B7\u53D6\u7B2C\u4E00\u4E2A\u53D8\u4F53\u7684\u6298\u6263\u4FE1\u606F\n const firstVariant = variants?.[0]\n const hasDiscount = firstVariant?.discount?.has_discount\n const discountPrice = hasDiscount ? firstVariant?.discount?.discount_price : null\n const discount = firstVariant?.discount\n\n // \u5F53\u524D\u663E\u793A\u4EF7\u683C\uFF1A\u6709\u6298\u6263\u65F6\u663E\u793A\u6298\u6263\u4EF7\uFF0C\u5426\u5219\u663E\u793A\u539F\u4EF7\n const currentPrice = discountPrice ? { amount: discountPrice, currency: price.currency } : price\n\n // \u683C\u5F0F\u5316\u6298\u6263\u6807\u7B7E\n const discountLabel = discount && hasDiscount ? formatDiscountLabel(discount, price.currency, offText) : ''\n\n const handleAddToCart = (e: React.MouseEvent) => {\n e.preventDefault()\n e.stopPropagation()\n if (onAddToCart) {\n onAddToCart(product)\n }\n }\n\n return (\n <div className=\"block w-full overflow-hidden rounded-2xl bg-[#F5F6F7] transition-shadow\">\n <div className=\"block\">\n <div className=\"flex gap-2 p-4\">\n {/* \u5546\u54C1\u56FE\u7247 */}\n <div className=\" flex shrink-0 items-center overflow-hidden rounded-md \" style={{ width: '40%' }}>\n <img\n src={imageUrl}\n alt={title}\n className={`h-auto w-full object-cover ${isOutOfStock ? 'opacity-50' : ''}`}\n loading=\"lazy\"\n />\n </div>\n\n {/* \u5546\u54C1\u4FE1\u606F */}\n <div className=\"flex flex-1 flex-col justify-center\">\n {/* \u6298\u6263\u6807\u7B7E */}\n {discountLabel && (\n <div\n className=\"livechat-tag-product mb-1 w-fit rounded-full px-2 text-sm font-bold leading-none tracking-[-0.04em] text-white\"\n style={{ backgroundColor: '#005D8E', paddingTop: '6px', paddingBottom: '4px' }}\n >\n {discountLabel}\n </div>\n )}\n\n {/* \u6807\u9898 */}\n <h4 className=\"line-clamp-2 text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#080A0F]\">\n {title}\n </h4>\n\n {/* \u63CF\u8FF0\uFF08\u53EF\u9009\uFF09 */}\n {description && (\n <p className=\"line-clamp-2 text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\">\n {description}\n </p>\n )}\n\n {/* \u4EF7\u683C\u548C\u8BC4\u5206 */}\n <div className=\"mt-4 flex items-center gap-2\">\n <div className=\"flex items-center gap-1\">\n {/* \u5F53\u524D\u4EF7\u683C\uFF08\u6709\u6298\u6263\u65F6\u663E\u793A\u6298\u6263\u4EF7\uFF0C\u5426\u5219\u663E\u793A\u539F\u4EF7\uFF09 */}\n <span className=\"text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\">\n {formatPrice(currentPrice)}\n </span>\n {/* \u539F\u4EF7\uFF08\u5212\u7EBF\u4EF7\uFF09- \u4EC5\u5728\u6709\u6298\u6263\u65F6\u663E\u793A */}\n {hasDiscount && (\n <span className=\"text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#6D6D6F] line-through\">\n {formatPrice(price)}\n </span>\n )}\n </div>\n {/* \u8BC4\u5206\uFF08\u53EF\u9009\uFF09 */}\n {averageRating !== undefined && (\n <div className=\"flex items-center gap-0.5 text-xs text-gray-600\">\n <span className=\"text-yellow-500\">\u2B50</span>\n <span>{averageRating.toFixed(1)}</span>\n </div>\n )}\n </div>\n\n {/* Add to Cart \u6309\u94AE - \u5728\u4EF7\u683C\u4E0B\u65B9 */}\n <button\n type=\"button\"\n onClick={handleAddToCart}\n className=\"livechat-btn-primary mt-2 w-fit rounded-full px-[20px] py-[10px] text-center text-sm font-bold leading-[1.2] tracking-[-0.04em] text-white\"\n style={{ backgroundColor: '#1D1D1F' }}\n >\n {addToCartText}\n </button>\n </div>\n </div>\n </div>\n </div>\n )\n}\n\n/**\n * \u5546\u54C1\u5217\u8868\u5185\u90E8\u7EC4\u4EF6\uFF08\u652F\u6301\u5C55\u5F00/\u6536\u8D77\uFF09\n */\nconst ProductListComponent: React.FC<{\n products: Product[]\n title?: string\n onAddToCart?: (product: Product) => void\n commonText?: CommonText\n}> = ({ products, title, onAddToCart, commonText }) => {\n const [isExpanded, setIsExpanded] = useState(false)\n\n // \u5408\u5E76\u9ED8\u8BA4\u6587\u6848\u548C\u81EA\u5B9A\u4E49\u6587\u6848\n const mergedText = { ...DEFAULT_COMMON_TEXT, ...commonText }\n\n // \u8FC7\u6EE4\u6389 null \u6216\u65E0\u6548\u7684\u4EA7\u54C1\n const validProducts = products.filter(p => p && p.shopifyId)\n\n // \u9ED8\u8BA4\u663E\u793A\u524D3\u4E2A\u4EA7\u54C1\n const INITIAL_DISPLAY_COUNT = 3\n const hasMore = validProducts.length > INITIAL_DISPLAY_COUNT\n const displayedProducts = isExpanded ? validProducts : validProducts.slice(0, INITIAL_DISPLAY_COUNT)\n\n return (\n <div className=\"flex w-full flex-col gap-2\">\n {/* \u5217\u8868\u6807\u9898\uFF08\u53EF\u9009\uFF09 */}\n {title && <h3 className=\"text-sm font-semibold text-gray-900\">{title}</h3>}\n\n {/* \u7EB5\u5411\u6392\u5217\u7684\u5546\u54C1\u5217\u8868 */}\n <div className=\"flex flex-col gap-1.5\">\n {displayedProducts.map(product => {\n if (!product || !product.shopifyId) return null\n return (\n <CompactProductCard\n key={product.shopifyId}\n product={product}\n onAddToCart={onAddToCart}\n addToCartText={mergedText.addToCart}\n offText={mergedText.off}\n />\n )\n })}\n </div>\n\n {/* Learn More \u6309\u94AE */}\n {hasMore && (\n <button\n type=\"button\"\n onClick={() => setIsExpanded(!isExpanded)}\n className=\"flex items-center justify-center gap-1.5 px-3 py-2 text-[14px] font-bold leading-[1.2] tracking-tighter text-[#080A0F]\"\n >\n <span>{isExpanded ? mergedText.showLess : mergedText.learnMore}</span>\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={`transition-transform ${isExpanded ? 'rotate-180' : ''}`}\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n </button>\n )}\n </div>\n )\n}\n\n/**\n * \u5546\u54C1\u5217\u8868\u6E32\u67D3\u5668\n *\n * \u529F\u80FD\uFF1A\n * - \u7EB5\u5411\u5C55\u793A\u591A\u4E2A\u5546\u54C1\n * - \u9ED8\u8BA4\u663E\u793A\u524D3\u4E2A\u4EA7\u54C1\n * - \u652F\u6301\u5C55\u5F00/\u6536\u8D77\u67E5\u770B\u5168\u90E8\n * - \u7D27\u51D1\u578B\u5361\u7247\u8BBE\u8BA1\n * - \u53EF\u9009\u6807\u9898\n *\n * \u5E03\u5C40\uFF1A\n * ```\n * \u6807\u9898\uFF08\u53EF\u9009\uFF09\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n * \u2502 [\u56FE] \u5546\u54C1\u6807\u9898 \u2502\n * \u2502 $29.99 \u2502\n * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n * \u2502 [\u56FE] \u5546\u54C1\u6807\u9898 \u2502\n * \u2502 $39.99 \u2502\n * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n * \u2502 [\u56FE] \u5546\u54C1\u6807\u9898 \u2502\n * \u2502 $49.99 \u2502\n * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n * [ Learn More \u2193 ]\n * ```\n *\n * @example\n * ```tsx\n * const content: ProductListContent = {\n * type: 'product_list',\n * data: {\n * title: '\u76F8\u5173\u5546\u54C1\u63A8\u8350',\n * products: [product1, product2, product3, product4, product5]\n * }\n * }\n * <ProductList.render(content, false, false) />\n * ```\n */\nexport const ProductList: MessageRenderer = {\n render: content => {\n const productListContent = content as ProductListContent\n const { products, title, onAddToCart, commonText } = productListContent.data\n\n // \u8FC7\u6EE4\u6389 null \u6216\u65E0\u6548\u7684\u4EA7\u54C1\n const validProducts = products?.filter(p => p && p.shopifyId) || []\n\n if (validProducts.length === 0) {\n return null\n }\n\n return (\n <ProductListComponent products={validProducts} title={title} onAddToCart={onAddToCart} commonText={commonText} />\n )\n },\n}\n"],
5
- "mappings": "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",
6
- "names": ["jsx", "jsxs", "useState", "CURRENCY_SYMBOLS", "DEFAULT_COMMON_TEXT", "formatPrice", "price", "amount", "currency", "formatDiscountLabel", "discount", "offText", "value", "CompactProductCard", "product", "onAddToCart", "addToCartText", "title", "description", "imageUrl", "stockStatus", "averageRating", "variants", "isOutOfStock", "firstVariant", "hasDiscount", "discountPrice", "currentPrice", "discountLabel", "handleAddToCart", "e", "ProductListComponent", "products", "commonText", "isExpanded", "setIsExpanded", "mergedText", "validProducts", "p", "INITIAL_DISPLAY_COUNT", "hasMore", "displayedProducts", "ProductList", "content", "productListContent"]
4
+ "sourcesContent": ["/**\n * \u5546\u54C1\u5217\u8868\u6E32\u67D3\u5668\n * \u663E\u793A\u591A\u4E2A\u5546\u54C1\u7684\u7EB5\u5411\u5217\u8868\uFF0C\u652F\u6301\u5C55\u5F00/\u6536\u8D77\n * \u57FA\u4E8E specs/livechat-widget/data-model.md \u7684\u5546\u54C1\u6570\u636E\u6A21\u578B\n */\n\nimport React, { useState } from 'react'\nimport type { MessageRenderer, ProductListContent, Product, CommonText } from '../../types'\nimport { formatCurrency, DEFAULT_COMMON_TEXT } from '../../constants.js'\n\n/**\n * \u683C\u5F0F\u5316\u4EF7\u683C\n */\nfunction formatPrice(price: Product['price']): string {\n const { amount, currency } = price\n return formatCurrency(amount, currency)\n}\n\n/**\n * \u683C\u5F0F\u5316\u6298\u6263\u6807\u7B7E\u6587\u672C\n * @param discount \u6298\u6263\u5BF9\u8C61\n * @param currency \u8D27\u5E01\u4EE3\u7801\n * @param offText \"OFF\" \u6587\u6848\n * @returns \u683C\u5F0F\u5316\u540E\u7684\u6298\u6263\u6587\u672C\uFF08\u5982 \"$10 OFF\" \u6216 \"20% OFF\"\uFF09\n */\nfunction formatDiscountLabel(\n discount: { discount_type?: string; discount_value?: string | number },\n currency: string,\n offText: string = DEFAULT_COMMON_TEXT.off\n): string {\n if (!discount.discount_type || discount.discount_value === undefined) {\n return ''\n }\n\n // \u5C06 discount_value \u8F6C\u6362\u4E3A\u6570\u5B57\n const value =\n typeof discount.discount_value === 'string' ? parseFloat(discount.discount_value) : discount.discount_value\n\n if (isNaN(value)) {\n return ''\n }\n\n if (discount.discount_type === 'percentage') {\n return `${Math.round(value)}% ${offText}`\n }\n\n if (discount.discount_type === 'fixed_amount') {\n return `${formatCurrency(Math.round(value), currency)} ${offText}`\n }\n\n return ''\n}\n\n/**\n * \u7D27\u51D1\u578B\u5546\u54C1\u5361\u7247\uFF08\u7528\u4E8E\u7EB5\u5411\u5217\u8868\uFF09\n */\nconst CompactProductCard: React.FC<{\n product: Product\n onAddToCart?: (product: Product) => void\n addToCartText?: string\n offText?: string\n}> = ({ product, onAddToCart, addToCartText = DEFAULT_COMMON_TEXT.addToCart, offText = DEFAULT_COMMON_TEXT.off }) => {\n const { title, description, price, imageUrl, stockStatus, averageRating, variants } = product\n\n const isOutOfStock = stockStatus === 'out_of_stock'\n\n // \u83B7\u53D6\u7B2C\u4E00\u4E2A\u53D8\u4F53\u7684\u6298\u6263\u4FE1\u606F\n const firstVariant = variants?.[0]\n const hasDiscount = firstVariant?.discount?.has_discount\n const discountPrice = hasDiscount ? firstVariant?.discount?.discount_price : null\n const discount = firstVariant?.discount\n\n // \u5F53\u524D\u663E\u793A\u4EF7\u683C\uFF1A\u6709\u6298\u6263\u65F6\u663E\u793A\u6298\u6263\u4EF7\uFF0C\u5426\u5219\u663E\u793A\u539F\u4EF7\n const currentPrice = discountPrice ? { amount: discountPrice, currency: price.currency } : price\n\n // \u683C\u5F0F\u5316\u6298\u6263\u6807\u7B7E\n const discountLabel = discount && hasDiscount ? formatDiscountLabel(discount, price.currency, offText) : ''\n\n const handleAddToCart = (e: React.MouseEvent) => {\n e.preventDefault()\n e.stopPropagation()\n if (onAddToCart) {\n onAddToCart(product)\n }\n }\n\n return (\n <div className=\"block w-full overflow-hidden rounded-2xl bg-[#F5F6F7] transition-shadow\">\n <div className=\"block\">\n <div className=\"flex gap-2 p-4\">\n {/* \u5546\u54C1\u56FE\u7247 */}\n <div className=\" flex shrink-0 items-center overflow-hidden rounded-md \" style={{ width: '40%' }}>\n <img\n src={imageUrl}\n alt={title}\n className={`h-auto w-full object-cover ${isOutOfStock ? 'opacity-50' : ''}`}\n loading=\"lazy\"\n />\n </div>\n\n {/* \u5546\u54C1\u4FE1\u606F */}\n <div className=\"flex flex-1 flex-col justify-center\">\n {/* \u6298\u6263\u6807\u7B7E */}\n {discountLabel && (\n <div\n className=\"livechat-tag-product mb-1 w-fit rounded-full px-2 text-sm font-bold leading-none tracking-[-0.04em] text-white\"\n style={{ backgroundColor: '#005D8E', paddingTop: '6px', paddingBottom: '4px' }}\n >\n {discountLabel}\n </div>\n )}\n\n {/* \u6807\u9898 */}\n <h4 className=\"line-clamp-2 text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#080A0F]\">\n {title}\n </h4>\n\n {/* \u63CF\u8FF0\uFF08\u53EF\u9009\uFF09 */}\n {description && (\n <p className=\"line-clamp-2 text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\">\n {description}\n </p>\n )}\n\n {/* \u4EF7\u683C\u548C\u8BC4\u5206 */}\n <div className=\"mt-4 flex items-center gap-2\">\n <div className=\"flex items-center gap-1\">\n {/* \u5F53\u524D\u4EF7\u683C\uFF08\u6709\u6298\u6263\u65F6\u663E\u793A\u6298\u6263\u4EF7\uFF0C\u5426\u5219\u663E\u793A\u539F\u4EF7\uFF09 */}\n <span className=\"text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\">\n {formatPrice(currentPrice)}\n </span>\n {/* \u539F\u4EF7\uFF08\u5212\u7EBF\u4EF7\uFF09- \u4EC5\u5728\u6709\u6298\u6263\u65F6\u663E\u793A */}\n {hasDiscount && (\n <span className=\"text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#6D6D6F] line-through\">\n {formatPrice(price)}\n </span>\n )}\n </div>\n {/* \u8BC4\u5206\uFF08\u53EF\u9009\uFF09 */}\n {averageRating !== undefined && (\n <div className=\"flex items-center gap-0.5 text-xs text-gray-600\">\n <span className=\"text-yellow-500\">\u2B50</span>\n <span>{averageRating.toFixed(1)}</span>\n </div>\n )}\n </div>\n\n {/* Add to Cart \u6309\u94AE - \u5728\u4EF7\u683C\u4E0B\u65B9 */}\n <button\n type=\"button\"\n onClick={handleAddToCart}\n className=\"livechat-btn-primary mt-2 w-fit rounded-full px-[20px] py-[10px] text-center text-sm font-bold leading-[1.2] tracking-[-0.04em] text-white\"\n style={{ backgroundColor: '#1D1D1F' }}\n >\n {addToCartText}\n </button>\n </div>\n </div>\n </div>\n </div>\n )\n}\n\n/**\n * \u5546\u54C1\u5217\u8868\u5185\u90E8\u7EC4\u4EF6\uFF08\u652F\u6301\u5C55\u5F00/\u6536\u8D77\uFF09\n */\nconst ProductListComponent: React.FC<{\n products: Product[]\n title?: string\n onAddToCart?: (product: Product) => void\n commonText?: CommonText\n}> = ({ products, title, onAddToCart, commonText }) => {\n const [isExpanded, setIsExpanded] = useState(false)\n\n // \u5408\u5E76\u9ED8\u8BA4\u6587\u6848\u548C\u81EA\u5B9A\u4E49\u6587\u6848\n const mergedText = { ...DEFAULT_COMMON_TEXT, ...commonText }\n\n // \u8FC7\u6EE4\u6389 null \u6216\u65E0\u6548\u7684\u4EA7\u54C1\n const validProducts = products.filter(p => p && p.shopifyId)\n\n // \u9ED8\u8BA4\u663E\u793A\u524D3\u4E2A\u4EA7\u54C1\n const INITIAL_DISPLAY_COUNT = 3\n const hasMore = validProducts.length > INITIAL_DISPLAY_COUNT\n const displayedProducts = isExpanded ? validProducts : validProducts.slice(0, INITIAL_DISPLAY_COUNT)\n\n return (\n <div className=\"flex w-full flex-col gap-2\">\n {/* \u5217\u8868\u6807\u9898\uFF08\u53EF\u9009\uFF09 */}\n {title && <h3 className=\"text-sm font-semibold text-gray-900\">{title}</h3>}\n\n {/* \u7EB5\u5411\u6392\u5217\u7684\u5546\u54C1\u5217\u8868 */}\n <div className=\"flex flex-col gap-1.5\">\n {displayedProducts.map(product => {\n if (!product || !product.shopifyId) return null\n return (\n <CompactProductCard\n key={product.shopifyId}\n product={product}\n onAddToCart={onAddToCart}\n addToCartText={mergedText.addToCart}\n offText={mergedText.off}\n />\n )\n })}\n </div>\n\n {/* Learn More \u6309\u94AE */}\n {hasMore && (\n <button\n type=\"button\"\n onClick={() => setIsExpanded(!isExpanded)}\n className=\"flex items-center justify-center gap-1.5 px-3 py-2 text-[14px] font-bold leading-[1.2] tracking-tighter text-[#080A0F]\"\n >\n <span>{isExpanded ? mergedText.showLess : mergedText.learnMore}</span>\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={`transition-transform ${isExpanded ? 'rotate-180' : ''}`}\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n </button>\n )}\n </div>\n )\n}\n\n/**\n * \u5546\u54C1\u5217\u8868\u6E32\u67D3\u5668\n *\n * \u529F\u80FD\uFF1A\n * - \u7EB5\u5411\u5C55\u793A\u591A\u4E2A\u5546\u54C1\n * - \u9ED8\u8BA4\u663E\u793A\u524D3\u4E2A\u4EA7\u54C1\n * - \u652F\u6301\u5C55\u5F00/\u6536\u8D77\u67E5\u770B\u5168\u90E8\n * - \u7D27\u51D1\u578B\u5361\u7247\u8BBE\u8BA1\n * - \u53EF\u9009\u6807\u9898\n *\n * \u5E03\u5C40\uFF1A\n * ```\n * \u6807\u9898\uFF08\u53EF\u9009\uFF09\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n * \u2502 [\u56FE] \u5546\u54C1\u6807\u9898 \u2502\n * \u2502 $29.99 \u2502\n * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n * \u2502 [\u56FE] \u5546\u54C1\u6807\u9898 \u2502\n * \u2502 $39.99 \u2502\n * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n * \u2502 [\u56FE] \u5546\u54C1\u6807\u9898 \u2502\n * \u2502 $49.99 \u2502\n * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n * [ Learn More \u2193 ]\n * ```\n *\n * @example\n * ```tsx\n * const content: ProductListContent = {\n * type: 'product_list',\n * data: {\n * title: '\u76F8\u5173\u5546\u54C1\u63A8\u8350',\n * products: [product1, product2, product3, product4, product5]\n * }\n * }\n * <ProductList.render(content, false, false) />\n * ```\n */\nexport const ProductList: MessageRenderer = {\n render: content => {\n const productListContent = content as ProductListContent\n const { products, title, onAddToCart, commonText } = productListContent.data\n\n // \u8FC7\u6EE4\u6389 null \u6216\u65E0\u6548\u7684\u4EA7\u54C1\n const validProducts = products?.filter(p => p && p.shopifyId) || []\n\n if (validProducts.length === 0) {\n return null\n }\n\n return (\n <ProductListComponent products={validProducts} title={title} onAddToCart={onAddToCart} commonText={commonText} />\n )\n },\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["jsx", "jsxs", "useState", "formatCurrency", "DEFAULT_COMMON_TEXT", "formatPrice", "price", "amount", "currency", "formatDiscountLabel", "discount", "offText", "value", "CompactProductCard", "product", "onAddToCart", "addToCartText", "title", "description", "imageUrl", "stockStatus", "averageRating", "variants", "isOutOfStock", "firstVariant", "hasDiscount", "discountPrice", "currentPrice", "discountLabel", "handleAddToCart", "e", "ProductListComponent", "products", "commonText", "isExpanded", "setIsExpanded", "mergedText", "validProducts", "p", "INITIAL_DISPLAY_COUNT", "hasMore", "displayedProducts", "ProductList", "content", "productListContent"]
7
7
  }
@@ -1,5 +1,5 @@
1
- import{jsx as t,jsxs as c}from"react/jsx-runtime";import{useEffect as n,useState as u,useCallback as d}from"react";import{ChatMessage as M}from"./ChatMessage";import{ScrollAnchor as w}from"./ScrollAnchor";const C=()=>t("div",{className:"flex h-full items-center justify-center text-gray-400",children:t("p",{className:"text-sm",children:"No messages yet"})}),E=()=>t("div",{className:"flex justify-center py-4",children:c("div",{className:"flex gap-1",children:[t("div",{className:"size-2 animate-bounce rounded-full bg-gray-400"}),t("div",{className:"size-2 animate-bounce rounded-full bg-gray-400",style:{animationDelay:"0.1s"}}),t("div",{className:"size-2 animate-bounce rounded-full bg-gray-400",style:{animationDelay:"0.2s"}})]})}),j=({messages:o,rendererRegistry:f,defaultRenderer:v,showTimestamp:g=!0,autoScroll:r=!0,isLoadingHistory:p=!1,emptyPlaceholder:h,className:l="",onAddToCart:y})=>{const[e,b]=u(null),R=d(s=>{b(s)},[]),[m,a]=u(!1),i=d((s=100)=>{if(!e)return!0;const{scrollTop:x,scrollHeight:T,clientHeight:L}=e;return T-x-L<s},[e]),N=d(()=>{e&&e.scrollTo({top:e.scrollHeight,behavior:"smooth"})},[e]);return n(()=>{if(!e)return;const s=()=>{a(!i())};return s(),e.addEventListener("scroll",s,{passive:!0}),()=>e.removeEventListener("scroll",s)},[e,i]),n(()=>{if(r)return;const s=setTimeout(()=>{a(!i())},150);return()=>clearTimeout(s)},[o,r,i]),n(()=>{if(!r||!e)return;const s=setTimeout(()=>{e&&(e.scrollTop=e.scrollHeight,a(!1))},100);return()=>clearTimeout(s)},[o,r,e]),o.length===0?p?t("div",{className:`flex flex-1 items-center justify-center overflow-hidden ${l}`,children:t(E,{})}):t("div",{className:`flex-1 overflow-hidden ${l}`,children:h||t(C,{})}):c("div",{className:"relative flex-1 overflow-hidden",children:[c("div",{ref:R,className:`
1
+ import{jsx as e,jsxs as m}from"react/jsx-runtime";import{useEffect as n,useState as d,useCallback as u}from"react";import{ChatMessage as C}from"./ChatMessage";import{ScrollAnchor as k}from"./ScrollAnchor";const B=()=>e("div",{className:"flex h-full items-center justify-center text-gray-400",children:e("p",{className:"text-sm",children:"No messages yet"})}),h=()=>e("div",{className:"flex justify-center py-4",children:m("div",{className:"flex gap-1",children:[e("div",{className:"size-2 animate-bounce rounded-full bg-gray-400"}),e("div",{className:"size-2 animate-bounce rounded-full bg-gray-400",style:{animationDelay:"0.1s"}}),e("div",{className:"size-2 animate-bounce rounded-full bg-gray-400",style:{animationDelay:"0.2s"}})]})}),A=({messages:o,rendererRegistry:p,defaultRenderer:g,showTimestamp:y=!0,autoScroll:s=!0,isLoadingHistory:a=!1,emptyPlaceholder:b,className:l="",onAddToCart:R})=>{const[t,x]=d(null),N=u(r=>{x(r)},[]),[f,c]=d(!1),[T,v]=d(!1),i=u((r=100)=>{if(!t)return!0;const{scrollTop:M,scrollHeight:E,clientHeight:L}=t;return E-M-L<r},[t]),w=u(()=>{t&&t.scrollTo({top:t.scrollHeight,behavior:"smooth"})},[t]);return n(()=>{if(!t)return;const r=()=>{c(!i())};return r(),t.addEventListener("scroll",r,{passive:!0}),()=>t.removeEventListener("scroll",r)},[t,i]),n(()=>{if(s)return;const r=setTimeout(()=>{c(!i())},150);return()=>clearTimeout(r)},[o,s,i]),n(()=>{if(!s||!t)return;const r=setTimeout(()=>{t&&(t.scrollTop=t.scrollHeight,c(!1))},100);return()=>clearTimeout(r)},[o,s,t]),n(()=>{if(!a&&o.length===0){const r=setTimeout(()=>v(!0),150);return()=>clearTimeout(r)}else v(!1)},[a,o.length]),a?e("div",{className:`flex flex-1 items-center justify-center overflow-hidden ${l}`,children:e(h,{})}):o.length===0?T?e("div",{className:`flex-1 overflow-hidden ${l}`,children:b||e(B,{})}):e("div",{className:`flex flex-1 items-center justify-center overflow-hidden ${l}`,children:e(h,{})}):m("div",{className:"relative flex-1 overflow-hidden",children:[m("div",{ref:N,className:`
2
2
  livechat-message-list absolute inset-0 overflow-y-auto p-4
3
3
  ${l}
4
- `,children:[t("div",{className:"flex flex-col gap-1",children:o.map(s=>t(M,{message:s,rendererRegistry:f,defaultRenderer:v,showTimestamp:g,onAddToCart:y},s.id))}),r&&t(w,{dependencies:[o]})]}),t("button",{onClick:N,className:`livechat-scroll-to-bottom ${m?"visible":"hidden"}`,"aria-label":"Scroll to bottom","aria-hidden":!m,children:t("svg",{width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"text-gray-700",children:t("polyline",{points:"6 9 12 15 18 9"})})})]})};export{j as MessageList};
4
+ `,children:[e("div",{className:"flex flex-col gap-1",children:o.map(r=>e(C,{message:r,rendererRegistry:p,defaultRenderer:g,showTimestamp:y,onAddToCart:R},r.id))}),s&&e(k,{dependencies:[o]})]}),e("button",{onClick:w,className:`livechat-scroll-to-bottom ${f?"visible":"hidden"}`,"aria-label":"Scroll to bottom","aria-hidden":!f,children:e("svg",{width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"text-gray-700",children:e("polyline",{points:"6 9 12 15 18 9"})})})]})};export{A as MessageList};
5
5
  //# sourceMappingURL=MessageList.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/components/LiveChatWidget/components/MessageList.tsx"],
4
- "sourcesContent": ["/**\n * \u6D88\u606F\u5217\u8868\u7EC4\u4EF6\n * \u663E\u793A\u6240\u6709\u804A\u5929\u6D88\u606F\uFF0C\u652F\u6301\u6EDA\u52A8\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u6D88\u606F\u5217\u8868\u8BBE\u8BA1\n */\n\nimport React, { useRef, useEffect, useState, useCallback } from 'react'\nimport type { Message, MessageRenderer } from '../types'\nimport { ChatMessage } from './ChatMessage'\nimport { ScrollAnchor } from './ScrollAnchor'\nimport { MessageRendererRegistry } from '../utils/messageRenderers'\n\nexport interface MessageListProps {\n /**\n * \u6D88\u606F\u5217\u8868\n */\n messages: Message[]\n\n /**\n * \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\u6CE8\u518C\u8868\n */\n rendererRegistry?: MessageRendererRegistry\n\n /**\n * \u9ED8\u8BA4\u6E32\u67D3\u5668\n */\n defaultRenderer?: MessageRenderer\n\n /**\n * \u662F\u5426\u663E\u793A\u65F6\u95F4\u6233\n * @default true\n */\n showTimestamp?: boolean\n\n /**\n * \u662F\u5426\u81EA\u52A8\u6EDA\u52A8\u5230\u5E95\u90E8\n * @default true\n */\n autoScroll?: boolean\n\n /**\n * \u662F\u5426\u6B63\u5728\u52A0\u8F7D\u5386\u53F2\u6D88\u606F\n * @default false\n */\n isLoadingHistory?: boolean\n\n /**\n * \u7A7A\u72B6\u6001\u5360\u4F4D\u5185\u5BB9\n */\n emptyPlaceholder?: React.ReactNode\n\n /**\n * \u81EA\u5B9A\u4E49\u6837\u5F0F\u7C7B\u540D\n */\n className?: string\n\n /**\n * \u5546\u54C1\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n */\n onAddToCart?: (product: any) => void\n}\n\n/**\n * \u9ED8\u8BA4\u7A7A\u72B6\u6001\u5360\u4F4D\n */\nconst DefaultEmptyPlaceholder: React.FC = () => (\n <div className=\"flex h-full items-center justify-center text-gray-400\">\n <p className=\"text-sm\">No messages yet</p>\n </div>\n)\n\n/**\n * \u52A0\u8F7D\u6307\u793A\u5668\n */\nconst LoadingIndicator: React.FC = () => (\n <div className=\"flex justify-center py-4\">\n <div className=\"flex gap-1\">\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" />\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" style={{ animationDelay: '0.1s' }} />\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" style={{ animationDelay: '0.2s' }} />\n </div>\n </div>\n)\n\n/**\n * \u6D88\u606F\u5217\u8868\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u663E\u793A\u6240\u6709\u6D88\u606F\uFF08\u7528\u6237\u3001\u52A9\u624B\u3001\u7CFB\u7EDF\uFF09\n * - \u81EA\u52A8\u6EDA\u52A8\u5230\u6700\u65B0\u6D88\u606F\n * - \u52A0\u8F7D\u5386\u53F2\u6D88\u606F\u65F6\u663E\u793A\u6307\u793A\u5668\n * - \u7A7A\u72B6\u6001\u5360\u4F4D\n *\n * \u6837\u5F0F\uFF1A\n * - \u4F7F\u7528\u81EA\u5B9A\u4E49\u6EDA\u52A8\u6761\u6837\u5F0F\uFF08livechat.css\uFF09\n * - \u5185\u5BB9\u95F4\u8DDD\u5408\u7406\n * - \u652F\u6301\u54CD\u5E94\u5F0F\u5E03\u5C40\n *\n * @example\n * ```tsx\n * <MessageList\n * messages={messages}\n * rendererRegistry={customRegistry}\n * autoScroll={true}\n * isLoadingHistory={isLoading}\n * />\n * ```\n */\nexport const MessageList: React.FC<MessageListProps> = ({\n messages,\n rendererRegistry,\n defaultRenderer,\n showTimestamp = true,\n autoScroll = true,\n isLoadingHistory = false,\n emptyPlaceholder,\n className = '',\n onAddToCart,\n}) => {\n // \u4F7F\u7528 callback ref + state \u786E\u4FDD DOM \u6302\u8F7D\u540E\u89E6\u53D1\u91CD\u65B0\u6E32\u67D3\n const [listElement, setListElement] = useState<HTMLDivElement | null>(null)\n const listRef = useCallback((node: HTMLDivElement | null) => {\n setListElement(node)\n }, [])\n const [showScrollButton, setShowScrollButton] = useState(false)\n\n // \u68C0\u67E5\u662F\u5426\u63A5\u8FD1\u5E95\u90E8\n const isNearBottom = useCallback(\n (threshold = 100) => {\n if (!listElement) return true\n\n const { scrollTop, scrollHeight, clientHeight } = listElement\n return scrollHeight - scrollTop - clientHeight < threshold\n },\n [listElement]\n )\n\n // \u5E73\u6ED1\u6EDA\u52A8\u5230\u5E95\u90E8\n const scrollToBottom = useCallback(() => {\n if (!listElement) return\n\n listElement.scrollTo({\n top: listElement.scrollHeight,\n behavior: 'smooth',\n })\n }, [listElement])\n\n // \u76D1\u542C\u6EDA\u52A8\u4E8B\u4EF6\uFF0C\u63A7\u5236\u6309\u94AE\u663E\u793A\n useEffect(() => {\n if (!listElement) return\n\n const handleScroll = () => {\n setShowScrollButton(!isNearBottom())\n }\n\n // \u521D\u59CB\u68C0\u67E5\n handleScroll()\n\n listElement.addEventListener('scroll', handleScroll, { passive: true })\n return () => listElement.removeEventListener('scroll', handleScroll)\n }, [listElement, isNearBottom])\n\n // \u5F53\u6D88\u606F\u5217\u8868\u53D8\u5316\u65F6\uFF0C\u91CD\u65B0\u68C0\u67E5\u6309\u94AE\u72B6\u6001\uFF08\u4F46\u4E0D\u5728\u81EA\u52A8\u6EDA\u52A8\u65F6\uFF09\n useEffect(() => {\n if (autoScroll) return // \u81EA\u52A8\u6EDA\u52A8\u4F1A\u5728\u53E6\u4E00\u4E2A effect \u4E2D\u5904\u7406\n\n const timeoutId = setTimeout(() => {\n setShowScrollButton(!isNearBottom())\n }, 150)\n\n return () => clearTimeout(timeoutId)\n }, [messages, autoScroll, isNearBottom])\n\n // \u76D1\u542C\u6D88\u606F\u5217\u8868\u53D8\u5316\uFF0C\u81EA\u52A8\u6EDA\u52A8\n useEffect(() => {\n if (!autoScroll || !listElement) return\n\n // \u5EF6\u8FDF\u6EDA\u52A8\u4EE5\u786E\u4FDD DOM \u5DF2\u66F4\u65B0\n const timeoutId = setTimeout(() => {\n if (listElement) {\n listElement.scrollTop = listElement.scrollHeight\n // \u81EA\u52A8\u6EDA\u52A8\u5230\u5E95\u90E8\u540E\uFF0C\u9690\u85CF\u6309\u94AE\n setShowScrollButton(false)\n }\n }, 100)\n\n return () => clearTimeout(timeoutId)\n }, [messages, autoScroll, listElement])\n\n // \u7A7A\u72B6\u6001 + \u52A0\u8F7D\u4E2D\n if (messages.length === 0) {\n if (isLoadingHistory) {\n // \u52A0\u8F7D\u4E2D\uFF0C\u663E\u793A loading\n return (\n <div className={`flex flex-1 items-center justify-center overflow-hidden ${className}`}>\n <LoadingIndicator />\n </div>\n )\n }\n // \u7A7A\u72B6\u6001\n return (\n <div className={`flex-1 overflow-hidden ${className}`}>{emptyPlaceholder || <DefaultEmptyPlaceholder />}</div>\n )\n }\n\n return (\n <div className=\"relative flex-1 overflow-hidden\">\n <div\n ref={listRef}\n className={`\n livechat-message-list absolute inset-0 overflow-y-auto p-4\n ${className}\n `}\n >\n {/* \u6D88\u606F\u5217\u8868 */}\n <div className=\"flex flex-col gap-1\">\n {messages.map(message => (\n <ChatMessage\n key={message.id}\n message={message}\n rendererRegistry={rendererRegistry}\n defaultRenderer={defaultRenderer}\n showTimestamp={showTimestamp}\n onAddToCart={onAddToCart}\n />\n ))}\n </div>\n\n {/* \u6EDA\u52A8\u951A\u70B9 */}\n {autoScroll && <ScrollAnchor dependencies={[messages]} />}\n </div>\n\n {/* \u56DE\u5230\u5E95\u90E8\u6309\u94AE */}\n <button\n onClick={scrollToBottom}\n className={`livechat-scroll-to-bottom ${showScrollButton ? 'visible' : 'hidden'}`}\n aria-label=\"Scroll to bottom\"\n aria-hidden={!showScrollButton}\n >\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"text-gray-700\"\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n </button>\n </div>\n )\n}\n"],
5
- "mappings": "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",
6
- "names": ["jsx", "jsxs", "useEffect", "useState", "useCallback", "ChatMessage", "ScrollAnchor", "DefaultEmptyPlaceholder", "LoadingIndicator", "MessageList", "messages", "rendererRegistry", "defaultRenderer", "showTimestamp", "autoScroll", "isLoadingHistory", "emptyPlaceholder", "className", "onAddToCart", "listElement", "setListElement", "listRef", "node", "showScrollButton", "setShowScrollButton", "isNearBottom", "threshold", "scrollTop", "scrollHeight", "clientHeight", "scrollToBottom", "handleScroll", "timeoutId", "message"]
4
+ "sourcesContent": ["/**\n * \u6D88\u606F\u5217\u8868\u7EC4\u4EF6\n * \u663E\u793A\u6240\u6709\u804A\u5929\u6D88\u606F\uFF0C\u652F\u6301\u6EDA\u52A8\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u6D88\u606F\u5217\u8868\u8BBE\u8BA1\n */\n\nimport React, { useRef, useEffect, useState, useCallback } from 'react'\nimport type { Message, MessageRenderer } from '../types'\nimport { ChatMessage } from './ChatMessage'\nimport { ScrollAnchor } from './ScrollAnchor'\nimport { MessageRendererRegistry } from '../utils/messageRenderers'\n\nexport interface MessageListProps {\n /**\n * \u6D88\u606F\u5217\u8868\n */\n messages: Message[]\n\n /**\n * \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\u6CE8\u518C\u8868\n */\n rendererRegistry?: MessageRendererRegistry\n\n /**\n * \u9ED8\u8BA4\u6E32\u67D3\u5668\n */\n defaultRenderer?: MessageRenderer\n\n /**\n * \u662F\u5426\u663E\u793A\u65F6\u95F4\u6233\n * @default true\n */\n showTimestamp?: boolean\n\n /**\n * \u662F\u5426\u81EA\u52A8\u6EDA\u52A8\u5230\u5E95\u90E8\n * @default true\n */\n autoScroll?: boolean\n\n /**\n * \u662F\u5426\u6B63\u5728\u52A0\u8F7D\u5386\u53F2\u6D88\u606F\n * @default false\n */\n isLoadingHistory?: boolean\n\n /**\n * \u7A7A\u72B6\u6001\u5360\u4F4D\u5185\u5BB9\n */\n emptyPlaceholder?: React.ReactNode\n\n /**\n * \u81EA\u5B9A\u4E49\u6837\u5F0F\u7C7B\u540D\n */\n className?: string\n\n /**\n * \u5546\u54C1\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n */\n onAddToCart?: (product: any) => void\n}\n\n/**\n * \u9ED8\u8BA4\u7A7A\u72B6\u6001\u5360\u4F4D\n */\nconst DefaultEmptyPlaceholder: React.FC = () => (\n <div className=\"flex h-full items-center justify-center text-gray-400\">\n <p className=\"text-sm\">No messages yet</p>\n </div>\n)\n\n/**\n * \u52A0\u8F7D\u6307\u793A\u5668\n */\nconst LoadingIndicator: React.FC = () => (\n <div className=\"flex justify-center py-4\">\n <div className=\"flex gap-1\">\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" />\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" style={{ animationDelay: '0.1s' }} />\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" style={{ animationDelay: '0.2s' }} />\n </div>\n </div>\n)\n\n/**\n * \u6D88\u606F\u5217\u8868\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u663E\u793A\u6240\u6709\u6D88\u606F\uFF08\u7528\u6237\u3001\u52A9\u624B\u3001\u7CFB\u7EDF\uFF09\n * - \u81EA\u52A8\u6EDA\u52A8\u5230\u6700\u65B0\u6D88\u606F\n * - \u52A0\u8F7D\u5386\u53F2\u6D88\u606F\u65F6\u663E\u793A\u6307\u793A\u5668\n * - \u7A7A\u72B6\u6001\u5360\u4F4D\n *\n * \u6837\u5F0F\uFF1A\n * - \u4F7F\u7528\u81EA\u5B9A\u4E49\u6EDA\u52A8\u6761\u6837\u5F0F\uFF08livechat.css\uFF09\n * - \u5185\u5BB9\u95F4\u8DDD\u5408\u7406\n * - \u652F\u6301\u54CD\u5E94\u5F0F\u5E03\u5C40\n *\n * @example\n * ```tsx\n * <MessageList\n * messages={messages}\n * rendererRegistry={customRegistry}\n * autoScroll={true}\n * isLoadingHistory={isLoading}\n * />\n * ```\n */\nexport const MessageList: React.FC<MessageListProps> = ({\n messages,\n rendererRegistry,\n defaultRenderer,\n showTimestamp = true,\n autoScroll = true,\n isLoadingHistory = false,\n emptyPlaceholder,\n className = '',\n onAddToCart,\n}) => {\n // \u4F7F\u7528 callback ref + state \u786E\u4FDD DOM \u6302\u8F7D\u540E\u89E6\u53D1\u91CD\u65B0\u6E32\u67D3\n const [listElement, setListElement] = useState<HTMLDivElement | null>(null)\n const listRef = useCallback((node: HTMLDivElement | null) => {\n setListElement(node)\n }, [])\n const [showScrollButton, setShowScrollButton] = useState(false)\n\n const [showEmpty, setShowEmpty] = useState(false)\n\n // \u68C0\u67E5\u662F\u5426\u63A5\u8FD1\u5E95\u90E8\n const isNearBottom = useCallback(\n (threshold = 100) => {\n if (!listElement) return true\n\n const { scrollTop, scrollHeight, clientHeight } = listElement\n return scrollHeight - scrollTop - clientHeight < threshold\n },\n [listElement]\n )\n\n // \u5E73\u6ED1\u6EDA\u52A8\u5230\u5E95\u90E8\n const scrollToBottom = useCallback(() => {\n if (!listElement) return\n\n listElement.scrollTo({\n top: listElement.scrollHeight,\n behavior: 'smooth',\n })\n }, [listElement])\n\n // \u76D1\u542C\u6EDA\u52A8\u4E8B\u4EF6\uFF0C\u63A7\u5236\u6309\u94AE\u663E\u793A\n useEffect(() => {\n if (!listElement) return\n\n const handleScroll = () => {\n setShowScrollButton(!isNearBottom())\n }\n\n // \u521D\u59CB\u68C0\u67E5\n handleScroll()\n\n listElement.addEventListener('scroll', handleScroll, { passive: true })\n return () => listElement.removeEventListener('scroll', handleScroll)\n }, [listElement, isNearBottom])\n\n // \u5F53\u6D88\u606F\u5217\u8868\u53D8\u5316\u65F6\uFF0C\u91CD\u65B0\u68C0\u67E5\u6309\u94AE\u72B6\u6001\uFF08\u4F46\u4E0D\u5728\u81EA\u52A8\u6EDA\u52A8\u65F6\uFF09\n useEffect(() => {\n if (autoScroll) return // \u81EA\u52A8\u6EDA\u52A8\u4F1A\u5728\u53E6\u4E00\u4E2A effect \u4E2D\u5904\u7406\n\n const timeoutId = setTimeout(() => {\n setShowScrollButton(!isNearBottom())\n }, 150)\n\n return () => clearTimeout(timeoutId)\n }, [messages, autoScroll, isNearBottom])\n\n // \u76D1\u542C\u6D88\u606F\u5217\u8868\u53D8\u5316\uFF0C\u81EA\u52A8\u6EDA\u52A8\n useEffect(() => {\n if (!autoScroll || !listElement) return\n\n // \u5EF6\u8FDF\u6EDA\u52A8\u4EE5\u786E\u4FDD DOM \u5DF2\u66F4\u65B0\n const timeoutId = setTimeout(() => {\n if (listElement) {\n listElement.scrollTop = listElement.scrollHeight\n // \u81EA\u52A8\u6EDA\u52A8\u5230\u5E95\u90E8\u540E\uFF0C\u9690\u85CF\u6309\u94AE\n setShowScrollButton(false)\n }\n }, 100)\n\n return () => clearTimeout(timeoutId)\n }, [messages, autoScroll, listElement])\n\n useEffect(() => {\n if (!isLoadingHistory && messages.length === 0) {\n const timer = setTimeout(() => setShowEmpty(true), 150)\n return () => clearTimeout(timer)\n } else {\n setShowEmpty(false)\n }\n }, [isLoadingHistory, messages.length])\n\n if (isLoadingHistory) {\n return (\n <div className={`flex flex-1 items-center justify-center overflow-hidden ${className}`}>\n <LoadingIndicator />\n </div>\n )\n }\n\n if (messages.length === 0) {\n if (!showEmpty) {\n return (\n <div className={`flex flex-1 items-center justify-center overflow-hidden ${className}`}>\n <LoadingIndicator />\n </div>\n )\n }\n return (\n <div className={`flex-1 overflow-hidden ${className}`}>{emptyPlaceholder || <DefaultEmptyPlaceholder />}</div>\n )\n }\n\n return (\n <div className=\"relative flex-1 overflow-hidden\">\n <div\n ref={listRef}\n className={`\n livechat-message-list absolute inset-0 overflow-y-auto p-4\n ${className}\n `}\n >\n {/* \u6D88\u606F\u5217\u8868 */}\n <div className=\"flex flex-col gap-1\">\n {messages.map(message => (\n <ChatMessage\n key={message.id}\n message={message}\n rendererRegistry={rendererRegistry}\n defaultRenderer={defaultRenderer}\n showTimestamp={showTimestamp}\n onAddToCart={onAddToCart}\n />\n ))}\n </div>\n\n {/* \u6EDA\u52A8\u951A\u70B9 */}\n {autoScroll && <ScrollAnchor dependencies={[messages]} />}\n </div>\n\n {/* \u56DE\u5230\u5E95\u90E8\u6309\u94AE */}\n <button\n onClick={scrollToBottom}\n className={`livechat-scroll-to-bottom ${showScrollButton ? 'visible' : 'hidden'}`}\n aria-label=\"Scroll to bottom\"\n aria-hidden={!showScrollButton}\n >\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"text-gray-700\"\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n </button>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["jsx", "jsxs", "useEffect", "useState", "useCallback", "ChatMessage", "ScrollAnchor", "DefaultEmptyPlaceholder", "LoadingIndicator", "MessageList", "messages", "rendererRegistry", "defaultRenderer", "showTimestamp", "autoScroll", "isLoadingHistory", "emptyPlaceholder", "className", "onAddToCart", "listElement", "setListElement", "listRef", "node", "showScrollButton", "setShowScrollButton", "showEmpty", "setShowEmpty", "isNearBottom", "threshold", "scrollTop", "scrollHeight", "clientHeight", "scrollToBottom", "handleScroll", "timeoutId", "timer", "message"]
7
7
  }
@@ -7,6 +7,7 @@ import type { CommonText } from './types';
7
7
  * 用于将货币代码转换为对应的符号
8
8
  */
9
9
  export declare const CURRENCY_SYMBOLS: Record<string, string>;
10
+ export declare function formatCurrency(amount: number | string, currency?: string): string;
10
11
  /**
11
12
  * 默认文案配置
12
13
  */
@@ -1,2 +1,2 @@
1
- const o={USD:"$",CAD:"CA$",GBP:"\xA3",EUR:"\u20AC",AUD:"$",SGD:"$",NZD:"NZ$",AED:"AED ",VND:"\u20AB",TWD:"NT$",PLN:"z\u0142",RON:"Lei"},e={learnMore:"Learn More",showLess:"Show Less",addToCart:"Add to Cart",viewMore:"View More",off:"OFF",total:"Total"};export{o as CURRENCY_SYMBOLS,e as DEFAULT_COMMON_TEXT};
1
+ const n={USD:"$",CAD:"CA$",GBP:"\xA3",EUR:"\u20AC",AUD:"$",SGD:"$",NZD:"NZ$",AED:"AED ",VND:"\u20AB",TWD:"NT$",PLN:"z\u0142",RON:"Lei"},i=new Set(["VND","RON","PLN"]);function N(t,e="USD"){const r=n[e]||e,o=typeof t=="string"?parseFloat(t):t,s=Number.isInteger(o)?String(o):o.toFixed(2);return i.has(e)?`${s} ${r}`:`${r}${s}`}const C={learnMore:"Learn More",showLess:"Show Less",addToCart:"Add to Cart",viewMore:"View More",off:"OFF",total:"Total"};export{n as CURRENCY_SYMBOLS,C as DEFAULT_COMMON_TEXT,N as formatCurrency};
2
2
  //# sourceMappingURL=constants.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/components/LiveChatWidget/constants.ts"],
4
- "sourcesContent": ["/**\n * LiveChat \u7EC4\u4EF6\u5E38\u91CF\u5B9A\u4E49\n */\n\nimport type { CommonText } from './types'\n\n/**\n * \u8D27\u5E01\u7B26\u53F7\u6620\u5C04\u8868\n * \u7528\u4E8E\u5C06\u8D27\u5E01\u4EE3\u7801\u8F6C\u6362\u4E3A\u5BF9\u5E94\u7684\u7B26\u53F7\n */\nexport const CURRENCY_SYMBOLS: Record<string, string> = {\n USD: \"$\",\n CAD: \"CA$\",\n GBP: \"\u00A3\",\n EUR: \"\u20AC\",\n AUD: \"$\",\n SGD: \"$\",\n NZD: \"NZ$\",\n AED: \"AED \",\n VND: \"\u20AB\",\n TWD: \"NT$\",\n PLN: \"z\u0142\",\n RON: \"Lei\"\n}\n\n/**\n * \u9ED8\u8BA4\u6587\u6848\u914D\u7F6E\n */\nexport const DEFAULT_COMMON_TEXT: Required<CommonText> = {\n learnMore: 'Learn More',\n showLess: 'Show Less',\n addToCart: 'Add to Cart',\n viewMore: 'View More',\n off: 'OFF',\n total: 'Total',\n}\n"],
5
- "mappings": "AAUO,MAAMA,EAA2C,CACpD,IAAK,IACL,IAAK,MACL,IAAK,OACL,IAAK,SACL,IAAK,IACL,IAAK,IACL,IAAK,MACL,IAAK,OACL,IAAK,SACL,IAAK,MACL,IAAK,UACL,IAAK,KACT,EAKaC,EAA4C,CACvD,UAAW,aACX,SAAU,YACV,UAAW,cACX,SAAU,YACV,IAAK,MACL,MAAO,OACT",
6
- "names": ["CURRENCY_SYMBOLS", "DEFAULT_COMMON_TEXT"]
4
+ "sourcesContent": ["/**\n * LiveChat \u7EC4\u4EF6\u5E38\u91CF\u5B9A\u4E49\n */\n\nimport type { CommonText } from './types'\n\n/**\n * \u8D27\u5E01\u7B26\u53F7\u6620\u5C04\u8868\n * \u7528\u4E8E\u5C06\u8D27\u5E01\u4EE3\u7801\u8F6C\u6362\u4E3A\u5BF9\u5E94\u7684\u7B26\u53F7\n */\nexport const CURRENCY_SYMBOLS: Record<string, string> = {\n USD: \"$\",\n CAD: \"CA$\",\n GBP: \"\u00A3\",\n EUR: \"\u20AC\",\n AUD: \"$\",\n SGD: \"$\",\n NZD: \"NZ$\",\n AED: \"AED \",\n VND: \"\u20AB\",\n TWD: \"NT$\",\n PLN: \"z\u0142\",\n RON: \"Lei\"\n}\n\nconst SUFFIX_CURRENCIES = new Set([\"VND\", \"RON\", \"PLN\"])\n\nexport function formatCurrency(amount: number | string, currency: string = 'USD'): string {\n const symbol = CURRENCY_SYMBOLS[currency] || currency\n const value = typeof amount === 'string' ? parseFloat(amount) : amount\n const formatted = Number.isInteger(value) ? String(value) : value.toFixed(2)\n return SUFFIX_CURRENCIES.has(currency) ? `${formatted} ${symbol}` : `${symbol}${formatted}`\n}\n\n/**\n * \u9ED8\u8BA4\u6587\u6848\u914D\u7F6E\n */\nexport const DEFAULT_COMMON_TEXT: Required<CommonText> = {\n learnMore: 'Learn More',\n showLess: 'Show Less',\n addToCart: 'Add to Cart',\n viewMore: 'View More',\n off: 'OFF',\n total: 'Total',\n}\n"],
5
+ "mappings": "AAUO,MAAMA,EAA2C,CACpD,IAAK,IACL,IAAK,MACL,IAAK,OACL,IAAK,SACL,IAAK,IACL,IAAK,IACL,IAAK,MACL,IAAK,OACL,IAAK,SACL,IAAK,MACL,IAAK,UACL,IAAK,KACT,EAEMC,EAAoB,IAAI,IAAI,CAAC,MAAO,MAAO,KAAK,CAAC,EAEhD,SAASC,EAAeC,EAAyBC,EAAmB,MAAe,CACxF,MAAMC,EAASL,EAAiBI,CAAQ,GAAKA,EACvCE,EAAQ,OAAOH,GAAW,SAAW,WAAWA,CAAM,EAAIA,EAC1DI,EAAY,OAAO,UAAUD,CAAK,EAAI,OAAOA,CAAK,EAAIA,EAAM,QAAQ,CAAC,EAC3E,OAAOL,EAAkB,IAAIG,CAAQ,EAAI,GAAGG,CAAS,IAAIF,CAAM,GAAK,GAAGA,CAAM,GAAGE,CAAS,EAC3F,CAKO,MAAMC,EAA4C,CACvD,UAAW,aACX,SAAU,YACV,UAAW,cACX,SAAU,YACV,IAAK,MACL,MAAO,OACT",
6
+ "names": ["CURRENCY_SYMBOLS", "SUFFIX_CURRENCIES", "formatCurrency", "amount", "currency", "symbol", "value", "formatted", "DEFAULT_COMMON_TEXT"]
7
7
  }
@@ -1,2 +1,2 @@
1
- import{useCallback as u,useRef as l}from"react";import{sendMessage as C,createNewSession as m}from"../api/chat";function E(S){const{apiBaseUrl:s,headers:o,recaptchaConfig:n,onError:a}=S,e=l(null),i=l(!1),f=u(async(c,r)=>{e.current&&e.current.abort(),e.current=new AbortController,i.current=!0;try{await C(s,c,r,o,n)}catch(t){if(t instanceof Error&&t.name==="AbortError")return;throw console.error("[useChatAPI] Stream error:",t),a?.(t),t}finally{i.current=!1,e.current=null}},[s,o,n,a]),h=u(async c=>{try{return await m(s,c,o,n)}catch(r){throw console.error("[useChatAPI] Create session error:",r),a?.(r),r}},[s,o,n,a]),p=u(()=>{e.current&&(e.current.abort(),e.current=null,i.current=!1)},[]);return{sendMessageStream:f,createSession:h,abortStream:p,isSending:i.current}}export{E as useChatAPI};
1
+ import{useCallback as u,useRef as l}from"react";import{sendMessage as m,createNewSession as R}from"../api/chat";function E(S){const{apiBaseUrl:s,headers:n,recaptchaConfig:o,onError:a}=S,e=l(null),i=l(!1),f=u(async(c,r)=>{e.current&&e.current.abort(),e.current=new AbortController,i.current=!0;const{signal:C}=e.current;try{await m(s,c,r,n,o,C)}catch(t){if(t instanceof Error&&t.name==="AbortError")return;throw console.error("[useChatAPI] Stream error:",t),a?.(t),t}finally{i.current=!1,e.current=null}},[s,n,o,a]),h=u(async c=>{try{return await R(s,c,n,o)}catch(r){throw console.error("[useChatAPI] Create session error:",r),a?.(r),r}},[s,n,o,a]),p=u(()=>{e.current&&(e.current.abort(),e.current=null,i.current=!1)},[]);return{sendMessageStream:f,createSession:h,abortStream:p,isSending:i.current}}export{E as useChatAPI};
2
2
  //# sourceMappingURL=useChatAPI.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/components/LiveChatWidget/hooks/useChatAPI.ts"],
4
- "sourcesContent": ["/**\n * \u804A\u5929 API \u8C03\u7528 Hook\n * \u5C01\u88C5 SSE \u6D41\u5F0F\u6D88\u606F\u63A5\u6536\u3001\u5386\u53F2\u6D88\u606F\u52A0\u8F7D\u3001\u65B0\u4F1A\u8BDD\u521B\u5EFA\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684 API \u8C03\u7528\u7B56\u7565\n */\n\nimport { useCallback, useRef } from 'react'\nimport type { ChatStreamRequest, SSEEvent, NewSessionRequest, NewSessionResponse } from '../types'\nimport { sendMessage, createNewSession, type RecaptchaConfig } from '../api/chat'\n\nexport interface UseChatAPIOptions {\n /**\n * API \u57FA\u7840 URL\n */\n apiBaseUrl: string\n\n /**\n * \u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\n */\n headers?: Record<string, string>\n\n /**\n * reCAPTCHA \u914D\u7F6E\n */\n recaptchaConfig?: RecaptchaConfig\n\n /**\n * \u9519\u8BEF\u5904\u7406\u56DE\u8C03\n */\n onError?: (error: Error) => void\n}\n\nexport interface UseChatAPIReturn {\n /**\n * \u53D1\u9001\u6D88\u606F\u5E76\u63A5\u6536 SSE \u6D41\u5F0F\u54CD\u5E94\n * @param request \u8BF7\u6C42\u53C2\u6570\n * @param onEvent SSE \u4E8B\u4EF6\u56DE\u8C03\n * @returns Promise<void>\n */\n sendMessageStream: (request: ChatStreamRequest, onEvent: (event: SSEEvent) => void) => Promise<void>\n\n /**\n * \u521B\u5EFA\u65B0\u4F1A\u8BDD\u6216\u6062\u590D\u73B0\u6709\u4F1A\u8BDD\n * @param request \u8BF7\u6C42\u53C2\u6570\n * @returns \u65B0\u4F1A\u8BDD\u4FE1\u606F\uFF0C\u53EF\u80FD\u5305\u542B\u5386\u53F2\u6D88\u606F\n */\n createSession: (request: NewSessionRequest) => Promise<NewSessionResponse>\n\n /**\n * \u4E2D\u65AD\u5F53\u524D\u7684 SSE \u6D41\n */\n abortStream: () => void\n\n /**\n * \u662F\u5426\u6B63\u5728\u53D1\u9001\u6D88\u606F\n */\n isSending: boolean\n}\n\n/**\n * \u804A\u5929 API \u8C03\u7528 Hook\n *\n * \u529F\u80FD\uFF1A\n * 1. sendMessageStream: \u53D1\u9001\u6D88\u606F\u5E76\u63A5\u6536 SSE \u6D41\u5F0F\u54CD\u5E94\n * 2. createSession: \u521B\u5EFA\u65B0\u4F1A\u8BDD\u6216\u6062\u590D\u73B0\u6709\u4F1A\u8BDD\n * 3. abortStream: \u4E2D\u65AD\u5F53\u524D\u6D41\n *\n * @param options Hook \u914D\u7F6E\u9009\u9879\n * @returns API \u8C03\u7528\u5DE5\u5177\u5BF9\u8C61\n */\nexport function useChatAPI(options: UseChatAPIOptions): UseChatAPIReturn {\n const { apiBaseUrl, headers, recaptchaConfig, onError } = options\n\n // \u7528\u4E8E\u4E2D\u65AD\u5F53\u524D\u8BF7\u6C42\u7684 AbortController\n const abortControllerRef = useRef<AbortController | null>(null)\n\n // \u53D1\u9001\u72B6\u6001\u6807\u8BB0\n const isSendingRef = useRef(false)\n\n /**\n * \u53D1\u9001\u6D88\u606F\u5E76\u63A5\u6536 SSE \u6D41\u5F0F\u54CD\u5E94\n */\n const sendMessageStream = useCallback(\n async (request: ChatStreamRequest, onEvent: (event: SSEEvent) => void) => {\n // \u5982\u679C\u6B63\u5728\u53D1\u9001\uFF0C\u5148\u4E2D\u65AD\u4E4B\u524D\u7684\u8BF7\u6C42\n if (abortControllerRef.current) {\n abortControllerRef.current.abort()\n }\n\n // \u521B\u5EFA\u65B0\u7684 AbortController\n abortControllerRef.current = new AbortController()\n isSendingRef.current = true\n\n try {\n await sendMessage(apiBaseUrl, request, onEvent, headers, recaptchaConfig)\n } catch (error) {\n // \u5FFD\u7565\u624B\u52A8\u4E2D\u65AD\u7684\u9519\u8BEF\n if (error instanceof Error && error.name === 'AbortError') {\n return\n }\n\n console.error('[useChatAPI] Stream error:', error)\n onError?.(error as Error)\n throw error\n } finally {\n isSendingRef.current = false\n abortControllerRef.current = null\n }\n },\n [apiBaseUrl, headers, recaptchaConfig, onError]\n )\n\n /**\n * \u521B\u5EFA\u65B0\u4F1A\u8BDD\u6216\u6062\u590D\u73B0\u6709\u4F1A\u8BDD\n */\n const createSession = useCallback(\n async (request: NewSessionRequest): Promise<NewSessionResponse> => {\n try {\n return await createNewSession(apiBaseUrl, request, headers, recaptchaConfig)\n } catch (error) {\n console.error('[useChatAPI] Create session error:', error)\n onError?.(error as Error)\n throw error\n }\n },\n [apiBaseUrl, headers, recaptchaConfig, onError]\n )\n\n /**\n * \u4E2D\u65AD\u5F53\u524D\u7684 SSE \u6D41\n */\n const abortStream = useCallback(() => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort()\n abortControllerRef.current = null\n isSendingRef.current = false\n }\n }, [])\n\n return {\n sendMessageStream,\n createSession,\n abortStream,\n isSending: isSendingRef.current,\n }\n}\n"],
5
- "mappings": "AAMA,OAAS,eAAAA,EAAa,UAAAC,MAAc,QAEpC,OAAS,eAAAC,EAAa,oBAAAC,MAA8C,cA8D7D,SAASC,EAAWC,EAA8C,CACvE,KAAM,CAAE,WAAAC,EAAY,QAAAC,EAAS,gBAAAC,EAAiB,QAAAC,CAAQ,EAAIJ,EAGpDK,EAAqBT,EAA+B,IAAI,EAGxDU,EAAeV,EAAO,EAAK,EAK3BW,EAAoBZ,EACxB,MAAOa,EAA4BC,IAAuC,CAEpEJ,EAAmB,SACrBA,EAAmB,QAAQ,MAAM,EAInCA,EAAmB,QAAU,IAAI,gBACjCC,EAAa,QAAU,GAEvB,GAAI,CACF,MAAMT,EAAYI,EAAYO,EAASC,EAASP,EAASC,CAAe,CAC1E,OAASO,EAAO,CAEd,GAAIA,aAAiB,OAASA,EAAM,OAAS,aAC3C,OAGF,cAAQ,MAAM,6BAA8BA,CAAK,EACjDN,IAAUM,CAAc,EAClBA,CACR,QAAE,CACAJ,EAAa,QAAU,GACvBD,EAAmB,QAAU,IAC/B,CACF,EACA,CAACJ,EAAYC,EAASC,EAAiBC,CAAO,CAChD,EAKMO,EAAgBhB,EACpB,MAAOa,GAA4D,CACjE,GAAI,CACF,OAAO,MAAMV,EAAiBG,EAAYO,EAASN,EAASC,CAAe,CAC7E,OAASO,EAAO,CACd,cAAQ,MAAM,qCAAsCA,CAAK,EACzDN,IAAUM,CAAc,EAClBA,CACR,CACF,EACA,CAACT,EAAYC,EAASC,EAAiBC,CAAO,CAChD,EAKMQ,EAAcjB,EAAY,IAAM,CAChCU,EAAmB,UACrBA,EAAmB,QAAQ,MAAM,EACjCA,EAAmB,QAAU,KAC7BC,EAAa,QAAU,GAE3B,EAAG,CAAC,CAAC,EAEL,MAAO,CACL,kBAAAC,EACA,cAAAI,EACA,YAAAC,EACA,UAAWN,EAAa,OAC1B,CACF",
6
- "names": ["useCallback", "useRef", "sendMessage", "createNewSession", "useChatAPI", "options", "apiBaseUrl", "headers", "recaptchaConfig", "onError", "abortControllerRef", "isSendingRef", "sendMessageStream", "request", "onEvent", "error", "createSession", "abortStream"]
4
+ "sourcesContent": ["/**\n * \u804A\u5929 API \u8C03\u7528 Hook\n * \u5C01\u88C5 SSE \u6D41\u5F0F\u6D88\u606F\u63A5\u6536\u3001\u5386\u53F2\u6D88\u606F\u52A0\u8F7D\u3001\u65B0\u4F1A\u8BDD\u521B\u5EFA\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684 API \u8C03\u7528\u7B56\u7565\n */\n\nimport { useCallback, useRef } from 'react'\nimport type { ChatStreamRequest, SSEEvent, NewSessionRequest, NewSessionResponse } from '../types'\nimport { sendMessage, createNewSession, type RecaptchaConfig } from '../api/chat'\n\nexport interface UseChatAPIOptions {\n /**\n * API \u57FA\u7840 URL\n */\n apiBaseUrl: string\n\n /**\n * \u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\n */\n headers?: Record<string, string>\n\n /**\n * reCAPTCHA \u914D\u7F6E\n */\n recaptchaConfig?: RecaptchaConfig\n\n /**\n * \u9519\u8BEF\u5904\u7406\u56DE\u8C03\n */\n onError?: (error: Error) => void\n}\n\nexport interface UseChatAPIReturn {\n /**\n * \u53D1\u9001\u6D88\u606F\u5E76\u63A5\u6536 SSE \u6D41\u5F0F\u54CD\u5E94\n * @param request \u8BF7\u6C42\u53C2\u6570\n * @param onEvent SSE \u4E8B\u4EF6\u56DE\u8C03\n * @returns Promise<void>\n */\n sendMessageStream: (request: ChatStreamRequest, onEvent: (event: SSEEvent) => void) => Promise<void>\n\n /**\n * \u521B\u5EFA\u65B0\u4F1A\u8BDD\u6216\u6062\u590D\u73B0\u6709\u4F1A\u8BDD\n * @param request \u8BF7\u6C42\u53C2\u6570\n * @returns \u65B0\u4F1A\u8BDD\u4FE1\u606F\uFF0C\u53EF\u80FD\u5305\u542B\u5386\u53F2\u6D88\u606F\n */\n createSession: (request: NewSessionRequest) => Promise<NewSessionResponse>\n\n /**\n * \u4E2D\u65AD\u5F53\u524D\u7684 SSE \u6D41\n */\n abortStream: () => void\n\n /**\n * \u662F\u5426\u6B63\u5728\u53D1\u9001\u6D88\u606F\n */\n isSending: boolean\n}\n\n/**\n * \u804A\u5929 API \u8C03\u7528 Hook\n *\n * \u529F\u80FD\uFF1A\n * 1. sendMessageStream: \u53D1\u9001\u6D88\u606F\u5E76\u63A5\u6536 SSE \u6D41\u5F0F\u54CD\u5E94\n * 2. createSession: \u521B\u5EFA\u65B0\u4F1A\u8BDD\u6216\u6062\u590D\u73B0\u6709\u4F1A\u8BDD\n * 3. abortStream: \u4E2D\u65AD\u5F53\u524D\u6D41\n *\n * @param options Hook \u914D\u7F6E\u9009\u9879\n * @returns API \u8C03\u7528\u5DE5\u5177\u5BF9\u8C61\n */\nexport function useChatAPI(options: UseChatAPIOptions): UseChatAPIReturn {\n const { apiBaseUrl, headers, recaptchaConfig, onError } = options\n\n // \u7528\u4E8E\u4E2D\u65AD\u5F53\u524D\u8BF7\u6C42\u7684 AbortController\n const abortControllerRef = useRef<AbortController | null>(null)\n\n // \u53D1\u9001\u72B6\u6001\u6807\u8BB0\n const isSendingRef = useRef(false)\n\n /**\n * \u53D1\u9001\u6D88\u606F\u5E76\u63A5\u6536 SSE \u6D41\u5F0F\u54CD\u5E94\n */\n const sendMessageStream = useCallback(\n async (request: ChatStreamRequest, onEvent: (event: SSEEvent) => void) => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort()\n }\n\n abortControllerRef.current = new AbortController()\n isSendingRef.current = true\n const { signal } = abortControllerRef.current\n\n try {\n await sendMessage(apiBaseUrl, request, onEvent, headers, recaptchaConfig, signal)\n } catch (error) {\n // \u5FFD\u7565\u624B\u52A8\u4E2D\u65AD\u7684\u9519\u8BEF\n if (error instanceof Error && error.name === 'AbortError') {\n return\n }\n\n console.error('[useChatAPI] Stream error:', error)\n onError?.(error as Error)\n throw error\n } finally {\n isSendingRef.current = false\n abortControllerRef.current = null\n }\n },\n [apiBaseUrl, headers, recaptchaConfig, onError]\n )\n\n /**\n * \u521B\u5EFA\u65B0\u4F1A\u8BDD\u6216\u6062\u590D\u73B0\u6709\u4F1A\u8BDD\n */\n const createSession = useCallback(\n async (request: NewSessionRequest): Promise<NewSessionResponse> => {\n try {\n return await createNewSession(apiBaseUrl, request, headers, recaptchaConfig)\n } catch (error) {\n console.error('[useChatAPI] Create session error:', error)\n onError?.(error as Error)\n throw error\n }\n },\n [apiBaseUrl, headers, recaptchaConfig, onError]\n )\n\n /**\n * \u4E2D\u65AD\u5F53\u524D\u7684 SSE \u6D41\n */\n const abortStream = useCallback(() => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort()\n abortControllerRef.current = null\n isSendingRef.current = false\n }\n }, [])\n\n return {\n sendMessageStream,\n createSession,\n abortStream,\n isSending: isSendingRef.current,\n }\n}\n"],
5
+ "mappings": "AAMA,OAAS,eAAAA,EAAa,UAAAC,MAAc,QAEpC,OAAS,eAAAC,EAAa,oBAAAC,MAA8C,cA8D7D,SAASC,EAAWC,EAA8C,CACvE,KAAM,CAAE,WAAAC,EAAY,QAAAC,EAAS,gBAAAC,EAAiB,QAAAC,CAAQ,EAAIJ,EAGpDK,EAAqBT,EAA+B,IAAI,EAGxDU,EAAeV,EAAO,EAAK,EAK3BW,EAAoBZ,EACxB,MAAOa,EAA4BC,IAAuC,CACpEJ,EAAmB,SACrBA,EAAmB,QAAQ,MAAM,EAGnCA,EAAmB,QAAU,IAAI,gBACjCC,EAAa,QAAU,GACvB,KAAM,CAAE,OAAAI,CAAO,EAAIL,EAAmB,QAEtC,GAAI,CACF,MAAMR,EAAYI,EAAYO,EAASC,EAASP,EAASC,EAAiBO,CAAM,CAClF,OAASC,EAAO,CAEd,GAAIA,aAAiB,OAASA,EAAM,OAAS,aAC3C,OAGF,cAAQ,MAAM,6BAA8BA,CAAK,EACjDP,IAAUO,CAAc,EAClBA,CACR,QAAE,CACAL,EAAa,QAAU,GACvBD,EAAmB,QAAU,IAC/B,CACF,EACA,CAACJ,EAAYC,EAASC,EAAiBC,CAAO,CAChD,EAKMQ,EAAgBjB,EACpB,MAAOa,GAA4D,CACjE,GAAI,CACF,OAAO,MAAMV,EAAiBG,EAAYO,EAASN,EAASC,CAAe,CAC7E,OAASQ,EAAO,CACd,cAAQ,MAAM,qCAAsCA,CAAK,EACzDP,IAAUO,CAAc,EAClBA,CACR,CACF,EACA,CAACV,EAAYC,EAASC,EAAiBC,CAAO,CAChD,EAKMS,EAAclB,EAAY,IAAM,CAChCU,EAAmB,UACrBA,EAAmB,QAAQ,MAAM,EACjCA,EAAmB,QAAU,KAC7BC,EAAa,QAAU,GAE3B,EAAG,CAAC,CAAC,EAEL,MAAO,CACL,kBAAAC,EACA,cAAAK,EACA,YAAAC,EACA,UAAWP,EAAa,OAC1B,CACF",
6
+ "names": ["useCallback", "useRef", "sendMessage", "createNewSession", "useChatAPI", "options", "apiBaseUrl", "headers", "recaptchaConfig", "onError", "abortControllerRef", "isSendingRef", "sendMessageStream", "request", "onEvent", "signal", "error", "createSession", "abortStream"]
7
7
  }
@@ -138,6 +138,11 @@ export interface UseChatStateReturn {
138
138
  * 处理 SSE 事件
139
139
  */
140
140
  handleSSEEvent: (event: SSEEvent) => void;
141
+ /**
142
+ * 开始新的 stream 会话,返回绑定了当前 streamId 的事件处理器。
143
+ * 调用此方法会使之前所有 stream 的事件处理器失效,防止并发 stream 污染共享 buffer。
144
+ */
145
+ startStream: () => (event: SSEEvent) => void;
141
146
  /**
142
147
  * 保存会话 ID
143
148
  */
@@ -1,2 +1,2 @@
1
- import{useState as A,useCallback as R,useRef as P,useEffect as at}from"react";import{getUserId as ct,saveUserId as it}from"../utils/userId";import{useSession as dt}from"./useSession";import{transformProducts as G}from"../utils/productTransformers";import{transformCartData as ut}from"../utils/cartTransformers";function lt(r,f,x,M,l){const o=[],d=/\{\{(?:product:)?([^}]+)\}\}/g;let c=0,i,h=!1;for(;(i=d.exec(r))!==null;){h=!0;const g=r.slice(c,i.index);g&&o.push({type:"text",text:g});const u=i[1].trim(),m=f.get(u),T=x.get(u);m?console.log("[useChatState] \u{1F3AF} \u5B9E\u65F6\u68C0\u6D4B\u5230\u4EA7\u54C1:",u,"\u2192",m.title):console.log("[useChatState] \u{1F4E6} \u5B9E\u65F6\u68C0\u6D4B\u5230\u4EA7\u54C1\u5360\u4F4D\u7B26\uFF0C\u4EA7\u54C1\u6570\u636E\u5F85\u5E94\u7528\u5C42\u67E5\u8BE2:",u),o.push({type:"product_card",data:{product:m,rawProduct:T,productHandle:u,onAddToCart:M,productCardRender:l}}),c=d.lastIndex}if(h){const g=r.slice(c);return{contents:o,remainingBuffer:g}}else{const g=r.match(/\{\{[^}]*$/);if(g){const u=r.slice(0,g.index);return u&&o.push({type:"text",text:u}),{contents:o,remainingBuffer:g[0]}}else return r&&o.push({type:"text",text:r}),{contents:o,remainingBuffer:""}}}function pt(r,f,x,M,l){const o=[],d=/\{\{(?:product:)?([^}]+)\}\}/g;let c=0,i;for(;(i=d.exec(r))!==null;){const g=r.slice(c,i.index).trim(),u=i[1].trim();g&&o.push({type:"text",text:g});const m=f.get(u),T=x.get(u);console.log(m?`[useChatState] \u2705 \u627E\u5230\u4EA7\u54C1\u5339\u914D: ${u} \u2192 ${m.title}`:`[useChatState] \u{1F4E6} \u4EA7\u54C1\u5360\u4F4D\u7B26\uFF0C\u4EA7\u54C1\u6570\u636E\u5F85\u5E94\u7528\u5C42\u67E5\u8BE2: ${u}`),o.push({type:"product_card",data:{product:m,rawProduct:T,productHandle:u,onAddToCart:M,productCardRender:l}}),c=d.lastIndex}const h=r.slice(c).trim();return h&&o.push({type:"text",text:h}),o}function gt(r,f,x,M,l){const o=[];for(const d of r)if(d.type==="text"){const i=pt(d.text,f,x,M,l);o.push(...i)}else{if(d.type==="product_list")continue;o.push(d)}return o}function ft(r,f,x){if(!r.content.some(c=>c.type==="text"&&/\{\{(?:product:)?[^}]+\}\}/.test(c.text)))return r;console.log("[useChatState] \u68C0\u6D4B\u5230\u5386\u53F2\u6D88\u606F\u9700\u8981\u91CD\u7EC4, \u6D88\u606FID:",r.id);const l=new Map,o=new Map;r.structured_content&&r.structured_content.forEach(c=>{c.type==="product_list"&&Array.isArray(c.data)&&c.data.forEach(i=>{i&&i.handle&&(o.set(i.handle,i),console.log("[useChatState] \u4ECE structured_content \u63D0\u53D6\u539F\u59CB\u4EA7\u54C1\u6570\u636E, handle:",i.handle))})}),r.content.forEach(c=>{c.type==="product_list"&&c.data.products.forEach(h=>{h&&h.handle&&l.set(h.handle,h)})});const d=gt(r.content,l,o,f,x);return{...r,content:d}}function Mt(r={}){const{welcomeMessage:f,site:x,open:M,onOpenChange:l,onOpen:o,onClose:d,onMessageSend:c,onError:i,onTextMessage:h,onProductList:g,onPromotionList:u,onAddToCart:m,onCart:T,productCardRender:B,productComparisonRender:J}=r,{sessionId:H,saveSession:L,clearSession:$}=dt(),[K,N]=A(void 0);at(()=>{ct().then(C=>N(C))},[]);const[Q,E]=A(()=>f?[{id:`welcome-${Date.now()}`,role:"assistant",content:[{type:"text",text:f}],timestamp:Date.now()}]:[]),[X,q]=A(!1),v=M!==void 0,z=v?M:X,[Y,Z]=A(""),[tt,O]=A(!1),t=P(null),V=P(!1),w=P(new Map),b=P(new Map),y=P(""),S=P([]),et=R(()=>{v||q(!0),l?.(!0),o?.()},[v,l,o]),nt=R(()=>{v||q(!1),l?.(!1),d?.()},[v,l,d]),st=R(()=>{const C=!z;v||q(C),l?.(C),C?o?.():d?.()},[v,z,l,o,d]),U=R(C=>{if(!C){console.warn("[useChatState] Attempted to add null/undefined message");return}E(D=>[...D,C])},[]),rt=R(C=>{const D=C.filter(e=>e!=null);D.length!==C.length&&console.warn("[useChatState] Filtered out null/undefined messages from batch set");const I=D.map(e=>ft(e,m,B));E(I)},[m,B]),F=R(()=>{E([])},[]),ot=R(C=>{const{event:D,data:I}=C;switch(D){case"message_start":{O(!0),V.current=!1,y.current="",S.current=[];const e=I;e.sessionId&&e.sessionId!==H&&L(e.sessionId),e.userId&&(it(e.userId),N(e.userId)),E(s=>{const n=s[s.length-1];if(n&&n.role==="assistant"&&n.content.length===1&&n.content[0].type==="thinking")return t.current=n,s;{const _=`msg-${Date.now()}`;return t.current={id:_,role:"assistant",content:[{type:"thinking",data:{status:"thinking"}}],timestamp:Date.now()},[...s,t.current]}});break}case"content_delta":{const e=I,s=e.delta||e.text||"";if(t.current&&s){V.current||(V.current=!0,h?.()),t.current.content.some(a=>a.type==="thinking")&&(t.current.content=t.current.content.filter(a=>a.type!=="thinking")),y.current+=s;const{contents:p,remainingBuffer:_}=lt(y.current,w.current,b.current,m,B);y.current=_,p.length>0&&(p.forEach(a=>{const k=t.current.content[t.current.content.length-1];a.type==="text"&&k&&k.type==="text"?k.text+=a.text:t.current.content.push(a)}),E(a=>{if(!t.current)return a;const k=[...a],W=k.findIndex(j=>j&&j.id===t.current.id);return W>=0?k[W]={...t.current}:k.push({...t.current}),k}))}break}case"content_block":{const e=I;if(t.current){const s=e.type||e.data?.type,n=e.data;if(!s||!n){console.warn("[useChatState] Invalid content_block:",e);break}let p;if(s==="product_list"&&Array.isArray(n)){g?.(),n.forEach(a=>{a&&a.handle&&(b.current.set(a.handle,a),console.log("[useChatState] \u7F13\u5B58\u539F\u59CB\u4EA7\u54C1\u6570\u636E, handle:",a.handle))}),G(n,x).forEach(a=>{a&&a.handle&&(w.current.set(a.handle,a),console.log("[useChatState] \u5EFA\u7ACB\u4EA7\u54C1\u6620\u5C04:",{handle:a.handle,title:a.title}))}),console.log("[useChatState] \u2705 \u4EA7\u54C1\u5217\u8868\u5DF2\u7F13\u5B58\uFF0C\u4E0D\u6DFB\u52A0\u5230\u6D88\u606F\u4E2D\uFF08\u907F\u514D\u95EA\u70C1\uFF09");break}else s==="product_comparison"&&n.products?p={type:"product_comparison",data:{products:G(n.products,x),dimensions:n.dimensions||{},productComparisonRender:J}}:s==="faq_list"&&n.found!==void 0?p={type:"faq_list",data:n}:s==="quick_replies"&&n.replies?p={type:"quick_replies",data:{replies:n.replies}}:s==="policy"&&n.title&&n.content?p={type:"policy",data:{title:n.title,content:n.content}}:s==="promotion_list"&&n.found!==void 0?(u?.(n.results||[]),p={type:"promotion_list",data:n}):s==="cart"&&n.id!==void 0?p={type:"cart",data:{...ut(n),onCart:T}}:p={type:s,data:n};console.log("[useChatState] \u2705 \u5361\u7247\u5DF2\u7F13\u5B58\uFF0C\u7B49\u5F85\u6587\u672C\u5B8C\u6210\u540E\u663E\u793A:",s),S.current.push(p)}break}case"tool_start":case"tool_end":break;case"message_end":{if(O(!1),t.current&&y.current){console.log("[useChatState] \u5904\u7406\u5269\u4F59\u7F13\u51B2\u533A:",y.current);const e=t.current.content[t.current.content.length-1];e&&e.type==="text"?e.text+=y.current:t.current.content.push({type:"text",text:y.current})}t.current&&S.current.length>0&&(console.log("[useChatState] \u{1F4CB} \u6587\u672C\u5DF2\u5B8C\u6210\uFF0C\u73B0\u5728\u6DFB\u52A0",S.current.length,"\u4E2A\u7F13\u5B58\u7684\u5361\u7247"),t.current.content.push(...S.current)),t.current&&t.current.content.some(s=>s.type==="thinking")&&(console.log("[useChatState] \u26A0\uFE0F message_end \u65F6\u4ECD\u5B58\u5728 thinking block\uFF0C\u7ACB\u5373\u79FB\u9664\uFF08\u89C6\u4E3A\u8D85\u65F6\uFF09"),t.current.content=t.current.content.filter(s=>s.type!=="thinking"),t.current.content.length===0&&t.current.content.push({type:"text",text:"Response timed out, please try again."})),t.current&&E(e=>{if(!t.current)return e;const s=[...e],n=s.findIndex(p=>p&&p.id===t.current.id);return n>=0&&(s[n]={...t.current}),s}),y.current="",S.current=[],w.current.clear(),b.current.clear(),t.current=null;break}case"status":{I.type==="session_expired"&&(F(),$(),f&&U({id:`welcome-${Date.now()}`,role:"assistant",content:[{type:"text",text:f}],timestamp:Date.now()}));break}case"error":{const e=I;O(!1),y.current="",S.current=[],w.current.clear(),b.current.clear(),t.current=null,U({id:`error-${Date.now()}`,role:"system",content:[{type:"error",data:{message:e.message,code:e.code}}],timestamp:Date.now()}),i?.(new Error(e.message));break}case"done":{O(!1),y.current="",S.current=[],w.current.clear(),b.current.clear(),t.current=null;break}default:break}},[f,x,U,F,$,L,H,i,h,g,u,m,T]);return{messages:Q,isOpen:z,userId:K,sessionId:H,inputValue:Y,isStreaming:tt,openChat:et,closeChat:nt,toggleChat:st,setInputValue:Z,setUserId:N,addMessage:U,setMessages:rt,clearMessages:F,handleSSEEvent:ot,saveSession:L,clearSession:$}}export{Mt as useChatState};
1
+ import{useState as A,useCallback as E,useRef as T,useEffect as dt}from"react";import{getUserId as ut,saveUserId as lt}from"../utils/userId";import{useSession as gt}from"./useSession";import{transformProducts as Q}from"../utils/productTransformers";import{transformCartData as pt}from"../utils/cartTransformers";function ft(r,h,M,y,g){const a=[],d=/\{\{(?:product:)?([^}]+)\}\}/g;let c=0,i,C=!1;for(;(i=d.exec(r))!==null;){C=!0;const f=r.slice(c,i.index);f&&a.push({type:"text",text:f});const u=i[1].trim(),m=h.get(u),D=M.get(u);m?console.log("[useChatState] \u{1F3AF} \u5B9E\u65F6\u68C0\u6D4B\u5230\u4EA7\u54C1:",u,"\u2192",m.title):console.log("[useChatState] \u{1F4E6} \u5B9E\u65F6\u68C0\u6D4B\u5230\u4EA7\u54C1\u5360\u4F4D\u7B26\uFF0C\u4EA7\u54C1\u6570\u636E\u5F85\u5E94\u7528\u5C42\u67E5\u8BE2:",u),a.push({type:"product_card",data:{product:m,rawProduct:D,productHandle:u,onAddToCart:y,productCardRender:g}}),c=d.lastIndex}if(C){const f=r.slice(c);return{contents:a,remainingBuffer:f}}else{const f=r.match(/\{\{[^}]*$/);if(f){const u=r.slice(0,f.index);return u&&a.push({type:"text",text:u}),{contents:a,remainingBuffer:f[0]}}else return r&&a.push({type:"text",text:r}),{contents:a,remainingBuffer:""}}}function ht(r,h,M,y,g){const a=[],d=/\{\{(?:product:)?([^}]+)\}\}/g;let c=0,i;for(;(i=d.exec(r))!==null;){const f=r.slice(c,i.index).trim(),u=i[1].trim();f&&a.push({type:"text",text:f});const m=h.get(u),D=M.get(u);console.log(m?`[useChatState] \u2705 \u627E\u5230\u4EA7\u54C1\u5339\u914D: ${u} \u2192 ${m.title}`:`[useChatState] \u{1F4E6} \u4EA7\u54C1\u5360\u4F4D\u7B26\uFF0C\u4EA7\u54C1\u6570\u636E\u5F85\u5E94\u7528\u5C42\u67E5\u8BE2: ${u}`),a.push({type:"product_card",data:{product:m,rawProduct:D,productHandle:u,onAddToCart:y,productCardRender:g}}),c=d.lastIndex}const C=r.slice(c).trim();return C&&a.push({type:"text",text:C}),a}function Ct(r,h,M,y,g){const a=[];for(const d of r)if(d.type==="text"){const i=ht(d.text,h,M,y,g);a.push(...i)}else{if(d.type==="product_list")continue;a.push(d)}return a}function mt(r,h,M){if(!r.content.some(c=>c.type==="text"&&/\{\{(?:product:)?[^}]+\}\}/.test(c.text)))return r;console.log("[useChatState] \u68C0\u6D4B\u5230\u5386\u53F2\u6D88\u606F\u9700\u8981\u91CD\u7EC4, \u6D88\u606FID:",r.id);const g=new Map,a=new Map;r.structured_content&&r.structured_content.forEach(c=>{c.type==="product_list"&&Array.isArray(c.data)&&c.data.forEach(i=>{i&&i.handle&&(a.set(i.handle,i),console.log("[useChatState] \u4ECE structured_content \u63D0\u53D6\u539F\u59CB\u4EA7\u54C1\u6570\u636E, handle:",i.handle))})}),r.content.forEach(c=>{c.type==="product_list"&&c.data.products.forEach(C=>{C&&C.handle&&g.set(C.handle,C)})});const d=Ct(r.content,g,a,h,M);return{...r,content:d}}function kt(r={}){const{welcomeMessage:h,site:M,open:y,onOpenChange:g,onOpen:a,onClose:d,onMessageSend:c,onError:i,onTextMessage:C,onProductList:f,onPromotionList:u,onAddToCart:m,onCart:D,productCardRender:B,productComparisonRender:X}=r,{sessionId:H,saveSession:L,clearSession:N}=gt(),[Y,q]=A(void 0);dt(()=>{ut().then(l=>q(l))},[]);const[Z,_]=A(()=>h?[{id:`welcome-${Date.now()}`,role:"assistant",content:[{type:"text",text:h}],timestamp:Date.now()}]:[]),[tt,z]=A(!1),R=y!==void 0,V=R?y:tt,[et,nt]=A(""),[st,O]=A(!1),t=T(null),F=T(!1),P=T(new Map),w=T(new Map),x=T(""),S=T([]),G=T(null),rt=E(()=>{R||z(!0),g?.(!0),a?.()},[R,g,a]),at=E(()=>{R||z(!1),g?.(!1),d?.()},[R,g,d]),ot=E(()=>{const l=!V;R||z(l),g?.(l),l?a?.():d?.()},[R,V,g,a,d]),$=E(l=>{if(!l){console.warn("[useChatState] Attempted to add null/undefined message");return}_(k=>[...k,l])},[]),ct=E(l=>{const k=l.filter(v=>v!=null);k.length!==l.length&&console.warn("[useChatState] Filtered out null/undefined messages from batch set");const U=k.map(v=>mt(v,m,B));_(U)},[m,B]),W=E(()=>{_([])},[]),j=E((l,k)=>{const{event:U,data:v}=l;if(!(U!=="message_start"&&k!==G.current))switch(U){case"message_start":{O(!0),F.current=!1,x.current="",S.current=[];const n=v;n.sessionId&&n.sessionId!==H&&L(n.sessionId),n.userId&&(lt(n.userId),q(n.userId)),_(s=>{const e=s[s.length-1];if(e&&e.role==="assistant"&&e.content.length===1&&e.content[0].type==="thinking")return t.current=e,s;{const b=`msg-${Date.now()}`;return t.current={id:b,role:"assistant",content:[{type:"thinking",data:{status:"thinking"}}],timestamp:Date.now()},[...s,t.current]}});break}case"content_delta":{const n=v,s=n.delta||n.text||"";if(t.current&&s){F.current||(F.current=!0,C?.()),t.current.content.some(o=>o.type==="thinking")&&(t.current.content=t.current.content.filter(o=>o.type!=="thinking")),x.current+=s;const{contents:p,remainingBuffer:b}=ft(x.current,P.current,w.current,m,B);x.current=b,p.length>0&&(p.forEach(o=>{const I=t.current.content[t.current.content.length-1];o.type==="text"&&I&&I.type==="text"?I.text+=o.text:t.current.content.push(o)}),_(o=>{if(!t.current)return o;const I=[...o],J=I.findIndex(K=>K&&K.id===t.current.id);return J>=0?I[J]={...t.current}:I.push({...t.current}),I}))}break}case"content_block":{const n=v;if(t.current){const s=n.type||n.data?.type,e=n.data;if(!s||!e){console.warn("[useChatState] Invalid content_block:",n);break}let p;if(s==="product_list"&&Array.isArray(e)){f?.(),e.forEach(o=>{o&&o.handle&&(w.current.set(o.handle,o),console.log("[useChatState] \u7F13\u5B58\u539F\u59CB\u4EA7\u54C1\u6570\u636E, handle:",o.handle))}),Q(e,M).forEach(o=>{o&&o.handle&&(P.current.set(o.handle,o),console.log("[useChatState] \u5EFA\u7ACB\u4EA7\u54C1\u6620\u5C04:",{handle:o.handle,title:o.title}))}),console.log("[useChatState] \u2705 \u4EA7\u54C1\u5217\u8868\u5DF2\u7F13\u5B58\uFF0C\u4E0D\u6DFB\u52A0\u5230\u6D88\u606F\u4E2D\uFF08\u907F\u514D\u95EA\u70C1\uFF09");break}else s==="product_comparison"&&e.products?p={type:"product_comparison",data:{products:Q(e.products,M),dimensions:e.dimensions||{},productComparisonRender:X}}:s==="faq_list"&&e.found!==void 0?p={type:"faq_list",data:e}:s==="quick_replies"&&e.replies?p={type:"quick_replies",data:{replies:e.replies}}:s==="policy"&&e.title&&e.content?p={type:"policy",data:{title:e.title,content:e.content}}:s==="promotion_list"&&e.found!==void 0?(u?.(e.results||[]),p={type:"promotion_list",data:e}):s==="cart"&&e.id!==void 0?p={type:"cart",data:{...pt(e),onCart:D}}:p={type:s,data:e};console.log("[useChatState] \u2705 \u5361\u7247\u5DF2\u7F13\u5B58\uFF0C\u7B49\u5F85\u6587\u672C\u5B8C\u6210\u540E\u663E\u793A:",s),S.current.push(p)}break}case"tool_start":case"tool_end":break;case"message_end":{if(O(!1),t.current&&x.current){console.log("[useChatState] \u5904\u7406\u5269\u4F59\u7F13\u51B2\u533A:",x.current);const n=t.current.content[t.current.content.length-1];n&&n.type==="text"?n.text+=x.current:t.current.content.push({type:"text",text:x.current})}t.current&&S.current.length>0&&(console.log("[useChatState] \u{1F4CB} \u6587\u672C\u5DF2\u5B8C\u6210\uFF0C\u73B0\u5728\u6DFB\u52A0",S.current.length,"\u4E2A\u7F13\u5B58\u7684\u5361\u7247"),t.current.content.push(...S.current)),t.current&&t.current.content.some(s=>s.type==="thinking")&&(console.log("[useChatState] \u26A0\uFE0F message_end \u65F6\u4ECD\u5B58\u5728 thinking block\uFF0C\u7ACB\u5373\u79FB\u9664\uFF08\u89C6\u4E3A\u8D85\u65F6\uFF09"),t.current.content=t.current.content.filter(s=>s.type!=="thinking"),t.current.content.length===0&&t.current.content.push({type:"text",text:"Response timed out, please try again."})),t.current&&_(n=>{if(!t.current)return n;const s=[...n],e=s.findIndex(p=>p&&p.id===t.current.id);return e>=0&&(s[e]={...t.current}),s}),x.current="",S.current=[],P.current.clear(),w.current.clear(),t.current=null;break}case"status":{v.type==="session_expired"&&(W(),N(),h&&$({id:`welcome-${Date.now()}`,role:"assistant",content:[{type:"text",text:h}],timestamp:Date.now()}));break}case"error":{const n=v;O(!1),x.current="",S.current=[],P.current.clear(),w.current.clear(),t.current=null,$({id:`error-${Date.now()}`,role:"system",content:[{type:"error",data:{message:n.message,code:n.code}}],timestamp:Date.now()}),i?.(new Error(n.message));break}case"done":{O(!1),x.current="",S.current=[],P.current.clear(),w.current.clear(),t.current=null;break}default:break}},[h,M,$,W,N,L,H,i,C,f,u,m,D]),it=E(()=>{const l=`stream-${Date.now()}-${Math.random().toString(36).slice(2)}`;return G.current=l,x.current="",S.current=[],P.current.clear(),w.current.clear(),t.current=null,k=>j(k,l)},[j]);return{messages:Z,isOpen:V,userId:Y,sessionId:H,inputValue:et,isStreaming:st,openChat:rt,closeChat:at,toggleChat:ot,setInputValue:nt,setUserId:q,addMessage:$,setMessages:ct,clearMessages:W,handleSSEEvent:j,startStream:it,saveSession:L,clearSession:N}}export{kt as useChatState};
2
2
  //# sourceMappingURL=useChatState.js.map