@anker-in/campaign-ui 0.4.0-beta.7 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) 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/components/MessageContent/ProductComparison.d.ts +1 -0
  4. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductComparison.js +1 -1
  5. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductComparison.js.map +3 -3
  6. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.d.ts +11 -2
  7. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js +1 -1
  8. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
  9. package/dist/cjs/components/LiveChatWidget/index.d.ts +1 -1
  10. package/dist/cjs/components/LiveChatWidget/index.js +1 -1
  11. package/dist/cjs/components/LiveChatWidget/index.js.map +2 -2
  12. package/dist/cjs/components/LiveChatWidget/types.d.ts +14 -0
  13. package/dist/cjs/components/LiveChatWidget/types.js.map +1 -1
  14. package/dist/cjs/components/LiveChatWidget/utils/userId.d.ts +7 -2
  15. package/dist/cjs/components/LiveChatWidget/utils/userId.js +1 -1
  16. package/dist/cjs/components/LiveChatWidget/utils/userId.js.map +3 -3
  17. package/dist/cjs/stories/LiveChatWidget.stories.js +1 -1
  18. package/dist/cjs/stories/LiveChatWidget.stories.js.map +2 -2
  19. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js +1 -1
  20. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
  21. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.d.ts +1 -0
  22. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.js +1 -1
  23. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.js.map +3 -3
  24. package/dist/esm/components/LiveChatWidget/hooks/useChatState.d.ts +11 -2
  25. package/dist/esm/components/LiveChatWidget/hooks/useChatState.js +1 -1
  26. package/dist/esm/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
  27. package/dist/esm/components/LiveChatWidget/index.d.ts +1 -1
  28. package/dist/esm/components/LiveChatWidget/index.js +1 -1
  29. package/dist/esm/components/LiveChatWidget/index.js.map +3 -3
  30. package/dist/esm/components/LiveChatWidget/types.d.ts +14 -0
  31. package/dist/esm/components/LiveChatWidget/utils/userId.d.ts +7 -2
  32. package/dist/esm/components/LiveChatWidget/utils/userId.js +1 -1
  33. package/dist/esm/components/LiveChatWidget/utils/userId.js.map +3 -3
  34. package/dist/esm/stories/LiveChatWidget.stories.js +1 -1
  35. package/dist/esm/stories/LiveChatWidget.stories.js.map +2 -2
  36. package/package.json +1 -1
  37. package/src/components/LiveChatWidget/LiveChatWidget.tsx +44 -8
  38. package/src/components/LiveChatWidget/components/MessageContent/ProductComparison.tsx +6 -13
  39. package/src/components/LiveChatWidget/hooks/useChatState.ts +26 -7
  40. package/src/components/LiveChatWidget/index.tsx +1 -1
  41. package/src/components/LiveChatWidget/types.ts +15 -0
  42. package/src/components/LiveChatWidget/utils/userId.ts +13 -62
  43. package/src/stories/LiveChatWidget.stories.tsx +1 -1
@@ -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}\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=\"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 {/* \u989C\u8272\u9009\u9879\uFF08\u5982\u679C\u6709variants\uFF09 */}\n {product.variants && product.variants.length > 1 && (\n <div className=\"flex gap-2\">\n {product.variants.slice(0, 3).map((variant, idx) => (\n <div\n key={variant.id || idx}\n className=\"size-6 rounded-full border-2 border-[#DADCE0]\"\n style={{ backgroundColor: variant.color || '#000' }}\n title={variant.title}\n />\n ))}\n </div>\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 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": ["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", "variant", "idx", "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, 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=\"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"]
7
7
  }
@@ -71,6 +71,11 @@ export interface UseChatStateOptions {
71
71
  * @param productHandle 文本占位符中的产品 ID,可用于应用层查询产品数据
72
72
  */
73
73
  productCardRender?: (product: any, productHandle: string) => React.ReactNode;
74
+ /**
75
+ * 自定义产品对比卡片渲染函数
76
+ * @param data 产品对比数据(包含 products 和 dimensions)
77
+ */
78
+ productComparisonRender?: (data: any) => React.ReactNode;
74
79
  }
