@anker-in/campaign-ui 0.3.4 → 0.3.5

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 (36) 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/PromotionList.js.map +2 -2
  4. package/dist/cjs/components/LiveChatWidget/components/MessageList.js +3 -3
  5. package/dist/cjs/components/LiveChatWidget/components/MessageList.js.map +3 -3
  6. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.d.ts +2 -1
  7. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js +1 -1
  8. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js.map +2 -2
  9. package/dist/cjs/components/LiveChatWidget/types.d.ts +2 -1
  10. package/dist/cjs/components/LiveChatWidget/types.js.map +1 -1
  11. package/dist/cjs/components/credits/creditsBanner/index.js +2 -2
  12. package/dist/cjs/components/credits/creditsBanner/index.js.map +2 -2
  13. package/dist/cjs/stories/LiveChatWidget.stories.js +2 -9
  14. package/dist/cjs/stories/LiveChatWidget.stories.js.map +2 -2
  15. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js +1 -1
  16. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
  17. package/dist/esm/components/LiveChatWidget/components/MessageContent/PromotionList.js.map +2 -2
  18. package/dist/esm/components/LiveChatWidget/components/MessageList.js +3 -3
  19. package/dist/esm/components/LiveChatWidget/components/MessageList.js.map +3 -3
  20. package/dist/esm/components/LiveChatWidget/hooks/useChatState.d.ts +2 -1
  21. package/dist/esm/components/LiveChatWidget/hooks/useChatState.js +1 -1
  22. package/dist/esm/components/LiveChatWidget/hooks/useChatState.js.map +2 -2
  23. package/dist/esm/components/LiveChatWidget/types.d.ts +2 -1
  24. package/dist/esm/components/credits/creditsBanner/index.js +2 -2
  25. package/dist/esm/components/credits/creditsBanner/index.js.map +2 -2
  26. package/dist/esm/stories/LiveChatWidget.stories.js +1 -8
  27. package/dist/esm/stories/LiveChatWidget.stories.js.map +2 -2
  28. package/package.json +2 -2
  29. package/src/components/LiveChatWidget/LiveChatWidget.tsx +20 -0
  30. package/src/components/LiveChatWidget/components/MessageContent/PromotionList.tsx +1 -1
  31. package/src/components/LiveChatWidget/components/MessageList.tsx +39 -44
  32. package/src/components/LiveChatWidget/hooks/useChatState.ts +4 -3
  33. package/src/components/LiveChatWidget/types.ts +2 -1
  34. package/src/components/credits/creditsBanner/index.tsx +5 -5
  35. package/src/stories/LiveChatWidget.stories.tsx +7 -12
  36. package/src/styles/livechat.css +29 -0
@@ -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 */\n onPromotionList?: () => 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\n onPromotionList?.()\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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",
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
6
  "names": ["useChatState_exports", "__export", "useChatState", "__toCommonJS", "import_react", "import_userId", "import_useSession", "import_productTransformers", "import_cartTransformers", "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", "c", "structuredContent", "reorganizedContent", "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", "lastContent", "updated", "existingIndex", "m", "blockData", "contentType", "contentData", "messageContent", "errorData"]
7
7
  }
@@ -745,8 +745,9 @@ export interface LiveChatWidgetProps {
745
745
  onProductList?: () => void;
746
746
  /**
747
747
  * AI 回复促销卡片时触发
748
+ * @param promotions 促销活动数组数据
748
749
  */
749
- onPromotionList?: () => void;
750
+ onPromotionList?: (promotions: PromotionItem[]) => void;
750
751
  /**
751
752
  * 商品操作回调
752
753
  */
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/components/LiveChatWidget/types.ts"],
4
- "sourcesContent": ["/**\n * LiveChat \u7EC4\u4EF6\u6838\u5FC3\u7C7B\u578B\u5B9A\u4E49\n * \u57FA\u4E8E specs/livechat-widget/data-model.md\n */\n\n// ============================================================================\n// Session Types (\u4F1A\u8BDD)\n// ============================================================================\n\nexport type SessionStatus = 'active' | 'expired'\n\nexport interface Session {\n sessionId: string\n userId: string\n site: string\n status: SessionStatus\n createdAt?: number\n lastActivityAt?: number\n}\n\n// ============================================================================\n// Message Types (\u6D88\u606F)\n// ============================================================================\n\nexport type MessageRole = 'user' | 'assistant' | 'system' | 'tool'\n\nexport interface MessageMetadata {\n tokenUsage?: {\n inputTokens: number\n outputTokens: number\n }\n toolCalls?: Array<{\n id: string\n type: string\n name: string\n }>\n}\n\nexport interface Message {\n id: string\n sessionId?: string\n role: MessageRole\n content: MessageContent[]\n timestamp: number\n metadata?: MessageMetadata\n structured_content?: Array<{\n type: string\n data: any\n }>\n}\n\n// ============================================================================\n// Message Content Types (\u6D88\u606F\u5185\u5BB9)\n// ============================================================================\n\nexport type MessageContentType =\n | 'text'\n | 'product_card'\n | 'product_list'\n | 'product_comparison'\n | 'policy'\n | 'quick_replies'\n | 'thinking'\n | 'error'\n | 'faq_list'\n | 'cart'\n\nexport type MessageContent =\n | TextContent\n | ProductCardContent\n | ProductListContent\n | ProductComparisonContent\n | PolicyContent\n | QuickRepliesContent\n | ThinkingContent\n | ErrorContent\n | FAQListContent\n | PromotionListContent\n | CartContent\n\nexport interface TextContent {\n type: 'text'\n text: string\n}\n\nexport interface ProductCardContent {\n type: 'product_card'\n data: {\n product?: Product\n rawProduct?: any // Raw backend product data (for custom render)\n productHandle: string // Product ID from placeholder {{product:ID}}, for app-level product lookup\n onAddToCart?: (product: Product) => void\n productCardRender?: (product: any, productHandle: string) => React.ReactNode\n }\n}\n\nexport interface ProductListContent {\n type: 'product_list'\n data: {\n products: Product[]\n title?: string\n onAddToCart?: (product: Product) => void\n commonText?: CommonText\n }\n}\n\nexport interface ProductComparisonContent {\n type: 'product_comparison'\n data: {\n products: Product[]\n dimensions: {\n price?: {\n label: string\n values: Array<{\n product_id: string\n min: number\n max: number\n currency: string\n has_discount: boolean\n }>\n }\n variants?: {\n label: string\n values: Array<{\n product_id: string\n count: number\n }>\n }\n member_price?: {\n label: string\n values: Array<{\n product_id: string\n available: boolean\n min: number\n max: number\n currency: string\n }>\n }\n discount?: {\n label: string\n values: Array<{\n product_id: string\n has_discount: boolean\n }>\n }\n [key: string]: any\n }\n onAddToCart?: (product: Product) => void\n commonText?: CommonText\n }\n}\n\nexport interface PolicyContent {\n type: 'policy'\n data: {\n title: string\n content: string\n }\n}\n\nexport interface QuickRepliesContent {\n type: 'quick_replies'\n data: {\n replies: QuickReply[]\n }\n}\n\nexport interface ThinkingContent {\n type: 'thinking'\n data: {\n status: string\n }\n}\n\nexport interface ErrorContent {\n type: 'error'\n data: {\n message: string\n code?: string\n }\n}\n\nexport interface FAQListContent {\n type: 'faq_list'\n data: {\n found: boolean\n count: number\n total?: number\n results: FAQItem[]\n }\n}\n\nexport interface PromotionListContent {\n type: 'promotion_list'\n data: {\n found: boolean\n count: number\n total?: number\n results: PromotionItem[]\n commonText?: CommonText\n }\n}\n\n// ============================================================================\n// FAQ Types (\u5E38\u89C1\u95EE\u9898)\n// ============================================================================\n\nexport type FAQCategory = 'shipping' | 'return' | 'product' | 'payment' | 'general'\n\nexport interface FAQItem {\n id: string\n question: string\n answer: string\n category: FAQCategory\n keywords?: string[]\n relatedQuestions?: string[]\n metadata?: {\n language?: string\n priority?: number\n lastUpdated?: string\n }\n}\n\n// ============================================================================\n// Promotion Types (\u4FC3\u9500\u6D3B\u52A8)\n// ============================================================================\n\nexport interface PromotionItem {\n id: string\n title: string\n subtitle?: string\n description?: string\n banner_url?: string\n url?: string\n time_range: {\n start: string\n end?: string | null\n is_active: boolean\n }\n priority?: number\n product_count?: number\n metadata?: {\n display_order?: number\n target_audience?: string\n }\n}\n\n// ============================================================================\n// Product Types (\u5546\u54C1)\n// ============================================================================\n\nexport type StockStatus = 'in_stock' | 'low_stock' | 'out_of_stock'\n\nexport interface Price {\n amount: number\n currency: string\n}\n\nexport interface PriceRange {\n min: number\n max: number\n currency: string\n}\n\nexport interface VariantDiscount {\n has_discount: boolean\n discount_price?: number\n discount_code?: string\n discount_percentage?: number\n /** \u6298\u6263\u7C7B\u578B\uFF1Afixed_amount \u56FA\u5B9A\u91D1\u989D\u6298\u6263\uFF0Cpercentage \u767E\u5206\u6BD4\u6298\u6263 */\n discount_type?: 'fixed_amount' | 'percentage'\n /** \u6298\u6263\u6570\u503C\uFF08\u53EF\u80FD\u662F\u5B57\u7B26\u4E32\u6216\u6570\u5B57\uFF09 */\n discount_value?: string | number\n}\n\nexport interface VariantMemberPrice {\n has_member_price: boolean\n price?: number\n}\n\nexport interface ProductFeatures {\n is_new?: boolean\n has_rental?: boolean\n has_presale?: boolean\n has_member_price?: boolean\n has_discount?: boolean\n}\n\nexport interface Variant {\n id: string\n title: string\n sku?: string\n price?: Price\n availableForSale: boolean\n color?: string\n discount?: VariantDiscount\n memberPrice?: VariantMemberPrice\n inventoryQuantity?: number\n option1?: string\n option2?: string\n option3?: string\n}\n\nexport interface Product {\n shopifyId: string\n sku?: string\n handle: string\n title: string\n description?: string\n vendor?: string\n price: Price\n priceRange?: PriceRange\n memberPriceRange?: PriceRange\n imageUrl: string\n productUrl: string\n stockStatus: StockStatus\n hotScore?: number\n averageRating?: number\n reviewCount?: number\n variants?: Variant[]\n variantCount?: number\n availableCount?: number\n features?: ProductFeatures\n tags?: string[]\n}\n\n// ============================================================================\n// Quick Reply Types (\u5FEB\u6377\u56DE\u590D)\n// ============================================================================\n\nexport interface QuickReply {\n id: string\n label: string\n value: string\n icon?: string\n}\n\n// ============================================================================\n// Policy Types (\u653F\u7B56)\n// ============================================================================\n\nexport interface Policy {\n title: string\n content: string\n}\n\n// ============================================================================\n// Cart Types (\u8D2D\u7269\u8F66)\n// ============================================================================\n\n/**\n * \u8D2D\u7269\u8F66\u91D1\u989D\u4FE1\u606F\n */\nexport interface CartAmount {\n /** \u91D1\u989D\u5B57\u7B26\u4E32\uFF08\u5982 \"99.99\"\uFF09 */\n amount: string\n /** \u8D27\u5E01\u4EE3\u7801\uFF08\u5982 \"USD\"\uFF09 */\n currencyCode: string\n}\n\n/**\n * \u8D2D\u7269\u8F66\u4EF7\u683C\u6C47\u603B\n */\nexport interface CartCost {\n /** \u5E94\u4ED8\u603B\u4EF7\uFF08\u5DF2\u5E94\u7528\u6298\u6263\uFF09 */\n totalAmount: CartAmount\n /** \u539F\u4EF7\u5C0F\u8BA1\uFF08\u672A\u5E94\u7528\u6298\u6263\uFF09 */\n subtotalAmount: CartAmount\n}\n\n/**\n * \u8D2D\u7269\u8F66\u5546\u54C1\u53D8\u4F53\u4FE1\u606F\n */\nexport interface CartMerchandise {\n /** \u53D8\u4F53 ID (Shopify ProductVariant GID) */\n id: string\n /** \u53D8\u4F53\u6807\u9898\uFF08\u5982 \"Black\", \"Large\" \u7B49\uFF09 */\n title: string\n /** \u5355\u4EF7 */\n price: CartAmount\n /** \u5546\u54C1\u56FE\u7247 URL */\n image?: {\n url: string\n altText?: string\n }\n /** \u5173\u8054\u7684\u5546\u54C1\u4FE1\u606F */\n product: {\n /** \u5546\u54C1 ID */\n id: string\n /** \u5546\u54C1\u6807\u9898 */\n title: string\n /** \u5546\u54C1 handle */\n handle: string\n }\n}\n\n/**\n * \u8D2D\u7269\u8F66\u5546\u54C1\u884C\n */\nexport interface CartLine {\n /** \u8D2D\u7269\u8F66\u884C ID (\u7528\u4E8E\u66F4\u65B0/\u5220\u9664\u64CD\u4F5C) */\n id: string\n /** \u5546\u54C1\u6570\u91CF */\n quantity: number\n /** \u4EF7\u683C\u4FE1\u606F */\n cost: {\n /** \u884C\u603B\u4EF7\uFF08\u5355\u4EF7 \u00D7 \u6570\u91CF\uFF0C\u5DF2\u5E94\u7528\u6298\u6263\uFF09 */\n totalAmount: CartAmount\n /** \u5355\u4EF7 */\n amountPerQuantity: CartAmount\n /** \u884C\u5C0F\u8BA1\uFF08\u5355\u4EF7 \u00D7 \u6570\u91CF\uFF0C\u672A\u5E94\u7528\u6298\u6263\uFF09 */\n subtotalAmount: CartAmount\n }\n /** \u5546\u54C1\u53D8\u4F53\u4FE1\u606F */\n merchandise: CartMerchandise\n /** \u81EA\u5B9A\u4E49\u5C5E\u6027\uFF08\u53EF\u9009\uFF09 */\n attributes?: Array<{\n key: string\n value: string\n }>\n}\n\n/**\n * \u8D2D\u7269\u8F66\u6298\u6263\u7801\n */\nexport interface CartDiscountCode {\n /** \u6298\u6263\u7801 */\n code: string\n /** \u662F\u5426\u6709\u6548/\u9002\u7528 */\n applicable: boolean\n}\n\n/**\n * \u8D2D\u7269\u8F66\u6570\u636E\n */\nexport interface CartData {\n /** \u8D2D\u7269\u8F66\u662F\u5426\u4E3A\u7A7A */\n isEmpty: boolean\n /** \u8D2D\u7269\u8F66 ID (Shopify Cart GID) */\n cartId: string\n /** \u5546\u54C1\u603B\u6570\u91CF */\n totalQuantity: number\n /** \u5546\u54C1\u5217\u8868 */\n lines: CartLine[]\n /** \u4EF7\u683C\u6C47\u603B */\n cost: CartCost\n /** \u6298\u6263\u7801\u5217\u8868 */\n discountCodes?: CartDiscountCode[]\n /** \u7ED3\u8D26\u9875\u9762 URL */\n checkoutUrl?: string\n /** \u8D2D\u7269\u8F66\u6309\u94AE\u56DE\u8C03\u51FD\u6570 */\n onCart?: (cartId: string, checkoutUrl?: string) => void\n /** \u901A\u7528\u6587\u6848\u914D\u7F6E */\n commonText?: CommonText\n}\n\n/**\n * \u8D2D\u7269\u8F66\u5185\u5BB9\u5757\n */\nexport interface CartContent {\n type: 'cart'\n data: CartData\n}\n\n// ============================================================================\n// Backend Cart Types (\u540E\u7AEF\u8D2D\u7269\u8F66\u6570\u636E\u683C\u5F0F - Shopify GraphQL)\n// ============================================================================\n\n/**\n * \u540E\u7AEF\u8FD4\u56DE\u7684\u8D2D\u7269\u8F66\u5546\u54C1\u884C\u6570\u636E (Shopify GraphQL \u683C\u5F0F)\n */\nexport interface BackendCartLineNode {\n id: string\n quantity: number\n cost: {\n totalAmount: CartAmount\n amountPerQuantity: CartAmount\n compareAtAmountPerQuantity?: CartAmount | null\n subtotalAmount: CartAmount\n }\n discountAllocations: any[]\n merchandise: {\n id: string\n title: string\n availableForSale: boolean\n quantityAvailable?: number\n price: CartAmount\n compareAtPrice?: CartAmount | null\n image?: {\n url: string\n altText?: string | null\n width?: number\n height?: number\n } | null\n product: {\n id: string\n title: string\n handle: string\n vendor?: string\n featuredImage?: {\n url: string\n altText?: string | null\n width?: number\n height?: number\n } | null\n tags?: string[]\n }\n }\n attributes?: Array<{\n key: string\n value: string\n }>\n}\n\n/**\n * \u540E\u7AEF\u8FD4\u56DE\u7684\u8D2D\u7269\u8F66\u6570\u636E (Shopify GraphQL \u683C\u5F0F)\n */\nexport interface BackendCartData {\n id: string\n checkoutUrl?: string\n totalQuantity: number\n lines: {\n edges: Array<{\n node: BackendCartLineNode\n }>\n }\n cost: {\n totalAmount: CartAmount\n subtotalAmount: CartAmount\n checkoutChargeAmount?: CartAmount\n totalAmountEstimated?: boolean\n subtotalAmountEstimated?: boolean\n totalTaxAmount?: CartAmount | null\n totalDutyAmount?: CartAmount | null\n }\n discountAllocations?: any[]\n buyerIdentity?: any\n attributes?: Array<{\n key: string\n value: string\n }>\n discountCodes?: any[]\n createdAt?: string\n updatedAt?: string\n}\n\n// ============================================================================\n// SSE Event Types (SSE \u4E8B\u4EF6)\n// ============================================================================\n\nexport type SSEEventType =\n | 'message_start'\n | 'content_delta'\n | 'content_block'\n | 'message_end'\n | 'tool_start'\n | 'tool_end'\n | 'status'\n | 'error'\n | 'done'\n\nexport interface SSEEvent<T = any> {\n event: SSEEventType | null\n data: T\n}\n\n// \u5177\u4F53\u4E8B\u4EF6\u6570\u636E\u7C7B\u578B\nexport interface MessageStartData {\n sessionId: string\n}\n\nexport interface ContentDeltaData {\n text: string\n}\n\nexport interface ContentBlockData {\n type: string\n data: any\n}\n\nexport interface MessageEndData {\n usage: {\n inputTokens: number\n outputTokens: number\n }\n}\n\nexport interface ToolStartData {\n id: string\n type: string\n name: string\n}\n\nexport interface ToolEndData {\n id: string\n}\n\nexport interface StatusData {\n type: string\n message?: string\n}\n\nexport interface ErrorData {\n message: string\n code?: string\n type?: string\n}\n\n// ============================================================================\n// API Request/Response Types (API \u8BF7\u6C42\u54CD\u5E94)\n// ============================================================================\n\n/**\n * \u6D41\u5F0F\u5BF9\u8BDD\u8BF7\u6C42\u53C2\u6570\n */\nexport interface ChatStreamRequest {\n /** \u7528\u6237\u7684\u6D88\u606F\u6587\u672C */\n message: string\n /** \u7528\u6237\u6807\u8BC6\u7B26 */\n user_id: string\n /** \u6765\u81EA new-session \u7AEF\u70B9\u7684\u4F1A\u8BDD ID */\n session_id: string\n /** \u53EF\u9009\u7684\u4E0A\u4E0B\u6587\u4FE1\u606F */\n context?: {\n /** \u7528\u4E8E\u8D2D\u7269\u8F66\u64CD\u4F5C\u7684 Shopify \u8D2D\u7269\u8F66 ID */\n cartId?: string\n /** Storefront API \u8BBF\u95EE\u4EE4\u724C */\n accessToken?: string\n /** \u5DF2\u767B\u5F55\u7528\u6237\u7684 ID */\n real_user_id?: string\n }\n}\n\nexport interface NewSessionRequest {\n user_id: string\n session_id?: string\n site?: string\n channel_code?: string\n real_user_id?: string\n}\n\nexport interface NewSessionResponse {\n success: boolean\n sessionId: string\n message: string\n resumed?: boolean\n messages?: Message[]\n welcomeMessage?: string\n quickQuestions?: string[]\n brand?: string\n}\n\nexport interface ErrorResponse {\n success: boolean\n error: string\n code?: string\n details?: any\n}\n\n// ============================================================================\n// Common Text Types (\u901A\u7528\u6587\u6848)\n// ============================================================================\n\n/**\n * \u901A\u7528\u6587\u6848\u914D\u7F6E\n * \u7528\u4E8E\u81EA\u5B9A\u4E49\u7EC4\u4EF6\u4E2D\u7684\u6309\u94AE\u6587\u6848\n */\nexport interface CommonText {\n /**\n * \u4EA7\u54C1\u5217\u8868\u5C55\u5F00\u6309\u94AE\u6587\u6848\n * @default \"Learn More\"\n */\n learnMore?: string\n\n /**\n * \u4EA7\u54C1\u5217\u8868\u6536\u8D77\u6309\u94AE\u6587\u6848\n * @default \"Show Less\"\n */\n showLess?: string\n\n /**\n * \u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u6309\u94AE\u6587\u6848\n * @default \"Add to Cart\"\n */\n addToCart?: string\n\n /**\n * \u67E5\u770B\u8D2D\u7269\u8F66/\u66F4\u591A\u6309\u94AE\u6587\u6848\n * @default \"View More\"\n */\n viewMore?: string\n\n /**\n * \u6298\u6263\u6807\u7B7E\u540E\u7F00\u6587\u6848\uFF08\u5982 \"20% OFF\" \u4E2D\u7684 \"OFF\"\uFF09\n * @default \"OFF\"\n */\n off?: string\n\n /**\n * \u8D2D\u7269\u8F66\u603B\u8BA1\u6587\u6848\n * @default \"Total\"\n */\n total?: string\n}\n\n// ============================================================================\n// Compliance Dialog Types (\u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97)\n// ============================================================================\n\n/**\n * \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u914D\u7F6E\n */\nexport interface ComplianceDialogConfig {\n /**\n * \u5F39\u7A97\u6807\u9898\n * @example \"Hi! I'm your eufy AI assistant.\"\n */\n title: string\n\n /**\n * \u5F39\u7A97\u5185\u5BB9\u6587\u672C\uFF08\u652F\u6301 HTML\uFF09\n * @example \"AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data\"\n */\n content: string\n\n /**\n * \u52FE\u9009\u6846\u6587\u672C\uFF08\u652F\u6301\u5B8C\u6574 HTML\uFF0C\u5305\u62EC\u94FE\u63A5\uFF09\n * \u53EF\u4EE5\u76F4\u63A5\u5305\u542B <a> \u6807\u7B7E\u7B49 HTML \u5143\u7D20\n * @example \"By starting to use \\\"Live Chat\\\", you agree to Anker's <a href=\\\"https://www.anker.com/privacy\\\" target=\\\"_blank\\\">LIVE CHAT PRIVACY NOTICE</a>.\"\n */\n checkboxText: string\n\n /**\n * \u540C\u610F\u6309\u94AE\u6587\u672C\n * @default \"Agree\"\n */\n agreeButtonText?: string\n\n /**\n * Cookie \u540D\u79F0\uFF0C\u7528\u4E8E\u8BB0\u5F55\u7528\u6237\u540C\u610F\u72B6\u6001\n * Cookie \u6709\u6548\u671F\u4E3A 365 \u5929\n * @default \"livechat_compliance_agreed\"\n */\n cookieName?: string\n}\n\n// ============================================================================\n// Component Props Types (\u7EC4\u4EF6 Props)\n// ============================================================================\n\nexport interface LiveChatWidgetProps {\n /**\n * API \u57FA\u7840 URL\n * @example \"https://beta-api-livechat.anker.com\"\n */\n apiBaseUrl: string\n\n /**\n * \u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\n * \u5C06\u5728\u6240\u6709 API \u8BF7\u6C42\u4E2D\u6DFB\u52A0\u8FD9\u4E9B\u8BF7\u6C42\u5934\n * @example { \"Authorization\": \"Bearer token\", \"X-Custom-Header\": \"value\" }\n */\n headers?: Record<string, string>\n\n /**\n * reCAPTCHA site key\n * \u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u81EA\u52A8\u542F\u7528 reCAPTCHA v3 \u9A8C\u8BC1\n * @example \"6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14\"\n */\n recaptchaSitekey?: string\n\n /**\n * reCAPTCHA action \u524D\u7F00\n * \u5B9E\u9645\u4F7F\u7528\u65F6\u4F1A\u6839\u636E\u4E0D\u540C\u63A5\u53E3\u6DFB\u52A0\u540E\u7F00\uFF08\u5982 chat_stream, new_session\uFF09\n * @default \"livechat\"\n */\n recaptchaAction?: string\n\n /**\n * Shopify \u5E97\u94FA\u57DF\u540D\n * @example \"www.eufy.com\"\n */\n site?: string\n\n /**\n * \u6E20\u9053\u7F16\u7801\n * \u7528\u4E8E\u6807\u8BC6\u6765\u6E90\u6E20\u9053\n * @example \"web_homepage\"\n */\n channelCode?: string\n\n /**\n * \u5DF2\u767B\u5F55\u7528\u6237\u7684 ID\uFF08\u53EF\u9009\uFF09\n * \u5982\u679C\u63D0\u4F9B\uFF0C\u5C06\u5728 API \u8BF7\u6C42\u4E2D\u4F20\u9012\n */\n loginUserId?: string\n\n /**\n * Shopify \u8D2D\u7269\u8F66 ID\uFF08\u53EF\u9009\uFF09\n * \u7528\u4E8E\u8D2D\u7269\u8F66\u64CD\u4F5C\uFF0C\u5C06\u5728 stream \u63A5\u53E3\u7684 context \u4E2D\u4F20\u9012\n * @example \"gid://shopify/Cart/Z2NwLXVzLWVhc3QxOjAxSkZH...\"\n */\n cartId?: string\n\n /**\n * Storefront API \u8BBF\u95EE\u4EE4\u724C\uFF08\u53EF\u9009\uFF09\n * \u7528\u4E8E\u8D2D\u7269\u8F66\u64CD\u4F5C\uFF0C\u5C06\u5728 stream \u63A5\u53E3\u7684 context \u4E2D\u4F20\u9012\n */\n accessToken?: string\n\n /**\n * \u6C14\u6CE1\u6309\u94AE\u4F4D\u7F6E\n * \u81EA\u5B9A\u4E49\u4F4D\u7F6E\u5BF9\u8C61\uFF1A{ top?: string, bottom?: string, left?: string, right?: string }\n * @default { bottom: \"1.5rem\", right: \"1.5rem\" }\n * @example\n * // \u81EA\u5B9A\u4E49\u4F4D\u7F6E\n * position={{ bottom: \"20px\", right: \"30px\" }}\n * position={{ top: \"100px\", left: \"50px\" }}\n */\n position?: BubblePosition\n\n /**\n * \u6B22\u8FCE\u6D88\u606F\n */\n welcomeMessage?: string\n\n /**\n * \u521D\u59CB\u5FEB\u6377\u56DE\u590D\u6309\u94AE\n */\n quickReplies?: QuickReply[]\n\n /**\n * \u81EA\u5B9A\u4E49\u6D88\u606F\u6E32\u67D3\u5668\n */\n customRenderers?: Record<string, MessageRenderer>\n\n /**\n * Logo URL\n */\n logoUrl?: string\n\n /**\n * \u804A\u5929\u7A97\u53E3\u6807\u9898\n * @default \"AI \u52A9\u624B\"\n */\n title?: string\n\n /**\n * \u804A\u5929\u6C14\u6CE1\u6309\u94AE\u56FE\u6807\uFF08\u56FE\u7247 URL\uFF09\n * \u5982\u679C\u63D0\u4F9B\uFF0C\u5C06\u4F7F\u7528\u56FE\u7247\u66FF\u4EE3\u9ED8\u8BA4\u7684 SVG \u56FE\u6807\n */\n chatBubbleIcon?: 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\u5C06\u5904\u4E8E\u53D7\u63A7\u6A21\u5F0F\n * @example\n * ```tsx\n * const [isOpen, setIsOpen] = useState(false)\n * <LiveChatWidget open={isOpen} onOpenChange={setIsOpen} />\n * ```\n */\n open?: boolean\n\n /**\n * \u53D7\u63A7\u6A21\u5F0F\uFF1A\u6253\u5F00/\u5173\u95ED\u72B6\u6001\u53D8\u5316\u56DE\u8C03\n * \u3010\u5FC5\u9700\u3011\u914D\u5408 `open` \u4F7F\u7528\uFF0C\u7528\u4E8E\u540C\u6B65\u72B6\u6001\u5230\u7236\u7EC4\u4EF6\n * \u5F53\u7528\u6237\u70B9\u51FB\u6253\u5F00\u6216\u5173\u95ED\u6309\u94AE\u65F6\u89E6\u53D1\n * @example\n * ```tsx\n * <LiveChatWidget\n * open={isOpen}\n * onOpenChange={setIsOpen} // \u5FC5\u9700\uFF1A\u540C\u6B65\u72B6\u6001\n * />\n * ```\n */\n onOpenChange?: (open: boolean) => void\n\n /**\n * \u3010\u53EF\u9009\u3011\u7A97\u53E3\u6253\u5F00\u4E8B\u4EF6\u76D1\u542C\n * \u7528\u4E8E\u57CB\u70B9\u3001\u65E5\u5FD7\u7B49\u526F\u4F5C\u7528\uFF0C\u4E0D\u5F71\u54CD\u72B6\u6001\u63A7\u5236\n * \u5728 onOpenChange \u4E4B\u540E\u89E6\u53D1\n * @example\n * ```tsx\n * <LiveChatWidget\n * open={isOpen}\n * onOpenChange={setIsOpen}\n * onOpen={() => trackEvent('chat_opened')} // \u53EF\u9009\uFF1A\u57CB\u70B9\n * />\n * ```\n */\n onOpen?: () => void\n\n /**\n * \u3010\u53EF\u9009\u3011\u7A97\u53E3\u5173\u95ED\u4E8B\u4EF6\u76D1\u542C\n * \u7528\u4E8E\u57CB\u70B9\u3001\u65E5\u5FD7\u7B49\u526F\u4F5C\u7528\uFF0C\u4E0D\u5F71\u54CD\u72B6\u6001\u63A7\u5236\n * \u5728 onOpenChange \u4E4B\u540E\u89E6\u53D1\n * @example\n * ```tsx\n * <LiveChatWidget\n * open={isOpen}\n * onOpenChange={setIsOpen}\n * onClose={() => trackEvent('chat_closed')} // \u53EF\u9009\uFF1A\u57CB\u70B9\n * />\n * ```\n */\n onClose?: () => void\n onMessageSend?: (message: string) => void\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 */\n onPromotionList?: () => void\n\n /**\n * \u5546\u54C1\u64CD\u4F5C\u56DE\u8C03\n */\n onAddToCart?: (product: Product) => void\n\n /**\n * \u8D2D\u7269\u8F66\u6309\u94AE\u70B9\u51FB\u56DE\u8C03\n */\n onCart?: (cartId: string, checkoutUrl?: string) => void\n\n /**\n * \u662F\u5426\u663E\u793A\u65B0\u4F1A\u8BDD\u6309\u94AE\n * @default true\n */\n showNewSessionButton?: boolean\n\n /**\n * \u901A\u7528\u6587\u6848\u914D\u7F6E\n * \u7528\u4E8E\u81EA\u5B9A\u4E49\u6309\u94AE\u6587\u6848\n */\n commonText?: CommonText\n\n /**\n * \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\u51FD\u6570\n * \u7528\u4E8E\u81EA\u5B9A\u4E49\u6E32\u67D3 product_card \u7C7B\u578B\u7684\u4EA7\u54C1\u5361\u7247\n * \u5F53\u63D0\u4F9B\u6B64\u51FD\u6570\u65F6\uFF0C\u5C06\u66FF\u4EE3\u9ED8\u8BA4\u7684\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\u903B\u8F91\n * @param product \u539F\u59CB\u540E\u7AEF\u4EA7\u54C1\u6570\u636E\uFF08\u672A\u7ECF\u8F6C\u6362\u7684\u6570\u636E\uFF09\uFF0C\u5982\u679C\u5728 product_list \u4E2D\u627E\u4E0D\u5230\u5219\u4E3A undefined\n * @param productHandle \u6587\u672C\u5360\u4F4D\u7B26\u4E2D\u7684\u4EA7\u54C1 ID\uFF08\u5982 {{product:ID}} \u4E2D\u7684 ID\uFF09\uFF0C\u53EF\u7528\u4E8E\u5E94\u7528\u5C42\u67E5\u8BE2\u4EA7\u54C1\u6570\u636E\n * @returns React \u53EF\u6E32\u67D3\u7684\u5185\u5BB9\n * @example\n * ```tsx\n * <LiveChatWidget\n * productCardRender={(product, productHandle) => {\n * // product \u53EF\u80FD\u4E3A undefined\uFF0C\u6B64\u65F6\u53EF\u7528 productHandle \u67E5\u8BE2\u4EA7\u54C1\n * if (!product) {\n * return <ProductCardByHandle handle={productHandle} />\n * }\n * return (\n * <div>\n * <h3>{product.title}</h3>\n * <p>{product.price_range?.min}</p>\n * </div>\n * )\n * }}\n * />\n * ```\n */\n productCardRender?: (product: any, productHandle: string) => React.ReactNode\n\n /**\n * \u8F93\u5165\u6846\u5E95\u90E8\u63D0\u793A\u6587\u672C\n * \u4E0D\u4F20\u5165\u5219\u4E0D\u663E\u793A\n */\n bottomTips?: string\n\n /**\n * \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u914D\u7F6E\n * \u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u5728\u7528\u6237\u9996\u6B21\u70B9\u51FB\u804A\u5929\u6C14\u6CE1\u65F6\u663E\u793A\u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\n * \u7528\u6237\u540C\u610F\u540E\u624D\u4F1A\u6253\u5F00\u804A\u5929\u7A97\u53E3\n */\n complianceConfig?: ComplianceDialogConfig\n}\n\nexport interface MessageRenderer {\n render: (content: MessageContent, isUser: boolean, isSystem: boolean) => React.ReactNode\n}\n\nexport interface CustomRendererMap {\n [type: string]: MessageRenderer\n}\n\n// ============================================================================\n// Utility Types (\u5DE5\u5177\u7C7B\u578B)\n// ============================================================================\n\nexport interface PositionStyles {\n bottom?: string\n top?: string\n left?: string\n right?: string\n}\n\nexport type BubblePosition = PositionStyles\n"],
4
+ "sourcesContent": ["/**\n * LiveChat \u7EC4\u4EF6\u6838\u5FC3\u7C7B\u578B\u5B9A\u4E49\n * \u57FA\u4E8E specs/livechat-widget/data-model.md\n */\n\n// ============================================================================\n// Session Types (\u4F1A\u8BDD)\n// ============================================================================\n\nexport type SessionStatus = 'active' | 'expired'\n\nexport interface Session {\n sessionId: string\n userId: string\n site: string\n status: SessionStatus\n createdAt?: number\n lastActivityAt?: number\n}\n\n// ============================================================================\n// Message Types (\u6D88\u606F)\n// ============================================================================\n\nexport type MessageRole = 'user' | 'assistant' | 'system' | 'tool'\n\nexport interface MessageMetadata {\n tokenUsage?: {\n inputTokens: number\n outputTokens: number\n }\n toolCalls?: Array<{\n id: string\n type: string\n name: string\n }>\n}\n\nexport interface Message {\n id: string\n sessionId?: string\n role: MessageRole\n content: MessageContent[]\n timestamp: number\n metadata?: MessageMetadata\n structured_content?: Array<{\n type: string\n data: any\n }>\n}\n\n// ============================================================================\n// Message Content Types (\u6D88\u606F\u5185\u5BB9)\n// ============================================================================\n\nexport type MessageContentType =\n | 'text'\n | 'product_card'\n | 'product_list'\n | 'product_comparison'\n | 'policy'\n | 'quick_replies'\n | 'thinking'\n | 'error'\n | 'faq_list'\n | 'cart'\n\nexport type MessageContent =\n | TextContent\n | ProductCardContent\n | ProductListContent\n | ProductComparisonContent\n | PolicyContent\n | QuickRepliesContent\n | ThinkingContent\n | ErrorContent\n | FAQListContent\n | PromotionListContent\n | CartContent\n\nexport interface TextContent {\n type: 'text'\n text: string\n}\n\nexport interface ProductCardContent {\n type: 'product_card'\n data: {\n product?: Product\n rawProduct?: any // Raw backend product data (for custom render)\n productHandle: string // Product ID from placeholder {{product:ID}}, for app-level product lookup\n onAddToCart?: (product: Product) => void\n productCardRender?: (product: any, productHandle: string) => React.ReactNode\n }\n}\n\nexport interface ProductListContent {\n type: 'product_list'\n data: {\n products: Product[]\n title?: string\n onAddToCart?: (product: Product) => void\n commonText?: CommonText\n }\n}\n\nexport interface ProductComparisonContent {\n type: 'product_comparison'\n data: {\n products: Product[]\n dimensions: {\n price?: {\n label: string\n values: Array<{\n product_id: string\n min: number\n max: number\n currency: string\n has_discount: boolean\n }>\n }\n variants?: {\n label: string\n values: Array<{\n product_id: string\n count: number\n }>\n }\n member_price?: {\n label: string\n values: Array<{\n product_id: string\n available: boolean\n min: number\n max: number\n currency: string\n }>\n }\n discount?: {\n label: string\n values: Array<{\n product_id: string\n has_discount: boolean\n }>\n }\n [key: string]: any\n }\n onAddToCart?: (product: Product) => void\n commonText?: CommonText\n }\n}\n\nexport interface PolicyContent {\n type: 'policy'\n data: {\n title: string\n content: string\n }\n}\n\nexport interface QuickRepliesContent {\n type: 'quick_replies'\n data: {\n replies: QuickReply[]\n }\n}\n\nexport interface ThinkingContent {\n type: 'thinking'\n data: {\n status: string\n }\n}\n\nexport interface ErrorContent {\n type: 'error'\n data: {\n message: string\n code?: string\n }\n}\n\nexport interface FAQListContent {\n type: 'faq_list'\n data: {\n found: boolean\n count: number\n total?: number\n results: FAQItem[]\n }\n}\n\nexport interface PromotionListContent {\n type: 'promotion_list'\n data: {\n found: boolean\n count: number\n total?: number\n results: PromotionItem[]\n commonText?: CommonText\n }\n}\n\n// ============================================================================\n// FAQ Types (\u5E38\u89C1\u95EE\u9898)\n// ============================================================================\n\nexport type FAQCategory = 'shipping' | 'return' | 'product' | 'payment' | 'general'\n\nexport interface FAQItem {\n id: string\n question: string\n answer: string\n category: FAQCategory\n keywords?: string[]\n relatedQuestions?: string[]\n metadata?: {\n language?: string\n priority?: number\n lastUpdated?: string\n }\n}\n\n// ============================================================================\n// Promotion Types (\u4FC3\u9500\u6D3B\u52A8)\n// ============================================================================\n\nexport interface PromotionItem {\n id: string\n title: string\n subtitle?: string\n description?: string\n banner_url?: string\n url?: string\n time_range: {\n start: string\n end?: string | null\n is_active: boolean\n }\n priority?: number\n product_count?: number\n metadata?: {\n display_order?: number\n target_audience?: string\n }\n}\n\n// ============================================================================\n// Product Types (\u5546\u54C1)\n// ============================================================================\n\nexport type StockStatus = 'in_stock' | 'low_stock' | 'out_of_stock'\n\nexport interface Price {\n amount: number\n currency: string\n}\n\nexport interface PriceRange {\n min: number\n max: number\n currency: string\n}\n\nexport interface VariantDiscount {\n has_discount: boolean\n discount_price?: number\n discount_code?: string\n discount_percentage?: number\n /** \u6298\u6263\u7C7B\u578B\uFF1Afixed_amount \u56FA\u5B9A\u91D1\u989D\u6298\u6263\uFF0Cpercentage \u767E\u5206\u6BD4\u6298\u6263 */\n discount_type?: 'fixed_amount' | 'percentage'\n /** \u6298\u6263\u6570\u503C\uFF08\u53EF\u80FD\u662F\u5B57\u7B26\u4E32\u6216\u6570\u5B57\uFF09 */\n discount_value?: string | number\n}\n\nexport interface VariantMemberPrice {\n has_member_price: boolean\n price?: number\n}\n\nexport interface ProductFeatures {\n is_new?: boolean\n has_rental?: boolean\n has_presale?: boolean\n has_member_price?: boolean\n has_discount?: boolean\n}\n\nexport interface Variant {\n id: string\n title: string\n sku?: string\n price?: Price\n availableForSale: boolean\n color?: string\n discount?: VariantDiscount\n memberPrice?: VariantMemberPrice\n inventoryQuantity?: number\n option1?: string\n option2?: string\n option3?: string\n}\n\nexport interface Product {\n shopifyId: string\n sku?: string\n handle: string\n title: string\n description?: string\n vendor?: string\n price: Price\n priceRange?: PriceRange\n memberPriceRange?: PriceRange\n imageUrl: string\n productUrl: string\n stockStatus: StockStatus\n hotScore?: number\n averageRating?: number\n reviewCount?: number\n variants?: Variant[]\n variantCount?: number\n availableCount?: number\n features?: ProductFeatures\n tags?: string[]\n}\n\n// ============================================================================\n// Quick Reply Types (\u5FEB\u6377\u56DE\u590D)\n// ============================================================================\n\nexport interface QuickReply {\n id: string\n label: string\n value: string\n icon?: string\n}\n\n// ============================================================================\n// Policy Types (\u653F\u7B56)\n// ============================================================================\n\nexport interface Policy {\n title: string\n content: string\n}\n\n// ============================================================================\n// Cart Types (\u8D2D\u7269\u8F66)\n// ============================================================================\n\n/**\n * \u8D2D\u7269\u8F66\u91D1\u989D\u4FE1\u606F\n */\nexport interface CartAmount {\n /** \u91D1\u989D\u5B57\u7B26\u4E32\uFF08\u5982 \"99.99\"\uFF09 */\n amount: string\n /** \u8D27\u5E01\u4EE3\u7801\uFF08\u5982 \"USD\"\uFF09 */\n currencyCode: string\n}\n\n/**\n * \u8D2D\u7269\u8F66\u4EF7\u683C\u6C47\u603B\n */\nexport interface CartCost {\n /** \u5E94\u4ED8\u603B\u4EF7\uFF08\u5DF2\u5E94\u7528\u6298\u6263\uFF09 */\n totalAmount: CartAmount\n /** \u539F\u4EF7\u5C0F\u8BA1\uFF08\u672A\u5E94\u7528\u6298\u6263\uFF09 */\n subtotalAmount: CartAmount\n}\n\n/**\n * \u8D2D\u7269\u8F66\u5546\u54C1\u53D8\u4F53\u4FE1\u606F\n */\nexport interface CartMerchandise {\n /** \u53D8\u4F53 ID (Shopify ProductVariant GID) */\n id: string\n /** \u53D8\u4F53\u6807\u9898\uFF08\u5982 \"Black\", \"Large\" \u7B49\uFF09 */\n title: string\n /** \u5355\u4EF7 */\n price: CartAmount\n /** \u5546\u54C1\u56FE\u7247 URL */\n image?: {\n url: string\n altText?: string\n }\n /** \u5173\u8054\u7684\u5546\u54C1\u4FE1\u606F */\n product: {\n /** \u5546\u54C1 ID */\n id: string\n /** \u5546\u54C1\u6807\u9898 */\n title: string\n /** \u5546\u54C1 handle */\n handle: string\n }\n}\n\n/**\n * \u8D2D\u7269\u8F66\u5546\u54C1\u884C\n */\nexport interface CartLine {\n /** \u8D2D\u7269\u8F66\u884C ID (\u7528\u4E8E\u66F4\u65B0/\u5220\u9664\u64CD\u4F5C) */\n id: string\n /** \u5546\u54C1\u6570\u91CF */\n quantity: number\n /** \u4EF7\u683C\u4FE1\u606F */\n cost: {\n /** \u884C\u603B\u4EF7\uFF08\u5355\u4EF7 \u00D7 \u6570\u91CF\uFF0C\u5DF2\u5E94\u7528\u6298\u6263\uFF09 */\n totalAmount: CartAmount\n /** \u5355\u4EF7 */\n amountPerQuantity: CartAmount\n /** \u884C\u5C0F\u8BA1\uFF08\u5355\u4EF7 \u00D7 \u6570\u91CF\uFF0C\u672A\u5E94\u7528\u6298\u6263\uFF09 */\n subtotalAmount: CartAmount\n }\n /** \u5546\u54C1\u53D8\u4F53\u4FE1\u606F */\n merchandise: CartMerchandise\n /** \u81EA\u5B9A\u4E49\u5C5E\u6027\uFF08\u53EF\u9009\uFF09 */\n attributes?: Array<{\n key: string\n value: string\n }>\n}\n\n/**\n * \u8D2D\u7269\u8F66\u6298\u6263\u7801\n */\nexport interface CartDiscountCode {\n /** \u6298\u6263\u7801 */\n code: string\n /** \u662F\u5426\u6709\u6548/\u9002\u7528 */\n applicable: boolean\n}\n\n/**\n * \u8D2D\u7269\u8F66\u6570\u636E\n */\nexport interface CartData {\n /** \u8D2D\u7269\u8F66\u662F\u5426\u4E3A\u7A7A */\n isEmpty: boolean\n /** \u8D2D\u7269\u8F66 ID (Shopify Cart GID) */\n cartId: string\n /** \u5546\u54C1\u603B\u6570\u91CF */\n totalQuantity: number\n /** \u5546\u54C1\u5217\u8868 */\n lines: CartLine[]\n /** \u4EF7\u683C\u6C47\u603B */\n cost: CartCost\n /** \u6298\u6263\u7801\u5217\u8868 */\n discountCodes?: CartDiscountCode[]\n /** \u7ED3\u8D26\u9875\u9762 URL */\n checkoutUrl?: string\n /** \u8D2D\u7269\u8F66\u6309\u94AE\u56DE\u8C03\u51FD\u6570 */\n onCart?: (cartId: string, checkoutUrl?: string) => void\n /** \u901A\u7528\u6587\u6848\u914D\u7F6E */\n commonText?: CommonText\n}\n\n/**\n * \u8D2D\u7269\u8F66\u5185\u5BB9\u5757\n */\nexport interface CartContent {\n type: 'cart'\n data: CartData\n}\n\n// ============================================================================\n// Backend Cart Types (\u540E\u7AEF\u8D2D\u7269\u8F66\u6570\u636E\u683C\u5F0F - Shopify GraphQL)\n// ============================================================================\n\n/**\n * \u540E\u7AEF\u8FD4\u56DE\u7684\u8D2D\u7269\u8F66\u5546\u54C1\u884C\u6570\u636E (Shopify GraphQL \u683C\u5F0F)\n */\nexport interface BackendCartLineNode {\n id: string\n quantity: number\n cost: {\n totalAmount: CartAmount\n amountPerQuantity: CartAmount\n compareAtAmountPerQuantity?: CartAmount | null\n subtotalAmount: CartAmount\n }\n discountAllocations: any[]\n merchandise: {\n id: string\n title: string\n availableForSale: boolean\n quantityAvailable?: number\n price: CartAmount\n compareAtPrice?: CartAmount | null\n image?: {\n url: string\n altText?: string | null\n width?: number\n height?: number\n } | null\n product: {\n id: string\n title: string\n handle: string\n vendor?: string\n featuredImage?: {\n url: string\n altText?: string | null\n width?: number\n height?: number\n } | null\n tags?: string[]\n }\n }\n attributes?: Array<{\n key: string\n value: string\n }>\n}\n\n/**\n * \u540E\u7AEF\u8FD4\u56DE\u7684\u8D2D\u7269\u8F66\u6570\u636E (Shopify GraphQL \u683C\u5F0F)\n */\nexport interface BackendCartData {\n id: string\n checkoutUrl?: string\n totalQuantity: number\n lines: {\n edges: Array<{\n node: BackendCartLineNode\n }>\n }\n cost: {\n totalAmount: CartAmount\n subtotalAmount: CartAmount\n checkoutChargeAmount?: CartAmount\n totalAmountEstimated?: boolean\n subtotalAmountEstimated?: boolean\n totalTaxAmount?: CartAmount | null\n totalDutyAmount?: CartAmount | null\n }\n discountAllocations?: any[]\n buyerIdentity?: any\n attributes?: Array<{\n key: string\n value: string\n }>\n discountCodes?: any[]\n createdAt?: string\n updatedAt?: string\n}\n\n// ============================================================================\n// SSE Event Types (SSE \u4E8B\u4EF6)\n// ============================================================================\n\nexport type SSEEventType =\n | 'message_start'\n | 'content_delta'\n | 'content_block'\n | 'message_end'\n | 'tool_start'\n | 'tool_end'\n | 'status'\n | 'error'\n | 'done'\n\nexport interface SSEEvent<T = any> {\n event: SSEEventType | null\n data: T\n}\n\n// \u5177\u4F53\u4E8B\u4EF6\u6570\u636E\u7C7B\u578B\nexport interface MessageStartData {\n sessionId: string\n}\n\nexport interface ContentDeltaData {\n text: string\n}\n\nexport interface ContentBlockData {\n type: string\n data: any\n}\n\nexport interface MessageEndData {\n usage: {\n inputTokens: number\n outputTokens: number\n }\n}\n\nexport interface ToolStartData {\n id: string\n type: string\n name: string\n}\n\nexport interface ToolEndData {\n id: string\n}\n\nexport interface StatusData {\n type: string\n message?: string\n}\n\nexport interface ErrorData {\n message: string\n code?: string\n type?: string\n}\n\n// ============================================================================\n// API Request/Response Types (API \u8BF7\u6C42\u54CD\u5E94)\n// ============================================================================\n\n/**\n * \u6D41\u5F0F\u5BF9\u8BDD\u8BF7\u6C42\u53C2\u6570\n */\nexport interface ChatStreamRequest {\n /** \u7528\u6237\u7684\u6D88\u606F\u6587\u672C */\n message: string\n /** \u7528\u6237\u6807\u8BC6\u7B26 */\n user_id: string\n /** \u6765\u81EA new-session \u7AEF\u70B9\u7684\u4F1A\u8BDD ID */\n session_id: string\n /** \u53EF\u9009\u7684\u4E0A\u4E0B\u6587\u4FE1\u606F */\n context?: {\n /** \u7528\u4E8E\u8D2D\u7269\u8F66\u64CD\u4F5C\u7684 Shopify \u8D2D\u7269\u8F66 ID */\n cartId?: string\n /** Storefront API \u8BBF\u95EE\u4EE4\u724C */\n accessToken?: string\n /** \u5DF2\u767B\u5F55\u7528\u6237\u7684 ID */\n real_user_id?: string\n }\n}\n\nexport interface NewSessionRequest {\n user_id: string\n session_id?: string\n site?: string\n channel_code?: string\n real_user_id?: string\n}\n\nexport interface NewSessionResponse {\n success: boolean\n sessionId: string\n message: string\n resumed?: boolean\n messages?: Message[]\n welcomeMessage?: string\n quickQuestions?: string[]\n brand?: string\n}\n\nexport interface ErrorResponse {\n success: boolean\n error: string\n code?: string\n details?: any\n}\n\n// ============================================================================\n// Common Text Types (\u901A\u7528\u6587\u6848)\n// ============================================================================\n\n/**\n * \u901A\u7528\u6587\u6848\u914D\u7F6E\n * \u7528\u4E8E\u81EA\u5B9A\u4E49\u7EC4\u4EF6\u4E2D\u7684\u6309\u94AE\u6587\u6848\n */\nexport interface CommonText {\n /**\n * \u4EA7\u54C1\u5217\u8868\u5C55\u5F00\u6309\u94AE\u6587\u6848\n * @default \"Learn More\"\n */\n learnMore?: string\n\n /**\n * \u4EA7\u54C1\u5217\u8868\u6536\u8D77\u6309\u94AE\u6587\u6848\n * @default \"Show Less\"\n */\n showLess?: string\n\n /**\n * \u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u6309\u94AE\u6587\u6848\n * @default \"Add to Cart\"\n */\n addToCart?: string\n\n /**\n * \u67E5\u770B\u8D2D\u7269\u8F66/\u66F4\u591A\u6309\u94AE\u6587\u6848\n * @default \"View More\"\n */\n viewMore?: string\n\n /**\n * \u6298\u6263\u6807\u7B7E\u540E\u7F00\u6587\u6848\uFF08\u5982 \"20% OFF\" \u4E2D\u7684 \"OFF\"\uFF09\n * @default \"OFF\"\n */\n off?: string\n\n /**\n * \u8D2D\u7269\u8F66\u603B\u8BA1\u6587\u6848\n * @default \"Total\"\n */\n total?: string\n}\n\n// ============================================================================\n// Compliance Dialog Types (\u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97)\n// ============================================================================\n\n/**\n * \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u914D\u7F6E\n */\nexport interface ComplianceDialogConfig {\n /**\n * \u5F39\u7A97\u6807\u9898\n * @example \"Hi! I'm your eufy AI assistant.\"\n */\n title: string\n\n /**\n * \u5F39\u7A97\u5185\u5BB9\u6587\u672C\uFF08\u652F\u6301 HTML\uFF09\n * @example \"AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data\"\n */\n content: string\n\n /**\n * \u52FE\u9009\u6846\u6587\u672C\uFF08\u652F\u6301\u5B8C\u6574 HTML\uFF0C\u5305\u62EC\u94FE\u63A5\uFF09\n * \u53EF\u4EE5\u76F4\u63A5\u5305\u542B <a> \u6807\u7B7E\u7B49 HTML \u5143\u7D20\n * @example \"By starting to use \\\"Live Chat\\\", you agree to Anker's <a href=\\\"https://www.anker.com/privacy\\\" target=\\\"_blank\\\">LIVE CHAT PRIVACY NOTICE</a>.\"\n */\n checkboxText: string\n\n /**\n * \u540C\u610F\u6309\u94AE\u6587\u672C\n * @default \"Agree\"\n */\n agreeButtonText?: string\n\n /**\n * Cookie \u540D\u79F0\uFF0C\u7528\u4E8E\u8BB0\u5F55\u7528\u6237\u540C\u610F\u72B6\u6001\n * Cookie \u6709\u6548\u671F\u4E3A 365 \u5929\n * @default \"livechat_compliance_agreed\"\n */\n cookieName?: string\n}\n\n// ============================================================================\n// Component Props Types (\u7EC4\u4EF6 Props)\n// ============================================================================\n\nexport interface LiveChatWidgetProps {\n /**\n * API \u57FA\u7840 URL\n * @example \"https://beta-api-livechat.anker.com\"\n */\n apiBaseUrl: string\n\n /**\n * \u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\n * \u5C06\u5728\u6240\u6709 API \u8BF7\u6C42\u4E2D\u6DFB\u52A0\u8FD9\u4E9B\u8BF7\u6C42\u5934\n * @example { \"Authorization\": \"Bearer token\", \"X-Custom-Header\": \"value\" }\n */\n headers?: Record<string, string>\n\n /**\n * reCAPTCHA site key\n * \u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u81EA\u52A8\u542F\u7528 reCAPTCHA v3 \u9A8C\u8BC1\n * @example \"6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14\"\n */\n recaptchaSitekey?: string\n\n /**\n * reCAPTCHA action \u524D\u7F00\n * \u5B9E\u9645\u4F7F\u7528\u65F6\u4F1A\u6839\u636E\u4E0D\u540C\u63A5\u53E3\u6DFB\u52A0\u540E\u7F00\uFF08\u5982 chat_stream, new_session\uFF09\n * @default \"livechat\"\n */\n recaptchaAction?: string\n\n /**\n * Shopify \u5E97\u94FA\u57DF\u540D\n * @example \"www.eufy.com\"\n */\n site?: string\n\n /**\n * \u6E20\u9053\u7F16\u7801\n * \u7528\u4E8E\u6807\u8BC6\u6765\u6E90\u6E20\u9053\n * @example \"web_homepage\"\n */\n channelCode?: string\n\n /**\n * \u5DF2\u767B\u5F55\u7528\u6237\u7684 ID\uFF08\u53EF\u9009\uFF09\n * \u5982\u679C\u63D0\u4F9B\uFF0C\u5C06\u5728 API \u8BF7\u6C42\u4E2D\u4F20\u9012\n */\n loginUserId?: string\n\n /**\n * Shopify \u8D2D\u7269\u8F66 ID\uFF08\u53EF\u9009\uFF09\n * \u7528\u4E8E\u8D2D\u7269\u8F66\u64CD\u4F5C\uFF0C\u5C06\u5728 stream \u63A5\u53E3\u7684 context \u4E2D\u4F20\u9012\n * @example \"gid://shopify/Cart/Z2NwLXVzLWVhc3QxOjAxSkZH...\"\n */\n cartId?: string\n\n /**\n * Storefront API \u8BBF\u95EE\u4EE4\u724C\uFF08\u53EF\u9009\uFF09\n * \u7528\u4E8E\u8D2D\u7269\u8F66\u64CD\u4F5C\uFF0C\u5C06\u5728 stream \u63A5\u53E3\u7684 context \u4E2D\u4F20\u9012\n */\n accessToken?: string\n\n /**\n * \u6C14\u6CE1\u6309\u94AE\u4F4D\u7F6E\n * \u81EA\u5B9A\u4E49\u4F4D\u7F6E\u5BF9\u8C61\uFF1A{ top?: string, bottom?: string, left?: string, right?: string }\n * @default { bottom: \"1.5rem\", right: \"1.5rem\" }\n * @example\n * // \u81EA\u5B9A\u4E49\u4F4D\u7F6E\n * position={{ bottom: \"20px\", right: \"30px\" }}\n * position={{ top: \"100px\", left: \"50px\" }}\n */\n position?: BubblePosition\n\n /**\n * \u6B22\u8FCE\u6D88\u606F\n */\n welcomeMessage?: string\n\n /**\n * \u521D\u59CB\u5FEB\u6377\u56DE\u590D\u6309\u94AE\n */\n quickReplies?: QuickReply[]\n\n /**\n * \u81EA\u5B9A\u4E49\u6D88\u606F\u6E32\u67D3\u5668\n */\n customRenderers?: Record<string, MessageRenderer>\n\n /**\n * Logo URL\n */\n logoUrl?: string\n\n /**\n * \u804A\u5929\u7A97\u53E3\u6807\u9898\n * @default \"AI \u52A9\u624B\"\n */\n title?: string\n\n /**\n * \u804A\u5929\u6C14\u6CE1\u6309\u94AE\u56FE\u6807\uFF08\u56FE\u7247 URL\uFF09\n * \u5982\u679C\u63D0\u4F9B\uFF0C\u5C06\u4F7F\u7528\u56FE\u7247\u66FF\u4EE3\u9ED8\u8BA4\u7684 SVG \u56FE\u6807\n */\n chatBubbleIcon?: 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\u5C06\u5904\u4E8E\u53D7\u63A7\u6A21\u5F0F\n * @example\n * ```tsx\n * const [isOpen, setIsOpen] = useState(false)\n * <LiveChatWidget open={isOpen} onOpenChange={setIsOpen} />\n * ```\n */\n open?: boolean\n\n /**\n * \u53D7\u63A7\u6A21\u5F0F\uFF1A\u6253\u5F00/\u5173\u95ED\u72B6\u6001\u53D8\u5316\u56DE\u8C03\n * \u3010\u5FC5\u9700\u3011\u914D\u5408 `open` \u4F7F\u7528\uFF0C\u7528\u4E8E\u540C\u6B65\u72B6\u6001\u5230\u7236\u7EC4\u4EF6\n * \u5F53\u7528\u6237\u70B9\u51FB\u6253\u5F00\u6216\u5173\u95ED\u6309\u94AE\u65F6\u89E6\u53D1\n * @example\n * ```tsx\n * <LiveChatWidget\n * open={isOpen}\n * onOpenChange={setIsOpen} // \u5FC5\u9700\uFF1A\u540C\u6B65\u72B6\u6001\n * />\n * ```\n */\n onOpenChange?: (open: boolean) => void\n\n /**\n * \u3010\u53EF\u9009\u3011\u7A97\u53E3\u6253\u5F00\u4E8B\u4EF6\u76D1\u542C\n * \u7528\u4E8E\u57CB\u70B9\u3001\u65E5\u5FD7\u7B49\u526F\u4F5C\u7528\uFF0C\u4E0D\u5F71\u54CD\u72B6\u6001\u63A7\u5236\n * \u5728 onOpenChange \u4E4B\u540E\u89E6\u53D1\n * @example\n * ```tsx\n * <LiveChatWidget\n * open={isOpen}\n * onOpenChange={setIsOpen}\n * onOpen={() => trackEvent('chat_opened')} // \u53EF\u9009\uFF1A\u57CB\u70B9\n * />\n * ```\n */\n onOpen?: () => void\n\n /**\n * \u3010\u53EF\u9009\u3011\u7A97\u53E3\u5173\u95ED\u4E8B\u4EF6\u76D1\u542C\n * \u7528\u4E8E\u57CB\u70B9\u3001\u65E5\u5FD7\u7B49\u526F\u4F5C\u7528\uFF0C\u4E0D\u5F71\u54CD\u72B6\u6001\u63A7\u5236\n * \u5728 onOpenChange \u4E4B\u540E\u89E6\u53D1\n * @example\n * ```tsx\n * <LiveChatWidget\n * open={isOpen}\n * onOpenChange={setIsOpen}\n * onClose={() => trackEvent('chat_closed')} // \u53EF\u9009\uFF1A\u57CB\u70B9\n * />\n * ```\n */\n onClose?: () => void\n onMessageSend?: (message: string) => void\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: PromotionItem[]) => void\n\n /**\n * \u5546\u54C1\u64CD\u4F5C\u56DE\u8C03\n */\n onAddToCart?: (product: Product) => void\n\n /**\n * \u8D2D\u7269\u8F66\u6309\u94AE\u70B9\u51FB\u56DE\u8C03\n */\n onCart?: (cartId: string, checkoutUrl?: string) => void\n\n /**\n * \u662F\u5426\u663E\u793A\u65B0\u4F1A\u8BDD\u6309\u94AE\n * @default true\n */\n showNewSessionButton?: boolean\n\n /**\n * \u901A\u7528\u6587\u6848\u914D\u7F6E\n * \u7528\u4E8E\u81EA\u5B9A\u4E49\u6309\u94AE\u6587\u6848\n */\n commonText?: CommonText\n\n /**\n * \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\u51FD\u6570\n * \u7528\u4E8E\u81EA\u5B9A\u4E49\u6E32\u67D3 product_card \u7C7B\u578B\u7684\u4EA7\u54C1\u5361\u7247\n * \u5F53\u63D0\u4F9B\u6B64\u51FD\u6570\u65F6\uFF0C\u5C06\u66FF\u4EE3\u9ED8\u8BA4\u7684\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\u903B\u8F91\n * @param product \u539F\u59CB\u540E\u7AEF\u4EA7\u54C1\u6570\u636E\uFF08\u672A\u7ECF\u8F6C\u6362\u7684\u6570\u636E\uFF09\uFF0C\u5982\u679C\u5728 product_list \u4E2D\u627E\u4E0D\u5230\u5219\u4E3A undefined\n * @param productHandle \u6587\u672C\u5360\u4F4D\u7B26\u4E2D\u7684\u4EA7\u54C1 ID\uFF08\u5982 {{product:ID}} \u4E2D\u7684 ID\uFF09\uFF0C\u53EF\u7528\u4E8E\u5E94\u7528\u5C42\u67E5\u8BE2\u4EA7\u54C1\u6570\u636E\n * @returns React \u53EF\u6E32\u67D3\u7684\u5185\u5BB9\n * @example\n * ```tsx\n * <LiveChatWidget\n * productCardRender={(product, productHandle) => {\n * // product \u53EF\u80FD\u4E3A undefined\uFF0C\u6B64\u65F6\u53EF\u7528 productHandle \u67E5\u8BE2\u4EA7\u54C1\n * if (!product) {\n * return <ProductCardByHandle handle={productHandle} />\n * }\n * return (\n * <div>\n * <h3>{product.title}</h3>\n * <p>{product.price_range?.min}</p>\n * </div>\n * )\n * }}\n * />\n * ```\n */\n productCardRender?: (product: any, productHandle: string) => React.ReactNode\n\n /**\n * \u8F93\u5165\u6846\u5E95\u90E8\u63D0\u793A\u6587\u672C\n * \u4E0D\u4F20\u5165\u5219\u4E0D\u663E\u793A\n */\n bottomTips?: string\n\n /**\n * \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u914D\u7F6E\n * \u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u5728\u7528\u6237\u9996\u6B21\u70B9\u51FB\u804A\u5929\u6C14\u6CE1\u65F6\u663E\u793A\u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\n * \u7528\u6237\u540C\u610F\u540E\u624D\u4F1A\u6253\u5F00\u804A\u5929\u7A97\u53E3\n */\n complianceConfig?: ComplianceDialogConfig\n}\n\nexport interface MessageRenderer {\n render: (content: MessageContent, isUser: boolean, isSystem: boolean) => React.ReactNode\n}\n\nexport interface CustomRendererMap {\n [type: string]: MessageRenderer\n}\n\n// ============================================================================\n// Utility Types (\u5DE5\u5177\u7C7B\u578B)\n// ============================================================================\n\nexport interface PositionStyles {\n bottom?: string\n top?: string\n left?: string\n right?: string\n}\n\nexport type BubblePosition = PositionStyles\n"],
5
5
  "mappings": "+WAAA,IAAAA,EAAA,kBAAAC,EAAAD",
6
6
  "names": ["types_exports", "__toCommonJS"]
7
7
  }