75
80
  export interface UseChatStateReturn {
76
81
  /**
@@ -82,9 +87,9 @@ export interface UseChatStateReturn {
82
87
  */
83
88
  isOpen: boolean;
84
89
  /**
85
- * 用户 ID
90
+ * 用户 ID(undefined 表示尚未初始化,空字符串表示由后端生成)
86
91
  */
87
- userId: string;
92
+ userId: string | undefined;
88
93
  /**
89
94
  * 会话 ID
90
95
  */
@@ -113,6 +118,10 @@ export interface UseChatStateReturn {
113
118
  * 设置输入框内容
114
119
  */
115
120
  setInputValue: (value: string) => void;
121
+ /**
122
+ * 设置用户 ID(用于保存后端返回的 userId)
123
+ */
124
+ setUserId: (id: string) => void;
116
125
  /**
117
126
  * 添加消息到列表
118
127
  */
@@ -1,2 +1,2 @@
1
- import{useState as A,useCallback as E,useRef as P,useEffect as at}from"react";import{getUserId as rt}from"../utils/userId";import{useSession as ct}from"./useSession";import{transformProducts as j}from"../utils/productTransformers";import{transformCartData as it}from"../utils/cartTransformers";function dt(o,f,x,y,l){const a=[],d=/\{\{(?:product:)?([^}]+)\}\}/g;let c=0,i,h=!1;for(;(i=d.exec(o))!==null;){h=!0;const p=o.slice(c,i.index);p&&a.push({type:"text",text:p});const u=i[1].trim(),m=f.get(u),I=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),a.push({type:"product_card",data:{product:m,rawProduct:I,productHandle:u,onAddToCart:y,productCardRender:l}}),c=d.lastIndex}if(h){const p=o.slice(c);return{contents:a,remainingBuffer:p}}else{const p=o.match(/\{\{[^}]*$/);if(p){const u=o.slice(0,p.index);return u&&a.push({type:"text",text:u}),{contents:a,remainingBuffer:p[0]}}else return o&&a.push({type:"text",text:o}),{contents:a,remainingBuffer:""}}}function ut(o,f,x,y,l){const a=[],d=/\{\{(?:product:)?([^}]+)\}\}/g;let c=0,i;for(;(i=d.exec(o))!==null;){const p=o.slice(c,i.index).trim(),u=i[1].trim();p&&a.push({type:"text",text:p});const m=f.get(u),I=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}`),a.push({type:"product_card",data:{product:m,rawProduct:I,productHandle:u,onAddToCart:y,productCardRender:l}}),c=d.lastIndex}const h=o.slice(c).trim();return h&&a.push({type:"text",text:h}),a}function lt(o,f,x,y,l){const a=[];for(const d of o)if(d.type==="text"){const i=ut(d.text,f,x,y,l);a.push(...i)}else{if(d.type==="product_list")continue;a.push(d)}return a}function gt(o,f,x){if(!o.content.some(c=>c.type==="text"&&/\{\{(?:product:)?[^}]+\}\}/.test(c.text)))return o;console.log("[useChatState] \u68C0\u6D4B\u5230\u5386\u53F2\u6D88\u606F\u9700\u8981\u91CD\u7EC4, \u6D88\u606FID:",o.id);const l=new Map,a=new Map;o.structured_content&&o.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))})}),o.content.forEach(c=>{c.type==="product_list"&&c.data.products.forEach(h=>{h&&h.handle&&l.set(h.handle,h)})});const d=lt(o.content,l,a,f,x);return{...o,content:d}}function xt(o={}){const{welcomeMessage:f,site:x,open:y,onOpenChange:l,onOpen:a,onClose:d,onMessageSend:c,onError:i,onTextMessage:h,onProductList:p,onPromotionList:u,onAddToCart:m,onCart:I,productCardRender:H}=o,{sessionId:L,saveSession:$,clearSession:U}=ct(),[G,J]=A("");at(()=>{rt().then(C=>J(C))},[]);const[K,D]=A(()=>f?[{id:`welcome-${Date.now()}`,role:"assistant",content:[{type:"text",text:f}],timestamp:Date.now()}]:[]),[Q,N]=A(!1),v=y!==void 0,q=v?y:Q,[X,Y]=A(""),[Z,O]=A(!1),t=P(null),z=P(!1),w=P(new Map),b=P(new Map),M=P(""),S=P([]),tt=E(()=>{v||N(!0),l?.(!0),a?.()},[v,l,a]),et=E(()=>{v||N(!1),l?.(!1),d?.()},[v,l,d]),nt=E(()=>{const C=!q;v||N(C),l?.(C),C?a?.():d?.()},[v,q,l,a,d]),B=E(C=>{if(!C){console.warn("[useChatState] Attempted to add null/undefined message");return}D(R=>[...R,C])},[]),st=E(C=>{const R=C.filter(n=>n!=null);R.length!==C.length&&console.warn("[useChatState] Filtered out null/undefined messages from batch set");const T=R.map(n=>gt(n,m,H));D(T)},[m,H]),V=E(()=>{D([])},[]),ot=E(C=>{const{event:R,data:T}=C;switch(R){case"message_start":{O(!0),z.current=!1,M.current="",S.current=[];const n=T;n.sessionId&&n.sessionId!==L&&$(n.sessionId),D(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 _=`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 n=T,s=n.delta||n.text||"";if(t.current&&s){z.current||(z.current=!0,h?.()),t.current.content.some(r=>r.type==="thinking")&&(t.current.content=t.current.content.filter(r=>r.type!=="thinking")),M.current+=s;const{contents:g,remainingBuffer:_}=dt(M.current,w.current,b.current,m,H);M.current=_,g.length>0&&(g.forEach(r=>{const k=t.current.content[t.current.content.length-1];r.type==="text"&&k&&k.type==="text"?k.text+=r.text:t.current.content.push(r)}),D(r=>{if(!t.current)return r;const k=[...r],F=k.findIndex(W=>W&&W.id===t.current.id);return F>=0?k[F]={...t.current}:k.push({...t.current}),k}))}break}case"content_block":{const n=T;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 g;if(s==="product_list"&&Array.isArray(e)){p?.(),e.forEach(r=>{r&&r.handle&&(b.current.set(r.handle,r),console.log("[useChatState] \u7F13\u5B58\u539F\u59CB\u4EA7\u54C1\u6570\u636E, handle:",r.handle))}),j(e,x).forEach(r=>{r&&r.handle&&(w.current.set(r.handle,r),console.log("[useChatState] \u5EFA\u7ACB\u4EA7\u54C1\u6620\u5C04:",{handle:r.handle,title:r.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?g={type:"product_comparison",data:{products:j(e.products,x),dimensions:e.dimensions||{}}}:s==="faq_list"&&e.found!==void 0?g={type:"faq_list",data:e}:s==="quick_replies"&&e.replies?g={type:"quick_replies",data:{replies:e.replies}}:s==="policy"&&e.title&&e.content?g={type:"policy",data:{title:e.title,content:e.content}}:s==="promotion_list"&&e.found!==void 0?(u?.(e.results||[]),g={type:"promotion_list",data:e}):s==="cart"&&e.id!==void 0?g={type:"cart",data:{...it(e),onCart:I}}:g={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(g)}break}case"tool_start":case"tool_end":break;case"message_end":{if(O(!1),t.current&&M.current){console.log("[useChatState] \u5904\u7406\u5269\u4F59\u7F13\u51B2\u533A:",M.current);const n=t.current.content[t.current.content.length-1];n&&n.type==="text"?n.text+=M.current:t.current.content.push({type:"text",text:M.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&&D(n=>{if(!t.current)return n;const s=[...n],e=s.findIndex(g=>g&&g.id===t.current.id);return e>=0&&(s[e]={...t.current}),s}),M.current="",S.current=[],w.current.clear(),b.current.clear(),t.current=null;break}case"status":{T.type==="session_expired"&&(V(),U(),f&&B({id:`welcome-${Date.now()}`,role:"assistant",content:[{type:"text",text:f}],timestamp:Date.now()}));break}case"error":{const n=T;O(!1),M.current="",S.current=[],w.current.clear(),b.current.clear(),t.current=null,B({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),M.current="",S.current=[],w.current.clear(),b.current.clear(),t.current=null;break}default:break}},[f,x,B,V,U,$,L,i,h,p,u,m,I]);return{messages:K,isOpen:q,userId:G,sessionId:L,inputValue:X,isStreaming:Z,openChat:tt,closeChat:et,toggleChat:nt,setInputValue:Y,addMessage:B,setMessages:st,clearMessages:V,handleSSEEvent:ot,saveSession:$,clearSession:U}}export{xt as useChatState};
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};
2
2
  //# sourceMappingURL=useChatState.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/components/LiveChatWidget/hooks/useChatState.ts"],
4
- "sourcesContent": ["/**\n * \u804A\u5929\u72B6\u6001\u7BA1\u7406 Hook\n * \u7BA1\u7406\u6D88\u606F\u5217\u8868\u3001\u7A97\u53E3\u72B6\u6001\u3001\u8F93\u5165\u6846\u72B6\u6001\u3001\u6D41\u5F0F\u6D88\u606F\u7D2F\u79EF\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u72B6\u6001\u7BA1\u7406\u7B56\u7565\n */\n\nimport { useState, useCallback, useRef, useEffect } from 'react'\nimport type { Message, MessageContent, SSEEvent, StatusData, ErrorData, MessageStartData, BackendCartData, Product, TextContent, ProductCardContent, ProductListContent } from '../types'\nimport { getUserId } from '../utils/userId'\nimport { useSession } from './useSession'\nimport { transformProducts } from '../utils/productTransformers'\nimport { transformCartData } from '../utils/cartTransformers'\n\n// ============================================================================\n// \u8F85\u52A9\u51FD\u6570\uFF1A\u6587\u672C\u89E3\u6790\u548C\u6D88\u606F\u91CD\u7EC4\n// ============================================================================\n\n/**\n * \u5B9E\u65F6\u89E3\u6790\u6D41\u5F0F\u6587\u672C\u4E2D\u7684\u4EA7\u54C1\u5360\u4F4D\u7B26\n * \u5904\u7406\u7F13\u51B2\u533A\u4E2D\u7684\u6587\u672C\uFF0C\u68C0\u6D4B\u5B8C\u6574\u7684 {{product:xxx}} \u5360\u4F4D\u7B26\n *\n * @param buffer \u5F53\u524D\u7F13\u51B2\u533A\u5185\u5BB9\uFF08\u5305\u542B\u65B0\u63A5\u6536\u7684\u6587\u672C\uFF09\n * @param productMap handle \u2192 Product \u7684\u6620\u5C04\u8868\n * @param rawProductMap handle \u2192 raw backend product \u7684\u6620\u5C04\u8868\n * @param onAddToCart \u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n * @param productCardRender \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\u51FD\u6570\n * @returns { contents: \u9700\u8981\u6DFB\u52A0\u7684\u5185\u5BB9\u6570\u7EC4, remainingBuffer: \u5269\u4F59\u7F13\u51B2\u533A\u5185\u5BB9 }\n */\nfunction parseStreamingText(\n buffer: string,\n productMap: Map<string, Product>,\n rawProductMap: Map<string, any>,\n onAddToCart?: (product: Product) => void,\n productCardRender?: (product: any, productHandle: string) => React.ReactNode\n): { contents: MessageContent[]; remainingBuffer: string } {\n const contents: MessageContent[] = []\n const regex = /\\{\\{(?:product:)?([^}]+)\\}\\}/g\n\n let lastIndex = 0\n let match: RegExpExecArray | null\n let foundMatch = false\n\n // \u67E5\u627E\u6240\u6709\u5B8C\u6574\u7684\u5360\u4F4D\u7B26\n while ((match = regex.exec(buffer)) !== null) {\n foundMatch = true\n\n // \u63D0\u53D6\u5360\u4F4D\u7B26\u524D\u7684\u6587\u672C\n const beforeText = buffer.slice(lastIndex, match.index)\n if (beforeText) {\n contents.push({ type: 'text', text: beforeText } as TextContent)\n }\n\n // \u63D0\u53D6\u4EA7\u54C1 ID \u5E76\u521B\u5EFA\u4EA7\u54C1\u5361\u7247\n const productId = match[1].trim()\n const product = productMap.get(productId)\n const rawProduct = rawProductMap.get(productId)\n\n // \u65E0\u8BBA\u662F\u5426\u627E\u5230\u4EA7\u54C1\u6570\u636E\uFF0C\u90FD\u6E32\u67D3 product_card\uFF0C\u5E94\u7528\u5C42\u53EF\u901A\u8FC7 productHandle \u67E5\u8BE2\u4EA7\u54C1\n if (product) {\n console.log('[useChatState] \uD83C\uDFAF \u5B9E\u65F6\u68C0\u6D4B\u5230\u4EA7\u54C1:', productId, '\u2192', product.title)\n } else {\n console.log('[useChatState] \uD83D\uDCE6 \u5B9E\u65F6\u68C0\u6D4B\u5230\u4EA7\u54C1\u5360\u4F4D\u7B26\uFF0C\u4EA7\u54C1\u6570\u636E\u5F85\u5E94\u7528\u5C42\u67E5\u8BE2:', productId)\n }\n\n contents.push({\n type: 'product_card',\n data: {\n product: product,\n rawProduct: rawProduct,\n productHandle: productId,\n onAddToCart: onAddToCart,\n productCardRender: productCardRender\n }\n } as ProductCardContent)\n\n lastIndex = regex.lastIndex\n }\n\n // \u5982\u679C\u627E\u5230\u4E86\u81F3\u5C11\u4E00\u4E2A\u5B8C\u6574\u5360\u4F4D\u7B26\n if (foundMatch) {\n // \u8FD4\u56DE\u5269\u4F59\u7684\u6587\u672C\u4F5C\u4E3A\u7F13\u51B2\u533A\uFF08\u53EF\u80FD\u5305\u542B\u4E0D\u5B8C\u6574\u7684\u5360\u4F4D\u7B26\uFF09\n const remainingBuffer = buffer.slice(lastIndex)\n return { contents, remainingBuffer }\n } else {\n // \u6CA1\u6709\u627E\u5230\u5B8C\u6574\u5360\u4F4D\u7B26\uFF0C\u68C0\u67E5\u662F\u5426\u6709\u4E0D\u5B8C\u6574\u7684\u5360\u4F4D\u7B26\u5F00\u5934\n // \u4F8B\u5982\uFF1A\u7F13\u51B2\u533A\u662F \"some text {{prod\"\uFF0C\u6211\u4EEC\u9700\u8981\u4FDD\u7559 \"{{prod\" \u7B49\u5F85\u66F4\u591A\u6587\u672C\n const incompleteMatch = buffer.match(/\\{\\{[^}]*$/)\n\n if (incompleteMatch) {\n // \u6709\u4E0D\u5B8C\u6574\u7684\u5360\u4F4D\u7B26\u5F00\u5934\n const completeText = buffer.slice(0, incompleteMatch.index)\n if (completeText) {\n contents.push({ type: 'text', text: completeText } as TextContent)\n }\n return { contents, remainingBuffer: incompleteMatch[0] }\n } else {\n // \u6CA1\u6709\u4E0D\u5B8C\u6574\u7684\u5360\u4F4D\u7B26\uFF0C\u6574\u4E2A\u7F13\u51B2\u533A\u90FD\u662F\u666E\u901A\u6587\u672C\n if (buffer) {\n contents.push({ type: 'text', text: buffer } as TextContent)\n }\n return { contents, remainingBuffer: '' }\n }\n }\n}\n\n/**\n * \u89E3\u6790\u6587\u672C\u4E2D\u7684 {{handle}}\uFF0C\u8FD4\u56DE [text, product_card, text, ...] \u6570\u7EC4\uFF08\u7528\u4E8E\u5386\u53F2\u6D88\u606F\u91CD\u7EC4\uFF09\n *\n * @param text \u5305\u542B {{handle}} \u6807\u8BB0\u7684\u6587\u672C\n * @param productMap handle \u2192 Product \u7684\u6620\u5C04\u8868\n * @param rawProductMap handle \u2192 raw backend product \u7684\u6620\u5C04\u8868\n * @param onAddToCart \u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n * @param productCardRender \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\u51FD\u6570\n * @returns MessageContent \u6570\u7EC4\n *\n * @example\n * \u8F93\u5165\uFF1A\n * text: \"\u6211\u63A8\u8350\u4EE5\u4E0B\u4EA7\u54C1\uFF1A\\n{{product-handle}}\\n\u8FD9\u6B3E\u4EA7\u54C1\u6027\u4EF7\u6BD4\u5F88\u9AD8\u3002\"\n * productMap: Map { 'product-handle' => Product {...} }\n * \u8F93\u51FA\uFF1A\n * [\n * { type: 'text', text: '\u6211\u63A8\u8350\u4EE5\u4E0B\u4EA7\u54C1\uFF1A' },\n * { type: 'product_card', data: { product: {...}, onAddToCart } },\n * { type: 'text', text: '\u8FD9\u6B3E\u4EA7\u54C1\u6027\u4EF7\u6BD4\u5F88\u9AD8\u3002' }\n * ]\n */\nfunction parseTextWithProductIds(\n text: string,\n productMap: Map<string, Product>,\n rawProductMap: Map<string, any>,\n onAddToCart?: (product: Product) => void,\n productCardRender?: (product: any, productHandle: string) => React.ReactNode\n): MessageContent[] {\n const result: MessageContent[] = []\n // \u4FEE\u6539\u6B63\u5219\u8868\u8FBE\u5F0F\u4EE5\u5339\u914D {{product:ID}} \u683C\u5F0F\n // \u5339\u914D {{product:xxx}} \u6216 {{xxx}}\uFF08\u517C\u5BB9\u4E24\u79CD\u683C\u5F0F\uFF09\n const regex = /\\{\\{(?:product:)?([^}]+)\\}\\}/g\n\n let lastIndex = 0\n let match: RegExpExecArray | null\n\n while ((match = regex.exec(text)) !== null) {\n const beforeText = text.slice(lastIndex, match.index).trim()\n // match[1] \u662F\u6355\u83B7\u7EC4\u4E2D\u7684\u5185\u5BB9\uFF0C\u5373 product: \u540E\u9762\u7684 ID\n const productId = match[1].trim()\n\n // \u6DFB\u52A0\u524D\u9762\u7684\u6587\u672C\uFF08\u5982\u679C\u6709\uFF09\n if (beforeText) {\n result.push({\n type: 'text',\n text: beforeText,\n } as TextContent)\n }\n\n // \u6DFB\u52A0\u4EA7\u54C1\u5361\u7247\uFF08\u65E0\u8BBA\u662F\u5426\u627E\u5230\u4EA7\u54C1\u6570\u636E\uFF0C\u90FD\u6E32\u67D3 product_card\uFF09\n const product = productMap.get(productId)\n const rawProduct = rawProductMap.get(productId)\n if (product) {\n console.log(`[useChatState] \u2705 \u627E\u5230\u4EA7\u54C1\u5339\u914D: ${productId} \u2192 ${product.title}`)\n } else {\n console.log(`[useChatState] \uD83D\uDCE6 \u4EA7\u54C1\u5360\u4F4D\u7B26\uFF0C\u4EA7\u54C1\u6570\u636E\u5F85\u5E94\u7528\u5C42\u67E5\u8BE2: ${productId}`)\n }\n\n result.push({\n type: 'product_card',\n data: {\n product: product,\n rawProduct: rawProduct,\n productHandle: productId,\n onAddToCart: onAddToCart,\n productCardRender: productCardRender,\n },\n } as ProductCardContent)\n\n lastIndex = regex.lastIndex\n }\n\n // \u6DFB\u52A0\u6700\u540E\u5269\u4F59\u7684\u6587\u672C\n const remainingText = text.slice(lastIndex).trim()\n if (remainingText) {\n result.push({\n type: 'text',\n text: remainingText,\n } as TextContent)\n }\n\n return result\n}\n\n/**\n * \u91CD\u7EC4\u6D88\u606F\u5185\u5BB9\uFF1A\u89E3\u6790\u6587\u672C\u4E2D\u7684 {{handle}}\uFF0C\u66FF\u6362\u4E3A\u4EA7\u54C1\u5361\u7247\n *\n * \u5904\u7406\u903B\u8F91\uFF1A\n * 1. \u904D\u5386\u6D88\u606F\u7684\u6240\u6709 content blocks\n * 2. \u5BF9\u4E8E text \u7C7B\u578B\uFF0C\u89E3\u6790\u5176\u4E2D\u7684 {{handle}} \u5E76\u62C6\u5206\u4E3A\u591A\u4E2A content\n * 3. \u8DF3\u8FC7 product_list \u7C7B\u578B\uFF08\u5DF2\u7ECF\u88AB\u62C6\u5206\u5230\u6587\u672C\u4E2D\uFF09\n * 4. \u5176\u4ED6\u7C7B\u578B\u76F4\u63A5\u4FDD\u7559\n *\n * @param contents \u539F\u59CB\u6D88\u606F\u5185\u5BB9\u6570\u7EC4\n * @param productMap handle \u2192 Product \u7684\u6620\u5C04\u8868\n * @param rawProductMap handle \u2192 raw backend product \u7684\u6620\u5C04\u8868\n * @param onAddToCart \u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n * @param productCardRender \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\u51FD\u6570\n * @returns \u91CD\u7EC4\u540E\u7684\u6D88\u606F\u5185\u5BB9\u6570\u7EC4\n */\nfunction reorganizeMessageContent(\n contents: MessageContent[],\n productMap: Map<string, Product>,\n rawProductMap: Map<string, any>,\n onAddToCart?: (product: Product) => void,\n productCardRender?: (product: any, productHandle: string) => React.ReactNode\n): MessageContent[] {\n const result: MessageContent[] = []\n\n for (const content of contents) {\n // \u53EA\u5904\u7406\u6587\u672C\u7C7B\u578B\n if (content.type === 'text') {\n const textContent = content as TextContent\n const segments = parseTextWithProductIds(textContent.text, productMap, rawProductMap, onAddToCart, productCardRender)\n result.push(...segments)\n }\n // \u8DF3\u8FC7 product_list\uFF08\u5DF2\u7ECF\u88AB\u62C6\u5206\u5230\u6587\u672C\u4E2D\uFF09\n else if (content.type === 'product_list') {\n continue\n }\n // \u5176\u4ED6\u7C7B\u578B\u76F4\u63A5\u4FDD\u7559\n else {\n result.push(content)\n }\n }\n\n return result\n}\n\n/**\n * \u5904\u7406\u5355\u6761\u6D88\u606F\u7684\u91CD\u7EC4\uFF08\u7528\u4E8E\u5386\u53F2\u6D88\u606F\u52A0\u8F7D\uFF09\n * \u5982\u679C\u6D88\u606F\u5305\u542B\u5E26\u6709 {{}} \u7684\u6587\u672C\uFF0C\u5219\u8FDB\u884C\u91CD\u7EC4\n *\n * @param message \u539F\u59CB\u6D88\u606F\n * @param onAddToCart \u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n * @param productCardRender \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\u51FD\u6570\n * @returns \u91CD\u7EC4\u540E\u7684\u6D88\u606F\uFF08\u5982\u679C\u9700\u8981\u91CD\u7EC4\uFF09\uFF0C\u5426\u5219\u8FD4\u56DE\u539F\u6D88\u606F\n */\nfunction maybeReorganizeHistoricalMessage(\n message: Message,\n onAddToCart?: (product: Product) => void,\n productCardRender?: (product: any, productHandle: string) => React.ReactNode\n): Message {\n // \u68C0\u67E5\u6D88\u606F\u662F\u5426\u5305\u542B\u5E26\u6709 {{}} \u7684\u6587\u672C\n const hasPlaceholder = message.content.some(\n c => c.type === 'text' && /\\{\\{(?:product:)?[^}]+\\}\\}/.test((c as TextContent).text)\n )\n if (!hasPlaceholder) {\n return message // \u6CA1\u6709\u5360\u4F4D\u7B26\uFF0C\u4E0D\u9700\u8981\u91CD\u7EC4\n }\n\n console.log('[useChatState] \u68C0\u6D4B\u5230\u5386\u53F2\u6D88\u606F\u9700\u8981\u91CD\u7EC4, \u6D88\u606FID:', message.id)\n\n // \u6784\u5EFA\u4EA7\u54C1\u6620\u5C04 (handle \u2192 Product)\n const productMap = new Map<string, Product>()\n // \u4ECE structured_content \u4E2D\u63D0\u53D6\u539F\u59CB\u540E\u7AEF\u4EA7\u54C1\u6570\u636E (handle \u2192 rawProduct)\n const rawProductMap = new Map<string, any>()\n\n // \u4F18\u5148\u4ECE structured_content \u83B7\u53D6\u539F\u59CB\u4EA7\u54C1\u6570\u636E\uFF08\u5982\u679C\u5B58\u5728\uFF09\n if (message.structured_content) {\n message.structured_content.forEach(structuredContent => {\n if (structuredContent.type === 'product_list' && Array.isArray(structuredContent.data)) {\n structuredContent.data.forEach((rawProduct: any) => {\n if (rawProduct && rawProduct.handle) {\n rawProductMap.set(rawProduct.handle, rawProduct)\n console.log('[useChatState] \u4ECE structured_content \u63D0\u53D6\u539F\u59CB\u4EA7\u54C1\u6570\u636E, handle:', rawProduct.handle)\n }\n })\n }\n })\n }\n\n // \u6784\u5EFA\u8F6C\u6362\u540E\u7684\u4EA7\u54C1\u6620\u5C04\uFF08\u7528\u4E8E\u9ED8\u8BA4\u6E32\u67D3\uFF09\n message.content.forEach(content => {\n if (content.type === 'product_list') {\n const productListContent = content as ProductListContent\n productListContent.data.products.forEach(product => {\n if (product && product.handle) {\n productMap.set(product.handle, product)\n }\n })\n }\n })\n\n // \u91CD\u7EC4\u6D88\u606F\u5185\u5BB9\n const reorganizedContent = reorganizeMessageContent(message.content, productMap, rawProductMap, onAddToCart, productCardRender)\n\n // \u8FD4\u56DE\u65B0\u6D88\u606F\u5BF9\u8C61\n return {\n ...message,\n content: reorganizedContent,\n }\n}\n\nexport interface UseChatStateOptions {\n /**\n * \u521D\u59CB\u6B22\u8FCE\u6D88\u606F\n */\n welcomeMessage?: string\n\n /**\n * Shopify \u5E97\u94FA\u57DF\u540D\n */\n site?: string\n\n /**\n * \u53D7\u63A7\u6A21\u5F0F\uFF1A\u662F\u5426\u6253\u5F00\u804A\u5929\u7A97\u53E3\n * \u63D0\u4F9B\u6B64\u53C2\u6570\u65F6\uFF0C\u7EC4\u4EF6\u5904\u4E8E\u53D7\u63A7\u6A21\u5F0F\n */\n open?: boolean\n\n /**\n * \u53D7\u63A7\u6A21\u5F0F\uFF1A\u72B6\u6001\u53D8\u5316\u56DE\u8C03\uFF08\u5FC5\u9700\uFF09\n * \u7528\u4E8E\u540C\u6B65\u72B6\u6001\u5230\u7236\u7EC4\u4EF6\n */\n onOpenChange?: (open: boolean) => void\n\n /**\n * \u7A97\u53E3\u6253\u5F00\u4E8B\u4EF6\u76D1\u542C\uFF08\u53EF\u9009\uFF09\n * \u7528\u4E8E\u57CB\u70B9\u3001\u65E5\u5FD7\u7B49\u526F\u4F5C\u7528\n */\n onOpen?: () => void\n\n /**\n * \u7A97\u53E3\u5173\u95ED\u4E8B\u4EF6\u76D1\u542C\uFF08\u53EF\u9009\uFF09\n * \u7528\u4E8E\u57CB\u70B9\u3001\u65E5\u5FD7\u7B49\u526F\u4F5C\u7528\n */\n onClose?: () => void\n\n /**\n * \u6D88\u606F\u53D1\u9001\u56DE\u8C03\n */\n onMessageSend?: (message: string) => void\n\n /**\n * \u9519\u8BEF\u5904\u7406\u56DE\u8C03\n */\n onError?: (error: Error) => void\n\n /**\n * AI \u6D88\u606F\u56DE\u8C03\n */\n /**\n * AI \u56DE\u590D\u6587\u672C\u6D88\u606F\u65F6\u89E6\u53D1\n */\n onTextMessage?: () => void\n\n /**\n * AI \u56DE\u590D\u5546\u54C1\u5217\u8868\u5361\u7247\u65F6\u89E6\u53D1\n */\n onProductList?: () => void\n\n /**\n * AI \u56DE\u590D\u4FC3\u9500\u5361\u7247\u65F6\u89E6\u53D1\n * @param promotions \u4FC3\u9500\u6D3B\u52A8\u6570\u7EC4\u6570\u636E\n */\n onPromotionList?: (promotions: any[]) => void\n\n /**\n * \u5546\u54C1\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n */\n onAddToCart?: (product: any) => void\n\n /**\n * \u8D2D\u7269\u8F66\u6309\u94AE\u70B9\u51FB\u56DE\u8C03\n */\n onCart?: (cartId: string, checkoutUrl?: string) => void\n\n /**\n * \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\u51FD\u6570\n * @param product \u4EA7\u54C1\u6570\u636E\uFF08\u5982\u679C\u5728 product_list \u4E2D\u627E\u5230\uFF09\uFF0C\u5426\u5219\u4E3A undefined\n * @param productHandle \u6587\u672C\u5360\u4F4D\u7B26\u4E2D\u7684\u4EA7\u54C1 ID\uFF0C\u53EF\u7528\u4E8E\u5E94\u7528\u5C42\u67E5\u8BE2\u4EA7\u54C1\u6570\u636E\n */\n productCardRender?: (product: any, productHandle: string) => React.ReactNode\n}\n\nexport interface UseChatStateReturn {\n /**\n * \u6D88\u606F\u5217\u8868\n */\n messages: Message[]\n\n /**\n * \u804A\u5929\u7A97\u53E3\u662F\u5426\u6253\u5F00\n */\n isOpen: boolean\n\n /**\n * \u7528\u6237 ID\n */\n userId: string\n\n /**\n * \u4F1A\u8BDD ID\n */\n sessionId: string | null\n\n /**\n * \u8F93\u5165\u6846\u5185\u5BB9\n */\n inputValue: string\n\n /**\n * \u662F\u5426\u6B63\u5728\u63A5\u6536\u6D41\u5F0F\u6D88\u606F\n */\n isStreaming: boolean\n\n /**\n * \u6253\u5F00\u804A\u5929\u7A97\u53E3\n */\n openChat: () => void\n\n /**\n * \u5173\u95ED\u804A\u5929\u7A97\u53E3\n */\n closeChat: () => void\n\n /**\n * \u5207\u6362\u804A\u5929\u7A97\u53E3\u72B6\u6001\n */\n toggleChat: () => void\n\n /**\n * \u8BBE\u7F6E\u8F93\u5165\u6846\u5185\u5BB9\n */\n setInputValue: (value: string) => void\n\n /**\n * \u6DFB\u52A0\u6D88\u606F\u5230\u5217\u8868\n */\n addMessage: (message: Message) => void\n\n /**\n * \u6279\u91CF\u8BBE\u7F6E\u6D88\u606F\u5217\u8868\uFF08\u7528\u4E8E\u52A0\u8F7D\u5386\u53F2\uFF09\n */\n setMessages: (messages: Message[]) => void\n\n /**\n * \u6E05\u7A7A\u6D88\u606F\u5217\u8868\n */\n clearMessages: () => void\n\n /**\n * \u5904\u7406 SSE \u4E8B\u4EF6\n */\n handleSSEEvent: (event: SSEEvent) => void\n\n /**\n * \u4FDD\u5B58\u4F1A\u8BDD ID\n */\n saveSession: (id: string) => void\n\n /**\n * \u6E05\u7A7A\u4F1A\u8BDD\n */\n clearSession: () => void\n}\n\n/**\n * \u804A\u5929\u72B6\u6001\u7BA1\u7406 Hook\n *\n * \u529F\u80FD\uFF1A\n * 1. \u7BA1\u7406\u6D88\u606F\u5217\u8868\uFF08\u6DFB\u52A0\u3001\u6E05\u7A7A\u3001\u6279\u91CF\u8BBE\u7F6E\uFF09\n * 2. \u7BA1\u7406\u7A97\u53E3\u72B6\u6001\uFF08\u6253\u5F00\u3001\u5173\u95ED\u3001\u5207\u6362\uFF09\n * 3. \u7BA1\u7406\u8F93\u5165\u6846\u72B6\u6001\n * 4. \u5904\u7406 SSE \u6D41\u5F0F\u6D88\u606F\u4E8B\u4EF6\n * 5. \u7D2F\u79EF\u6D41\u5F0F\u6587\u672C\u5185\u5BB9\n *\n * @param options Hook \u914D\u7F6E\u9009\u9879\n * @returns \u72B6\u6001\u7BA1\u7406\u5DE5\u5177\u5BF9\u8C61\n */\nexport function useChatState(options: UseChatStateOptions = {}): UseChatStateReturn {\n const {\n welcomeMessage,\n site,\n open: controlledOpen,\n onOpenChange,\n onOpen,\n onClose,\n onMessageSend,\n onError,\n onTextMessage,\n onProductList,\n onPromotionList,\n onAddToCart,\n onCart,\n productCardRender,\n } = options\n\n // \u4F1A\u8BDD\u7BA1\u7406\n const { sessionId, saveSession, clearSession } = useSession()\n\n // \u7528\u6237 ID (\u521D\u59CB\u5316\u65F6\u5F02\u6B65\u751F\u6210)\n const [userId, setUserId] = useState<string>('')\n\n // \u521D\u59CB\u5316 userId\n useEffect(() => {\n getUserId().then(id => setUserId(id))\n }, [])\n\n // \u6D88\u606F\u5217\u8868\n const [messages, setMessagesState] = useState<Message[]>(() => {\n // \u5982\u679C\u6709\u6B22\u8FCE\u6D88\u606F\uFF0C\u521D\u59CB\u5316\u65F6\u6DFB\u52A0\n if (welcomeMessage) {\n return [\n {\n id: `welcome-${Date.now()}`,\n role: 'assistant',\n content: [{ type: 'text', text: welcomeMessage }],\n timestamp: Date.now(),\n },\n ]\n }\n return []\n })\n\n // \u804A\u5929\u7A97\u53E3\u662F\u5426\u6253\u5F00\uFF08\u652F\u6301\u53D7\u63A7\u548C\u975E\u53D7\u63A7\u4E24\u79CD\u6A21\u5F0F\uFF09\n const [internalOpen, setInternalOpen] = useState(false)\n const isControlled = controlledOpen !== undefined\n const isOpen = isControlled ? controlledOpen : internalOpen\n\n // \u8F93\u5165\u6846\u5185\u5BB9\n const [inputValue, setInputValue] = useState('')\n\n // \u662F\u5426\u6B63\u5728\u63A5\u6536\u6D41\u5F0F\u6D88\u606F\n const [isStreaming, setIsStreaming] = useState(false)\n\n // \u5F53\u524D\u6B63\u5728\u7D2F\u79EF\u7684\u6D41\u5F0F\u6D88\u606F (\u4E34\u65F6\u5B58\u50A8)\n const currentMessageRef = useRef<Message | null>(null)\n\n // \u6807\u8BB0\u5F53\u524D\u6D88\u606F\u662F\u5426\u5DF2\u89E6\u53D1 onTextMessage \u56DE\u8C03\uFF08\u907F\u514D\u91CD\u590D\u89E6\u53D1\uFF09\n const textMessageCallbackTriggeredRef = useRef<boolean>(false)\n\n // \u4EA7\u54C1\u6620\u5C04\u7F13\u5B58 (handle \u2192 Product)\uFF0C\u7528\u4E8E\u5B9E\u65F6\u89E3\u6790\u5360\u4F4D\u7B26\n const productMapRef = useRef<Map<string, Product>>(new Map())\n\n // \u539F\u59CB\u4EA7\u54C1\u6570\u636E\u7F13\u5B58 (handle \u2192 raw backend product)\uFF0C\u7528\u4E8E productCardRender\n const rawProductMapRef = useRef<Map<string, any>>(new Map())\n\n // \u6587\u672C\u7F13\u51B2\u533A\uFF0C\u7528\u4E8E\u5B58\u50A8\u672A\u5B8C\u6210\u7684\u6587\u672C\uFF08\u5904\u7406\u5360\u4F4D\u7B26\u8DE8\u8D8A\u591A\u4E2A delta \u7684\u60C5\u51B5\uFF09\n const textBufferRef = useRef<string>('')\n\n // \u5361\u7247\u7F13\u5B58\u961F\u5217\uFF0C\u7528\u4E8E\u5B58\u50A8\u9700\u8981\u5EF6\u8FDF\u663E\u793A\u7684\u5361\u7247\uFF08\u5728\u6587\u672C\u5B8C\u6210\u540E\u663E\u793A\uFF09\n const pendingCardsRef = useRef<MessageContent[]>([])\n\n /**\n * \u6253\u5F00\u804A\u5929\u7A97\u53E3\n */\n const openChat = useCallback(() => {\n if (!isControlled) {\n setInternalOpen(true)\n }\n onOpenChange?.(true)\n onOpen?.()\n }, [isControlled, onOpenChange, onOpen])\n\n /**\n * \u5173\u95ED\u804A\u5929\u7A97\u53E3\n */\n const closeChat = useCallback(() => {\n if (!isControlled) {\n setInternalOpen(false)\n }\n onOpenChange?.(false)\n onClose?.()\n }, [isControlled, onOpenChange, onClose])\n\n /**\n * \u5207\u6362\u804A\u5929\u7A97\u53E3\u72B6\u6001\n */\n const toggleChat = useCallback(() => {\n const newState = !isOpen\n if (!isControlled) {\n setInternalOpen(newState)\n }\n onOpenChange?.(newState)\n if (newState) {\n onOpen?.()\n } else {\n onClose?.()\n }\n }, [isControlled, isOpen, onOpenChange, onOpen, onClose])\n\n /**\n * \u6DFB\u52A0\u6D88\u606F\u5230\u5217\u8868\n */\n const addMessage = useCallback((message: Message) => {\n // \u9632\u62A4\uFF1A\u5982\u679C\u6D88\u606F\u4E3A null \u6216 undefined\uFF0C\u4E0D\u6DFB\u52A0\n if (!message) {\n console.warn('[useChatState] Attempted to add null/undefined message')\n return\n }\n setMessagesState(prev => [...prev, message])\n }, [])\n\n /**\n * \u6279\u91CF\u8BBE\u7F6E\u6D88\u606F\u5217\u8868\uFF08\u7528\u4E8E\u52A0\u8F7D\u5386\u53F2\uFF09\n */\n const setMessages = useCallback(\n (newMessages: Message[]) => {\n // \u9632\u62A4\uFF1A\u8FC7\u6EE4\u6389 null/undefined \u6D88\u606F\n const validMessages = newMessages.filter(msg => msg != null)\n if (validMessages.length !== newMessages.length) {\n console.warn('[useChatState] Filtered out null/undefined messages from batch set')\n }\n\n // \u5BF9\u6BCF\u6761\u5386\u53F2\u6D88\u606F\u8FDB\u884C\u91CD\u7EC4\uFF08\u5982\u679C\u9700\u8981\uFF09\n const reorganizedMessages = validMessages.map(msg => maybeReorganizeHistoricalMessage(msg, onAddToCart, productCardRender))\n\n setMessagesState(reorganizedMessages)\n },\n [onAddToCart, productCardRender]\n )\n\n /**\n * \u6E05\u7A7A\u6D88\u606F\u5217\u8868\n */\n const clearMessages = useCallback(() => {\n setMessagesState([])\n }, [])\n\n /**\n * \u5904\u7406 SSE \u4E8B\u4EF6\n * \u6839\u636E\u4E8B\u4EF6\u7C7B\u578B\u8FDB\u884C\u4E0D\u540C\u7684\u5904\u7406\n */\n const handleSSEEvent = useCallback(\n (event: SSEEvent) => {\n const { event: eventType, data } = event\n\n switch (eventType) {\n case 'message_start': {\n // \u5F00\u59CB\u63A5\u6536\u65B0\u6D88\u606F\n setIsStreaming(true)\n\n // \u91CD\u7F6E\u6587\u672C\u6D88\u606F\u56DE\u8C03\u6807\u8BB0\n textMessageCallbackTriggeredRef.current = false\n\n // \u91CD\u7F6E\u6587\u672C\u7F13\u51B2\u533A\n textBufferRef.current = ''\n\n // \u91CD\u7F6E\u5361\u7247\u7F13\u5B58\u961F\u5217\n pendingCardsRef.current = []\n\n // T039: \u4FDD\u5B58 sessionId\uFF08\u5982\u679C\u540E\u7AEF\u8FD4\u56DE\uFF09\n const messageStartData = data as MessageStartData\n if (messageStartData.sessionId && messageStartData.sessionId !== sessionId) {\n saveSession(messageStartData.sessionId)\n }\n\n // \u68C0\u67E5\u6700\u540E\u4E00\u6761\u6D88\u606F\u662F\u5426\u662F thinking \u6D88\u606F\uFF08\u7528\u6237\u53D1\u9001\u6D88\u606F\u65F6\u5DF2\u6DFB\u52A0\uFF09\n setMessagesState(prev => {\n const lastMessage = prev[prev.length - 1]\n const hasThinking =\n lastMessage &&\n lastMessage.role === 'assistant' &&\n lastMessage.content.length === 1 &&\n lastMessage.content[0].type === 'thinking'\n\n if (hasThinking) {\n // \u590D\u7528\u5DF2\u5B58\u5728\u7684 thinking \u6D88\u606F\n currentMessageRef.current = lastMessage\n return prev // \u4E0D\u9700\u8981\u6DFB\u52A0\u65B0\u6D88\u606F\n } else {\n // \u6CA1\u6709 thinking \u6D88\u606F\uFF0C\u521B\u5EFA\u65B0\u7684\uFF08\u517C\u5BB9\u5176\u4ED6\u573A\u666F\uFF09\n const messageId = `msg-${Date.now()}`\n currentMessageRef.current = {\n id: messageId,\n role: 'assistant',\n content: [{ type: 'thinking', data: { status: 'thinking' } }],\n timestamp: Date.now(),\n }\n return [...prev, currentMessageRef.current!]\n }\n })\n break\n }\n\n case 'content_delta': {\n // \u7D2F\u79EF\u6D41\u5F0F\u6587\u672C\u5185\u5BB9\uFF0C\u5E76\u5B9E\u65F6\u68C0\u6D4B\u4EA7\u54C1\u5360\u4F4D\u7B26\n const deltaData = data as any\n const deltaText = deltaData.delta || deltaData.text || ''\n\n if (currentMessageRef.current && deltaText) {\n // \u89E6\u53D1\u6587\u672C\u6D88\u606F\u56DE\u8C03\uFF08\u4EC5\u89E6\u53D1\u4E00\u6B21\uFF09\n if (!textMessageCallbackTriggeredRef.current) {\n textMessageCallbackTriggeredRef.current = true\n onTextMessage?.()\n }\n\n // \u79FB\u9664\u601D\u8003\u6C14\u6CE1\uFF08\u5982\u679C\u5B58\u5728\uFF09\n const hasThinking = currentMessageRef.current.content.some(c => c.type === 'thinking')\n if (hasThinking) {\n currentMessageRef.current.content = currentMessageRef.current.content.filter(c => c.type !== 'thinking')\n }\n\n // \u5C06\u65B0\u6587\u672C\u6DFB\u52A0\u5230\u7F13\u51B2\u533A\n textBufferRef.current += deltaText\n\n // \u5B9E\u65F6\u89E3\u6790\u7F13\u51B2\u533A\u4E2D\u7684\u5360\u4F4D\u7B26\n const { contents, remainingBuffer } = parseStreamingText(\n textBufferRef.current,\n productMapRef.current,\n rawProductMapRef.current,\n onAddToCart,\n productCardRender\n )\n\n // \u66F4\u65B0\u7F13\u51B2\u533A\u4E3A\u5269\u4F59\u5185\u5BB9\n textBufferRef.current = remainingBuffer\n\n // \u5C06\u89E3\u6790\u51FA\u7684\u5185\u5BB9\u6DFB\u52A0\u5230\u6D88\u606F\u4E2D\n if (contents.length > 0) {\n contents.forEach(content => {\n const lastContent = currentMessageRef.current!.content[\n currentMessageRef.current!.content.length - 1\n ] as MessageContent | undefined\n\n // \u5982\u679C\u662F\u6587\u672C\u5185\u5BB9\u4E14\u6700\u540E\u4E00\u4E2A\u4E5F\u662F\u6587\u672C\uFF0C\u5219\u5408\u5E76\n if (content.type === 'text' && lastContent && lastContent.type === 'text') {\n lastContent.text += content.text\n } else {\n // \u5426\u5219\u6DFB\u52A0\u65B0\u5185\u5BB9\n currentMessageRef.current!.content.push(content)\n }\n })\n\n // \u66F4\u65B0\u6D88\u606F\u5217\u8868\u4EE5\u89E6\u53D1\u6E32\u67D3\n setMessagesState(prev => {\n if (!currentMessageRef.current) return prev\n\n const updated = [...prev]\n const existingIndex = updated.findIndex(m => m && m.id === currentMessageRef.current!.id)\n\n if (existingIndex >= 0) {\n updated[existingIndex] = { ...currentMessageRef.current! }\n } else {\n updated.push({ ...currentMessageRef.current! })\n }\n\n return updated\n })\n }\n }\n break\n }\n\n case 'content_block': {\n // \u63A5\u6536\u7ED3\u6784\u5316\u5185\u5BB9\u5757\uFF08\u5546\u54C1\u3001\u653F\u7B56\u7B49\uFF09\n // API \u8FD4\u56DE\u683C\u5F0F\u53D8\u66F4:\n // \u65B0\u683C\u5F0F: {index: number, type: string, data: {...}} <- type \u5728\u5916\u5C42\n // \u65E7\u683C\u5F0F: {index: number, data: {type: string, ...}} <- type \u5728 data \u5185\n const blockData = data as any\n if (currentMessageRef.current) {\n // \u83B7\u53D6 type \u548C data\n // \u4F18\u5148\u4ECE\u5916\u5C42\u83B7\u53D6 type\uFF0C\u517C\u5BB9\u65E7\u683C\u5F0F\u4ECE data \u5185\u83B7\u53D6\n const contentType = blockData.type || blockData.data?.type\n const contentData = blockData.data\n\n if (!contentType || !contentData) {\n console.warn('[useChatState] Invalid content_block:', blockData)\n break\n }\n\n // ============================================================\n // \u8F6C\u6362\u6570\u636E\u7ED3\u6784\u4EE5\u5339\u914D\u7C7B\u578B\u5B9A\u4E49\n // \u6839\u636E\u540E\u7AEF\u6570\u636E\u7ED3\u6784\u89C4\u8303\uFF08\u98DE\u4E66\u6587\u6863\uFF09\u8FDB\u884C\u8F6C\u6362\n // ============================================================\n let messageContent: MessageContent\n\n // ========== 1. \u4EA7\u54C1\u5217\u8868\u5361\u7247 (Product List) ==========\n // \u540E\u7AEF\u683C\u5F0F: {type: \"product_list\", data: [product1, product2, ...]}\n // data \u76F4\u63A5\u662F\u4EA7\u54C1\u6570\u7EC4\uFF0C\u4E0D\u662F {products: [...]}\n if (contentType === 'product_list' && Array.isArray(contentData)) {\n // \u89E6\u53D1\u5546\u54C1\u5217\u8868\u56DE\u8C03\n onProductList?.()\n\n // \u7F13\u5B58\u539F\u59CB\u540E\u7AEF\u4EA7\u54C1\u6570\u636E\uFF08\u7528\u4E8E productCardRender\uFF09\n // \u4F7F\u7528 handle \u4F5C\u4E3A key \u8FDB\u884C\u5339\u914D\n contentData.forEach((rawProduct: any) => {\n if (rawProduct && rawProduct.handle) {\n rawProductMapRef.current.set(rawProduct.handle, rawProduct)\n console.log('[useChatState] \u7F13\u5B58\u539F\u59CB\u4EA7\u54C1\u6570\u636E, handle:', rawProduct.handle)\n }\n })\n\n // \u4F7F\u7528\u7EDF\u4E00\u7684\u4EA7\u54C1\u8F6C\u6362\u5DE5\u5177\u51FD\u6570\n const transformedProducts = transformProducts(contentData, site)\n\n // \u5EFA\u7ACB\u4EA7\u54C1\u6620\u5C04\u7F13\u5B58 (handle \u2192 Product)\n // \u7528\u4E8E\u540E\u7EED\u5728 message_end \u65F6\u89E3\u6790\u6587\u672C\u4E2D\u7684 {{handle}}\n transformedProducts.forEach(product => {\n if (product && product.handle) {\n productMapRef.current.set(product.handle, product)\n\n console.log('[useChatState] \u5EFA\u7ACB\u4EA7\u54C1\u6620\u5C04:', {\n handle: product.handle,\n title: product.title,\n })\n }\n })\n\n // \u26A0\uFE0F \u4E0D\u8981\u628A product_list \u6DFB\u52A0\u5230\u6D88\u606F\u4E2D\uFF0C\u907F\u514D\u95EA\u70C1\n // \u7B49\u5230 message_end \u65F6\uFF0C\u901A\u8FC7\u6587\u672C\u89E3\u6790\u521B\u5EFA product_card\uFF0C\u76F4\u63A5\u663E\u793A\u6700\u7EC8\u7684\u4EA4\u66FF\u683C\u5F0F\n 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')\n break // \u76F4\u63A5\u8DF3\u51FA\uFF0C\u4E0D\u6267\u884C\u540E\u7EED\u7684 push \u64CD\u4F5C\n }\n // ========== 2. \u4EA7\u54C1\u5BF9\u6BD4\u5361\u7247 (Product Comparison) ==========\n // \u540E\u7AEF\u683C\u5F0F: {type: \"product_comparison\", data: {products: [...], dimensions: {...}}}\n else if (contentType === 'product_comparison' && contentData.products) {\n // \u4F7F\u7528\u7EDF\u4E00\u7684\u4EA7\u54C1\u8F6C\u6362\u5DE5\u5177\u51FD\u6570\n const transformedProducts = transformProducts(contentData.products, site)\n\n messageContent = {\n type: 'product_comparison',\n data: {\n products: transformedProducts,\n dimensions: contentData.dimensions || {},\n },\n } as MessageContent\n }\n // ========== 3. FAQ \u5217\u8868\u5361\u7247 (FAQ List) ==========\n // \u540E\u7AEF\u683C\u5F0F: {type: \"faq_list\", data: {found, count, total, results: [...]}}\n else if (contentType === 'faq_list' && contentData.found !== undefined) {\n messageContent = {\n type: 'faq_list',\n data: contentData, // \u76F4\u63A5\u4F7F\u7528\uFF0C\u7ED3\u6784\u5DF2\u5339\u914D\n } as MessageContent\n }\n // ========== 4. \u5FEB\u6377\u56DE\u590D (Quick Replies) ==========\n else if (contentType === 'quick_replies' && contentData.replies) {\n messageContent = {\n type: 'quick_replies',\n data: {\n replies: contentData.replies,\n },\n } as MessageContent\n }\n // ========== 5. \u653F\u7B56\u5185\u5BB9 (Policy) ==========\n else if (contentType === 'policy' && contentData.title && contentData.content) {\n messageContent = {\n type: 'policy',\n data: {\n title: contentData.title,\n content: contentData.content,\n },\n } as MessageContent\n }\n // ========== 6. \u4FC3\u9500\u6D3B\u52A8\u5217\u8868 (Promotion List) ==========\n // \u540E\u7AEF\u683C\u5F0F: {type: \"promotion_list\", data: {found, count, total, results: [...]}}\n else if (contentType === 'promotion_list' && contentData.found !== undefined) {\n // \u89E6\u53D1\u4FC3\u9500\u5361\u7247\u56DE\u8C03\uFF0C\u4F20\u9012\u4FC3\u9500\u6D3B\u52A8\u6570\u7EC4\n onPromotionList?.(contentData.results || [])\n\n messageContent = {\n type: 'promotion_list',\n data: contentData, // \u76F4\u63A5\u4F7F\u7528\uFF0C\u7ED3\u6784\u5DF2\u5339\u914D\n } as MessageContent\n }\n // ========== 7. \u8D2D\u7269\u8F66 (Cart) ==========\n // \u540E\u7AEF\u683C\u5F0F: {type: \"cart\", data: {id, lines: {edges: [...]}, cost, ...}} (Shopify GraphQL)\n // \u9700\u8981\u8F6C\u6362\u4E3A\u524D\u7AEF\u683C\u5F0F: {cartId, lines: [...], cost, ...}\n else if (contentType === 'cart' && contentData.id !== undefined) {\n // \u8F6C\u6362\u540E\u7AEF Shopify GraphQL \u683C\u5F0F\u4E3A\u524D\u7AEF\u6807\u51C6\u683C\u5F0F\n const transformedData = transformCartData(contentData as BackendCartData)\n messageContent = {\n type: 'cart',\n data: {\n ...transformedData,\n onCart: onCart, // \u6CE8\u5165\u8D2D\u7269\u8F66\u6309\u94AE\u56DE\u8C03\n },\n } as MessageContent\n }\n // ========== 8. \u5176\u4ED6\u7C7B\u578B\uFF08\u901A\u7528\u5904\u7406\uFF09 ==========\n else {\n messageContent = {\n type: contentType,\n data: contentData,\n } as MessageContent\n }\n\n // \u26A0\uFE0F \u91CD\u8981\u4FEE\u6539\uFF1A\u5C06\u5361\u7247\u7F13\u5B58\u8D77\u6765\uFF0C\u4E0D\u7ACB\u5373\u6DFB\u52A0\u5230\u6D88\u606F\u4E2D\n // \u7B49\u5F85 message_end \u65F6\uFF0C\u5728\u6587\u672C\u5B8C\u6210\u540E\u518D\u7EDF\u4E00\u6DFB\u52A0\u5361\u7247\n console.log('[useChatState] \u2705 \u5361\u7247\u5DF2\u7F13\u5B58\uFF0C\u7B49\u5F85\u6587\u672C\u5B8C\u6210\u540E\u663E\u793A:', contentType)\n pendingCardsRef.current.push(messageContent)\n\n // \u4E0D\u518D\u7ACB\u5373\u66F4\u65B0\u6D88\u606F\u5217\u8868\uFF0C\u907F\u514D\u5361\u7247\u5728\u6587\u672C\u4E4B\u524D\u663E\u793A\n // \u539F\u6765\u7684\u4EE3\u7801\uFF1A\n // currentMessageRef.current.content.push(messageContent)\n // setMessagesState(prev => { ... })\n }\n break\n }\n\n case 'tool_start':\n case 'tool_end': {\n // \u5DE5\u5177\u8C03\u7528\u4E8B\u4EF6\uFF0C\u6682\u65F6\u5FFD\u7565\n // \u53EF\u4EE5\u5728\u672A\u6765\u7528\u4E8E\u663E\u793A\u5DE5\u5177\u8C03\u7528\u72B6\u6001\n break\n }\n\n case 'message_end': {\n // \u6D88\u606F\u63A5\u6536\u5B8C\u6210\n setIsStreaming(false)\n\n // \u5904\u7406\u7F13\u51B2\u533A\u4E2D\u5269\u4F59\u7684\u6587\u672C\uFF08\u5982\u679C\u6709\uFF09\n if (currentMessageRef.current && textBufferRef.current) {\n console.log('[useChatState] \u5904\u7406\u5269\u4F59\u7F13\u51B2\u533A:', textBufferRef.current)\n\n const lastContent = currentMessageRef.current.content[\n currentMessageRef.current.content.length - 1\n ] as MessageContent | undefined\n\n // \u5982\u679C\u6700\u540E\u4E00\u4E2A\u5185\u5BB9\u662F\u6587\u672C\uFF0C\u5219\u5408\u5E76\n if (lastContent && lastContent.type === 'text') {\n lastContent.text += textBufferRef.current\n } else {\n // \u5426\u5219\u6DFB\u52A0\u4E3A\u65B0\u7684\u6587\u672C\u5757\n currentMessageRef.current.content.push({\n type: 'text',\n text: textBufferRef.current,\n })\n }\n }\n\n // \u26A0\uFE0F \u91CD\u8981\u4FEE\u6539\uFF1A\u5728\u6587\u672C\u5B8C\u6210\u540E\uFF0C\u6DFB\u52A0\u6240\u6709\u7F13\u5B58\u7684\u5361\u7247\n if (currentMessageRef.current && pendingCardsRef.current.length > 0) {\n console.log('[useChatState] \uD83D\uDCCB \u6587\u672C\u5DF2\u5B8C\u6210\uFF0C\u73B0\u5728\u6DFB\u52A0', pendingCardsRef.current.length, '\u4E2A\u7F13\u5B58\u7684\u5361\u7247')\n\n // \u5C06\u6240\u6709\u7F13\u5B58\u7684\u5361\u7247\u6DFB\u52A0\u5230\u6D88\u606F\u5185\u5BB9\u4E2D\n currentMessageRef.current.content.push(...pendingCardsRef.current)\n }\n\n // \u26A0\uFE0F \u8D85\u65F6\u68C0\u6D4B\uFF1A\u5982\u679C message_end \u65F6\u4ECD\u5B58\u5728 thinking block\uFF0C\u89C6\u4E3A\u8D85\u65F6/\u5F02\u5E38\n // \u6CE8\u610F\uFF1A\u5728\u6DFB\u52A0\u5361\u7247\u4E4B\u540E\u68C0\u6D4B\uFF0C\u8FD9\u6837\u5982\u679C\u6709\u5361\u7247\u5C31\u4E0D\u4F1A\u6DFB\u52A0\u8D85\u65F6\u63D0\u793A\n if (currentMessageRef.current) {\n const hasThinking = currentMessageRef.current.content.some(c => c.type === 'thinking')\n\n if (hasThinking) {\n console.log('[useChatState] \u26A0\uFE0F message_end \u65F6\u4ECD\u5B58\u5728 thinking block\uFF0C\u7ACB\u5373\u79FB\u9664\uFF08\u89C6\u4E3A\u8D85\u65F6\uFF09')\n\n // \u79FB\u9664 thinking block\n currentMessageRef.current.content = currentMessageRef.current.content.filter(c => c.type !== 'thinking')\n\n // \u5982\u679C\u6CA1\u6709\u5176\u4ED6\u5185\u5BB9\uFF08\u5305\u62EC\u7F13\u5B58\u7684\u5361\u7247\uFF09\uFF0C\u6DFB\u52A0\u8D85\u65F6\u63D0\u793A\n if (currentMessageRef.current.content.length === 0) {\n currentMessageRef.current.content.push({\n type: 'text',\n text: 'Response timed out, please try again.',\n } as TextContent)\n }\n }\n }\n\n // \u66F4\u65B0\u6D88\u606F\u5217\u8868\uFF08\u7EDF\u4E00\u66F4\u65B0\uFF0C\u5305\u542B\u6587\u672C\u548C\u5361\u7247\uFF09\n if (currentMessageRef.current) {\n setMessagesState(prev => {\n if (!currentMessageRef.current) return prev\n\n const updated = [...prev]\n const existingIndex = updated.findIndex(m => m && m.id === currentMessageRef.current!.id)\n\n if (existingIndex >= 0) {\n updated[existingIndex] = { ...currentMessageRef.current! }\n }\n\n return updated\n })\n }\n\n // \u6E05\u7A7A\u7F13\u51B2\u533A\u3001\u4EA7\u54C1\u6620\u5C04\u548C\u5361\u7247\u7F13\u5B58\n textBufferRef.current = ''\n pendingCardsRef.current = []\n productMapRef.current.clear()\n rawProductMapRef.current.clear()\n currentMessageRef.current = null\n break\n }\n\n case 'status': {\n // T040: \u72B6\u6001\u66F4\u65B0\uFF08\u5982\u4F1A\u8BDD\u8FC7\u671F\uFF09\n const statusData = data as StatusData\n if (statusData.type === 'session_expired') {\n // \u4F1A\u8BDD\u8FC7\u671F\uFF0C\u6E05\u7A7A\u6D88\u606F\u5217\u8868\u548C\u4F1A\u8BDD\n clearMessages()\n clearSession()\n if (welcomeMessage) {\n addMessage({\n id: `welcome-${Date.now()}`,\n role: 'assistant',\n content: [{ type: 'text', text: welcomeMessage }],\n timestamp: Date.now(),\n })\n }\n }\n break\n }\n\n case 'error': {\n // \u9519\u8BEF\u5904\u7406\n const errorData = data as ErrorData\n setIsStreaming(false)\n\n // \u6E05\u7406\u7F13\u5B58\uFF08\u9632\u6B62\u6CC4\u6F0F\u5230\u4E0B\u6B21\u6D88\u606F\uFF09\n textBufferRef.current = ''\n pendingCardsRef.current = []\n productMapRef.current.clear()\n rawProductMapRef.current.clear()\n currentMessageRef.current = null\n\n // \u6DFB\u52A0\u9519\u8BEF\u6D88\u606F\u5230\u754C\u9762\n addMessage({\n id: `error-${Date.now()}`,\n role: 'system',\n content: [\n {\n type: 'error',\n data: {\n message: errorData.message,\n code: errorData.code,\n },\n },\n ],\n timestamp: Date.now(),\n })\n\n onError?.(new Error(errorData.message))\n break\n }\n\n case 'done': {\n // \u6D41\u7ED3\u675F\n setIsStreaming(false)\n\n // \u6E05\u7406\u7F13\u5B58\uFF08\u9632\u6B62\u6CC4\u6F0F\u5230\u4E0B\u6B21\u6D88\u606F\uFF09\n textBufferRef.current = ''\n pendingCardsRef.current = []\n productMapRef.current.clear()\n rawProductMapRef.current.clear()\n\n // \u6E05\u7406\u5F53\u524D\u6D88\u606F\u5F15\u7528\n currentMessageRef.current = null\n break\n }\n\n default:\n // \u5176\u4ED6\u4E8B\u4EF6\u7C7B\u578B\uFF08tool_start, tool_end \u7B49\uFF09\n break\n }\n },\n [\n welcomeMessage,\n site,\n addMessage,\n clearMessages,\n clearSession,\n saveSession,\n sessionId,\n onError,\n onTextMessage,\n onProductList,\n onPromotionList,\n onAddToCart,\n onCart,\n ]\n )\n\n\n return {\n messages,\n isOpen,\n userId,\n sessionId,\n inputValue,\n isStreaming,\n openChat,\n closeChat,\n toggleChat,\n setInputValue,\n addMessage,\n setMessages,\n clearMessages,\n handleSSEEvent,\n saveSession,\n clearSession,\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["useState", "useCallback", "useRef", "useEffect", "getUserId", "useSession", "transformProducts", "transformCartData", "parseStreamingText", "buffer", "productMap", "rawProductMap", "onAddToCart", "productCardRender", "contents", "regex", "lastIndex", "match", "foundMatch", "beforeText", "productId", "product", "rawProduct", "remainingBuffer", "incompleteMatch", "completeText", "parseTextWithProductIds", "text", "result", "remainingText", "reorganizeMessageContent", "content", "segments", "maybeReorganizeHistoricalMessage", "message", "structuredContent", "reorganizedContent", "useChatState", "options", "welcomeMessage", "site", "controlledOpen", "onOpenChange", "onOpen", "onClose", "onMessageSend", "onError", "onTextMessage", "onProductList", "onPromotionList", "onCart", "sessionId", "saveSession", "clearSession", "userId", "setUserId", "id", "messages", "setMessagesState", "internalOpen", "setInternalOpen", "isControlled", "isOpen", "inputValue", "setInputValue", "isStreaming", "setIsStreaming", "currentMessageRef", "textMessageCallbackTriggeredRef", "productMapRef", "rawProductMapRef", "textBufferRef", "pendingCardsRef", "openChat", "closeChat", "toggleChat", "newState", "addMessage", "prev", "setMessages", "newMessages", "validMessages", "msg", "reorganizedMessages", "clearMessages", "handleSSEEvent", "event", "eventType", "data", "messageStartData", "lastMessage", "messageId", "deltaData", "deltaText", "c", "lastContent", "updated", "existingIndex", "m", "blockData", "contentType", "contentData", "messageContent", "errorData"]
4
+ "sourcesContent": ["/**\n * \u804A\u5929\u72B6\u6001\u7BA1\u7406 Hook\n * \u7BA1\u7406\u6D88\u606F\u5217\u8868\u3001\u7A97\u53E3\u72B6\u6001\u3001\u8F93\u5165\u6846\u72B6\u6001\u3001\u6D41\u5F0F\u6D88\u606F\u7D2F\u79EF\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u72B6\u6001\u7BA1\u7406\u7B56\u7565\n */\n\nimport { useState, useCallback, useRef, useEffect } from 'react'\nimport type { Message, MessageContent, SSEEvent, StatusData, ErrorData, MessageStartData, BackendCartData, Product, TextContent, ProductCardContent, ProductListContent } from '../types'\nimport { getUserId, saveUserId } from '../utils/userId'\nimport { useSession } from './useSession'\nimport { transformProducts } from '../utils/productTransformers'\nimport { transformCartData } from '../utils/cartTransformers'\n\n// ============================================================================\n// \u8F85\u52A9\u51FD\u6570\uFF1A\u6587\u672C\u89E3\u6790\u548C\u6D88\u606F\u91CD\u7EC4\n// ============================================================================\n\n/**\n * \u5B9E\u65F6\u89E3\u6790\u6D41\u5F0F\u6587\u672C\u4E2D\u7684\u4EA7\u54C1\u5360\u4F4D\u7B26\n * \u5904\u7406\u7F13\u51B2\u533A\u4E2D\u7684\u6587\u672C\uFF0C\u68C0\u6D4B\u5B8C\u6574\u7684 {{product:xxx}} \u5360\u4F4D\u7B26\n *\n * @param buffer \u5F53\u524D\u7F13\u51B2\u533A\u5185\u5BB9\uFF08\u5305\u542B\u65B0\u63A5\u6536\u7684\u6587\u672C\uFF09\n * @param productMap handle \u2192 Product \u7684\u6620\u5C04\u8868\n * @param rawProductMap handle \u2192 raw backend product \u7684\u6620\u5C04\u8868\n * @param onAddToCart \u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n * @param productCardRender \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\u51FD\u6570\n * @returns { contents: \u9700\u8981\u6DFB\u52A0\u7684\u5185\u5BB9\u6570\u7EC4, remainingBuffer: \u5269\u4F59\u7F13\u51B2\u533A\u5185\u5BB9 }\n */\nfunction parseStreamingText(\n buffer: string,\n productMap: Map<string, Product>,\n rawProductMap: Map<string, any>,\n onAddToCart?: (product: Product) => void,\n productCardRender?: (product: any, productHandle: string) => React.ReactNode\n): { contents: MessageContent[]; remainingBuffer: string } {\n const contents: MessageContent[] = []\n const regex = /\\{\\{(?:product:)?([^}]+)\\}\\}/g\n\n let lastIndex = 0\n let match: RegExpExecArray | null\n let foundMatch = false\n\n // \u67E5\u627E\u6240\u6709\u5B8C\u6574\u7684\u5360\u4F4D\u7B26\n while ((match = regex.exec(buffer)) !== null) {\n foundMatch = true\n\n // \u63D0\u53D6\u5360\u4F4D\u7B26\u524D\u7684\u6587\u672C\n const beforeText = buffer.slice(lastIndex, match.index)\n if (beforeText) {\n contents.push({ type: 'text', text: beforeText } as TextContent)\n }\n\n // \u63D0\u53D6\u4EA7\u54C1 ID \u5E76\u521B\u5EFA\u4EA7\u54C1\u5361\u7247\n const productId = match[1].trim()\n const product = productMap.get(productId)\n const rawProduct = rawProductMap.get(productId)\n\n // \u65E0\u8BBA\u662F\u5426\u627E\u5230\u4EA7\u54C1\u6570\u636E\uFF0C\u90FD\u6E32\u67D3 product_card\uFF0C\u5E94\u7528\u5C42\u53EF\u901A\u8FC7 productHandle \u67E5\u8BE2\u4EA7\u54C1\n if (product) {\n console.log('[useChatState] \uD83C\uDFAF \u5B9E\u65F6\u68C0\u6D4B\u5230\u4EA7\u54C1:', productId, '\u2192', product.title)\n } else {\n console.log('[useChatState] \uD83D\uDCE6 \u5B9E\u65F6\u68C0\u6D4B\u5230\u4EA7\u54C1\u5360\u4F4D\u7B26\uFF0C\u4EA7\u54C1\u6570\u636E\u5F85\u5E94\u7528\u5C42\u67E5\u8BE2:', productId)\n }\n\n contents.push({\n type: 'product_card',\n data: {\n product: product,\n rawProduct: rawProduct,\n productHandle: productId,\n onAddToCart: onAddToCart,\n productCardRender: productCardRender\n }\n } as ProductCardContent)\n\n lastIndex = regex.lastIndex\n }\n\n // \u5982\u679C\u627E\u5230\u4E86\u81F3\u5C11\u4E00\u4E2A\u5B8C\u6574\u5360\u4F4D\u7B26\n if (foundMatch) {\n // \u8FD4\u56DE\u5269\u4F59\u7684\u6587\u672C\u4F5C\u4E3A\u7F13\u51B2\u533A\uFF08\u53EF\u80FD\u5305\u542B\u4E0D\u5B8C\u6574\u7684\u5360\u4F4D\u7B26\uFF09\n const remainingBuffer = buffer.slice(lastIndex)\n return { contents, remainingBuffer }\n } else {\n // \u6CA1\u6709\u627E\u5230\u5B8C\u6574\u5360\u4F4D\u7B26\uFF0C\u68C0\u67E5\u662F\u5426\u6709\u4E0D\u5B8C\u6574\u7684\u5360\u4F4D\u7B26\u5F00\u5934\n // \u4F8B\u5982\uFF1A\u7F13\u51B2\u533A\u662F \"some text {{prod\"\uFF0C\u6211\u4EEC\u9700\u8981\u4FDD\u7559 \"{{prod\" \u7B49\u5F85\u66F4\u591A\u6587\u672C\n const incompleteMatch = buffer.match(/\\{\\{[^}]*$/)\n\n if (incompleteMatch) {\n // \u6709\u4E0D\u5B8C\u6574\u7684\u5360\u4F4D\u7B26\u5F00\u5934\n const completeText = buffer.slice(0, incompleteMatch.index)\n if (completeText) {\n contents.push({ type: 'text', text: completeText } as TextContent)\n }\n return { contents, remainingBuffer: incompleteMatch[0] }\n } else {\n // \u6CA1\u6709\u4E0D\u5B8C\u6574\u7684\u5360\u4F4D\u7B26\uFF0C\u6574\u4E2A\u7F13\u51B2\u533A\u90FD\u662F\u666E\u901A\u6587\u672C\n if (buffer) {\n contents.push({ type: 'text', text: buffer } as TextContent)\n }\n return { contents, remainingBuffer: '' }\n }\n }\n}\n\n/**\n * \u89E3\u6790\u6587\u672C\u4E2D\u7684 {{handle}}\uFF0C\u8FD4\u56DE [text, product_card, text, ...] \u6570\u7EC4\uFF08\u7528\u4E8E\u5386\u53F2\u6D88\u606F\u91CD\u7EC4\uFF09\n *\n * @param text \u5305\u542B {{handle}} \u6807\u8BB0\u7684\u6587\u672C\n * @param productMap handle \u2192 Product \u7684\u6620\u5C04\u8868\n * @param rawProductMap handle \u2192 raw backend product \u7684\u6620\u5C04\u8868\n * @param onAddToCart \u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n * @param productCardRender \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\u51FD\u6570\n * @returns MessageContent \u6570\u7EC4\n *\n * @example\n * \u8F93\u5165\uFF1A\n * text: \"\u6211\u63A8\u8350\u4EE5\u4E0B\u4EA7\u54C1\uFF1A\\n{{product-handle}}\\n\u8FD9\u6B3E\u4EA7\u54C1\u6027\u4EF7\u6BD4\u5F88\u9AD8\u3002\"\n * productMap: Map { 'product-handle' => Product {...} }\n * \u8F93\u51FA\uFF1A\n * [\n * { type: 'text', text: '\u6211\u63A8\u8350\u4EE5\u4E0B\u4EA7\u54C1\uFF1A' },\n * { type: 'product_card', data: { product: {...}, onAddToCart } },\n * { type: 'text', text: '\u8FD9\u6B3E\u4EA7\u54C1\u6027\u4EF7\u6BD4\u5F88\u9AD8\u3002' }\n * ]\n */\nfunction parseTextWithProductIds(\n text: string,\n productMap: Map<string, Product>,\n rawProductMap: Map<string, any>,\n onAddToCart?: (product: Product) => void,\n productCardRender?: (product: any, productHandle: string) => React.ReactNode\n): MessageContent[] {\n const result: MessageContent[] = []\n // \u4FEE\u6539\u6B63\u5219\u8868\u8FBE\u5F0F\u4EE5\u5339\u914D {{product:ID}} \u683C\u5F0F\n // \u5339\u914D {{product:xxx}} \u6216 {{xxx}}\uFF08\u517C\u5BB9\u4E24\u79CD\u683C\u5F0F\uFF09\n const regex = /\\{\\{(?:product:)?([^}]+)\\}\\}/g\n\n let lastIndex = 0\n let match: RegExpExecArray | null\n\n while ((match = regex.exec(text)) !== null) {\n const beforeText = text.slice(lastIndex, match.index).trim()\n // match[1] \u662F\u6355\u83B7\u7EC4\u4E2D\u7684\u5185\u5BB9\uFF0C\u5373 product: \u540E\u9762\u7684 ID\n const productId = match[1].trim()\n\n // \u6DFB\u52A0\u524D\u9762\u7684\u6587\u672C\uFF08\u5982\u679C\u6709\uFF09\n if (beforeText) {\n result.push({\n type: 'text',\n text: beforeText,\n } as TextContent)\n }\n\n // \u6DFB\u52A0\u4EA7\u54C1\u5361\u7247\uFF08\u65E0\u8BBA\u662F\u5426\u627E\u5230\u4EA7\u54C1\u6570\u636E\uFF0C\u90FD\u6E32\u67D3 product_card\uFF09\n const product = productMap.get(productId)\n const rawProduct = rawProductMap.get(productId)\n if (product) {\n console.log(`[useChatState] \u2705 \u627E\u5230\u4EA7\u54C1\u5339\u914D: ${productId} \u2192 ${product.title}`)\n } else {\n console.log(`[useChatState] \uD83D\uDCE6 \u4EA7\u54C1\u5360\u4F4D\u7B26\uFF0C\u4EA7\u54C1\u6570\u636E\u5F85\u5E94\u7528\u5C42\u67E5\u8BE2: ${productId}`)\n }\n\n result.push({\n type: 'product_card',\n data: {\n product: product,\n rawProduct: rawProduct,\n productHandle: productId,\n onAddToCart: onAddToCart,\n productCardRender: productCardRender,\n },\n } as ProductCardContent)\n\n lastIndex = regex.lastIndex\n }\n\n // \u6DFB\u52A0\u6700\u540E\u5269\u4F59\u7684\u6587\u672C\n const remainingText = text.slice(lastIndex).trim()\n if (remainingText) {\n result.push({\n type: 'text',\n text: remainingText,\n } as TextContent)\n }\n\n return result\n}\n\n/**\n * \u91CD\u7EC4\u6D88\u606F\u5185\u5BB9\uFF1A\u89E3\u6790\u6587\u672C\u4E2D\u7684 {{handle}}\uFF0C\u66FF\u6362\u4E3A\u4EA7\u54C1\u5361\u7247\n *\n * \u5904\u7406\u903B\u8F91\uFF1A\n * 1. \u904D\u5386\u6D88\u606F\u7684\u6240\u6709 content blocks\n * 2. \u5BF9\u4E8E text \u7C7B\u578B\uFF0C\u89E3\u6790\u5176\u4E2D\u7684 {{handle}} \u5E76\u62C6\u5206\u4E3A\u591A\u4E2A content\n * 3. \u8DF3\u8FC7 product_list \u7C7B\u578B\uFF08\u5DF2\u7ECF\u88AB\u62C6\u5206\u5230\u6587\u672C\u4E2D\uFF09\n * 4. \u5176\u4ED6\u7C7B\u578B\u76F4\u63A5\u4FDD\u7559\n *\n * @param contents \u539F\u59CB\u6D88\u606F\u5185\u5BB9\u6570\u7EC4\n * @param productMap handle \u2192 Product \u7684\u6620\u5C04\u8868\n * @param rawProductMap handle \u2192 raw backend product \u7684\u6620\u5C04\u8868\n * @param onAddToCart \u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n * @param productCardRender \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\u51FD\u6570\n * @returns \u91CD\u7EC4\u540E\u7684\u6D88\u606F\u5185\u5BB9\u6570\u7EC4\n */\nfunction reorganizeMessageContent(\n contents: MessageContent[],\n productMap: Map<string, Product>,\n rawProductMap: Map<string, any>,\n onAddToCart?: (product: Product) => void,\n productCardRender?: (product: any, productHandle: string) => React.ReactNode\n): MessageContent[] {\n const result: MessageContent[] = []\n\n for (const content of contents) {\n // \u53EA\u5904\u7406\u6587\u672C\u7C7B\u578B\n if (content.type === 'text') {\n const textContent = content as TextContent\n const segments = parseTextWithProductIds(textContent.text, productMap, rawProductMap, onAddToCart, productCardRender)\n result.push(...segments)\n }\n // \u8DF3\u8FC7 product_list\uFF08\u5DF2\u7ECF\u88AB\u62C6\u5206\u5230\u6587\u672C\u4E2D\uFF09\n else if (content.type === 'product_list') {\n continue\n }\n // \u5176\u4ED6\u7C7B\u578B\u76F4\u63A5\u4FDD\u7559\n else {\n result.push(content)\n }\n }\n\n return result\n}\n\n/**\n * \u5904\u7406\u5355\u6761\u6D88\u606F\u7684\u91CD\u7EC4\uFF08\u7528\u4E8E\u5386\u53F2\u6D88\u606F\u52A0\u8F7D\uFF09\n * \u5982\u679C\u6D88\u606F\u5305\u542B\u5E26\u6709 {{}} \u7684\u6587\u672C\uFF0C\u5219\u8FDB\u884C\u91CD\u7EC4\n *\n * @param message \u539F\u59CB\u6D88\u606F\n * @param onAddToCart \u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n * @param productCardRender \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\u51FD\u6570\n * @returns \u91CD\u7EC4\u540E\u7684\u6D88\u606F\uFF08\u5982\u679C\u9700\u8981\u91CD\u7EC4\uFF09\uFF0C\u5426\u5219\u8FD4\u56DE\u539F\u6D88\u606F\n */\nfunction maybeReorganizeHistoricalMessage(\n message: Message,\n onAddToCart?: (product: Product) => void,\n productCardRender?: (product: any, productHandle: string) => React.ReactNode\n): Message {\n // \u68C0\u67E5\u6D88\u606F\u662F\u5426\u5305\u542B\u5E26\u6709 {{}} \u7684\u6587\u672C\n const hasPlaceholder = message.content.some(\n c => c.type === 'text' && /\\{\\{(?:product:)?[^}]+\\}\\}/.test((c as TextContent).text)\n )\n if (!hasPlaceholder) {\n return message // \u6CA1\u6709\u5360\u4F4D\u7B26\uFF0C\u4E0D\u9700\u8981\u91CD\u7EC4\n }\n\n console.log('[useChatState] \u68C0\u6D4B\u5230\u5386\u53F2\u6D88\u606F\u9700\u8981\u91CD\u7EC4, \u6D88\u606FID:', message.id)\n\n // \u6784\u5EFA\u4EA7\u54C1\u6620\u5C04 (handle \u2192 Product)\n const productMap = new Map<string, Product>()\n // \u4ECE structured_content \u4E2D\u63D0\u53D6\u539F\u59CB\u540E\u7AEF\u4EA7\u54C1\u6570\u636E (handle \u2192 rawProduct)\n const rawProductMap = new Map<string, any>()\n\n // \u4F18\u5148\u4ECE structured_content \u83B7\u53D6\u539F\u59CB\u4EA7\u54C1\u6570\u636E\uFF08\u5982\u679C\u5B58\u5728\uFF09\n if (message.structured_content) {\n message.structured_content.forEach(structuredContent => {\n if (structuredContent.type === 'product_list' && Array.isArray(structuredContent.data)) {\n structuredContent.data.forEach((rawProduct: any) => {\n if (rawProduct && rawProduct.handle) {\n rawProductMap.set(rawProduct.handle, rawProduct)\n console.log('[useChatState] \u4ECE structured_content \u63D0\u53D6\u539F\u59CB\u4EA7\u54C1\u6570\u636E, handle:', rawProduct.handle)\n }\n })\n }\n })\n }\n\n // \u6784\u5EFA\u8F6C\u6362\u540E\u7684\u4EA7\u54C1\u6620\u5C04\uFF08\u7528\u4E8E\u9ED8\u8BA4\u6E32\u67D3\uFF09\n message.content.forEach(content => {\n if (content.type === 'product_list') {\n const productListContent = content as ProductListContent\n productListContent.data.products.forEach(product => {\n if (product && product.handle) {\n productMap.set(product.handle, product)\n }\n })\n }\n })\n\n // \u91CD\u7EC4\u6D88\u606F\u5185\u5BB9\n const reorganizedContent = reorganizeMessageContent(message.content, productMap, rawProductMap, onAddToCart, productCardRender)\n\n // \u8FD4\u56DE\u65B0\u6D88\u606F\u5BF9\u8C61\n return {\n ...message,\n content: reorganizedContent,\n }\n}\n\nexport interface UseChatStateOptions {\n /**\n * \u521D\u59CB\u6B22\u8FCE\u6D88\u606F\n */\n welcomeMessage?: string\n\n /**\n * Shopify \u5E97\u94FA\u57DF\u540D\n */\n site?: string\n\n /**\n * \u53D7\u63A7\u6A21\u5F0F\uFF1A\u662F\u5426\u6253\u5F00\u804A\u5929\u7A97\u53E3\n * \u63D0\u4F9B\u6B64\u53C2\u6570\u65F6\uFF0C\u7EC4\u4EF6\u5904\u4E8E\u53D7\u63A7\u6A21\u5F0F\n */\n open?: boolean\n\n /**\n * \u53D7\u63A7\u6A21\u5F0F\uFF1A\u72B6\u6001\u53D8\u5316\u56DE\u8C03\uFF08\u5FC5\u9700\uFF09\n * \u7528\u4E8E\u540C\u6B65\u72B6\u6001\u5230\u7236\u7EC4\u4EF6\n */\n onOpenChange?: (open: boolean) => void\n\n /**\n * \u7A97\u53E3\u6253\u5F00\u4E8B\u4EF6\u76D1\u542C\uFF08\u53EF\u9009\uFF09\n * \u7528\u4E8E\u57CB\u70B9\u3001\u65E5\u5FD7\u7B49\u526F\u4F5C\u7528\n */\n onOpen?: () => void\n\n /**\n * \u7A97\u53E3\u5173\u95ED\u4E8B\u4EF6\u76D1\u542C\uFF08\u53EF\u9009\uFF09\n * \u7528\u4E8E\u57CB\u70B9\u3001\u65E5\u5FD7\u7B49\u526F\u4F5C\u7528\n */\n onClose?: () => void\n\n /**\n * \u6D88\u606F\u53D1\u9001\u56DE\u8C03\n */\n onMessageSend?: (message: string) => void\n\n /**\n * \u9519\u8BEF\u5904\u7406\u56DE\u8C03\n */\n onError?: (error: Error) => void\n\n /**\n * AI \u6D88\u606F\u56DE\u8C03\n */\n /**\n * AI \u56DE\u590D\u6587\u672C\u6D88\u606F\u65F6\u89E6\u53D1\n */\n onTextMessage?: () => void\n\n /**\n * AI \u56DE\u590D\u5546\u54C1\u5217\u8868\u5361\u7247\u65F6\u89E6\u53D1\n */\n onProductList?: () => void\n\n /**\n * AI \u56DE\u590D\u4FC3\u9500\u5361\u7247\u65F6\u89E6\u53D1\n * @param promotions \u4FC3\u9500\u6D3B\u52A8\u6570\u7EC4\u6570\u636E\n */\n onPromotionList?: (promotions: any[]) => void\n\n /**\n * \u5546\u54C1\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n */\n onAddToCart?: (product: any) => void\n\n /**\n * \u8D2D\u7269\u8F66\u6309\u94AE\u70B9\u51FB\u56DE\u8C03\n */\n onCart?: (cartId: string, checkoutUrl?: string) => void\n\n /**\n * \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\u51FD\u6570\n * @param product \u4EA7\u54C1\u6570\u636E\uFF08\u5982\u679C\u5728 product_list \u4E2D\u627E\u5230\uFF09\uFF0C\u5426\u5219\u4E3A undefined\n * @param productHandle \u6587\u672C\u5360\u4F4D\u7B26\u4E2D\u7684\u4EA7\u54C1 ID\uFF0C\u53EF\u7528\u4E8E\u5E94\u7528\u5C42\u67E5\u8BE2\u4EA7\u54C1\u6570\u636E\n */\n productCardRender?: (product: any, productHandle: string) => React.ReactNode\n\n /**\n * \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5BF9\u6BD4\u5361\u7247\u6E32\u67D3\u51FD\u6570\n * @param data \u4EA7\u54C1\u5BF9\u6BD4\u6570\u636E\uFF08\u5305\u542B products \u548C dimensions\uFF09\n */\n productComparisonRender?: (data: any) => React.ReactNode\n}\n\nexport interface UseChatStateReturn {\n /**\n * \u6D88\u606F\u5217\u8868\n */\n messages: Message[]\n\n /**\n * \u804A\u5929\u7A97\u53E3\u662F\u5426\u6253\u5F00\n */\n isOpen: boolean\n\n /**\n * \u7528\u6237 ID\uFF08undefined \u8868\u793A\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u7A7A\u5B57\u7B26\u4E32\u8868\u793A\u7531\u540E\u7AEF\u751F\u6210\uFF09\n */\n userId: string | undefined\n\n /**\n * \u4F1A\u8BDD ID\n */\n sessionId: string | null\n\n /**\n * \u8F93\u5165\u6846\u5185\u5BB9\n */\n inputValue: string\n\n /**\n * \u662F\u5426\u6B63\u5728\u63A5\u6536\u6D41\u5F0F\u6D88\u606F\n */\n isStreaming: boolean\n\n /**\n * \u6253\u5F00\u804A\u5929\u7A97\u53E3\n */\n openChat: () => void\n\n /**\n * \u5173\u95ED\u804A\u5929\u7A97\u53E3\n */\n closeChat: () => void\n\n /**\n * \u5207\u6362\u804A\u5929\u7A97\u53E3\u72B6\u6001\n */\n toggleChat: () => void\n\n /**\n * \u8BBE\u7F6E\u8F93\u5165\u6846\u5185\u5BB9\n */\n setInputValue: (value: string) => void\n\n /**\n * \u8BBE\u7F6E\u7528\u6237 ID\uFF08\u7528\u4E8E\u4FDD\u5B58\u540E\u7AEF\u8FD4\u56DE\u7684 userId\uFF09\n */\n setUserId: (id: string) => void\n\n /**\n * \u6DFB\u52A0\u6D88\u606F\u5230\u5217\u8868\n */\n addMessage: (message: Message) => void\n\n /**\n * \u6279\u91CF\u8BBE\u7F6E\u6D88\u606F\u5217\u8868\uFF08\u7528\u4E8E\u52A0\u8F7D\u5386\u53F2\uFF09\n */\n setMessages: (messages: Message[]) => void\n\n /**\n * \u6E05\u7A7A\u6D88\u606F\u5217\u8868\n */\n clearMessages: () => void\n\n /**\n * \u5904\u7406 SSE \u4E8B\u4EF6\n */\n handleSSEEvent: (event: SSEEvent) => void\n\n /**\n * \u4FDD\u5B58\u4F1A\u8BDD ID\n */\n saveSession: (id: string) => void\n\n /**\n * \u6E05\u7A7A\u4F1A\u8BDD\n */\n clearSession: () => void\n}\n\n/**\n * \u804A\u5929\u72B6\u6001\u7BA1\u7406 Hook\n *\n * \u529F\u80FD\uFF1A\n * 1. \u7BA1\u7406\u6D88\u606F\u5217\u8868\uFF08\u6DFB\u52A0\u3001\u6E05\u7A7A\u3001\u6279\u91CF\u8BBE\u7F6E\uFF09\n * 2. \u7BA1\u7406\u7A97\u53E3\u72B6\u6001\uFF08\u6253\u5F00\u3001\u5173\u95ED\u3001\u5207\u6362\uFF09\n * 3. \u7BA1\u7406\u8F93\u5165\u6846\u72B6\u6001\n * 4. \u5904\u7406 SSE \u6D41\u5F0F\u6D88\u606F\u4E8B\u4EF6\n * 5. \u7D2F\u79EF\u6D41\u5F0F\u6587\u672C\u5185\u5BB9\n *\n * @param options Hook \u914D\u7F6E\u9009\u9879\n * @returns \u72B6\u6001\u7BA1\u7406\u5DE5\u5177\u5BF9\u8C61\n */\nexport function useChatState(options: UseChatStateOptions = {}): UseChatStateReturn {\n const {\n welcomeMessage,\n site,\n open: controlledOpen,\n onOpenChange,\n onOpen,\n onClose,\n onMessageSend,\n onError,\n onTextMessage,\n onProductList,\n onPromotionList,\n onAddToCart,\n onCart,\n productCardRender,\n productComparisonRender,\n } = options\n\n // \u4F1A\u8BDD\u7BA1\u7406\n const { sessionId, saveSession, clearSession } = useSession()\n\n // \u7528\u6237 ID (\u521D\u59CB\u5316\u65F6\u5F02\u6B65\u751F\u6210\uFF0Cundefined \u8868\u793A\u5C1A\u672A\u521D\u59CB\u5316)\n const [userId, setUserId] = useState<string | undefined>(undefined)\n\n // \u521D\u59CB\u5316 userId\n useEffect(() => {\n getUserId().then(id => setUserId(id))\n }, [])\n\n // \u6D88\u606F\u5217\u8868\n const [messages, setMessagesState] = useState<Message[]>(() => {\n // \u5982\u679C\u6709\u6B22\u8FCE\u6D88\u606F\uFF0C\u521D\u59CB\u5316\u65F6\u6DFB\u52A0\n if (welcomeMessage) {\n return [\n {\n id: `welcome-${Date.now()}`,\n role: 'assistant',\n content: [{ type: 'text', text: welcomeMessage }],\n timestamp: Date.now(),\n },\n ]\n }\n return []\n })\n\n // \u804A\u5929\u7A97\u53E3\u662F\u5426\u6253\u5F00\uFF08\u652F\u6301\u53D7\u63A7\u548C\u975E\u53D7\u63A7\u4E24\u79CD\u6A21\u5F0F\uFF09\n const [internalOpen, setInternalOpen] = useState(false)\n const isControlled = controlledOpen !== undefined\n const isOpen = isControlled ? controlledOpen : internalOpen\n\n // \u8F93\u5165\u6846\u5185\u5BB9\n const [inputValue, setInputValue] = useState('')\n\n // \u662F\u5426\u6B63\u5728\u63A5\u6536\u6D41\u5F0F\u6D88\u606F\n const [isStreaming, setIsStreaming] = useState(false)\n\n // \u5F53\u524D\u6B63\u5728\u7D2F\u79EF\u7684\u6D41\u5F0F\u6D88\u606F (\u4E34\u65F6\u5B58\u50A8)\n const currentMessageRef = useRef<Message | null>(null)\n\n // \u6807\u8BB0\u5F53\u524D\u6D88\u606F\u662F\u5426\u5DF2\u89E6\u53D1 onTextMessage \u56DE\u8C03\uFF08\u907F\u514D\u91CD\u590D\u89E6\u53D1\uFF09\n const textMessageCallbackTriggeredRef = useRef<boolean>(false)\n\n // \u4EA7\u54C1\u6620\u5C04\u7F13\u5B58 (handle \u2192 Product)\uFF0C\u7528\u4E8E\u5B9E\u65F6\u89E3\u6790\u5360\u4F4D\u7B26\n const productMapRef = useRef<Map<string, Product>>(new Map())\n\n // \u539F\u59CB\u4EA7\u54C1\u6570\u636E\u7F13\u5B58 (handle \u2192 raw backend product)\uFF0C\u7528\u4E8E productCardRender\n const rawProductMapRef = useRef<Map<string, any>>(new Map())\n\n // \u6587\u672C\u7F13\u51B2\u533A\uFF0C\u7528\u4E8E\u5B58\u50A8\u672A\u5B8C\u6210\u7684\u6587\u672C\uFF08\u5904\u7406\u5360\u4F4D\u7B26\u8DE8\u8D8A\u591A\u4E2A delta \u7684\u60C5\u51B5\uFF09\n const textBufferRef = useRef<string>('')\n\n // \u5361\u7247\u7F13\u5B58\u961F\u5217\uFF0C\u7528\u4E8E\u5B58\u50A8\u9700\u8981\u5EF6\u8FDF\u663E\u793A\u7684\u5361\u7247\uFF08\u5728\u6587\u672C\u5B8C\u6210\u540E\u663E\u793A\uFF09\n const pendingCardsRef = useRef<MessageContent[]>([])\n\n /**\n * \u6253\u5F00\u804A\u5929\u7A97\u53E3\n */\n const openChat = useCallback(() => {\n if (!isControlled) {\n setInternalOpen(true)\n }\n onOpenChange?.(true)\n onOpen?.()\n }, [isControlled, onOpenChange, onOpen])\n\n /**\n * \u5173\u95ED\u804A\u5929\u7A97\u53E3\n */\n const closeChat = useCallback(() => {\n if (!isControlled) {\n setInternalOpen(false)\n }\n onOpenChange?.(false)\n onClose?.()\n }, [isControlled, onOpenChange, onClose])\n\n /**\n * \u5207\u6362\u804A\u5929\u7A97\u53E3\u72B6\u6001\n */\n const toggleChat = useCallback(() => {\n const newState = !isOpen\n if (!isControlled) {\n setInternalOpen(newState)\n }\n onOpenChange?.(newState)\n if (newState) {\n onOpen?.()\n } else {\n onClose?.()\n }\n }, [isControlled, isOpen, onOpenChange, onOpen, onClose])\n\n /**\n * \u6DFB\u52A0\u6D88\u606F\u5230\u5217\u8868\n */\n const addMessage = useCallback((message: Message) => {\n // \u9632\u62A4\uFF1A\u5982\u679C\u6D88\u606F\u4E3A null \u6216 undefined\uFF0C\u4E0D\u6DFB\u52A0\n if (!message) {\n console.warn('[useChatState] Attempted to add null/undefined message')\n return\n }\n setMessagesState(prev => [...prev, message])\n }, [])\n\n /**\n * \u6279\u91CF\u8BBE\u7F6E\u6D88\u606F\u5217\u8868\uFF08\u7528\u4E8E\u52A0\u8F7D\u5386\u53F2\uFF09\n */\n const setMessages = useCallback(\n (newMessages: Message[]) => {\n // \u9632\u62A4\uFF1A\u8FC7\u6EE4\u6389 null/undefined \u6D88\u606F\n const validMessages = newMessages.filter(msg => msg != null)\n if (validMessages.length !== newMessages.length) {\n console.warn('[useChatState] Filtered out null/undefined messages from batch set')\n }\n\n // \u5BF9\u6BCF\u6761\u5386\u53F2\u6D88\u606F\u8FDB\u884C\u91CD\u7EC4\uFF08\u5982\u679C\u9700\u8981\uFF09\n const reorganizedMessages = validMessages.map(msg => maybeReorganizeHistoricalMessage(msg, onAddToCart, productCardRender))\n\n setMessagesState(reorganizedMessages)\n },\n [onAddToCart, productCardRender]\n )\n\n /**\n * \u6E05\u7A7A\u6D88\u606F\u5217\u8868\n */\n const clearMessages = useCallback(() => {\n setMessagesState([])\n }, [])\n\n /**\n * \u5904\u7406 SSE \u4E8B\u4EF6\n * \u6839\u636E\u4E8B\u4EF6\u7C7B\u578B\u8FDB\u884C\u4E0D\u540C\u7684\u5904\u7406\n */\n const handleSSEEvent = useCallback(\n (event: SSEEvent) => {\n const { event: eventType, data } = event\n\n switch (eventType) {\n case 'message_start': {\n // \u5F00\u59CB\u63A5\u6536\u65B0\u6D88\u606F\n setIsStreaming(true)\n\n // \u91CD\u7F6E\u6587\u672C\u6D88\u606F\u56DE\u8C03\u6807\u8BB0\n textMessageCallbackTriggeredRef.current = false\n\n // \u91CD\u7F6E\u6587\u672C\u7F13\u51B2\u533A\n textBufferRef.current = ''\n\n // \u91CD\u7F6E\u5361\u7247\u7F13\u5B58\u961F\u5217\n pendingCardsRef.current = []\n\n // T039: \u4FDD\u5B58 sessionId \u548C userId\uFF08\u5982\u679C\u540E\u7AEF\u8FD4\u56DE\uFF09\n const messageStartData = data as MessageStartData & { userId?: string }\n if (messageStartData.sessionId && messageStartData.sessionId !== sessionId) {\n saveSession(messageStartData.sessionId)\n }\n // \u4FDD\u5B58\u540E\u7AEF\u8FD4\u56DE\u7684 userId\uFF08\u5982\u679C\u6709\uFF09\n if (messageStartData.userId) {\n saveUserId(messageStartData.userId)\n setUserId(messageStartData.userId)\n }\n\n // \u68C0\u67E5\u6700\u540E\u4E00\u6761\u6D88\u606F\u662F\u5426\u662F thinking \u6D88\u606F\uFF08\u7528\u6237\u53D1\u9001\u6D88\u606F\u65F6\u5DF2\u6DFB\u52A0\uFF09\n setMessagesState(prev => {\n const lastMessage = prev[prev.length - 1]\n const hasThinking =\n lastMessage &&\n lastMessage.role === 'assistant' &&\n lastMessage.content.length === 1 &&\n lastMessage.content[0].type === 'thinking'\n\n if (hasThinking) {\n // \u590D\u7528\u5DF2\u5B58\u5728\u7684 thinking \u6D88\u606F\n currentMessageRef.current = lastMessage\n return prev // \u4E0D\u9700\u8981\u6DFB\u52A0\u65B0\u6D88\u606F\n } else {\n // \u6CA1\u6709 thinking \u6D88\u606F\uFF0C\u521B\u5EFA\u65B0\u7684\uFF08\u517C\u5BB9\u5176\u4ED6\u573A\u666F\uFF09\n const messageId = `msg-${Date.now()}`\n currentMessageRef.current = {\n id: messageId,\n role: 'assistant',\n content: [{ type: 'thinking', data: { status: 'thinking' } }],\n timestamp: Date.now(),\n }\n return [...prev, currentMessageRef.current!]\n }\n })\n break\n }\n\n case 'content_delta': {\n // \u7D2F\u79EF\u6D41\u5F0F\u6587\u672C\u5185\u5BB9\uFF0C\u5E76\u5B9E\u65F6\u68C0\u6D4B\u4EA7\u54C1\u5360\u4F4D\u7B26\n const deltaData = data as any\n const deltaText = deltaData.delta || deltaData.text || ''\n\n if (currentMessageRef.current && deltaText) {\n // \u89E6\u53D1\u6587\u672C\u6D88\u606F\u56DE\u8C03\uFF08\u4EC5\u89E6\u53D1\u4E00\u6B21\uFF09\n if (!textMessageCallbackTriggeredRef.current) {\n textMessageCallbackTriggeredRef.current = true\n onTextMessage?.()\n }\n\n // \u79FB\u9664\u601D\u8003\u6C14\u6CE1\uFF08\u5982\u679C\u5B58\u5728\uFF09\n const hasThinking = currentMessageRef.current.content.some(c => c.type === 'thinking')\n if (hasThinking) {\n currentMessageRef.current.content = currentMessageRef.current.content.filter(c => c.type !== 'thinking')\n }\n\n // \u5C06\u65B0\u6587\u672C\u6DFB\u52A0\u5230\u7F13\u51B2\u533A\n textBufferRef.current += deltaText\n\n // \u5B9E\u65F6\u89E3\u6790\u7F13\u51B2\u533A\u4E2D\u7684\u5360\u4F4D\u7B26\n const { contents, remainingBuffer } = parseStreamingText(\n textBufferRef.current,\n productMapRef.current,\n rawProductMapRef.current,\n onAddToCart,\n productCardRender\n )\n\n // \u66F4\u65B0\u7F13\u51B2\u533A\u4E3A\u5269\u4F59\u5185\u5BB9\n textBufferRef.current = remainingBuffer\n\n // \u5C06\u89E3\u6790\u51FA\u7684\u5185\u5BB9\u6DFB\u52A0\u5230\u6D88\u606F\u4E2D\n if (contents.length > 0) {\n contents.forEach(content => {\n const lastContent = currentMessageRef.current!.content[\n currentMessageRef.current!.content.length - 1\n ] as MessageContent | undefined\n\n // \u5982\u679C\u662F\u6587\u672C\u5185\u5BB9\u4E14\u6700\u540E\u4E00\u4E2A\u4E5F\u662F\u6587\u672C\uFF0C\u5219\u5408\u5E76\n if (content.type === 'text' && lastContent && lastContent.type === 'text') {\n lastContent.text += content.text\n } else {\n // \u5426\u5219\u6DFB\u52A0\u65B0\u5185\u5BB9\n currentMessageRef.current!.content.push(content)\n }\n })\n\n // \u66F4\u65B0\u6D88\u606F\u5217\u8868\u4EE5\u89E6\u53D1\u6E32\u67D3\n setMessagesState(prev => {\n if (!currentMessageRef.current) return prev\n\n const updated = [...prev]\n const existingIndex = updated.findIndex(m => m && m.id === currentMessageRef.current!.id)\n\n if (existingIndex >= 0) {\n updated[existingIndex] = { ...currentMessageRef.current! }\n } else {\n updated.push({ ...currentMessageRef.current! })\n }\n\n return updated\n })\n }\n }\n break\n }\n\n case 'content_block': {\n // \u63A5\u6536\u7ED3\u6784\u5316\u5185\u5BB9\u5757\uFF08\u5546\u54C1\u3001\u653F\u7B56\u7B49\uFF09\n // API \u8FD4\u56DE\u683C\u5F0F\u53D8\u66F4:\n // \u65B0\u683C\u5F0F: {index: number, type: string, data: {...}} <- type \u5728\u5916\u5C42\n // \u65E7\u683C\u5F0F: {index: number, data: {type: string, ...}} <- type \u5728 data \u5185\n const blockData = data as any\n if (currentMessageRef.current) {\n // \u83B7\u53D6 type \u548C data\n // \u4F18\u5148\u4ECE\u5916\u5C42\u83B7\u53D6 type\uFF0C\u517C\u5BB9\u65E7\u683C\u5F0F\u4ECE data \u5185\u83B7\u53D6\n const contentType = blockData.type || blockData.data?.type\n const contentData = blockData.data\n\n if (!contentType || !contentData) {\n console.warn('[useChatState] Invalid content_block:', blockData)\n break\n }\n\n // ============================================================\n // \u8F6C\u6362\u6570\u636E\u7ED3\u6784\u4EE5\u5339\u914D\u7C7B\u578B\u5B9A\u4E49\n // \u6839\u636E\u540E\u7AEF\u6570\u636E\u7ED3\u6784\u89C4\u8303\uFF08\u98DE\u4E66\u6587\u6863\uFF09\u8FDB\u884C\u8F6C\u6362\n // ============================================================\n let messageContent: MessageContent\n\n // ========== 1. \u4EA7\u54C1\u5217\u8868\u5361\u7247 (Product List) ==========\n // \u540E\u7AEF\u683C\u5F0F: {type: \"product_list\", data: [product1, product2, ...]}\n // data \u76F4\u63A5\u662F\u4EA7\u54C1\u6570\u7EC4\uFF0C\u4E0D\u662F {products: [...]}\n if (contentType === 'product_list' && Array.isArray(contentData)) {\n // \u89E6\u53D1\u5546\u54C1\u5217\u8868\u56DE\u8C03\n onProductList?.()\n\n // \u7F13\u5B58\u539F\u59CB\u540E\u7AEF\u4EA7\u54C1\u6570\u636E\uFF08\u7528\u4E8E productCardRender\uFF09\n // \u4F7F\u7528 handle \u4F5C\u4E3A key \u8FDB\u884C\u5339\u914D\n contentData.forEach((rawProduct: any) => {\n if (rawProduct && rawProduct.handle) {\n rawProductMapRef.current.set(rawProduct.handle, rawProduct)\n console.log('[useChatState] \u7F13\u5B58\u539F\u59CB\u4EA7\u54C1\u6570\u636E, handle:', rawProduct.handle)\n }\n })\n\n // \u4F7F\u7528\u7EDF\u4E00\u7684\u4EA7\u54C1\u8F6C\u6362\u5DE5\u5177\u51FD\u6570\n const transformedProducts = transformProducts(contentData, site)\n\n // \u5EFA\u7ACB\u4EA7\u54C1\u6620\u5C04\u7F13\u5B58 (handle \u2192 Product)\n // \u7528\u4E8E\u540E\u7EED\u5728 message_end \u65F6\u89E3\u6790\u6587\u672C\u4E2D\u7684 {{handle}}\n transformedProducts.forEach(product => {\n if (product && product.handle) {\n productMapRef.current.set(product.handle, product)\n\n console.log('[useChatState] \u5EFA\u7ACB\u4EA7\u54C1\u6620\u5C04:', {\n handle: product.handle,\n title: product.title,\n })\n }\n })\n\n // \u26A0\uFE0F \u4E0D\u8981\u628A product_list \u6DFB\u52A0\u5230\u6D88\u606F\u4E2D\uFF0C\u907F\u514D\u95EA\u70C1\n // \u7B49\u5230 message_end \u65F6\uFF0C\u901A\u8FC7\u6587\u672C\u89E3\u6790\u521B\u5EFA product_card\uFF0C\u76F4\u63A5\u663E\u793A\u6700\u7EC8\u7684\u4EA4\u66FF\u683C\u5F0F\n 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')\n break // \u76F4\u63A5\u8DF3\u51FA\uFF0C\u4E0D\u6267\u884C\u540E\u7EED\u7684 push \u64CD\u4F5C\n }\n // ========== 2. \u4EA7\u54C1\u5BF9\u6BD4\u5361\u7247 (Product Comparison) ==========\n // \u540E\u7AEF\u683C\u5F0F: {type: \"product_comparison\", data: {products: [...], dimensions: {...}}}\n else if (contentType === 'product_comparison' && contentData.products) {\n // \u4F7F\u7528\u7EDF\u4E00\u7684\u4EA7\u54C1\u8F6C\u6362\u5DE5\u5177\u51FD\u6570\n const transformedProducts = transformProducts(contentData.products, site)\n\n messageContent = {\n type: 'product_comparison',\n data: {\n products: transformedProducts,\n dimensions: contentData.dimensions || {},\n productComparisonRender: productComparisonRender,\n },\n } as MessageContent\n }\n // ========== 3. FAQ \u5217\u8868\u5361\u7247 (FAQ List) ==========\n // \u540E\u7AEF\u683C\u5F0F: {type: \"faq_list\", data: {found, count, total, results: [...]}}\n else if (contentType === 'faq_list' && contentData.found !== undefined) {\n messageContent = {\n type: 'faq_list',\n data: contentData, // \u76F4\u63A5\u4F7F\u7528\uFF0C\u7ED3\u6784\u5DF2\u5339\u914D\n } as MessageContent\n }\n // ========== 4. \u5FEB\u6377\u56DE\u590D (Quick Replies) ==========\n else if (contentType === 'quick_replies' && contentData.replies) {\n messageContent = {\n type: 'quick_replies',\n data: {\n replies: contentData.replies,\n },\n } as MessageContent\n }\n // ========== 5. \u653F\u7B56\u5185\u5BB9 (Policy) ==========\n else if (contentType === 'policy' && contentData.title && contentData.content) {\n messageContent = {\n type: 'policy',\n data: {\n title: contentData.title,\n content: contentData.content,\n },\n } as MessageContent\n }\n // ========== 6. \u4FC3\u9500\u6D3B\u52A8\u5217\u8868 (Promotion List) ==========\n // \u540E\u7AEF\u683C\u5F0F: {type: \"promotion_list\", data: {found, count, total, results: [...]}}\n else if (contentType === 'promotion_list' && contentData.found !== undefined) {\n // \u89E6\u53D1\u4FC3\u9500\u5361\u7247\u56DE\u8C03\uFF0C\u4F20\u9012\u4FC3\u9500\u6D3B\u52A8\u6570\u7EC4\n onPromotionList?.(contentData.results || [])\n\n messageContent = {\n type: 'promotion_list',\n data: contentData, // \u76F4\u63A5\u4F7F\u7528\uFF0C\u7ED3\u6784\u5DF2\u5339\u914D\n } as MessageContent\n }\n // ========== 7. \u8D2D\u7269\u8F66 (Cart) ==========\n // \u540E\u7AEF\u683C\u5F0F: {type: \"cart\", data: {id, lines: {edges: [...]}, cost, ...}} (Shopify GraphQL)\n // \u9700\u8981\u8F6C\u6362\u4E3A\u524D\u7AEF\u683C\u5F0F: {cartId, lines: [...], cost, ...}\n else if (contentType === 'cart' && contentData.id !== undefined) {\n // \u8F6C\u6362\u540E\u7AEF Shopify GraphQL \u683C\u5F0F\u4E3A\u524D\u7AEF\u6807\u51C6\u683C\u5F0F\n const transformedData = transformCartData(contentData as BackendCartData)\n messageContent = {\n type: 'cart',\n data: {\n ...transformedData,\n onCart: onCart, // \u6CE8\u5165\u8D2D\u7269\u8F66\u6309\u94AE\u56DE\u8C03\n },\n } as MessageContent\n }\n // ========== 8. \u5176\u4ED6\u7C7B\u578B\uFF08\u901A\u7528\u5904\u7406\uFF09 ==========\n else {\n messageContent = {\n type: contentType,\n data: contentData,\n } as MessageContent\n }\n\n // \u26A0\uFE0F \u91CD\u8981\u4FEE\u6539\uFF1A\u5C06\u5361\u7247\u7F13\u5B58\u8D77\u6765\uFF0C\u4E0D\u7ACB\u5373\u6DFB\u52A0\u5230\u6D88\u606F\u4E2D\n // \u7B49\u5F85 message_end \u65F6\uFF0C\u5728\u6587\u672C\u5B8C\u6210\u540E\u518D\u7EDF\u4E00\u6DFB\u52A0\u5361\u7247\n console.log('[useChatState] \u2705 \u5361\u7247\u5DF2\u7F13\u5B58\uFF0C\u7B49\u5F85\u6587\u672C\u5B8C\u6210\u540E\u663E\u793A:', contentType)\n pendingCardsRef.current.push(messageContent)\n\n // \u4E0D\u518D\u7ACB\u5373\u66F4\u65B0\u6D88\u606F\u5217\u8868\uFF0C\u907F\u514D\u5361\u7247\u5728\u6587\u672C\u4E4B\u524D\u663E\u793A\n // \u539F\u6765\u7684\u4EE3\u7801\uFF1A\n // currentMessageRef.current.content.push(messageContent)\n // setMessagesState(prev => { ... })\n }\n break\n }\n\n case 'tool_start':\n case 'tool_end': {\n // \u5DE5\u5177\u8C03\u7528\u4E8B\u4EF6\uFF0C\u6682\u65F6\u5FFD\u7565\n // \u53EF\u4EE5\u5728\u672A\u6765\u7528\u4E8E\u663E\u793A\u5DE5\u5177\u8C03\u7528\u72B6\u6001\n break\n }\n\n case 'message_end': {\n // \u6D88\u606F\u63A5\u6536\u5B8C\u6210\n setIsStreaming(false)\n\n // \u5904\u7406\u7F13\u51B2\u533A\u4E2D\u5269\u4F59\u7684\u6587\u672C\uFF08\u5982\u679C\u6709\uFF09\n if (currentMessageRef.current && textBufferRef.current) {\n console.log('[useChatState] \u5904\u7406\u5269\u4F59\u7F13\u51B2\u533A:', textBufferRef.current)\n\n const lastContent = currentMessageRef.current.content[\n currentMessageRef.current.content.length - 1\n ] as MessageContent | undefined\n\n // \u5982\u679C\u6700\u540E\u4E00\u4E2A\u5185\u5BB9\u662F\u6587\u672C\uFF0C\u5219\u5408\u5E76\n if (lastContent && lastContent.type === 'text') {\n lastContent.text += textBufferRef.current\n } else {\n // \u5426\u5219\u6DFB\u52A0\u4E3A\u65B0\u7684\u6587\u672C\u5757\n currentMessageRef.current.content.push({\n type: 'text',\n text: textBufferRef.current,\n })\n }\n }\n\n // \u26A0\uFE0F \u91CD\u8981\u4FEE\u6539\uFF1A\u5728\u6587\u672C\u5B8C\u6210\u540E\uFF0C\u6DFB\u52A0\u6240\u6709\u7F13\u5B58\u7684\u5361\u7247\n if (currentMessageRef.current && pendingCardsRef.current.length > 0) {\n console.log('[useChatState] \uD83D\uDCCB \u6587\u672C\u5DF2\u5B8C\u6210\uFF0C\u73B0\u5728\u6DFB\u52A0', pendingCardsRef.current.length, '\u4E2A\u7F13\u5B58\u7684\u5361\u7247')\n\n // \u5C06\u6240\u6709\u7F13\u5B58\u7684\u5361\u7247\u6DFB\u52A0\u5230\u6D88\u606F\u5185\u5BB9\u4E2D\n currentMessageRef.current.content.push(...pendingCardsRef.current)\n }\n\n // \u26A0\uFE0F \u8D85\u65F6\u68C0\u6D4B\uFF1A\u5982\u679C message_end \u65F6\u4ECD\u5B58\u5728 thinking block\uFF0C\u89C6\u4E3A\u8D85\u65F6/\u5F02\u5E38\n // \u6CE8\u610F\uFF1A\u5728\u6DFB\u52A0\u5361\u7247\u4E4B\u540E\u68C0\u6D4B\uFF0C\u8FD9\u6837\u5982\u679C\u6709\u5361\u7247\u5C31\u4E0D\u4F1A\u6DFB\u52A0\u8D85\u65F6\u63D0\u793A\n if (currentMessageRef.current) {\n const hasThinking = currentMessageRef.current.content.some(c => c.type === 'thinking')\n\n if (hasThinking) {\n console.log('[useChatState] \u26A0\uFE0F message_end \u65F6\u4ECD\u5B58\u5728 thinking block\uFF0C\u7ACB\u5373\u79FB\u9664\uFF08\u89C6\u4E3A\u8D85\u65F6\uFF09')\n\n // \u79FB\u9664 thinking block\n currentMessageRef.current.content = currentMessageRef.current.content.filter(c => c.type !== 'thinking')\n\n // \u5982\u679C\u6CA1\u6709\u5176\u4ED6\u5185\u5BB9\uFF08\u5305\u62EC\u7F13\u5B58\u7684\u5361\u7247\uFF09\uFF0C\u6DFB\u52A0\u8D85\u65F6\u63D0\u793A\n if (currentMessageRef.current.content.length === 0) {\n currentMessageRef.current.content.push({\n type: 'text',\n text: 'Response timed out, please try again.',\n } as TextContent)\n }\n }\n }\n\n // \u66F4\u65B0\u6D88\u606F\u5217\u8868\uFF08\u7EDF\u4E00\u66F4\u65B0\uFF0C\u5305\u542B\u6587\u672C\u548C\u5361\u7247\uFF09\n if (currentMessageRef.current) {\n setMessagesState(prev => {\n if (!currentMessageRef.current) return prev\n\n const updated = [...prev]\n const existingIndex = updated.findIndex(m => m && m.id === currentMessageRef.current!.id)\n\n if (existingIndex >= 0) {\n updated[existingIndex] = { ...currentMessageRef.current! }\n }\n\n return updated\n })\n }\n\n // \u6E05\u7A7A\u7F13\u51B2\u533A\u3001\u4EA7\u54C1\u6620\u5C04\u548C\u5361\u7247\u7F13\u5B58\n textBufferRef.current = ''\n pendingCardsRef.current = []\n productMapRef.current.clear()\n rawProductMapRef.current.clear()\n currentMessageRef.current = null\n break\n }\n\n case 'status': {\n // T040: \u72B6\u6001\u66F4\u65B0\uFF08\u5982\u4F1A\u8BDD\u8FC7\u671F\uFF09\n const statusData = data as StatusData\n if (statusData.type === 'session_expired') {\n // \u4F1A\u8BDD\u8FC7\u671F\uFF0C\u6E05\u7A7A\u6D88\u606F\u5217\u8868\u548C\u4F1A\u8BDD\n clearMessages()\n clearSession()\n if (welcomeMessage) {\n addMessage({\n id: `welcome-${Date.now()}`,\n role: 'assistant',\n content: [{ type: 'text', text: welcomeMessage }],\n timestamp: Date.now(),\n })\n }\n }\n break\n }\n\n case 'error': {\n // \u9519\u8BEF\u5904\u7406\n const errorData = data as ErrorData\n setIsStreaming(false)\n\n // \u6E05\u7406\u7F13\u5B58\uFF08\u9632\u6B62\u6CC4\u6F0F\u5230\u4E0B\u6B21\u6D88\u606F\uFF09\n textBufferRef.current = ''\n pendingCardsRef.current = []\n productMapRef.current.clear()\n rawProductMapRef.current.clear()\n currentMessageRef.current = null\n\n // \u6DFB\u52A0\u9519\u8BEF\u6D88\u606F\u5230\u754C\u9762\n addMessage({\n id: `error-${Date.now()}`,\n role: 'system',\n content: [\n {\n type: 'error',\n data: {\n message: errorData.message,\n code: errorData.code,\n },\n },\n ],\n timestamp: Date.now(),\n })\n\n onError?.(new Error(errorData.message))\n break\n }\n\n case 'done': {\n // \u6D41\u7ED3\u675F\n setIsStreaming(false)\n\n // \u6E05\u7406\u7F13\u5B58\uFF08\u9632\u6B62\u6CC4\u6F0F\u5230\u4E0B\u6B21\u6D88\u606F\uFF09\n textBufferRef.current = ''\n pendingCardsRef.current = []\n productMapRef.current.clear()\n rawProductMapRef.current.clear()\n\n // \u6E05\u7406\u5F53\u524D\u6D88\u606F\u5F15\u7528\n currentMessageRef.current = null\n break\n }\n\n default:\n // \u5176\u4ED6\u4E8B\u4EF6\u7C7B\u578B\uFF08tool_start, tool_end \u7B49\uFF09\n break\n }\n },\n [\n welcomeMessage,\n site,\n addMessage,\n clearMessages,\n clearSession,\n saveSession,\n sessionId,\n onError,\n onTextMessage,\n onProductList,\n onPromotionList,\n onAddToCart,\n onCart,\n ]\n )\n\n\n return {\n messages,\n isOpen,\n userId,\n sessionId,\n inputValue,\n isStreaming,\n openChat,\n closeChat,\n toggleChat,\n setInputValue,\n setUserId,\n addMessage,\n setMessages,\n clearMessages,\n handleSSEEvent,\n saveSession,\n clearSession,\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["useState", "useCallback", "useRef", "useEffect", "getUserId", "saveUserId", "useSession", "transformProducts", "transformCartData", "parseStreamingText", "buffer", "productMap", "rawProductMap", "onAddToCart", "productCardRender", "contents", "regex", "lastIndex", "match", "foundMatch", "beforeText", "productId", "product", "rawProduct", "remainingBuffer", "incompleteMatch", "completeText", "parseTextWithProductIds", "text", "result", "remainingText", "reorganizeMessageContent", "content", "segments", "maybeReorganizeHistoricalMessage", "message", "structuredContent", "reorganizedContent", "useChatState", "options", "welcomeMessage", "site", "controlledOpen", "onOpenChange", "onOpen", "onClose", "onMessageSend", "onError", "onTextMessage", "onProductList", "onPromotionList", "onCart", "productComparisonRender", "sessionId", "saveSession", "clearSession", "userId", "setUserId", "id", "messages", "setMessagesState", "internalOpen", "setInternalOpen", "isControlled", "isOpen", "inputValue", "setInputValue", "isStreaming", "setIsStreaming", "currentMessageRef", "textMessageCallbackTriggeredRef", "productMapRef", "rawProductMapRef", "textBufferRef", "pendingCardsRef", "openChat", "closeChat", "toggleChat", "newState", "addMessage", "prev", "setMessages", "newMessages", "validMessages", "msg", "reorganizedMessages", "clearMessages", "handleSSEEvent", "event", "eventType", "data", "messageStartData", "lastMessage", "messageId", "deltaData", "deltaText", "c", "lastContent", "updated", "existingIndex", "m", "blockData", "contentType", "contentData", "messageContent", "errorData"]
7
7
  }
@@ -7,6 +7,6 @@ export { useChatState } from './hooks/useChatState';
7
7
  export { useChatAPI } from './hooks/useChatAPI';
8
8
  export { useSession } from './hooks/useSession';
9
9
  export { MessageRendererRegistry } from './utils/messageRenderers';
10
- export { getUserId } from './utils/userId';
10
+ export { getUserId, saveUserId, clearUserId } from './utils/userId';
11
11
  export { sanitizeInput, isValidUrl, isValidUUID, isValidMessageContent, escapeHtml } from './utils/validation';
12
12
  export { TextBlock, ProductCard, ProductList, PolicyBlock, QuickReplies, ThinkingBlock, ErrorBlock, FAQList, PromotionList, CartCard, createQuickRepliesRenderer, } from './components/MessageContent/index.js';
@@ -1,2 +1,2 @@
1
- import{LiveChatWidget as o}from"./LiveChatWidget";import{useChatState as n}from"./hooks/useChatState";import{useChatAPI as i}from"./hooks/useChatAPI";import{useSession as C}from"./hooks/useSession";import{MessageRendererRegistry as c}from"./utils/messageRenderers";import{getUserId as l}from"./utils/userId";import{sanitizeInput as g,isValidUrl as u,isValidUUID as P,isValidMessageContent as f,escapeHtml as k}from"./utils/validation";import{TextBlock as R,ProductCard as L,ProductList as M,PolicyBlock as y,QuickReplies as Q,ThinkingBlock as h,ErrorBlock as A,FAQList as I,PromotionList as S,CartCard as B,createQuickRepliesRenderer as D}from"./components/MessageContent/index.js";export{B as CartCard,A as ErrorBlock,I as FAQList,o as LiveChatWidget,c as MessageRendererRegistry,y as PolicyBlock,L as ProductCard,M as ProductList,S as PromotionList,Q as QuickReplies,R as TextBlock,h as ThinkingBlock,D as createQuickRepliesRenderer,k as escapeHtml,l as getUserId,f as isValidMessageContent,P as isValidUUID,u as isValidUrl,g as sanitizeInput,i as useChatAPI,n as useChatState,C as useSession};
1
+ import{LiveChatWidget as o}from"./LiveChatWidget";import{useChatState as s}from"./hooks/useChatState";import{useChatAPI as i}from"./hooks/useChatAPI";import{useSession as C}from"./hooks/useSession";import{MessageRendererRegistry as c}from"./utils/messageRenderers";import{getUserId as p,saveUserId as m,clearUserId as g}from"./utils/userId";import{sanitizeInput as P,isValidUrl as f,isValidUUID as k,isValidMessageContent as x,escapeHtml as R}from"./utils/validation";import{TextBlock as M,ProductCard as y,ProductList as I,PolicyBlock as Q,QuickReplies as h,ThinkingBlock as A,ErrorBlock as S,FAQList as U,PromotionList as B,CartCard as v,createQuickRepliesRenderer as D}from"./components/MessageContent/index.js";export{v as CartCard,S as ErrorBlock,U as FAQList,o as LiveChatWidget,c as MessageRendererRegistry,Q as PolicyBlock,y as ProductCard,I as ProductList,B as PromotionList,h as QuickReplies,M as TextBlock,A as ThinkingBlock,g as clearUserId,D as createQuickRepliesRenderer,R as escapeHtml,p as getUserId,x as isValidMessageContent,k as isValidUUID,f as isValidUrl,P as sanitizeInput,m as saveUserId,i as useChatAPI,s as useChatState,C as useSession};
2
2
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/components/LiveChatWidget/index.tsx"],
4
- "sourcesContent": ["/**\n * LiveChatWidget \u7EC4\u4EF6\u5165\u53E3\n */\n\nexport { LiveChatWidget } from './LiveChatWidget'\nexport type {\n LiveChatWidgetProps,\n Message,\n MessageContent,\n MessageRole,\n MessageContentType,\n MessageMetadata,\n Product,\n QuickReply,\n Session,\n BubblePosition,\n MessageRenderer,\n ProductCardContent,\n ProductListContent,\n PolicyContent,\n QuickRepliesContent,\n ThinkingContent,\n ErrorContent,\n FAQListContent,\n FAQItem,\n FAQCategory,\n PromotionListContent,\n PromotionItem,\n CartContent,\n CartData,\n CartLine,\n CartCost,\n CartAmount,\n CartDiscountCode,\n SSEEvent,\n ChatStreamRequest,\n ComplianceDialogConfig,\n} from './types'\n\n// \u5BFC\u51FA Hook (\u4F9B\u9AD8\u7EA7\u7528\u6237\u81EA\u5B9A\u4E49\u4F7F\u7528)\nexport { useChatState } from './hooks/useChatState'\nexport { useChatAPI } from './hooks/useChatAPI'\nexport { useSession } from './hooks/useSession'\n\n// \u5BFC\u51FA\u5DE5\u5177\u7C7B\nexport { MessageRendererRegistry } from './utils/messageRenderers'\nexport { getUserId } from './utils/userId'\nexport { sanitizeInput, isValidUrl, isValidUUID, isValidMessageContent, escapeHtml } from './utils/validation'\n\n// \u5BFC\u51FA\u6D88\u606F\u6E32\u67D3\u5668\uFF08\u4F9B\u81EA\u5B9A\u4E49\u4F7F\u7528\uFF09\nexport {\n TextBlock,\n ProductCard,\n ProductList,\n PolicyBlock,\n QuickReplies,\n ThinkingBlock,\n ErrorBlock,\n FAQList,\n PromotionList,\n CartCard,\n createQuickRepliesRenderer,\n} from './components/MessageContent/index.js'\n"],
5
- "mappings": "AAIA,OAAS,kBAAAA,MAAsB,mBAoC/B,OAAS,gBAAAC,MAAoB,uBAC7B,OAAS,cAAAC,MAAkB,qBAC3B,OAAS,cAAAC,MAAkB,qBAG3B,OAAS,2BAAAC,MAA+B,2BACxC,OAAS,aAAAC,MAAiB,iBAC1B,OAAS,iBAAAC,EAAe,cAAAC,EAAY,eAAAC,EAAa,yBAAAC,EAAuB,cAAAC,MAAkB,qBAG1F,OACE,aAAAC,EACA,eAAAC,EACA,eAAAC,EACA,eAAAC,EACA,gBAAAC,EACA,iBAAAC,EACA,cAAAC,EACA,WAAAC,EACA,iBAAAC,EACA,YAAAC,EACA,8BAAAC,MACK",
6
- "names": ["LiveChatWidget", "useChatState", "useChatAPI", "useSession", "MessageRendererRegistry", "getUserId", "sanitizeInput", "isValidUrl", "isValidUUID", "isValidMessageContent", "escapeHtml", "TextBlock", "ProductCard", "ProductList", "PolicyBlock", "QuickReplies", "ThinkingBlock", "ErrorBlock", "FAQList", "PromotionList", "CartCard", "createQuickRepliesRenderer"]
4
+ "sourcesContent": ["/**\n * LiveChatWidget \u7EC4\u4EF6\u5165\u53E3\n */\n\nexport { LiveChatWidget } from './LiveChatWidget'\nexport type {\n LiveChatWidgetProps,\n Message,\n MessageContent,\n MessageRole,\n MessageContentType,\n MessageMetadata,\n Product,\n QuickReply,\n Session,\n BubblePosition,\n MessageRenderer,\n ProductCardContent,\n ProductListContent,\n PolicyContent,\n QuickRepliesContent,\n ThinkingContent,\n ErrorContent,\n FAQListContent,\n FAQItem,\n FAQCategory,\n PromotionListContent,\n PromotionItem,\n CartContent,\n CartData,\n CartLine,\n CartCost,\n CartAmount,\n CartDiscountCode,\n SSEEvent,\n ChatStreamRequest,\n ComplianceDialogConfig,\n} from './types'\n\n// \u5BFC\u51FA Hook (\u4F9B\u9AD8\u7EA7\u7528\u6237\u81EA\u5B9A\u4E49\u4F7F\u7528)\nexport { useChatState } from './hooks/useChatState'\nexport { useChatAPI } from './hooks/useChatAPI'\nexport { useSession } from './hooks/useSession'\n\n// \u5BFC\u51FA\u5DE5\u5177\u7C7B\nexport { MessageRendererRegistry } from './utils/messageRenderers'\nexport { getUserId, saveUserId, clearUserId } from './utils/userId'\nexport { sanitizeInput, isValidUrl, isValidUUID, isValidMessageContent, escapeHtml } from './utils/validation'\n\n// \u5BFC\u51FA\u6D88\u606F\u6E32\u67D3\u5668\uFF08\u4F9B\u81EA\u5B9A\u4E49\u4F7F\u7528\uFF09\nexport {\n TextBlock,\n ProductCard,\n ProductList,\n PolicyBlock,\n QuickReplies,\n ThinkingBlock,\n ErrorBlock,\n FAQList,\n PromotionList,\n CartCard,\n createQuickRepliesRenderer,\n} from './components/MessageContent/index.js'\n"],
5
+ "mappings": "AAIA,OAAS,kBAAAA,MAAsB,mBAoC/B,OAAS,gBAAAC,MAAoB,uBAC7B,OAAS,cAAAC,MAAkB,qBAC3B,OAAS,cAAAC,MAAkB,qBAG3B,OAAS,2BAAAC,MAA+B,2BACxC,OAAS,aAAAC,EAAW,cAAAC,EAAY,eAAAC,MAAmB,iBACnD,OAAS,iBAAAC,EAAe,cAAAC,EAAY,eAAAC,EAAa,yBAAAC,EAAuB,cAAAC,MAAkB,qBAG1F,OACE,aAAAC,EACA,eAAAC,EACA,eAAAC,EACA,eAAAC,EACA,gBAAAC,EACA,iBAAAC,EACA,cAAAC,EACA,WAAAC,EACA,iBAAAC,EACA,YAAAC,EACA,8BAAAC,MACK",
6
+ "names": ["LiveChatWidget", "useChatState", "useChatAPI", "useSession", "MessageRendererRegistry", "getUserId", "saveUserId", "clearUserId", "sanitizeInput", "isValidUrl", "isValidUUID", "isValidMessageContent", "escapeHtml", "TextBlock", "ProductCard", "ProductList", "PolicyBlock", "QuickReplies", "ThinkingBlock", "ErrorBlock", "FAQList", "PromotionList", "CartCard", "createQuickRepliesRenderer"]
7
7
  }
@@ -103,6 +103,10 @@ export interface ProductComparisonContent {
103
103
  };
104
104
  onAddToCart?: (product: Product) => void;
105
105
  commonText?: CommonText;
106
+ productComparisonRender?: (data: {
107
+ products: Product[];
108
+ dimensions: ProductComparisonContent['data']['dimensions'];
109
+ }) => React.ReactNode;
106
110
  };
107
111
  }
108
112
  export interface PolicyContent {
@@ -507,10 +511,12 @@ export interface NewSessionRequest {
507
511
  site?: string;
508
512
  channel_code?: string;
509
513
  real_user_id?: string;
514
+ page_url?: string;
510
515
  }
511
516
  export interface NewSessionResponse {
512
517
  success: boolean;
513
518
  sessionId: string;
519
+ userId?: string;
514
520
  message: string;
515
521
  resumed?: boolean;
516
522
  messages?: Message[];
@@ -792,6 +798,14 @@ export interface LiveChatWidgetProps {
792
798
  * ```
793
799
  */
794
800
  productCardRender?: (product: any, productHandle: string) => React.ReactNode;
801
+ /**
802
+ * 自定义产品对比卡片渲染函数
803
+ * 用于自定义渲染 product_comparison 类型的产品对比卡片
804
+ * 当提供此函数时,将替代默认的产品对比卡片渲染逻辑
805
+ * @param data 产品对比数据(包含 products 和 dimensions)
806
+ * @returns React 可渲染的内容
807
+ */
808
+ productComparisonRender?: (data: ProductComparisonContent['data']) => React.ReactNode;
795
809
  /**
796
810
  * 输入框底部提示文本
797
811
  * 不传入则不显示