@@ -1,4 +1,4 @@
1
- "use strict";var f=Object.create;var m=Object.defineProperty;var b=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var v=Object.getPrototypeOf,C=Object.prototype.hasOwnProperty;var N=(t,a)=>{for(var e in a)m(t,e,{get:a[e],enumerable:!0})},o=(t,a,e,r)=>{if(a&&typeof a=="object"||typeof a=="function")for(let n of w(a))!C.call(t,n)&&n!==e&&m(t,n,{get:()=>a[n],enumerable:!(r=b(a,n))||r.enumerable});return t};var $=(t,a,e)=>(e=t!=null?f(v(t)):{},o(a||!t||!t.__esModule?m(e,"default",{value:t,enumerable:!0}):e,t)),B=t=>o(m({},"__esModule",{value:!0}),t);var k={};N(k,{CreditsBanner:()=>j});module.exports=B(k);var i=require("react/jsx-runtime"),l=require("@anker-in/headless-ui"),d=$(require("classnames")),p=require("../context/provider");function j({copy:t,id:a}){const{profile:e,openSignInPopup:r,openSignUpPopup:n}=(0,p.useCreditsContext)(),s=e?Object.keys(e).length!==0:!1,c=e?.first_name?e?.first_name+" "+e?.last_name:e?.email?.split("@")[0],g=I=>{I==="primary"?n():r()},h=t.pcImg?.width&&t.pcImg?.height?`${t.pcImg.width} / ${t.pcImg.height}`:"1920 / 804",u=t.laptopImg?.width&&t.laptopImg?.height?`${t.laptopImg.width} / ${t.laptopImg.height}`:"768 / 660",x=t.mobileImg?.width&&t.mobileImg?.height?`${t.mobileImg.width} / ${t.mobileImg.height}`:"390 / 660";return(0,i.jsx)(i.Fragment,{children:(0,i.jsxs)("div",{id:a,className:" relative w-full",children:[(0,i.jsx)(l.Picture,{className:"credits-banner-image w-full",style:{aspectRatio:h},imgClassName:"object-cover w-full h-full",source:`${t.pcImg?.url}, ${t.laptopImg?.url} 1024, ${t.mobileImg?.url} 768`,alt:t.pcImg?.alt,loading:"eager"}),(0,i.jsx)("style",{children:`
1
+ "use strict";var I=Object.create;var m=Object.defineProperty;var f=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var v=Object.getPrototypeOf,C=Object.prototype.hasOwnProperty;var N=(t,a)=>{for(var e in a)m(t,e,{get:a[e],enumerable:!0})},o=(t,a,e,r)=>{if(a&&typeof a=="object"||typeof a=="function")for(let n of w(a))!C.call(t,n)&&n!==e&&m(t,n,{get:()=>a[n],enumerable:!(r=f(a,n))||r.enumerable});return t};var $=(t,a,e)=>(e=t!=null?I(v(t)):{},o(a||!t||!t.__esModule?m(e,"default",{value:t,enumerable:!0}):e,t)),B=t=>o(m({},"__esModule",{value:!0}),t);var k={};N(k,{CreditsBanner:()=>j});module.exports=B(k);var i=require("react/jsx-runtime"),l=require("@anker-in/headless-ui"),d=$(require("classnames")),p=require("../context/provider");function j({copy:t,id:a}){const{profile:e,openSignInPopup:r,openSignUpPopup:n}=(0,p.useCreditsContext)(),s=e?Object.keys(e).length!==0:!1,c=e?.first_name?e?.first_name+" "+e?.last_name:e?.email?.split("@")[0],g=b=>{b==="primary"?n():r()},h=t.pcImg?.width&&t.pcImg?.height?`${t.pcImg.width} / ${t.pcImg.height}`:"1920 / 804",u=t.laptopImg?.width&&t.laptopImg?.height?`${t.laptopImg.width} / ${t.laptopImg.height}`:"768 / 660",x=t.mobileImg?.width&&t.mobileImg?.height?`${t.mobileImg.width} / ${t.mobileImg.height}`:"390 / 660";return(0,i.jsx)(i.Fragment,{children:(0,i.jsxs)("div",{id:a,className:" relative w-full",children:[(0,i.jsx)(l.Picture,{className:"credits-banner-image w-full",style:{aspectRatio:h},imgClassName:"object-cover w-full h-full",source:`${t.pcImg?.url}, ${t.laptopImg?.url} 1024, ${t.mobileImg?.url} 768`,alt:t.pcImg?.alt,loading:"eager"}),(0,i.jsx)("style",{children:`
2
2
  @media (max-width: 1024px) and (min-width: 768px) {
3
3
  .credits-banner-image {
4
4
  aspect-ratio: ${u} !important;
@@ -9,5 +9,5 @@
9
9
  aspect-ratio: ${x} !important;
10
10
  }
11
11
  }
12
- `}),s&&(0,i.jsx)("div",{className:"absolute inset-0 ",style:{background:"linear-gradient(180deg, rgba(245, 245, 247, 0) 70.31%, #F5F5F7 100%)"}}),(0,i.jsx)(l.Container,{className:"l:h-auto !absolute inset-0 mx-auto grid h-full",asChild:!0,children:(0,i.jsx)("div",{className:"grid grid-cols-12",children:(0,i.jsxs)("div",{className:"l:col-span-12 l:justify-start l:truncate l:pt-[32px] col-span-5 flex h-full flex-col justify-center text-[#1F2021]",children:[(0,i.jsx)(l.Heading,{as:"h1",className:"text-[48px] font-bold xl-xxl:text-[40px] l:text-[32px]",html:s?t.login.title?.replace("$name",c||""):t.unLogin.title}),(0,i.jsx)(l.Text,{size:"3",className:"l:mt-[4px] l-xxl:text-[14px] mt-[16px]",html:s?t.login.description:t.unLogin.description}),!s&&(0,i.jsxs)("div",{className:(0,d.default)("mt-[32px] grid w-fit grid-flow-col gap-[12px] l:mt-[24px]",s&&"hidden"),children:[(0,i.jsx)(l.Button,{variant:"secondary",size:"lg",onClick:()=>{g("primary")},children:t.joinNow}),(0,i.jsx)(l.Button,{variant:"primary",size:"lg",onClick:()=>{g("secondary")},children:t.loginIn})]})]})})})]})})}
12
+ `}),s&&(0,i.jsx)("div",{className:"absolute inset-0 ",style:{background:"linear-gradient(180deg, rgba(245, 245, 247, 0) 70.31%, #F5F5F7 100%)"}}),(0,i.jsx)(l.Container,{className:"l-tablet:h-auto !absolute inset-0 mx-auto grid h-full",asChild:!0,children:(0,i.jsx)("div",{className:"grid grid-cols-12",children:(0,i.jsxs)("div",{className:"l-tablet:col-span-12 l-tablet:justify-start l-tablet:truncate l-tablet:pt-[32px] col-span-5 flex h-full flex-col justify-center text-[#1F2021]",children:[(0,i.jsx)(l.Heading,{as:"h1",className:"text-[48px] font-bold xl-xxl:text-[40px] l-tablet:text-[32px]",html:s?t.login.title?.replace("$name",c||""):t.unLogin.title}),(0,i.jsx)(l.Text,{size:"3",className:"l-tablet:mt-[4px] l-xxl:text-[14px] mt-[16px]",html:s?t.login.description:t.unLogin.description}),!s&&(0,i.jsxs)("div",{className:(0,d.default)("mt-[32px] grid w-fit grid-flow-col gap-[12px] l-tablet:mt-[24px]",s&&"hidden"),children:[(0,i.jsx)(l.Button,{variant:"secondary",size:"lg",onClick:()=>{g("primary")},children:t.joinNow}),(0,i.jsx)(l.Button,{variant:"primary",size:"lg",onClick:()=>{g("secondary")},children:t.loginIn})]})]})})})]})})}
13
13
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/components/credits/creditsBanner/index.tsx"],
4
- "sourcesContent": ["import { Button, Container, Heading, Picture, Text } from '@anker-in/headless-ui'\nimport classNames from 'classnames'\nimport { useCreditsContext } from '../context/provider'\n\ntype Img = {\n url: string\n alt: string\n width?: number\n height?: number\n}\nexport type CreditsBannerCopy = {\n pcImg: Img\n laptopImg: Img\n mobileImg: Img\n joinNow: string\n loginIn: string\n login: {\n title: string\n description: string\n }\n unLogin: {\n title: string\n description: string\n }\n}\n\nexport function CreditsBanner({ copy, id }: { copy: CreditsBannerCopy; id?: string }) {\n const { profile, openSignInPopup, openSignUpPopup } = useCreditsContext()\n const isLogin = profile ? Object.keys(profile).length !== 0 : false\n\n const displayName = profile?.first_name\n ? profile?.first_name + ' ' + profile?.last_name\n : profile?.email?.split('@')[0]\n\n const handleButtonClick = (buttonType: 'primary' | 'secondary') => {\n buttonType === 'primary' ? openSignUpPopup() : openSignInPopup()\n }\n\n // \u8BA1\u7B97\u56FE\u7247\u6BD4\u4F8B\uFF0C\u5982\u679C\u6CA1\u6709\u914D\u7F6E\u5219\u4F7F\u7528\u9ED8\u8BA4\u503C\n const pcAspectRatio =\n copy.pcImg?.width && copy.pcImg?.height ? `${copy.pcImg.width} / ${copy.pcImg.height}` : '1920 / 804'\n const laptopAspectRatio =\n copy.laptopImg?.width && copy.laptopImg?.height ? `${copy.laptopImg.width} / ${copy.laptopImg.height}` : '768 / 660'\n const mobileAspectRatio =\n copy.mobileImg?.width && copy.mobileImg?.height ? `${copy.mobileImg.width} / ${copy.mobileImg.height}` : '390 / 660'\n\n return (\n <>\n <div id={id} className=\" relative w-full\">\n <Picture\n className=\"credits-banner-image w-full\"\n style={{\n aspectRatio: pcAspectRatio,\n }}\n imgClassName=\"object-cover w-full h-full\"\n source={`${copy.pcImg?.url}, ${copy.laptopImg?.url} 1024, ${copy.mobileImg?.url} 768`}\n alt={copy.pcImg?.alt}\n loading=\"eager\"\n ></Picture>\n <style>\n {`\n @media (max-width: 1024px) and (min-width: 768px) {\n .credits-banner-image {\n aspect-ratio: ${laptopAspectRatio} !important;\n }\n }\n @media (max-width: 767px) {\n .credits-banner-image {\n aspect-ratio: ${mobileAspectRatio} !important;\n }\n }\n `}\n </style>\n {isLogin && (\n <div\n className=\"absolute inset-0 \"\n style={{\n background: 'linear-gradient(180deg, rgba(245, 245, 247, 0) 70.31%, #F5F5F7 100%)',\n }}\n ></div>\n )}\n\n <Container className=\"l:h-auto !absolute inset-0 mx-auto grid h-full\" asChild>\n <div className=\"grid grid-cols-12\">\n <div className=\"l:col-span-12 l:justify-start l:truncate l:pt-[32px] col-span-5 flex h-full flex-col justify-center text-[#1F2021]\">\n <Heading\n as=\"h1\"\n className=\"text-[48px] font-bold xl-xxl:text-[40px] l:text-[32px]\"\n html={isLogin ? copy.login.title?.replace('$name', displayName || '') : copy.unLogin.title}\n ></Heading>\n\n <Text\n size=\"3\"\n className=\"l:mt-[4px] l-xxl:text-[14px] mt-[16px]\"\n html={isLogin ? copy.login.description : copy.unLogin.description}\n ></Text>\n\n {!isLogin && (\n <div\n className={classNames(\n 'mt-[32px] grid w-fit grid-flow-col gap-[12px] l:mt-[24px]',\n isLogin && 'hidden'\n )}\n >\n <Button\n variant=\"secondary\"\n size=\"lg\"\n onClick={() => {\n handleButtonClick('primary')\n }}\n >\n {copy.joinNow}\n </Button>\n <Button\n variant=\"primary\"\n size=\"lg\"\n onClick={() => {\n handleButtonClick('secondary')\n }}\n >\n {copy.loginIn}\n </Button>\n </div>\n )}\n </div>\n </div>\n </Container>\n </div>\n </>\n )\n}\n"],
5
- "mappings": "0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,mBAAAE,IAAA,eAAAC,EAAAH,GA+CI,IAAAI,EAAA,6BA/CJC,EAA0D,iCAC1DC,EAAuB,yBACvBC,EAAkC,+BAwB3B,SAASL,EAAc,CAAE,KAAAM,EAAM,GAAAC,CAAG,EAA6C,CACpF,KAAM,CAAE,QAAAC,EAAS,gBAAAC,EAAiB,gBAAAC,CAAgB,KAAI,qBAAkB,EAClEC,EAAUH,EAAU,OAAO,KAAKA,CAAO,EAAE,SAAW,EAAI,GAExDI,EAAcJ,GAAS,WACzBA,GAAS,WAAa,IAAMA,GAAS,UACrCA,GAAS,OAAO,MAAM,GAAG,EAAE,CAAC,EAE1BK,EAAqBC,GAAwC,CACjEA,IAAe,UAAYJ,EAAgB,EAAID,EAAgB,CACjE,EAGMM,EACJT,EAAK,OAAO,OAASA,EAAK,OAAO,OAAS,GAAGA,EAAK,MAAM,KAAK,MAAMA,EAAK,MAAM,MAAM,GAAK,aACrFU,EACJV,EAAK,WAAW,OAASA,EAAK,WAAW,OAAS,GAAGA,EAAK,UAAU,KAAK,MAAMA,EAAK,UAAU,MAAM,GAAK,YACrGW,EACJX,EAAK,WAAW,OAASA,EAAK,WAAW,OAAS,GAAGA,EAAK,UAAU,KAAK,MAAMA,EAAK,UAAU,MAAM,GAAK,YAE3G,SACE,mBACE,oBAAC,OAAI,GAAIC,EAAI,UAAU,mBACrB,oBAAC,WACC,UAAU,8BACV,MAAO,CACL,YAAaQ,CACf,EACA,aAAa,6BACb,OAAQ,GAAGT,EAAK,OAAO,GAAG,KAAKA,EAAK,WAAW,GAAG,UAAUA,EAAK,WAAW,GAAG,OAC/E,IAAKA,EAAK,OAAO,IACjB,QAAQ,QACT,KACD,OAAC,SACE;AAAA;AAAA;AAAA,gCAGqBU,CAAiB;AAAA;AAAA;AAAA;AAAA;AAAA,gCAKjBC,CAAiB;AAAA;AAAA;AAAA,YAIzC,EACCN,MACC,OAAC,OACC,UAAU,oBACV,MAAO,CACL,WAAY,sEACd,EACD,KAGH,OAAC,aAAU,UAAU,iDAAiD,QAAO,GAC3E,mBAAC,OAAI,UAAU,oBACb,oBAAC,OAAI,UAAU,qHACb,oBAAC,WACC,GAAG,KACH,UAAU,yDACV,KAAMA,EAAUL,EAAK,MAAM,OAAO,QAAQ,QAASM,GAAe,EAAE,EAAIN,EAAK,QAAQ,MACtF,KAED,OAAC,QACC,KAAK,IACL,UAAU,yCACV,KAAMK,EAAUL,EAAK,MAAM,YAAcA,EAAK,QAAQ,YACvD,EAEA,CAACK,MACA,QAAC,OACC,aAAW,EAAAO,SACT,4DACAP,GAAW,QACb,EAEA,oBAAC,UACC,QAAQ,YACR,KAAK,KACL,QAAS,IAAM,CACbE,EAAkB,SAAS,CAC7B,EAEC,SAAAP,EAAK,QACR,KACA,OAAC,UACC,QAAQ,UACR,KAAK,KACL,QAAS,IAAM,CACbO,EAAkB,WAAW,CAC/B,EAEC,SAAAP,EAAK,QACR,GACF,GAEJ,EACF,EACF,GACF,EACF,CAEJ",
4
+ "sourcesContent": ["import { Button, Container, Heading, Picture, Text } from '@anker-in/headless-ui'\nimport classNames from 'classnames'\nimport { useCreditsContext } from '../context/provider'\n\ntype Img = {\n url: string\n alt: string\n width?: number\n height?: number\n}\nexport type CreditsBannerCopy = {\n pcImg: Img\n laptopImg: Img\n mobileImg: Img\n joinNow: string\n loginIn: string\n login: {\n title: string\n description: string\n }\n unLogin: {\n title: string\n description: string\n }\n}\n\nexport function CreditsBanner({ copy, id }: { copy: CreditsBannerCopy; id?: string }) {\n const { profile, openSignInPopup, openSignUpPopup } = useCreditsContext()\n const isLogin = profile ? Object.keys(profile).length !== 0 : false\n\n const displayName = profile?.first_name\n ? profile?.first_name + ' ' + profile?.last_name\n : profile?.email?.split('@')[0]\n\n const handleButtonClick = (buttonType: 'primary' | 'secondary') => {\n buttonType === 'primary' ? openSignUpPopup() : openSignInPopup()\n }\n\n // \u8BA1\u7B97\u56FE\u7247\u6BD4\u4F8B\uFF0C\u5982\u679C\u6CA1\u6709\u914D\u7F6E\u5219\u4F7F\u7528\u9ED8\u8BA4\u503C\n const pcAspectRatio =\n copy.pcImg?.width && copy.pcImg?.height ? `${copy.pcImg.width} / ${copy.pcImg.height}` : '1920 / 804'\n const laptopAspectRatio =\n copy.laptopImg?.width && copy.laptopImg?.height ? `${copy.laptopImg.width} / ${copy.laptopImg.height}` : '768 / 660'\n const mobileAspectRatio =\n copy.mobileImg?.width && copy.mobileImg?.height ? `${copy.mobileImg.width} / ${copy.mobileImg.height}` : '390 / 660'\n\n return (\n <>\n <div id={id} className=\" relative w-full\">\n <Picture\n className=\"credits-banner-image w-full\"\n style={{\n aspectRatio: pcAspectRatio,\n }}\n imgClassName=\"object-cover w-full h-full\"\n source={`${copy.pcImg?.url}, ${copy.laptopImg?.url} 1024, ${copy.mobileImg?.url} 768`}\n alt={copy.pcImg?.alt}\n loading=\"eager\"\n ></Picture>\n <style>\n {`\n @media (max-width: 1024px) and (min-width: 768px) {\n .credits-banner-image {\n aspect-ratio: ${laptopAspectRatio} !important;\n }\n }\n @media (max-width: 767px) {\n .credits-banner-image {\n aspect-ratio: ${mobileAspectRatio} !important;\n }\n }\n `}\n </style>\n {isLogin && (\n <div\n className=\"absolute inset-0 \"\n style={{\n background: 'linear-gradient(180deg, rgba(245, 245, 247, 0) 70.31%, #F5F5F7 100%)',\n }}\n ></div>\n )}\n\n <Container className=\"l-tablet:h-auto !absolute inset-0 mx-auto grid h-full\" asChild>\n <div className=\"grid grid-cols-12\">\n <div className=\"l-tablet:col-span-12 l-tablet:justify-start l-tablet:truncate l-tablet:pt-[32px] col-span-5 flex h-full flex-col justify-center text-[#1F2021]\">\n <Heading\n as=\"h1\"\n className=\"text-[48px] font-bold xl-xxl:text-[40px] l-tablet:text-[32px]\"\n html={isLogin ? copy.login.title?.replace('$name', displayName || '') : copy.unLogin.title}\n ></Heading>\n\n <Text\n size=\"3\"\n className=\"l-tablet:mt-[4px] l-xxl:text-[14px] mt-[16px]\"\n html={isLogin ? copy.login.description : copy.unLogin.description}\n ></Text>\n\n {!isLogin && (\n <div\n className={classNames(\n 'mt-[32px] grid w-fit grid-flow-col gap-[12px] l-tablet:mt-[24px]',\n isLogin && 'hidden'\n )}\n >\n <Button\n variant=\"secondary\"\n size=\"lg\"\n onClick={() => {\n handleButtonClick('primary')\n }}\n >\n {copy.joinNow}\n </Button>\n <Button\n variant=\"primary\"\n size=\"lg\"\n onClick={() => {\n handleButtonClick('secondary')\n }}\n >\n {copy.loginIn}\n </Button>\n </div>\n )}\n </div>\n </div>\n </Container>\n </div>\n </>\n )\n}\n"],
5
+ "mappings": "0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,mBAAAE,IAAA,eAAAC,EAAAH,GA+CI,IAAAI,EAAA,6BA/CJC,EAA0D,iCAC1DC,EAAuB,yBACvBC,EAAkC,+BAwB3B,SAASL,EAAc,CAAE,KAAAM,EAAM,GAAAC,CAAG,EAA6C,CACpF,KAAM,CAAE,QAAAC,EAAS,gBAAAC,EAAiB,gBAAAC,CAAgB,KAAI,qBAAkB,EAClEC,EAAUH,EAAU,OAAO,KAAKA,CAAO,EAAE,SAAW,EAAI,GAExDI,EAAcJ,GAAS,WACzBA,GAAS,WAAa,IAAMA,GAAS,UACrCA,GAAS,OAAO,MAAM,GAAG,EAAE,CAAC,EAE1BK,EAAqBC,GAAwC,CACjEA,IAAe,UAAYJ,EAAgB,EAAID,EAAgB,CACjE,EAGMM,EACJT,EAAK,OAAO,OAASA,EAAK,OAAO,OAAS,GAAGA,EAAK,MAAM,KAAK,MAAMA,EAAK,MAAM,MAAM,GAAK,aACrFU,EACJV,EAAK,WAAW,OAASA,EAAK,WAAW,OAAS,GAAGA,EAAK,UAAU,KAAK,MAAMA,EAAK,UAAU,MAAM,GAAK,YACrGW,EACJX,EAAK,WAAW,OAASA,EAAK,WAAW,OAAS,GAAGA,EAAK,UAAU,KAAK,MAAMA,EAAK,UAAU,MAAM,GAAK,YAE3G,SACE,mBACE,oBAAC,OAAI,GAAIC,EAAI,UAAU,mBACrB,oBAAC,WACC,UAAU,8BACV,MAAO,CACL,YAAaQ,CACf,EACA,aAAa,6BACb,OAAQ,GAAGT,EAAK,OAAO,GAAG,KAAKA,EAAK,WAAW,GAAG,UAAUA,EAAK,WAAW,GAAG,OAC/E,IAAKA,EAAK,OAAO,IACjB,QAAQ,QACT,KACD,OAAC,SACE;AAAA;AAAA;AAAA,gCAGqBU,CAAiB;AAAA;AAAA;AAAA;AAAA;AAAA,gCAKjBC,CAAiB;AAAA;AAAA;AAAA,YAIzC,EACCN,MACC,OAAC,OACC,UAAU,oBACV,MAAO,CACL,WAAY,sEACd,EACD,KAGH,OAAC,aAAU,UAAU,wDAAwD,QAAO,GAClF,mBAAC,OAAI,UAAU,oBACb,oBAAC,OAAI,UAAU,iJACb,oBAAC,WACC,GAAG,KACH,UAAU,gEACV,KAAMA,EAAUL,EAAK,MAAM,OAAO,QAAQ,QAASM,GAAe,EAAE,EAAIN,EAAK,QAAQ,MACtF,KAED,OAAC,QACC,KAAK,IACL,UAAU,gDACV,KAAMK,EAAUL,EAAK,MAAM,YAAcA,EAAK,QAAQ,YACvD,EAEA,CAACK,MACA,QAAC,OACC,aAAW,EAAAO,SACT,mEACAP,GAAW,QACb,EAEA,oBAAC,UACC,QAAQ,YACR,KAAK,KACL,QAAS,IAAM,CACbE,EAAkB,SAAS,CAC7B,EAEC,SAAAP,EAAK,QACR,KACA,OAAC,UACC,QAAQ,UACR,KAAK,KACL,QAAS,IAAM,CACbO,EAAkB,WAAW,CAC/B,EAEC,SAAAP,EAAK,QACR,GACF,GAEJ,EACF,EACF,GACF,EACF,CAEJ",
6
6
  "names": ["creditsBanner_exports", "__export", "CreditsBanner", "__toCommonJS", "import_jsx_runtime", "import_headless_ui", "import_classnames", "import_provider", "copy", "id", "profile", "openSignInPopup", "openSignUpPopup", "isLogin", "displayName", "handleButtonClick", "buttonType", "pcAspectRatio", "laptopAspectRatio", "mobileAspectRatio", "classNames"]
7
7
  }
@@ -1,4 +1,4 @@
1
- "use strict";var n=Object.defineProperty;var c=Object.getOwnPropertyDescriptor;var d=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var g=(t,e)=>{for(var a in e)n(t,a,{get:e[a],enumerable:!0})},u=(t,e,a,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of d(e))!p.call(t,r)&&r!==a&&n(t,r,{get:()=>e[r],enumerable:!(i=c(e,r))||i.enumerable});return t};var m=t=>u(n({},"__esModule",{value:!0}),t);var f={};g(f,{Default:()=>v,default:()=>h});module.exports=m(f);var o=require("react/jsx-runtime"),s=require("../components/LiveChatWidget"),b=require("../styles/livechat.css");const y={title:"Campaign/LiveChatWidget",component:s.LiveChatWidget,parameters:{layout:"fullscreen",docs:{story:{inline:!1,iframeHeight:500},description:{component:`
1
+ "use strict";var n=Object.defineProperty;var c=Object.getOwnPropertyDescriptor;var d=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var g=(t,e)=>{for(var a in e)n(t,a,{get:e[a],enumerable:!0})},m=(t,e,a,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of d(e))!p.call(t,r)&&r!==a&&n(t,r,{get:()=>e[r],enumerable:!(i=c(e,r))||i.enumerable});return t};var u=t=>m(n({},"__esModule",{value:!0}),t);var f={};g(f,{Default:()=>y,default:()=>h});module.exports=u(f);var o=require("react/jsx-runtime"),s=require("../components/LiveChatWidget"),b=require("../styles/livechat.css");const v={title:"Campaign/LiveChatWidget",component:s.LiveChatWidget,parameters:{layout:"fullscreen",docs:{story:{inline:!1,iframeHeight:500},description:{component:`
2
2
  # LiveChat \u804A\u5929\u7EC4\u4EF6
3
3
 
4
4
  \u53EF\u590D\u7528\u7684\u6C14\u6CE1\u5F39\u7A97\u804A\u5929\u7EC4\u4EF6\uFF0C\u652F\u6301 SSE \u6D41\u5F0F\u6D88\u606F\u3001\u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\u548C\u591A\u79CD\u6D88\u606F\u7C7B\u578B\u3002
@@ -48,12 +48,5 @@ const customRenderers = {
48
48
  customRenderers={customRenderers}
49
49
  />
50
50
  \`\`\`
51
- `}}},tags:["autodocs"],argTypes:{apiBaseUrl:{control:"text",description:"API \u57FA\u7840 URL"},headers:{control:"object",description:"\u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\uFF0C\u5C06\u5728\u6240\u6709 API \u8BF7\u6C42\u4E2D\u6DFB\u52A0",table:{defaultValue:{summary:"undefined"}}},recaptchaSitekey:{control:"text",description:"Google reCAPTCHA v3 site key\uFF0C\u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u81EA\u52A8\u542F\u7528 reCAPTCHA v3 \u9A8C\u8BC1",table:{defaultValue:{summary:"undefined"}}},recaptchaAction:{control:"text",description:"reCAPTCHA action \u540D\u79F0\uFF0C\u7528\u4E8E\u533A\u5206\u4E0D\u540C\u7684\u9A8C\u8BC1\u573A\u666F",table:{defaultValue:{summary:'"activity"'}}},site:{control:"text",description:"Shopify \u5E97\u94FA URL"},channelCode:{control:"text",description:"\u6E20\u9053\u7F16\u7801\uFF0C\u7528\u4E8E\u6807\u8BC6\u6765\u6E90\u6E20\u9053",table:{defaultValue:{summary:"undefined"}}},welcomeMessage:{control:"text",description:"\u6B22\u8FCE\u6D88\u606F",table:{defaultValue:{summary:"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F"}}},logoUrl:{control:"text",description:"Logo URL"},position:{control:"object",description:"\u6C14\u6CE1\u6309\u94AE\u4F4D\u7F6E\u5BF9\u8C61",table:{defaultValue:{summary:'{ bottom: "1.5rem", right: "1.5rem" }'}}}},args:{apiBaseUrl:"http://172.16.38.183:3003",site:"www.eufy.com",loginUserId:"test_test",welcomeMessage:"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F"}};var h=y;const v={args:{loginUserId:"test_test",apiBaseUrl:"http://172.16.38.183:3003",site:"beta.eufy.com",channelCode:"dtc",title:"eufy AI Assistant",cartId:"gid://shopify/Cart/hWN7wB3Pa12gh78d8hPOAUBI?key=0e73db1d3fb5ac21da19099c45033253",accessToken:"47b1aa2c0797043f9baba39388029d70",position:{bottom:"24px",right:"30px"},welcomeMessage:`Welcome to eufy AI Assistant!
52
-
53
- I can help you with:
54
- - Product recommendations
55
- - Order tracking
56
- - FAQs and support
57
-
58
- How can I assist you today?`,quickReplies:[{id:"1",label:"Product Info",value:"Tell me about your products",icon:"\u{1F4E6}"},{id:"2",label:"Track Order",value:"I want to track my order",icon:"\u{1F69A}"},{id:"3",label:"Support",value:"I need help with my device",icon:"\u{1F527}"},{id:"4",label:"Recommendations",value:"Recommend products for me",icon:"\u2B50"}],complianceConfig:{title:"Hi! I'm your eufy AI assistant.",content:"AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data.",checkboxText:`By starting to use "Live Chat", you agree to Anker's <a href="https://www.anker.com/pages/privacy-policy" target="_blank" rel="noopener noreferrer" style="text-decoration: underline;">LIVE CHAT PRIVACY NOTICE</a>.`,agreeButtonText:"Agree"},recaptchaAction:"livechat",showNewSessionButton:!0,commonText:{learnMore:"Learn More",total:"Total"},customRenderers:{video:{render:t=>{const e=t;return(0,o.jsxs)("div",{className:"w-full",children:[(0,o.jsx)("video",{src:e.url,controls:!0,className:"w-full rounded-lg",poster:e.poster,children:"Your browser does not support video playback"}),e.title&&(0,o.jsx)("p",{className:"mt-2 text-sm text-gray-600",children:e.title})]})}},image_gallery:{render:t=>{const a=t.images||[];return(0,o.jsx)("div",{className:"grid grid-cols-2 gap-2",children:a.map((i,r)=>(0,o.jsx)("div",{className:"relative aspect-square",children:(0,o.jsx)("img",{src:i.url,alt:i.alt||`Image ${r+1}`,className:"size-full rounded-lg object-cover"})},r))})}}},productCardRender:(t,e)=>{if(!t)return(0,o.jsx)("div",{style:{padding:"16px",border:"1px dashed #ccc",borderRadius:"8px",textAlign:"center"},children:(0,o.jsxs)("p",{children:["Product loading... (handle: ",e,")"]})});const a=t?.featured_image||"",i=t?.title||"",r=t?.description||"",l=t?.average_rating;return(0,o.jsx)("div",{style:{border:"2px solid #4CAF50",borderRadius:"16px",padding:"16px",margin:"12px 0",backgroundColor:"#f0f9ff",boxShadow:"0 4px 12px rgba(76, 175, 80, 0.15)"},children:(0,o.jsxs)("div",{style:{display:"flex",gap:"12px",alignItems:"center"},children:[a&&(0,o.jsx)("img",{src:a,alt:i,style:{width:"60px",height:"60px",borderRadius:"8px",objectFit:"cover"}}),(0,o.jsxs)("div",{style:{flex:1},children:[(0,o.jsx)("h4",{style:{margin:"0 0 4px 0",fontSize:"16px",fontWeight:"bold"},children:i}),r&&(0,o.jsxs)("p",{style:{margin:0,fontSize:"12px",color:"#666",lineHeight:1.4},children:[r.slice(0,80),"..."]}),l&&(0,o.jsxs)("span",{style:{fontSize:"12px",color:"#FFB800"},children:["Rating: ",l.toFixed(1)]})]}),(0,o.jsx)("button",{style:{padding:"8px 16px",backgroundColor:"#4CAF50",color:"white",borderRadius:"8px",border:"none",cursor:"pointer"},onClick:()=>console.log("View product:",e,t),children:"View"})]})})}},render:t=>(0,o.jsx)(s.LiveChatWidget,{...t,onOpen:()=>console.log("[LiveChat] Chat opened"),onClose:()=>console.log("[LiveChat] Chat closed"),onMessageSend:e=>console.log("[LiveChat] Message sent:",e),onError:e=>console.error("[LiveChat] Error:",e),onTextMessage:()=>console.log("[LiveChat] AI text message received"),onProductList:()=>console.log("[LiveChat] Product list received"),onPromotionList:()=>console.log("[LiveChat] Promotion list received"),onAddToCart:e=>{console.log("[LiveChat] Add to cart:",e),alert(`Added "${e.title}" to cart!`)},onCart:(e,a)=>{console.log("[LiveChat] Cart clicked:",{cartId:e,checkoutUrl:a}),alert(`Cart ID: ${e}`)}})};
51
+ `}}},tags:["autodocs"],argTypes:{apiBaseUrl:{control:"text",description:"API \u57FA\u7840 URL"},headers:{control:"object",description:"\u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\uFF0C\u5C06\u5728\u6240\u6709 API \u8BF7\u6C42\u4E2D\u6DFB\u52A0",table:{defaultValue:{summary:"undefined"}}},recaptchaSitekey:{control:"text",description:"Google reCAPTCHA v3 site key\uFF0C\u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u81EA\u52A8\u542F\u7528 reCAPTCHA v3 \u9A8C\u8BC1",table:{defaultValue:{summary:"undefined"}}},recaptchaAction:{control:"text",description:"reCAPTCHA action \u540D\u79F0\uFF0C\u7528\u4E8E\u533A\u5206\u4E0D\u540C\u7684\u9A8C\u8BC1\u573A\u666F",table:{defaultValue:{summary:'"activity"'}}},site:{control:"text",description:"Shopify \u5E97\u94FA URL"},channelCode:{control:"text",description:"\u6E20\u9053\u7F16\u7801\uFF0C\u7528\u4E8E\u6807\u8BC6\u6765\u6E90\u6E20\u9053",table:{defaultValue:{summary:"undefined"}}},welcomeMessage:{control:"text",description:"\u6B22\u8FCE\u6D88\u606F",table:{defaultValue:{summary:"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F"}}},logoUrl:{control:"text",description:"Logo URL"},position:{control:"object",description:"\u6C14\u6CE1\u6309\u94AE\u4F4D\u7F6E\u5BF9\u8C61",table:{defaultValue:{summary:'{ bottom: "1.5rem", right: "1.5rem" }'}}}},args:{apiBaseUrl:"http://172.16.38.183:3003",site:"www.eufy.com",loginUserId:"test_test",welcomeMessage:"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F"}};var h=v;const y={args:{loginUserId:"test_test1",apiBaseUrl:"http://172.16.38.183:3003",site:"beta.eufy.com",channelCode:"dtc",title:"eufy AI Assistant",cartId:"gid://shopify/Cart/hWN7wB3Pa12gh78d8hPOAUBI?key=0e73db1d3fb5ac21da19099c45033253",accessToken:"47b1aa2c0797043f9baba39388029d70",position:{bottom:"24px",right:"30px"},welcomeMessage:"",quickReplies:[{id:"1",label:"Product Info",value:"Tell me about your products",icon:"\u{1F4E6}"},{id:"2",label:"Track Order",value:"I want to track my order",icon:"\u{1F69A}"},{id:"3",label:"Support",value:"I need help with my device",icon:"\u{1F527}"},{id:"4",label:"Recommendations",value:"Recommend products for me",icon:"\u2B50"}],complianceConfig:{title:"Hi! I'm your eufy AI assistant.",content:"AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data.",checkboxText:`By starting to use "Live Chat", you agree to Anker's <a href="https://www.anker.com/pages/privacy-policy" target="_blank" rel="noopener noreferrer" style="text-decoration: underline;">LIVE CHAT PRIVACY NOTICE</a>.`,agreeButtonText:"Agree"},recaptchaAction:"livechat",showNewSessionButton:!0,commonText:{learnMore:"Learn More",total:"Total"},customRenderers:{video:{render:t=>{const e=t;return(0,o.jsxs)("div",{className:"w-full",children:[(0,o.jsx)("video",{src:e.url,controls:!0,className:"w-full rounded-lg",poster:e.poster,children:"Your browser does not support video playback"}),e.title&&(0,o.jsx)("p",{className:"mt-2 text-sm text-gray-600",children:e.title})]})}},image_gallery:{render:t=>{const a=t.images||[];return(0,o.jsx)("div",{className:"grid grid-cols-2 gap-2",children:a.map((i,r)=>(0,o.jsx)("div",{className:"relative aspect-square",children:(0,o.jsx)("img",{src:i.url,alt:i.alt||`Image ${r+1}`,className:"size-full rounded-lg object-cover"})},r))})}}},productCardRender:(t,e)=>{if(!t)return(0,o.jsx)("div",{style:{padding:"16px",border:"1px dashed #ccc",borderRadius:"8px",textAlign:"center"},children:(0,o.jsxs)("p",{children:["Product loading... (handle: ",e,")"]})});const a=t?.featured_image||"",i=t?.title||"",r=t?.description||"",l=t?.average_rating;return(0,o.jsx)("div",{style:{border:"2px solid #4CAF50",borderRadius:"16px",padding:"16px",margin:"12px 0",backgroundColor:"#f0f9ff",boxShadow:"0 4px 12px rgba(76, 175, 80, 0.15)"},children:(0,o.jsxs)("div",{style:{display:"flex",gap:"12px",alignItems:"center"},children:[a&&(0,o.jsx)("img",{src:a,alt:i,style:{width:"60px",height:"60px",borderRadius:"8px",objectFit:"cover"}}),(0,o.jsxs)("div",{style:{flex:1},children:[(0,o.jsx)("h4",{style:{margin:"0 0 4px 0",fontSize:"16px",fontWeight:"bold"},children:i}),r&&(0,o.jsxs)("p",{style:{margin:0,fontSize:"12px",color:"#666",lineHeight:1.4},children:[r.slice(0,80),"..."]}),l&&(0,o.jsxs)("span",{style:{fontSize:"12px",color:"#FFB800"},children:["Rating: ",l.toFixed(1)]})]}),(0,o.jsx)("button",{style:{padding:"8px 16px",backgroundColor:"#4CAF50",color:"white",borderRadius:"8px",border:"none",cursor:"pointer"},onClick:()=>console.log("View product:",e,t),children:"View"})]})})}},render:t=>(0,o.jsx)(s.LiveChatWidget,{...t,onOpen:()=>console.log("[LiveChat] Chat opened"),onClose:()=>console.log("[LiveChat] Chat closed"),onMessageSend:e=>console.log("[LiveChat] Message sent:",e),onError:e=>console.error("[LiveChat] Error:",e),onTextMessage:()=>console.log("[LiveChat] AI text message received"),onProductList:()=>console.log("[LiveChat] Product list received"),onPromotionList:()=>console.log("[LiveChat] Promotion list received"),onAddToCart:e=>{console.log("[LiveChat] Add to cart:",e),alert(`Added "${e.title}" to cart!`)},onCart:(e,a)=>{console.log("[LiveChat] Cart clicked:",{cartId:e,checkoutUrl:a}),alert(`Cart ID: ${e}`)}})};
59
52
  //# sourceMappingURL=LiveChatWidget.stories.js.map