@anker-in/campaign-ui 0.2.11-beta.30 → 0.2.11-beta.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js +1 -1
- package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/ChatInput.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/ChatInput.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/ChatMessage.js +2 -2
- package/dist/cjs/components/LiveChatWidget/components/ChatMessage.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/ChatWindow.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/ChatWindow.js.map +2 -2
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/FAQList.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/FAQList.js.map +2 -2
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/PolicyBlock.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/PolicyBlock.js.map +2 -2
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.d.ts +3 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductComparison.d.ts +1 -0
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductComparison.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductComparison.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductList.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductList.js.map +2 -2
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/QuickReplies.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/QuickReplies.js.map +2 -2
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/TextBlock.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/TextBlock.js.map +2 -2
- package/dist/cjs/components/LiveChatWidget/constants.js +1 -1
- package/dist/cjs/components/LiveChatWidget/constants.js.map +2 -2
- package/dist/cjs/components/LiveChatWidget/hooks/useChatState.d.ts +5 -1
- package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js +1 -1
- package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/types.d.ts +26 -0
- package/dist/cjs/components/LiveChatWidget/types.js.map +1 -1
- package/dist/cjs/stories/LiveChatWidget.stories.d.ts +8 -0
- package/dist/cjs/stories/LiveChatWidget.stories.js +21 -4
- package/dist/cjs/stories/LiveChatWidget.stories.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/LiveChatWidget.js +1 -1
- package/dist/esm/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/ChatInput.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/ChatInput.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/ChatMessage.js +2 -2
- package/dist/esm/components/LiveChatWidget/components/ChatMessage.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/ChatWindow.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/ChatWindow.js.map +2 -2
- package/dist/esm/components/LiveChatWidget/components/MessageContent/FAQList.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/FAQList.js.map +2 -2
- package/dist/esm/components/LiveChatWidget/components/MessageContent/PolicyBlock.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/PolicyBlock.js.map +2 -2
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.d.ts +3 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.d.ts +1 -0
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductList.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductList.js.map +2 -2
- package/dist/esm/components/LiveChatWidget/components/MessageContent/QuickReplies.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/QuickReplies.js.map +2 -2
- package/dist/esm/components/LiveChatWidget/components/MessageContent/TextBlock.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/TextBlock.js.map +2 -2
- package/dist/esm/components/LiveChatWidget/constants.js +1 -1
- package/dist/esm/components/LiveChatWidget/constants.js.map +2 -2
- package/dist/esm/components/LiveChatWidget/hooks/useChatState.d.ts +5 -1
- package/dist/esm/components/LiveChatWidget/hooks/useChatState.js +1 -1
- package/dist/esm/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/types.d.ts +26 -0
- package/dist/esm/stories/LiveChatWidget.stories.d.ts +8 -0
- package/dist/esm/stories/LiveChatWidget.stories.js +24 -7
- package/dist/esm/stories/LiveChatWidget.stories.js.map +3 -3
- package/package.json +1 -1
- package/src/components/LiveChatWidget/LiveChatWidget.tsx +97 -48
- package/src/components/LiveChatWidget/components/ChatInput.tsx +26 -1
- package/src/components/LiveChatWidget/components/ChatMessage.tsx +11 -4
- package/src/components/LiveChatWidget/components/ChatWindow.tsx +2 -0
- package/src/components/LiveChatWidget/components/MessageContent/FAQList.tsx +1 -1
- package/src/components/LiveChatWidget/components/MessageContent/PolicyBlock.tsx +1 -1
- package/src/components/LiveChatWidget/components/MessageContent/ProductCard.tsx +12 -4
- package/src/components/LiveChatWidget/components/MessageContent/ProductComparison.tsx +23 -6
- package/src/components/LiveChatWidget/components/MessageContent/ProductList.tsx +1 -1
- package/src/components/LiveChatWidget/components/MessageContent/QuickReplies.tsx +1 -1
- package/src/components/LiveChatWidget/components/MessageContent/TextBlock.tsx +2 -2
- package/src/components/LiveChatWidget/constants.ts +12 -5
- package/src/components/LiveChatWidget/hooks/useChatState.ts +141 -10
- package/src/components/LiveChatWidget/types.ts +27 -0
- package/src/stories/LiveChatWidget.stories.tsx +182 -0
- package/src/styles/livechat.css +1 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/stories/LiveChatWidget.stories.tsx"],
|
|
4
|
-
"sourcesContent": ["/**\n * LiveChatWidget Storybook Stories\n * \u5C55\u793A LiveChat \u7EC4\u4EF6\u7684\u5404\u79CD\u4F7F\u7528\u573A\u666F\u548C\u914D\u7F6E\n */\n\nimport type { Meta, StoryObj } from '@storybook/react'\nimport { LiveChatWidget } from '../components/LiveChatWidget'\nimport type { MessageRenderer, MessageContent } from '../components/LiveChatWidget'\nimport '../styles/livechat.css'\n\nconst meta: Meta<typeof LiveChatWidget> = {\n title: 'Campaign/LiveChatWidget',\n component: LiveChatWidget,\n parameters: {\n layout: 'fullscreen',\n docs: {\n story: {\n inline: false,\n iframeHeight: 500,\n },\n description: {\n component: `\n# LiveChat \u804A\u5929\u7EC4\u4EF6\n\n\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\n\n## \u529F\u80FD\u7279\u6027\n\n- \uD83C\uDF88 **\u6C14\u6CE1\u5F39\u7A97**: \u53EF\u81EA\u5B9A\u4E49\u4F4D\u7F6E\u7684\u60AC\u6D6E\u6C14\u6CE1\u6309\u94AE\n- \uD83D\uDCAC **\u6D41\u5F0F\u6D88\u606F**: \u57FA\u4E8E SSE \u7684\u5B9E\u65F6\u6D41\u5F0F\u54CD\u5E94\n- \uD83D\uDCE6 **\u591A\u79CD\u6D88\u606F\u7C7B\u578B**: \u6587\u672C\u3001\u5546\u54C1\u5361\u7247\u3001\u5546\u54C1\u5217\u8868\u3001\u653F\u7B56\u3001\u5FEB\u6377\u56DE\u590D\u7B49\n- \uD83C\uDFA8 **\u53EF\u5B9A\u5236**: \u652F\u6301\u81EA\u5B9A\u4E49\u54C1\u724C\u989C\u8272\u3001Logo\u3001\u6E32\u67D3\u5668\n- \uD83D\uDCF1 **\u54CD\u5E94\u5F0F**: \u79FB\u52A8\u7AEF\u5168\u5C4F\uFF0C\u684C\u9762\u7AEF\u56FA\u5B9A\u5C3A\u5BF8\n- \uD83D\uDCBE **\u4F1A\u8BDD\u7BA1\u7406**: \u81EA\u52A8\u7BA1\u7406 userId \u548C sessionId\n- \uD83D\uDD12 **\u5B89\u5168\u9632\u62A4**: \u5185\u7F6E XSS \u9632\u62A4\u548C\u8F93\u5165\u9A8C\u8BC1\n\n## \u57FA\u7840\u7528\u6CD5\n\n\\`\\`\\`tsx\nimport { LiveChatWidget } from '@anker-in/campaign-ui'\nimport '@anker-in/campaign-ui/livechat.css'\n\nfunction App() {\n return (\n <LiveChatWidget\n apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n site=\"www.eufy.com\"\n channel_code=\"web_homepage\"\n welcomeMessage=\"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\"\n />\n )\n}\n\\`\\`\\`\n\n## \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\n\n\\`\\`\\`tsx\nconst customRenderers = {\n video: {\n render: (content) => (\n <video src={content.url} controls className=\"w-full rounded\" />\n )\n }\n}\n\n<LiveChatWidget\n apiBaseUrl=\"...\"\n site=\"...\"\n customRenderers={customRenderers}\n/>\n\\`\\`\\`\n `,\n },\n },\n },\n tags: ['autodocs'],\n argTypes: {\n apiBaseUrl: {\n control: 'text',\n description: 'API \u57FA\u7840 URL',\n },\n headers: {\n control: 'object',\n description: '\u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\uFF0C\u5C06\u5728\u6240\u6709 API \u8BF7\u6C42\u4E2D\u6DFB\u52A0',\n table: {\n defaultValue: { summary: 'undefined' },\n },\n },\n recaptchaSitekey: {\n control: 'text',\n description: 'Google reCAPTCHA v3 site key\uFF0C\u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u81EA\u52A8\u542F\u7528 reCAPTCHA v3 \u9A8C\u8BC1',\n table: {\n defaultValue: { summary: 'undefined' },\n },\n },\n recaptchaAction: {\n control: 'text',\n description: 'reCAPTCHA action \u540D\u79F0\uFF0C\u7528\u4E8E\u533A\u5206\u4E0D\u540C\u7684\u9A8C\u8BC1\u573A\u666F',\n table: {\n defaultValue: { summary: '\"activity\"' },\n },\n },\n site: {\n control: 'text',\n description: 'Shopify \u5E97\u94FA URL',\n },\n channelCode: {\n control: 'text',\n description: '\u6E20\u9053\u7F16\u7801\uFF0C\u7528\u4E8E\u6807\u8BC6\u6765\u6E90\u6E20\u9053',\n table: {\n defaultValue: { summary: 'undefined' },\n },\n },\n welcomeMessage: {\n control: 'text',\n description: '\u6B22\u8FCE\u6D88\u606F',\n table: {\n defaultValue: { summary: '\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F' },\n },\n },\n logoUrl: {\n control: 'text',\n description: 'Logo URL',\n },\n position: {\n control: 'object',\n description: '\u6C14\u6CE1\u6309\u94AE\u4F4D\u7F6E\u5BF9\u8C61',\n table: {\n defaultValue: { summary: '{ bottom: \"1.5rem\", right: \"1.5rem\" }' },\n },\n },\n },\n args: {\n apiBaseUrl: 'http://172.16.38.183:3003',\n site: 'www.eufy.com',\n loginUserId: 'test_test',\n welcomeMessage: '\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F',\n },\n}\n\nexport default meta\ntype Story = StoryObj<typeof LiveChatWidget>\n\n/**\n * \u9ED8\u8BA4\u914D\u7F6E\n *\n * \u6700\u7B80\u5355\u7684\u4F7F\u7528\u65B9\u5F0F\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u914D\u7F6E\u3002\n */\nexport const Default: Story = {\n args: {\n loginUserId: 'test_test',\n apiBaseUrl: 'http://172.16.38.183:3003',\n cartId: 'gid://shopify/Cart/hWN7wB3Pa12gh78d8hPOAUBI?key=0e73db1d3fb5ac21da19099c45033253',\n accessToken: '47b1aa2c0797043f9baba39388029d70',\n title: 'eufy',\n\n commonText: {\n learnMore: 'Learn More',\n total: 'Total',\n },\n\n site: 'beta.eufy.com',\n channelCode: 'dtc',\n\n // reCAPTCHA \u914D\u7F6E\uFF08\u63D0\u4F9B sitekey \u5373\u81EA\u52A8\u542F\u7528\uFF09\n // recaptchaSitekey: '6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14',\n recaptchaAction: 'chat',\n },\n}\n\n/**\n * \u81EA\u5B9A\u4E49\u4F4D\u7F6E\n *\n * \u4F7F\u7528\u81EA\u5B9A\u4E49\u4F4D\u7F6E\u5BF9\u8C61\u6765\u63A7\u5236\u6C14\u6CE1\u6309\u94AE\u7684\u4F4D\u7F6E\u3002\n */\nexport const CustomPosition: Story = {\n args: {\n position: { bottom: '1.5rem', left: '1.5rem' },\n },\n}\n\n/**\n * \u81EA\u5B9A\u4E49\u54C1\u724C\u6837\u5F0F\n *\n * \u81EA\u5B9A\u4E49 Logo \u548C\u6B22\u8FCE\u6D88\u606F\u3002\n */\nexport const CustomBranding: Story = {\n args: {\n logoUrl: 'https://images.unsplash.com/photo-1599305445671-ac291c95aaa9?w=100&h=100&fit=crop',\n welcomeMessage: '\u6B22\u8FCE\u6765\u5230 Soundcore\uFF01\u6211\u662F\u60A8\u7684\u4E13\u5C5E AI \u52A9\u624B \uD83C\uDFA7',\n },\n}\n\n/**\n * \u5E26\u5FEB\u6377\u56DE\u590D\n *\n * \u5728\u6B22\u8FCE\u6D88\u606F\u540E\u663E\u793A\u5FEB\u6377\u56DE\u590D\u6309\u94AE\u3002\n */\nexport const WithQuickReplies: Story = {\n args: {\n quickReplies: [\n {\n id: '1',\n label: '\u67E5\u8BE2\u4EF7\u683C',\n value: '\u6211\u60F3\u4E86\u89E3\u4EA7\u54C1\u4EF7\u683C',\n icon: '\uD83D\uDCB0',\n },\n {\n id: '2',\n label: '\u67E5\u8BE2\u7269\u6D41',\n value: '\u6211\u60F3\u67E5\u8BE2\u8BA2\u5355\u7269\u6D41',\n icon: '\uD83D\uDCE6',\n },\n {\n id: '3',\n label: '\u552E\u540E\u670D\u52A1',\n value: '\u6211\u9700\u8981\u552E\u540E\u5E2E\u52A9',\n icon: '\uD83D\uDD27',\n },\n {\n id: '4',\n label: '\u4EA7\u54C1\u63A8\u8350',\n value: '\u8BF7\u63A8\u8350\u9002\u5408\u6211\u7684\u4EA7\u54C1',\n icon: '\u2B50',\n },\n ],\n },\n}\n\n/**\n * \u81EA\u5B9A\u4E49\u6B22\u8FCE\u6D88\u606F\n *\n * \u81EA\u5B9A\u4E49\u9996\u6B21\u6253\u5F00\u804A\u5929\u7A97\u53E3\u65F6\u7684\u6B22\u8FCE\u6D88\u606F\u3002\n */\nexport const CustomWelcomeMessage: Story = {\n args: {\n welcomeMessage: `\n\uD83D\uDC4B \u60A8\u597D\uFF01\u6B22\u8FCE\u6765\u5230 Soundcore \u5B98\u65B9\u5546\u57CE\u3002\n\n\u6211\u662F\u60A8\u7684 AI \u8D2D\u7269\u52A9\u624B\uFF0C\u53EF\u4EE5\u5E2E\u60A8\uFF1A\n- \uD83D\uDD0D \u67E5\u627E\u4EA7\u54C1\n- \uD83D\uDCB0 \u6BD4\u8F83\u4EF7\u683C\n- \uD83D\uDCE6 \u8DDF\u8E2A\u8BA2\u5355\n- \uD83D\uDCAC \u89E3\u7B54\u7591\u95EE\n\n\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u52A9\u60A8\u7684\u5417\uFF1F\n `.trim(),\n },\n}\n\n/**\n * \u4E8B\u4EF6\u56DE\u8C03\n *\n * \u76D1\u542C\u7EC4\u4EF6\u4E8B\u4EF6\u8FDB\u884C\u81EA\u5B9A\u4E49\u5904\u7406\u3002\n */\nexport const WithEventCallbacks: Story = {\n args: {\n title: '',\n showNewSessionButton: true,\n\n position: {\n bottom: '24px',\n right: '30px',\n },\n\n chatBubbleIcon: 'https://cdn.shopify.com/s/files/1/0504/7094/4954/files/Rectangle_400770314.png?v=1768894153',\n\n // \u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\u793A\u4F8B\n headers: undefined,\n\n site: 'beta.eufy.com',\n },\n\n render: args => (\n <LiveChatWidget\n {...args}\n onOpen={() => {\n console.log('[Story] Chat opened')\n }}\n onClose={() => {\n console.log('[Story] Chat closed')\n }}\n onMessageSend={(message: string) => {\n console.log('[Story] Message sent:', message)\n }}\n onError={(error: Error) => {\n console.error('[Story] Error occurred:', error)\n }}\n onTextMessage={() => {\n console.log('\uD83D\uDCDD [Story] AI started replying with text message')\n }}\n onProductList={() => {\n console.log('\uD83D\uDCE6 [Story] AI replied with product list card')\n }}\n onPromotionList={() => {\n console.log('\uD83C\uDF89 [Story] AI replied with promotion list card')\n }}\n onAddToCart={(product: any) => {\n console.log('\uD83D\uDED2 [Story] Add to Cart clicked!')\n console.log('\uD83D\uDCE6 Product Info:', {\n id: product.shopifyId,\n title: product.title,\n price: product.price,\n imageUrl: product.imageUrl,\n productUrl: product.productUrl,\n })\n console.log('\uD83D\uDCCB Full Product Object:', product)\n\n // \u6A21\u62DF\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u6210\u529F\n alert(`\u2705 \"${product.title}\" \u5DF2\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\uFF01\\n\\n\u4EF7\u683C: ${product.price.currency} ${product.price.amount}`)\n }}\n onCart={(cartId: string, checkoutUrl?: string) => {\n console.log('\uD83D\uDED2 [Story] Cart button clicked!')\n console.log('\uD83D\uDCCB Cart ID:', cartId)\n console.log('\uD83D\uDD17 Checkout URL:', checkoutUrl)\n\n // \u6A21\u62DF\u8D2D\u7269\u8F66\u6309\u94AE\u70B9\u51FB\u6210\u529F\n alert(`\u8D2D\u7269\u8F66\u6309\u94AE\u88AB\u70B9\u51FB\uFF01\\n\\nCart ID: ${cartId}\\nCheckout URL: ${checkoutUrl || '\u65E0'}`)\n }}\n />\n ),\n}\n\n/**\n * \u81EA\u5B9A\u4E49\u89C6\u9891\u6D88\u606F\u6E32\u67D3\u5668\n *\n * \u6269\u5C55\u6D88\u606F\u7C7B\u578B\uFF0C\u6DFB\u52A0\u89C6\u9891\u6D88\u606F\u652F\u6301\u3002\n */\nexport const CustomVideoRenderer: Story = {\n args: {\n customRenderers: {\n video: {\n render: (content: MessageContent) => {\n const videoContent = content as any\n return (\n <div className=\"w-full\">\n <video src={videoContent.url} controls className=\"w-full rounded-lg\" poster={videoContent.poster}>\n \u60A8\u7684\u6D4F\u89C8\u5668\u4E0D\u652F\u6301\u89C6\u9891\u64AD\u653E\n </video>\n {videoContent.title && <p className=\"mt-2 text-sm text-gray-600\">{videoContent.title}</p>}\n </div>\n )\n },\n } as MessageRenderer,\n },\n },\n}\n\n/**\n * \u81EA\u5B9A\u4E49\u56FE\u7247\u753B\u5ECA\u6E32\u67D3\u5668\n *\n * \u6269\u5C55\u6D88\u606F\u7C7B\u578B\uFF0C\u6DFB\u52A0\u56FE\u7247\u753B\u5ECA\u652F\u6301\u3002\n */\nexport const CustomImageGalleryRenderer: Story = {\n args: {\n customRenderers: {\n image_gallery: {\n render: (content: MessageContent) => {\n const galleryContent = content as any\n const images = galleryContent.images || []\n\n return (\n <div className=\"grid grid-cols-2 gap-2\">\n {images.map((image: any, index: number) => (\n <div key={index} className=\"relative aspect-square\">\n <img\n src={image.url}\n alt={image.alt || `Image ${index + 1}`}\n className=\"size-full rounded-lg object-cover\"\n />\n </div>\n ))}\n </div>\n )\n },\n } as MessageRenderer,\n },\n },\n}\n\n/**\n * \u79FB\u52A8\u7AEF\u9884\u89C8\n *\n * \u5728\u79FB\u52A8\u7AEF\u5C3A\u5BF8\u4E0B\u67E5\u770B\u6548\u679C\uFF08\u5168\u5C4F\u663E\u793A\uFF09\u3002\n */\nexport const MobileView: Story = {\n parameters: {\n viewport: {\n defaultViewport: 'mobile1',\n },\n },\n args: {},\n}\n\n/**\n * \u5E73\u677F\u7AEF\u9884\u89C8\n *\n * \u5728\u5E73\u677F\u7AEF\u5C3A\u5BF8\u4E0B\u67E5\u770B\u6548\u679C\u3002\n */\nexport const TabletView: Story = {\n parameters: {\n viewport: {\n defaultViewport: 'tablet',\n },\n },\n args: {},\n}\n\n/**\n * \u9ED8\u8BA4\u4F4D\u7F6E\u5C55\u793A\n *\n * \u5C55\u793A\u6C14\u6CE1\u6309\u94AE\u7684\u9ED8\u8BA4\u4F4D\u7F6E\uFF08\u53F3\u4E0B\u89D2\uFF09\u3002\n */\nexport const DefaultPositionDemo: Story = {\n render: () => (\n <div className=\"relative h-screen w-full bg-gray-50\">\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <p className=\"text-gray-500\">\u9875\u9762\u5185\u5BB9\u533A\u57DF</p>\n </div>\n\n {/* \u6F14\u793A\u9ED8\u8BA4\u4F4D\u7F6E */}\n <div className=\"absolute bottom-4 right-4 text-xs text-gray-400\">\u9ED8\u8BA4\u4F4D\u7F6E (\u53F3\u4E0B\u89D2)</div>\n\n <LiveChatWidget apiBaseUrl=\"https://beta-api-livechat.anker.com\" site=\"www.eufy.com\" />\n </div>\n ),\n}\n\n/**\n * \u4EA7\u54C1\u5BF9\u6BD4\u6D88\u606F\n *\n * \u5C55\u793A\u4EA7\u54C1\u5BF9\u6BD4\u7C7B\u578B\u7684\u6D88\u606F\u6E32\u67D3\u6548\u679C\uFF0C\u5305\u62EC\u4EF7\u683C\u3001\u4F1A\u5458\u4EF7\u3001\u53D8\u4F53\u6570\u91CF\u3001\u6298\u6263\u7B49\u591A\u4E2A\u7EF4\u5EA6\u7684\u5BF9\u6BD4\u3002\n *\n * \u4EA7\u54C1\u5BF9\u6BD4\u7EC4\u4EF6\u4F1A\u4EE5\u7F51\u683C\u5E03\u5C40\u5C55\u793A\u591A\u4E2A\u4EA7\u54C1\u7684\u57FA\u672C\u4FE1\u606F\uFF0C\u5E76\u5728\u4E0B\u65B9\u663E\u793A\u5404\u7EF4\u5EA6\u7684\u5BF9\u6BD4\u6570\u636E\u3002\n */\nexport const ProductComparisonMessage: Story = {\n render: args => (\n <LiveChatWidget\n {...args}\n onAddToCart={(product: any) => {\n console.log('\uD83D\uDED2 [ProductComparison Story] Add to Cart clicked!')\n console.log('\uD83D\uDCE6 Product Info:', {\n id: product.shopifyId,\n title: product.title,\n price: product.price,\n imageUrl: product.imageUrl,\n productUrl: product.productUrl,\n })\n console.log('\uD83D\uDCCB Full Product Object:', product)\n\n // \u6A21\u62DF\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u6210\u529F\n alert(`\u2705 \"${product.title}\" \u5DF2\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\uFF01\\n\\n\u4EF7\u683C: ${product.price.currency} ${product.price.amount}`)\n }}\n />\n ),\n args: {\n welcomeMessage: '\u4EE5\u4E0B\u662F\u60A8\u5173\u6CE8\u7684\u4EA7\u54C1\u5BF9\u6BD4\u4FE1\u606F\uFF1A',\n },\n parameters: {\n docs: {\n description: {\n story: `\n\u4EA7\u54C1\u5BF9\u6BD4\u6D88\u606F\u652F\u6301\u4EE5\u4E0B\u7EF4\u5EA6\uFF1A\n\n- **\u4EF7\u683C\u5BF9\u6BD4**: \u663E\u793A\u4EA7\u54C1\u7684\u4EF7\u683C\u533A\u95F4\u548C\u4FC3\u9500\u6807\u7B7E\n- **\u4F1A\u5458\u4EF7\u5BF9\u6BD4**: \u663E\u793A\u4F1A\u5458\u4E13\u4EAB\u4EF7\u683C\uFF08\u5982\u679C\u53EF\u7528\uFF09\n- **\u53D8\u4F53\u6570\u91CF\u5BF9\u6BD4**: \u663E\u793A\u4EA7\u54C1\u7684\u53EF\u9009\u53D8\u4F53\u6570\u91CF\n- **\u6298\u6263\u5BF9\u6BD4**: \u663E\u793A\u4EA7\u54C1\u662F\u5426\u6709\u6298\u6263\u6D3B\u52A8\n\n\u7EC4\u4EF6\u4F1A\u81EA\u52A8\u5904\u7406\u4ECE\u540E\u7AEF\u8FD4\u56DE\u7684\u4EA7\u54C1\u5BF9\u6BD4\u6570\u636E\uFF0C\u5305\u62EC\uFF1A\n- \u4EA7\u54C1\u57FA\u672C\u4FE1\u606F\uFF08\u56FE\u7247\u3001\u6807\u9898\u3001\u4EF7\u683C\u7B49\uFF09\n- \u591A\u7EF4\u5EA6\u5BF9\u6BD4\u6570\u636E\uFF08\u4EF7\u683C\u3001\u4F1A\u5458\u4EF7\u3001\u53D8\u4F53\u3001\u6298\u6263\uFF09\n- \u54CD\u5E94\u5F0F\u5E03\u5C40\uFF08\u79FB\u52A8\u7AEF\u53EF\u6A2A\u5411\u6EDA\u52A8\uFF09\n\n**\u6D4B\u8BD5 Add to Cart \u529F\u80FD\uFF1A**\n- \u70B9\u51FB\u4EA7\u54C1\u5BF9\u6BD4\u5361\u7247\u4E2D\u7684 \"Add to Cart\" \u6309\u94AE\n- \u67E5\u770B\u63A7\u5236\u53F0\u8F93\u51FA\u7684\u4EA7\u54C1\u4FE1\u606F\n- \u4F1A\u5F39\u51FA\u786E\u8BA4\u63D0\u793A\u6846\u663E\u793A\u5DF2\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\n `,\n },\n },\n },\n}\n\n/**\n * FAQ \u5217\u8868\u6D88\u606F\n *\n * \u5C55\u793A\u5E38\u89C1\u95EE\u9898\u5217\u8868\uFF0C\u652F\u6301\u6298\u53E0/\u5C55\u5F00\u3002\n *\n * FAQ \u7EC4\u4EF6\u4F1A\u663E\u793A\u641C\u7D22\u7ED3\u679C\u7684\u5E38\u89C1\u95EE\u9898\uFF0C\u6BCF\u4E2A\u95EE\u9898\u53EF\u4EE5\u70B9\u51FB\u5C55\u5F00\u67E5\u770B\u7B54\u6848\u3002\n */\nexport const FAQListMessage: Story = {\n args: {\n welcomeMessage: '\u4EE5\u4E0B\u662F\u4E0E\u60A8\u7684\u95EE\u9898\u76F8\u5173\u7684\u5E38\u89C1\u89E3\u7B54\uFF1A',\n },\n parameters: {\n docs: {\n description: {\n story: `\nFAQ \u5217\u8868\u6D88\u606F\u652F\u6301\uFF1A\n\n- **\u53EF\u6298\u53E0\u95EE\u9898**: \u70B9\u51FB\u95EE\u9898\u6807\u9898\u5C55\u5F00/\u6298\u53E0\u7B54\u6848\n- **Markdown \u7B54\u6848**: \u7B54\u6848\u652F\u6301 Markdown \u683C\u5F0F\uFF0C\u53EF\u663E\u793A\u5BCC\u6587\u672C\n- **\u76F8\u5173\u95EE\u9898**: \u5C55\u5F00\u7B54\u6848\u540E\u663E\u793A\u76F8\u5173\u95EE\u9898\uFF0C\u70B9\u51FB\u53EF\u89E6\u53D1\u65B0\u641C\u7D22\n- **\u5206\u7C7B\u6807\u8BB0**: \u6839\u636E\u95EE\u9898\u5206\u7C7B\u663E\u793A\uFF08\u914D\u9001\u3001\u9000\u8D27\u3001\u4EA7\u54C1\u3001\u652F\u4ED8\u3001\u901A\u7528\uFF09\n- **\u641C\u7D22\u7ED3\u679C\u7EDF\u8BA1**: \u663E\u793A\u627E\u5230\u7684\u95EE\u9898\u6570\u91CF\n\n\u4F7F\u7528\u573A\u666F\uFF1A\n- \u7528\u6237\u8BE2\u95EE\u5E38\u89C1\u95EE\u9898\u65F6\u8FD4\u56DE FAQ \u5217\u8868\n- \u4EA7\u54C1\u4E13\u5C5E\u95EE\u9898\u67E5\u8BE2\n- \u6309\u5206\u7C7B\u67E5\u8BE2 FAQ\n `,\n },\n },\n },\n}\n\n/**\n * \u5E26 reCAPTCHA \u914D\u7F6E\n *\n * \u5C55\u793A\u5982\u4F55\u914D\u7F6E Google reCAPTCHA v3 \u9A8C\u8BC1\uFF0C\u4FDD\u62A4 API \u8BF7\u6C42\u514D\u53D7\u6EE5\u7528\u3002\n *\n * reCAPTCHA \u4F1A\u5728\u540E\u53F0\u81EA\u52A8\u9A8C\u8BC1\u7528\u6237\u884C\u4E3A\uFF0C\u65E0\u9700\u7528\u6237\u624B\u52A8\u64CD\u4F5C\u3002\n */\nexport const WithRecaptcha: Story = {\n args: {\n // \u914D\u7F6E\u4F60\u7684 reCAPTCHA site key\uFF08\u63D0\u4F9B\u5373\u81EA\u52A8\u542F\u7528\uFF09\n recaptchaSitekey: '6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14',\n // \u53EF\u9009\uFF1A\u81EA\u5B9A\u4E49 action \u540D\u79F0\n recaptchaAction: 'livechat',\n },\n parameters: {\n docs: {\n description: {\n story: `\n### reCAPTCHA v3 \u914D\u7F6E\n\n\u542F\u7528 reCAPTCHA v3 \u53EF\u4EE5\u4FDD\u62A4\u4F60\u7684 API \u514D\u53D7\u673A\u5668\u4EBA\u548C\u6EE5\u7528\u884C\u4E3A\u7684\u653B\u51FB\u3002\n\n**\u914D\u7F6E\u6B65\u9AA4\uFF1A**\n\n1. \u5728 [Google reCAPTCHA](https://www.google.com/recaptcha/admin) \u521B\u5EFA v3 \u5BC6\u94A5\n2. \u5728\u9875\u9762\u4E2D\u52A0\u8F7D reCAPTCHA \u811A\u672C\uFF1A\n \\`\\`\\`html\n <script src=\"https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY\"></script>\n \\`\\`\\`\n3. \u914D\u7F6E LiveChatWidget \u7EC4\u4EF6\uFF08\u63D0\u4F9B sitekey \u5373\u81EA\u52A8\u542F\u7528\uFF09\uFF1A\n \\`\\`\\`tsx\n <LiveChatWidget\n recaptchaSitekey=\"YOUR_SITE_KEY\"\n recaptchaAction=\"livechat\"\n />\n \\`\\`\\`\n\n**\u5DE5\u4F5C\u539F\u7406\uFF1A**\n\n- \u63D0\u4F9B recaptchaSitekey \u53C2\u6570\u5373\u81EA\u52A8\u542F\u7528 reCAPTCHA \u9A8C\u8BC1\n- \u6BCF\u6B21\u53D1\u9001\u6D88\u606F\u6216\u521B\u5EFA\u4F1A\u8BDD\u65F6\uFF0C\u4F1A\u81EA\u52A8\u83B7\u53D6 reCAPTCHA token\n- token \u4F1A\u901A\u8FC7 \\`X-Recaptcha-Token\\` header \u53D1\u9001\u5230\u540E\u7AEF\n- \u540E\u7AEF\u9700\u8981\u9A8C\u8BC1 token \u7684\u6709\u6548\u6027\n\n**\u6CE8\u610F\u4E8B\u9879\uFF1A**\n\n- reCAPTCHA v3 \u5728\u540E\u53F0\u8FD0\u884C\uFF0C\u4E0D\u4F1A\u6253\u65AD\u7528\u6237\u4F53\u9A8C\n- \u5EFA\u8BAE\u4E3A\u4E0D\u540C\u7684\u64CD\u4F5C\u4F7F\u7528\u4E0D\u540C\u7684 \\`recaptchaAction\\` \u4EE5\u4FBF\u5206\u6790\n- \u786E\u4FDD\u540E\u7AEF\u6B63\u786E\u9A8C\u8BC1 reCAPTCHA token\n- \u4E0D\u63D0\u4F9B recaptchaSitekey \u5219\u4E0D\u542F\u7528\u9A8C\u8BC1\uFF08\u4EC5\u7528\u4E8E\u5F00\u53D1\u73AF\u5883\uFF09\n `,\n },\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["jsx", "jsxs", "LiveChatWidget", "meta", "LiveChatWidget_stories_default", "Default", "CustomPosition", "CustomBranding", "WithQuickReplies", "CustomWelcomeMessage", "WithEventCallbacks", "args", "message", "error", "product", "cartId", "checkoutUrl", "CustomVideoRenderer", "content", "videoContent", "CustomImageGalleryRenderer", "images", "image", "index", "MobileView", "TabletView", "DefaultPositionDemo", "ProductComparisonMessage", "FAQListMessage", "WithRecaptcha"]
|
|
4
|
+
"sourcesContent": ["/**\n * LiveChatWidget Storybook Stories\n * \u5C55\u793A LiveChat \u7EC4\u4EF6\u7684\u5404\u79CD\u4F7F\u7528\u573A\u666F\u548C\u914D\u7F6E\n */\n\nimport type { Meta, StoryObj } from '@storybook/react'\nimport { LiveChatWidget } from '../components/LiveChatWidget'\nimport type { MessageRenderer, MessageContent } from '../components/LiveChatWidget'\nimport '../styles/livechat.css'\n\nconst meta: Meta<typeof LiveChatWidget> = {\n title: 'Campaign/LiveChatWidget',\n component: LiveChatWidget,\n parameters: {\n layout: 'fullscreen',\n docs: {\n story: {\n inline: false,\n iframeHeight: 500,\n },\n description: {\n component: `\n# LiveChat \u804A\u5929\u7EC4\u4EF6\n\n\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\n\n## \u529F\u80FD\u7279\u6027\n\n- \uD83C\uDF88 **\u6C14\u6CE1\u5F39\u7A97**: \u53EF\u81EA\u5B9A\u4E49\u4F4D\u7F6E\u7684\u60AC\u6D6E\u6C14\u6CE1\u6309\u94AE\n- \uD83D\uDCAC **\u6D41\u5F0F\u6D88\u606F**: \u57FA\u4E8E SSE \u7684\u5B9E\u65F6\u6D41\u5F0F\u54CD\u5E94\n- \uD83D\uDCE6 **\u591A\u79CD\u6D88\u606F\u7C7B\u578B**: \u6587\u672C\u3001\u5546\u54C1\u5361\u7247\u3001\u5546\u54C1\u5217\u8868\u3001\u653F\u7B56\u3001\u5FEB\u6377\u56DE\u590D\u7B49\n- \uD83C\uDFA8 **\u53EF\u5B9A\u5236**: \u652F\u6301\u81EA\u5B9A\u4E49\u54C1\u724C\u989C\u8272\u3001Logo\u3001\u6E32\u67D3\u5668\n- \uD83D\uDCF1 **\u54CD\u5E94\u5F0F**: \u79FB\u52A8\u7AEF\u5168\u5C4F\uFF0C\u684C\u9762\u7AEF\u56FA\u5B9A\u5C3A\u5BF8\n- \uD83D\uDCBE **\u4F1A\u8BDD\u7BA1\u7406**: \u81EA\u52A8\u7BA1\u7406 userId \u548C sessionId\n- \uD83D\uDD12 **\u5B89\u5168\u9632\u62A4**: \u5185\u7F6E XSS \u9632\u62A4\u548C\u8F93\u5165\u9A8C\u8BC1\n\n## \u57FA\u7840\u7528\u6CD5\n\n\\`\\`\\`tsx\nimport { LiveChatWidget } from '@anker-in/campaign-ui'\nimport '@anker-in/campaign-ui/livechat.css'\n\nfunction App() {\n return (\n <LiveChatWidget\n apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n site=\"www.eufy.com\"\n channel_code=\"web_homepage\"\n welcomeMessage=\"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\"\n />\n )\n}\n\\`\\`\\`\n\n## \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\n\n\\`\\`\\`tsx\nconst customRenderers = {\n video: {\n render: (content) => (\n <video src={content.url} controls className=\"w-full rounded\" />\n )\n }\n}\n\n<LiveChatWidget\n apiBaseUrl=\"...\"\n site=\"...\"\n customRenderers={customRenderers}\n/>\n\\`\\`\\`\n `,\n },\n },\n },\n tags: ['autodocs'],\n argTypes: {\n apiBaseUrl: {\n control: 'text',\n description: 'API \u57FA\u7840 URL',\n },\n headers: {\n control: 'object',\n description: '\u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\uFF0C\u5C06\u5728\u6240\u6709 API \u8BF7\u6C42\u4E2D\u6DFB\u52A0',\n table: {\n defaultValue: { summary: 'undefined' },\n },\n },\n recaptchaSitekey: {\n control: 'text',\n description: 'Google reCAPTCHA v3 site key\uFF0C\u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u81EA\u52A8\u542F\u7528 reCAPTCHA v3 \u9A8C\u8BC1',\n table: {\n defaultValue: { summary: 'undefined' },\n },\n },\n recaptchaAction: {\n control: 'text',\n description: 'reCAPTCHA action \u540D\u79F0\uFF0C\u7528\u4E8E\u533A\u5206\u4E0D\u540C\u7684\u9A8C\u8BC1\u573A\u666F',\n table: {\n defaultValue: { summary: '\"activity\"' },\n },\n },\n site: {\n control: 'text',\n description: 'Shopify \u5E97\u94FA URL',\n },\n channelCode: {\n control: 'text',\n description: '\u6E20\u9053\u7F16\u7801\uFF0C\u7528\u4E8E\u6807\u8BC6\u6765\u6E90\u6E20\u9053',\n table: {\n defaultValue: { summary: 'undefined' },\n },\n },\n welcomeMessage: {\n control: 'text',\n description: '\u6B22\u8FCE\u6D88\u606F',\n table: {\n defaultValue: { summary: '\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F' },\n },\n },\n logoUrl: {\n control: 'text',\n description: 'Logo URL',\n },\n position: {\n control: 'object',\n description: '\u6C14\u6CE1\u6309\u94AE\u4F4D\u7F6E\u5BF9\u8C61',\n table: {\n defaultValue: { summary: '{ bottom: \"1.5rem\", right: \"1.5rem\" }' },\n },\n },\n },\n args: {\n apiBaseUrl: 'http://172.16.38.183:3003',\n site: 'www.eufy.com',\n loginUserId: 'test_test',\n welcomeMessage: '\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F',\n },\n}\n\nexport default meta\ntype Story = StoryObj<typeof LiveChatWidget>\n\n/**\n * \u9ED8\u8BA4\u914D\u7F6E\n *\n * \u6700\u7B80\u5355\u7684\u4F7F\u7528\u65B9\u5F0F\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u914D\u7F6E\u3002\n */\nexport const Default: Story = {\n args: {\n loginUserId: 'test_test',\n apiBaseUrl: 'http://172.16.38.183:3003',\n cartId: 'gid://shopify/Cart/hWN7wB3Pa12gh78d8hPOAUBI?key=0e73db1d3fb5ac21da19099c45033253',\n accessToken: '47b1aa2c0797043f9baba39388029d70',\n title: 'eufy',\n\n commonText: {\n learnMore: 'Learn More',\n total: 'Total',\n },\n\n site: 'beta.eufy.com',\n channelCode: 'dtc',\n\n // reCAPTCHA \u914D\u7F6E\uFF08\u63D0\u4F9B sitekey \u5373\u81EA\u52A8\u542F\u7528\uFF09\n // recaptchaSitekey: '6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14',\n recaptchaAction: 'chat',\n },\n}\n\n/**\n * \u81EA\u5B9A\u4E49\u4F4D\u7F6E\n *\n * \u4F7F\u7528\u81EA\u5B9A\u4E49\u4F4D\u7F6E\u5BF9\u8C61\u6765\u63A7\u5236\u6C14\u6CE1\u6309\u94AE\u7684\u4F4D\u7F6E\u3002\n */\nexport const CustomPosition: Story = {\n args: {\n position: { bottom: '1.5rem', left: '1.5rem' },\n },\n}\n\n/**\n * \u81EA\u5B9A\u4E49\u54C1\u724C\u6837\u5F0F\n *\n * \u81EA\u5B9A\u4E49 Logo \u548C\u6B22\u8FCE\u6D88\u606F\u3002\n */\nexport const CustomBranding: Story = {\n args: {\n logoUrl: 'https://images.unsplash.com/photo-1599305445671-ac291c95aaa9?w=100&h=100&fit=crop',\n welcomeMessage: '\u6B22\u8FCE\u6765\u5230 Soundcore\uFF01\u6211\u662F\u60A8\u7684\u4E13\u5C5E AI \u52A9\u624B \uD83C\uDFA7',\n },\n}\n\n/**\n * \u5E26\u5FEB\u6377\u56DE\u590D\n *\n * \u5728\u6B22\u8FCE\u6D88\u606F\u540E\u663E\u793A\u5FEB\u6377\u56DE\u590D\u6309\u94AE\u3002\n */\nexport const WithQuickReplies: Story = {\n args: {\n quickReplies: [\n {\n id: '1',\n label: '\u67E5\u8BE2\u4EF7\u683C',\n value: '\u6211\u60F3\u4E86\u89E3\u4EA7\u54C1\u4EF7\u683C',\n icon: '\uD83D\uDCB0',\n },\n {\n id: '2',\n label: '\u67E5\u8BE2\u7269\u6D41',\n value: '\u6211\u60F3\u67E5\u8BE2\u8BA2\u5355\u7269\u6D41',\n icon: '\uD83D\uDCE6',\n },\n {\n id: '3',\n label: '\u552E\u540E\u670D\u52A1',\n value: '\u6211\u9700\u8981\u552E\u540E\u5E2E\u52A9',\n icon: '\uD83D\uDD27',\n },\n {\n id: '4',\n label: '\u4EA7\u54C1\u63A8\u8350',\n value: '\u8BF7\u63A8\u8350\u9002\u5408\u6211\u7684\u4EA7\u54C1',\n icon: '\u2B50',\n },\n ],\n },\n}\n\n/**\n * \u81EA\u5B9A\u4E49\u6B22\u8FCE\u6D88\u606F\n *\n * \u81EA\u5B9A\u4E49\u9996\u6B21\u6253\u5F00\u804A\u5929\u7A97\u53E3\u65F6\u7684\u6B22\u8FCE\u6D88\u606F\u3002\n */\nexport const CustomWelcomeMessage: Story = {\n args: {\n welcomeMessage: `\n\uD83D\uDC4B \u60A8\u597D\uFF01\u6B22\u8FCE\u6765\u5230 Soundcore \u5B98\u65B9\u5546\u57CE\u3002\n\n\u6211\u662F\u60A8\u7684 AI \u8D2D\u7269\u52A9\u624B\uFF0C\u53EF\u4EE5\u5E2E\u60A8\uFF1A\n- \uD83D\uDD0D \u67E5\u627E\u4EA7\u54C1\n- \uD83D\uDCB0 \u6BD4\u8F83\u4EF7\u683C\n- \uD83D\uDCE6 \u8DDF\u8E2A\u8BA2\u5355\n- \uD83D\uDCAC \u89E3\u7B54\u7591\u95EE\n\n\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u52A9\u60A8\u7684\u5417\uFF1F\n `.trim(),\n },\n}\n\n/**\n * \u4E8B\u4EF6\u56DE\u8C03\n *\n * \u76D1\u542C\u7EC4\u4EF6\u4E8B\u4EF6\u8FDB\u884C\u81EA\u5B9A\u4E49\u5904\u7406\u3002\n */\nexport const WithEventCallbacks: Story = {\n args: {\n title: '',\n showNewSessionButton: true,\n\n position: {\n bottom: '24px',\n right: '30px',\n },\n\n chatBubbleIcon: 'https://cdn.shopify.com/s/files/1/0504/7094/4954/files/Rectangle_400770314.png?v=1768894153',\n\n // \u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\u793A\u4F8B\n headers: undefined,\n\n site: 'beta.eufy.com',\n },\n\n render: args => (\n <LiveChatWidget\n {...args}\n onOpen={() => {\n console.log('[Story] Chat opened')\n }}\n onClose={() => {\n console.log('[Story] Chat closed')\n }}\n onMessageSend={(message: string) => {\n console.log('[Story] Message sent:', message)\n }}\n onError={(error: Error) => {\n console.error('[Story] Error occurred:', error)\n }}\n onTextMessage={() => {\n console.log('\uD83D\uDCDD [Story] AI started replying with text message')\n }}\n onProductList={() => {\n console.log('\uD83D\uDCE6 [Story] AI replied with product list card')\n }}\n onPromotionList={() => {\n console.log('\uD83C\uDF89 [Story] AI replied with promotion list card')\n }}\n onAddToCart={(product: any) => {\n console.log('\uD83D\uDED2 [Story] Add to Cart clicked!')\n console.log('\uD83D\uDCE6 Product Info:', {\n id: product.shopifyId,\n title: product.title,\n price: product.price,\n imageUrl: product.imageUrl,\n productUrl: product.productUrl,\n })\n console.log('\uD83D\uDCCB Full Product Object:', product)\n\n // \u6A21\u62DF\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u6210\u529F\n alert(`\u2705 \"${product.title}\" \u5DF2\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\uFF01\\n\\n\u4EF7\u683C: ${product.price.currency} ${product.price.amount}`)\n }}\n onCart={(cartId: string, checkoutUrl?: string) => {\n console.log('\uD83D\uDED2 [Story] Cart button clicked!')\n console.log('\uD83D\uDCCB Cart ID:', cartId)\n console.log('\uD83D\uDD17 Checkout URL:', checkoutUrl)\n\n // \u6A21\u62DF\u8D2D\u7269\u8F66\u6309\u94AE\u70B9\u51FB\u6210\u529F\n alert(`\u8D2D\u7269\u8F66\u6309\u94AE\u88AB\u70B9\u51FB\uFF01\\n\\nCart ID: ${cartId}\\nCheckout URL: ${checkoutUrl || '\u65E0'}`)\n }}\n />\n ),\n}\n\n/**\n * \u81EA\u5B9A\u4E49\u89C6\u9891\u6D88\u606F\u6E32\u67D3\u5668\n *\n * \u6269\u5C55\u6D88\u606F\u7C7B\u578B\uFF0C\u6DFB\u52A0\u89C6\u9891\u6D88\u606F\u652F\u6301\u3002\n */\nexport const CustomVideoRenderer: Story = {\n args: {\n customRenderers: {\n video: {\n render: (content: MessageContent) => {\n const videoContent = content as any\n return (\n <div className=\"w-full\">\n <video src={videoContent.url} controls className=\"w-full rounded-lg\" poster={videoContent.poster}>\n \u60A8\u7684\u6D4F\u89C8\u5668\u4E0D\u652F\u6301\u89C6\u9891\u64AD\u653E\n </video>\n {videoContent.title && <p className=\"mt-2 text-sm text-gray-600\">{videoContent.title}</p>}\n </div>\n )\n },\n } as MessageRenderer,\n },\n },\n}\n\n/**\n * \u81EA\u5B9A\u4E49\u56FE\u7247\u753B\u5ECA\u6E32\u67D3\u5668\n *\n * \u6269\u5C55\u6D88\u606F\u7C7B\u578B\uFF0C\u6DFB\u52A0\u56FE\u7247\u753B\u5ECA\u652F\u6301\u3002\n */\nexport const CustomImageGalleryRenderer: Story = {\n args: {\n customRenderers: {\n image_gallery: {\n render: (content: MessageContent) => {\n const galleryContent = content as any\n const images = galleryContent.images || []\n\n return (\n <div className=\"grid grid-cols-2 gap-2\">\n {images.map((image: any, index: number) => (\n <div key={index} className=\"relative aspect-square\">\n <img\n src={image.url}\n alt={image.alt || `Image ${index + 1}`}\n className=\"size-full rounded-lg object-cover\"\n />\n </div>\n ))}\n </div>\n )\n },\n } as MessageRenderer,\n },\n },\n}\n\n/**\n * \u79FB\u52A8\u7AEF\u9884\u89C8\n *\n * \u5728\u79FB\u52A8\u7AEF\u5C3A\u5BF8\u4E0B\u67E5\u770B\u6548\u679C\uFF08\u5168\u5C4F\u663E\u793A\uFF09\u3002\n */\nexport const MobileView: Story = {\n parameters: {\n viewport: {\n defaultViewport: 'mobile1',\n },\n },\n args: {},\n}\n\n/**\n * \u5E73\u677F\u7AEF\u9884\u89C8\n *\n * \u5728\u5E73\u677F\u7AEF\u5C3A\u5BF8\u4E0B\u67E5\u770B\u6548\u679C\u3002\n */\nexport const TabletView: Story = {\n parameters: {\n viewport: {\n defaultViewport: 'tablet',\n },\n },\n args: {},\n}\n\n/**\n * \u9ED8\u8BA4\u4F4D\u7F6E\u5C55\u793A\n *\n * \u5C55\u793A\u6C14\u6CE1\u6309\u94AE\u7684\u9ED8\u8BA4\u4F4D\u7F6E\uFF08\u53F3\u4E0B\u89D2\uFF09\u3002\n */\nexport const DefaultPositionDemo: Story = {\n render: () => (\n <div className=\"relative h-screen w-full bg-gray-50\">\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <p className=\"text-gray-500\">\u9875\u9762\u5185\u5BB9\u533A\u57DF</p>\n </div>\n\n {/* \u6F14\u793A\u9ED8\u8BA4\u4F4D\u7F6E */}\n <div className=\"absolute bottom-4 right-4 text-xs text-gray-400\">\u9ED8\u8BA4\u4F4D\u7F6E (\u53F3\u4E0B\u89D2)</div>\n\n <LiveChatWidget apiBaseUrl=\"https://beta-api-livechat.anker.com\" site=\"www.eufy.com\" />\n </div>\n ),\n}\n\n/**\n * \u4EA7\u54C1\u5BF9\u6BD4\u6D88\u606F\n *\n * \u5C55\u793A\u4EA7\u54C1\u5BF9\u6BD4\u7C7B\u578B\u7684\u6D88\u606F\u6E32\u67D3\u6548\u679C\uFF0C\u5305\u62EC\u4EF7\u683C\u3001\u4F1A\u5458\u4EF7\u3001\u53D8\u4F53\u6570\u91CF\u3001\u6298\u6263\u7B49\u591A\u4E2A\u7EF4\u5EA6\u7684\u5BF9\u6BD4\u3002\n *\n * \u4EA7\u54C1\u5BF9\u6BD4\u7EC4\u4EF6\u4F1A\u4EE5\u7F51\u683C\u5E03\u5C40\u5C55\u793A\u591A\u4E2A\u4EA7\u54C1\u7684\u57FA\u672C\u4FE1\u606F\uFF0C\u5E76\u5728\u4E0B\u65B9\u663E\u793A\u5404\u7EF4\u5EA6\u7684\u5BF9\u6BD4\u6570\u636E\u3002\n */\nexport const ProductComparisonMessage: Story = {\n render: args => (\n <LiveChatWidget\n {...args}\n onAddToCart={(product: any) => {\n console.log('\uD83D\uDED2 [ProductComparison Story] Add to Cart clicked!')\n console.log('\uD83D\uDCE6 Product Info:', {\n id: product.shopifyId,\n title: product.title,\n price: product.price,\n imageUrl: product.imageUrl,\n productUrl: product.productUrl,\n })\n console.log('\uD83D\uDCCB Full Product Object:', product)\n\n // \u6A21\u62DF\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u6210\u529F\n alert(`\u2705 \"${product.title}\" \u5DF2\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\uFF01\\n\\n\u4EF7\u683C: ${product.price.currency} ${product.price.amount}`)\n }}\n />\n ),\n args: {\n welcomeMessage: '\u4EE5\u4E0B\u662F\u60A8\u5173\u6CE8\u7684\u4EA7\u54C1\u5BF9\u6BD4\u4FE1\u606F\uFF1A',\n },\n parameters: {\n docs: {\n description: {\n story: `\n\u4EA7\u54C1\u5BF9\u6BD4\u6D88\u606F\u652F\u6301\u4EE5\u4E0B\u7EF4\u5EA6\uFF1A\n\n- **\u4EF7\u683C\u5BF9\u6BD4**: \u663E\u793A\u4EA7\u54C1\u7684\u4EF7\u683C\u533A\u95F4\u548C\u4FC3\u9500\u6807\u7B7E\n- **\u4F1A\u5458\u4EF7\u5BF9\u6BD4**: \u663E\u793A\u4F1A\u5458\u4E13\u4EAB\u4EF7\u683C\uFF08\u5982\u679C\u53EF\u7528\uFF09\n- **\u53D8\u4F53\u6570\u91CF\u5BF9\u6BD4**: \u663E\u793A\u4EA7\u54C1\u7684\u53EF\u9009\u53D8\u4F53\u6570\u91CF\n- **\u6298\u6263\u5BF9\u6BD4**: \u663E\u793A\u4EA7\u54C1\u662F\u5426\u6709\u6298\u6263\u6D3B\u52A8\n\n\u7EC4\u4EF6\u4F1A\u81EA\u52A8\u5904\u7406\u4ECE\u540E\u7AEF\u8FD4\u56DE\u7684\u4EA7\u54C1\u5BF9\u6BD4\u6570\u636E\uFF0C\u5305\u62EC\uFF1A\n- \u4EA7\u54C1\u57FA\u672C\u4FE1\u606F\uFF08\u56FE\u7247\u3001\u6807\u9898\u3001\u4EF7\u683C\u7B49\uFF09\n- \u591A\u7EF4\u5EA6\u5BF9\u6BD4\u6570\u636E\uFF08\u4EF7\u683C\u3001\u4F1A\u5458\u4EF7\u3001\u53D8\u4F53\u3001\u6298\u6263\uFF09\n- \u54CD\u5E94\u5F0F\u5E03\u5C40\uFF08\u79FB\u52A8\u7AEF\u53EF\u6A2A\u5411\u6EDA\u52A8\uFF09\n\n**\u6D4B\u8BD5 Add to Cart \u529F\u80FD\uFF1A**\n- \u70B9\u51FB\u4EA7\u54C1\u5BF9\u6BD4\u5361\u7247\u4E2D\u7684 \"Add to Cart\" \u6309\u94AE\n- \u67E5\u770B\u63A7\u5236\u53F0\u8F93\u51FA\u7684\u4EA7\u54C1\u4FE1\u606F\n- \u4F1A\u5F39\u51FA\u786E\u8BA4\u63D0\u793A\u6846\u663E\u793A\u5DF2\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\n `,\n },\n },\n },\n}\n\n/**\n * FAQ \u5217\u8868\u6D88\u606F\n *\n * \u5C55\u793A\u5E38\u89C1\u95EE\u9898\u5217\u8868\uFF0C\u652F\u6301\u6298\u53E0/\u5C55\u5F00\u3002\n *\n * FAQ \u7EC4\u4EF6\u4F1A\u663E\u793A\u641C\u7D22\u7ED3\u679C\u7684\u5E38\u89C1\u95EE\u9898\uFF0C\u6BCF\u4E2A\u95EE\u9898\u53EF\u4EE5\u70B9\u51FB\u5C55\u5F00\u67E5\u770B\u7B54\u6848\u3002\n */\nexport const FAQListMessage: Story = {\n args: {\n welcomeMessage: '\u4EE5\u4E0B\u662F\u4E0E\u60A8\u7684\u95EE\u9898\u76F8\u5173\u7684\u5E38\u89C1\u89E3\u7B54\uFF1A',\n },\n parameters: {\n docs: {\n description: {\n story: `\nFAQ \u5217\u8868\u6D88\u606F\u652F\u6301\uFF1A\n\n- **\u53EF\u6298\u53E0\u95EE\u9898**: \u70B9\u51FB\u95EE\u9898\u6807\u9898\u5C55\u5F00/\u6298\u53E0\u7B54\u6848\n- **Markdown \u7B54\u6848**: \u7B54\u6848\u652F\u6301 Markdown \u683C\u5F0F\uFF0C\u53EF\u663E\u793A\u5BCC\u6587\u672C\n- **\u76F8\u5173\u95EE\u9898**: \u5C55\u5F00\u7B54\u6848\u540E\u663E\u793A\u76F8\u5173\u95EE\u9898\uFF0C\u70B9\u51FB\u53EF\u89E6\u53D1\u65B0\u641C\u7D22\n- **\u5206\u7C7B\u6807\u8BB0**: \u6839\u636E\u95EE\u9898\u5206\u7C7B\u663E\u793A\uFF08\u914D\u9001\u3001\u9000\u8D27\u3001\u4EA7\u54C1\u3001\u652F\u4ED8\u3001\u901A\u7528\uFF09\n- **\u641C\u7D22\u7ED3\u679C\u7EDF\u8BA1**: \u663E\u793A\u627E\u5230\u7684\u95EE\u9898\u6570\u91CF\n\n\u4F7F\u7528\u573A\u666F\uFF1A\n- \u7528\u6237\u8BE2\u95EE\u5E38\u89C1\u95EE\u9898\u65F6\u8FD4\u56DE FAQ \u5217\u8868\n- \u4EA7\u54C1\u4E13\u5C5E\u95EE\u9898\u67E5\u8BE2\n- \u6309\u5206\u7C7B\u67E5\u8BE2 FAQ\n `,\n },\n },\n },\n}\n\n/**\n * \u5E26 reCAPTCHA \u914D\u7F6E\n *\n * \u5C55\u793A\u5982\u4F55\u914D\u7F6E Google reCAPTCHA v3 \u9A8C\u8BC1\uFF0C\u4FDD\u62A4 API \u8BF7\u6C42\u514D\u53D7\u6EE5\u7528\u3002\n *\n * reCAPTCHA \u4F1A\u5728\u540E\u53F0\u81EA\u52A8\u9A8C\u8BC1\u7528\u6237\u884C\u4E3A\uFF0C\u65E0\u9700\u7528\u6237\u624B\u52A8\u64CD\u4F5C\u3002\n */\nexport const WithRecaptcha: Story = {\n args: {\n // \u914D\u7F6E\u4F60\u7684 reCAPTCHA site key\uFF08\u63D0\u4F9B\u5373\u81EA\u52A8\u542F\u7528\uFF09\n recaptchaSitekey: '6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14',\n // \u53EF\u9009\uFF1A\u81EA\u5B9A\u4E49 action \u540D\u79F0\n recaptchaAction: 'livechat',\n },\n parameters: {\n docs: {\n description: {\n story: `\n### reCAPTCHA v3 \u914D\u7F6E\n\n\u542F\u7528 reCAPTCHA v3 \u53EF\u4EE5\u4FDD\u62A4\u4F60\u7684 API \u514D\u53D7\u673A\u5668\u4EBA\u548C\u6EE5\u7528\u884C\u4E3A\u7684\u653B\u51FB\u3002\n\n**\u914D\u7F6E\u6B65\u9AA4\uFF1A**\n\n1. \u5728 [Google reCAPTCHA](https://www.google.com/recaptcha/admin) \u521B\u5EFA v3 \u5BC6\u94A5\n2. \u5728\u9875\u9762\u4E2D\u52A0\u8F7D reCAPTCHA \u811A\u672C\uFF1A\n \\`\\`\\`html\n <script src=\"https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY\"></script>\n \\`\\`\\`\n3. \u914D\u7F6E LiveChatWidget \u7EC4\u4EF6\uFF08\u63D0\u4F9B sitekey \u5373\u81EA\u52A8\u542F\u7528\uFF09\uFF1A\n \\`\\`\\`tsx\n <LiveChatWidget\n recaptchaSitekey=\"YOUR_SITE_KEY\"\n recaptchaAction=\"livechat\"\n />\n \\`\\`\\`\n\n**\u5DE5\u4F5C\u539F\u7406\uFF1A**\n\n- \u63D0\u4F9B recaptchaSitekey \u53C2\u6570\u5373\u81EA\u52A8\u542F\u7528 reCAPTCHA \u9A8C\u8BC1\n- \u6BCF\u6B21\u53D1\u9001\u6D88\u606F\u6216\u521B\u5EFA\u4F1A\u8BDD\u65F6\uFF0C\u4F1A\u81EA\u52A8\u83B7\u53D6 reCAPTCHA token\n- token \u4F1A\u901A\u8FC7 \\`X-Recaptcha-Token\\` header \u53D1\u9001\u5230\u540E\u7AEF\n- \u540E\u7AEF\u9700\u8981\u9A8C\u8BC1 token \u7684\u6709\u6548\u6027\n\n**\u6CE8\u610F\u4E8B\u9879\uFF1A**\n\n- reCAPTCHA v3 \u5728\u540E\u53F0\u8FD0\u884C\uFF0C\u4E0D\u4F1A\u6253\u65AD\u7528\u6237\u4F53\u9A8C\n- \u5EFA\u8BAE\u4E3A\u4E0D\u540C\u7684\u64CD\u4F5C\u4F7F\u7528\u4E0D\u540C\u7684 \\`recaptchaAction\\` \u4EE5\u4FBF\u5206\u6790\n- \u786E\u4FDD\u540E\u7AEF\u6B63\u786E\u9A8C\u8BC1 reCAPTCHA token\n- \u4E0D\u63D0\u4F9B recaptchaSitekey \u5219\u4E0D\u542F\u7528\u9A8C\u8BC1\uFF08\u4EC5\u7528\u4E8E\u5F00\u53D1\u73AF\u5883\uFF09\n `,\n },\n },\n },\n}\n\n/**\n * \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\n *\n * \u6F14\u793A\u5982\u4F55\u4F7F\u7528 productCardRender \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u7684\u6E32\u67D3\u6837\u5F0F\u3002\n *\n * \u5F53\u540E\u7AEF\u8FD4\u56DE\u4EA7\u54C1\u5360\u4F4D\u7B26 {{product:xxx}} \u65F6\uFF0C\u4F1A\u4F7F\u7528\u81EA\u5B9A\u4E49\u6E32\u67D3\u51FD\u6570\u6765\u5C55\u793A\u4EA7\u54C1\u5361\u7247\u3002\n */\nexport const CustomProductCardRender: Story = {\n args: {\n loginUserId: 'test_test',\n apiBaseUrl: 'http://172.16.38.183:3003',\n cartId: 'gid://shopify/Cart/hWN7wB3Pa12gh78d8hPOAUBI?key=0e73db1d3fb5ac21da19099c45033253',\n accessToken: '47b1aa2c0797043f9baba39388029d70',\n title: 'eufy',\n site: 'beta.eufy.com',\n channelCode: 'dtc',\n welcomeMessage: '\u4F60\u597D\uFF01\u6211\u53EF\u4EE5\u4E3A\u4F60\u63A8\u8350\u4EA7\u54C1\uFF0C\u8BD5\u8BD5\u53D1\u9001 \"\u63A8\u8350\u4EA7\u54C1\" \u6765\u67E5\u770B\u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6548\u679C\u3002',\n\n // \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\u51FD\u6570\n // \u6CE8\u610F\uFF1Aproduct \u53C2\u6570\u662F\u539F\u59CB\u540E\u7AEF\u6570\u636E\uFF08\u672A\u7ECF\u8F6C\u6362\uFF09\n productCardRender: (product) => {\n // \u539F\u59CB\u540E\u7AEF\u6570\u636E\u4F7F\u7528 snake_case \u5B57\u6BB5\u540D\n const imageUrl = product.featured_image || ''\n const title = product.title || ''\n const description = product.description || ''\n const price = product.price_range?.min || product.variants?.[0]?.price || 0\n const currency = product.price_range?.currency || product.variants?.[0]?.currency || 'USD'\n const averageRating = product.average_rating\n\n return (\n <div\n style={{\n border: '2px solid #4CAF50',\n borderRadius: '16px',\n padding: '20px',\n margin: '12px 0',\n backgroundColor: '#f0f9ff',\n boxShadow: '0 4px 12px rgba(76, 175, 80, 0.15)',\n transition: 'transform 0.2s',\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = 'scale(1.02)'\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = 'scale(1)'\n }}\n >\n <div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>\n <div\n style={{\n width: '70px',\n height: 'auto',\n flexShrink: 0,\n borderRadius: '12px',\n overflow: 'hidden',\n border: '2px solid #e0e0e0',\n }}\n >\n <img\n src={imageUrl}\n alt={title}\n style={{\n width: '100%',\n height: '100%',\n objectFit: 'cover',\n }}\n />\n </div>\n\n <div style={{ flex: 1 }}>\n <h3\n style={{\n margin: '0 0 8px 0',\n fontSize: '18px',\n fontWeight: 'bold',\n color: '#1a1a1a',\n lineHeight: '1.4',\n }}\n >\n {title}\n </h3>\n\n {description && (\n <p\n style={{\n margin: '0 0 12px 0',\n fontSize: '14px',\n color: '#666',\n lineHeight: '1.5',\n display: '-webkit-box',\n WebkitLineClamp: 2,\n WebkitBoxOrient: 'vertical',\n overflow: 'hidden',\n }}\n >\n {description}\n </p>\n )}\n\n <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>\n <div\n style={{\n fontSize: '24px',\n fontWeight: 'bold',\n color: '#4CAF50',\n }}\n >\n 'test price'\n </div>\n\n {averageRating !== undefined && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: '4px',\n fontSize: '14px',\n color: '#666',\n }}\n >\n <span style={{ color: '#FFB800' }}>\u2B50</span>\n <span>{averageRating.toFixed(1)}</span>\n </div>\n )}\n\n <button\n \n style={{\n marginLeft: 'auto',\n padding: '8px 20px',\n backgroundColor: '#4CAF50',\n color: 'white',\n borderRadius: '8px',\n textDecoration: 'none',\n fontSize: '14px',\n fontWeight: 'bold',\n transition: 'background-color 0.2s',\n }}\n onClick={() => {\n console.log('View Details', product)\n }}\n >\n View Details\n </button>\n </div>\n </div>\n </div>\n </div>\n )\n },\n\n onAddToCart: (product) => {\n console.log('Add to cart:', product)\n alert(`Added ${product.title} to cart!`)\n },\n },\n parameters: {\n docs: {\n description: {\n story: `\n### \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\n\n\u4F7F\u7528 \\`productCardRender\\` \u5C5E\u6027\u53EF\u4EE5\u5B8C\u5168\u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u7684\u5C55\u793A\u6837\u5F0F\u3002\n\n**\u529F\u80FD\u8BF4\u660E\uFF1A**\n\n- \u5F53\u540E\u7AEF\u8FD4\u56DE\u5E26\u6709\u4EA7\u54C1\u5360\u4F4D\u7B26 \\`{{product:xxx}}\\` \u7684\u6587\u672C\u6D41\u65F6\uFF0C\u4F1A\u4F7F\u7528\u81EA\u5B9A\u4E49\u6E32\u67D3\u51FD\u6570\n- \u81EA\u5B9A\u4E49\u51FD\u6570\u63A5\u6536\u5B8C\u6574\u7684\u4EA7\u54C1\u5BF9\u8C61\u4F5C\u4E3A\u53C2\u6570\n- \u8FD4\u56DE React \u8282\u70B9\uFF0C\u53EF\u4EE5\u662F\u4EFB\u4F55\u5408\u6CD5\u7684 JSX\n- \u5982\u679C\u4E0D\u63D0\u4F9B \\`productCardRender\\`\uFF0C\u5219\u4F7F\u7528\u9ED8\u8BA4\u7684\u4EA7\u54C1\u5361\u7247\u6837\u5F0F\n\n**\u6D4B\u8BD5\u6B65\u9AA4\uFF1A**\n\n1. \u6253\u5F00\u804A\u5929\u7A97\u53E3\n2. \u53D1\u9001 \"\u63A8\u8350\u4EA7\u54C1\" \u6216\u4EFB\u4F55\u4F1A\u89E6\u53D1\u4EA7\u54C1\u63A8\u8350\u7684\u6D88\u606F\n3. \u89C2\u5BDF\u8FD4\u56DE\u7684\u4EA7\u54C1\u5361\u7247\u4F7F\u7528\u7684\u662F\u81EA\u5B9A\u4E49\u6837\u5F0F\uFF08\u7EFF\u8272\u8FB9\u6846\u3001\u60AC\u505C\u653E\u5927\u6548\u679C\uFF09\n `,\n },\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["jsx", "jsxs", "LiveChatWidget", "meta", "LiveChatWidget_stories_default", "Default", "CustomPosition", "CustomBranding", "WithQuickReplies", "CustomWelcomeMessage", "WithEventCallbacks", "args", "message", "error", "product", "cartId", "checkoutUrl", "CustomVideoRenderer", "content", "videoContent", "CustomImageGalleryRenderer", "images", "image", "index", "MobileView", "TabletView", "DefaultPositionDemo", "ProductComparisonMessage", "FAQListMessage", "WithRecaptcha", "CustomProductCardRender", "imageUrl", "title", "description", "price", "currency", "averageRating", "e"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -123,6 +123,7 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
|
|
|
123
123
|
onCart,
|
|
124
124
|
showNewSessionButton,
|
|
125
125
|
commonText,
|
|
126
|
+
productCardRender,
|
|
126
127
|
}) => {
|
|
127
128
|
// 合并默认文案和自定义文案
|
|
128
129
|
const mergedText: Required<CommonText> = React.useMemo(
|
|
@@ -148,6 +149,7 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
|
|
|
148
149
|
onPromotionList,
|
|
149
150
|
onAddToCart,
|
|
150
151
|
onCart,
|
|
152
|
+
productCardRender,
|
|
151
153
|
})
|
|
152
154
|
|
|
153
155
|
const {
|
|
@@ -461,47 +463,59 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
|
|
|
461
463
|
|
|
462
464
|
if (response.success && response.resumed) {
|
|
463
465
|
// 会话恢复成功
|
|
466
|
+
|
|
467
|
+
// 准备欢迎消息(无论是否有历史消息都需要)
|
|
468
|
+
const messageText = response.welcomeMessage || welcomeMessage
|
|
469
|
+
const welcomeContent: MessageContent[] = messageText ? [{ type: 'text', text: messageText }] : []
|
|
470
|
+
|
|
471
|
+
// 添加快捷回复到欢迎消息
|
|
472
|
+
const questions = response.quickQuestions
|
|
473
|
+
if (questions && questions.length > 0) {
|
|
474
|
+
const quickRepliesFromBackend = questions.map((question, index) => ({
|
|
475
|
+
id: `quick-${index}`,
|
|
476
|
+
label: question,
|
|
477
|
+
value: question,
|
|
478
|
+
}))
|
|
479
|
+
|
|
480
|
+
welcomeContent.push({
|
|
481
|
+
type: 'quick_replies',
|
|
482
|
+
data: {
|
|
483
|
+
replies: quickRepliesFromBackend,
|
|
484
|
+
},
|
|
485
|
+
})
|
|
486
|
+
} else if (quickReplies && quickReplies.length > 0) {
|
|
487
|
+
welcomeContent.push({
|
|
488
|
+
type: 'quick_replies',
|
|
489
|
+
data: {
|
|
490
|
+
replies: quickReplies,
|
|
491
|
+
},
|
|
492
|
+
})
|
|
493
|
+
}
|
|
494
|
+
|
|
464
495
|
if (response.messages && response.messages.length > 0) {
|
|
465
496
|
// 有历史消息,规范化并加载(过滤掉 null/undefined 值和空内容消息)
|
|
466
497
|
const normalizedMessages = response.messages
|
|
467
498
|
.filter((msg: any) => msg != null)
|
|
468
499
|
.map(normalizeMessage)
|
|
469
500
|
.filter((msg: Message) => msg.content && msg.content.length > 0 && !(msg.content.length === 1 && msg.content[0].type === 'text' && !msg.content[0].text))
|
|
470
|
-
setMessages(normalizedMessages)
|
|
471
|
-
} else {
|
|
472
|
-
// 没有历史消息,显示欢迎消息
|
|
473
|
-
clearMessages()
|
|
474
501
|
|
|
475
|
-
//
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const questions = response.quickQuestions
|
|
483
|
-
if (questions && questions.length > 0) {
|
|
484
|
-
const quickRepliesFromBackend = questions.map((question, index) => ({
|
|
485
|
-
id: `quick-${index}`,
|
|
486
|
-
label: question,
|
|
487
|
-
value: question,
|
|
488
|
-
}))
|
|
489
|
-
|
|
490
|
-
welcomeContent.push({
|
|
491
|
-
type: 'quick_replies',
|
|
492
|
-
data: {
|
|
493
|
-
replies: quickRepliesFromBackend,
|
|
494
|
-
},
|
|
495
|
-
})
|
|
496
|
-
} else if (quickReplies && quickReplies.length > 0) {
|
|
497
|
-
welcomeContent.push({
|
|
498
|
-
type: 'quick_replies',
|
|
499
|
-
data: {
|
|
500
|
-
replies: quickReplies,
|
|
501
|
-
},
|
|
502
|
-
})
|
|
502
|
+
// 如果有欢迎消息,将其添加到历史消息的开头
|
|
503
|
+
if (welcomeContent.length > 0) {
|
|
504
|
+
const welcomeMsg: Message = {
|
|
505
|
+
id: `welcome-${Date.now()}`,
|
|
506
|
+
role: 'assistant',
|
|
507
|
+
content: welcomeContent,
|
|
508
|
+
timestamp: Date.now(),
|
|
503
509
|
}
|
|
510
|
+
setMessages([welcomeMsg, ...normalizedMessages])
|
|
511
|
+
} else {
|
|
512
|
+
setMessages(normalizedMessages)
|
|
513
|
+
}
|
|
514
|
+
} else {
|
|
515
|
+
// 没有历史消息,仅显示欢迎消息
|
|
516
|
+
clearMessages()
|
|
504
517
|
|
|
518
|
+
if (welcomeContent.length > 0) {
|
|
505
519
|
addMessage({
|
|
506
520
|
id: `welcome-${Date.now()}`,
|
|
507
521
|
role: 'assistant',
|
|
@@ -560,13 +574,13 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
|
|
|
560
574
|
* 发送消息
|
|
561
575
|
*/
|
|
562
576
|
const handleSendMessage = useCallback(
|
|
563
|
-
async (message?: string) => {
|
|
577
|
+
async (message?: string, isRetry: boolean = false) => {
|
|
564
578
|
const textToSend = message || inputValue.trim()
|
|
565
579
|
|
|
566
580
|
if (!textToSend) return
|
|
567
581
|
|
|
568
|
-
//
|
|
569
|
-
if (!message) {
|
|
582
|
+
// 清空输入框(仅在非重试时)
|
|
583
|
+
if (!message && !isRetry) {
|
|
570
584
|
setInputValue('')
|
|
571
585
|
}
|
|
572
586
|
|
|
@@ -577,14 +591,16 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
|
|
|
577
591
|
return
|
|
578
592
|
}
|
|
579
593
|
|
|
580
|
-
//
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
594
|
+
// 添加用户消息到界面(仅在非重试时)
|
|
595
|
+
if (!isRetry) {
|
|
596
|
+
const userMessage = {
|
|
597
|
+
id: `user-${Date.now()}`,
|
|
598
|
+
role: 'user' as const,
|
|
599
|
+
content: [{ type: 'text' as const, text: sanitized }],
|
|
600
|
+
timestamp: Date.now(),
|
|
601
|
+
}
|
|
602
|
+
addMessage(userMessage)
|
|
586
603
|
}
|
|
587
|
-
addMessage(userMessage)
|
|
588
604
|
|
|
589
605
|
// 立即添加思考状态消息,提升用户体验
|
|
590
606
|
const thinkingMessage = {
|
|
@@ -595,8 +611,13 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
|
|
|
595
611
|
}
|
|
596
612
|
addMessage(thinkingMessage)
|
|
597
613
|
|
|
598
|
-
//
|
|
599
|
-
|
|
614
|
+
// 触发消息发送回调(仅在非重试时)
|
|
615
|
+
if (!isRetry) {
|
|
616
|
+
onMessageSend?.(sanitized)
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// 标记是否检测到会话过期
|
|
620
|
+
let sessionExpiredDetected = false
|
|
600
621
|
|
|
601
622
|
try {
|
|
602
623
|
// 确保有 sessionId,如果没有则先创建会话
|
|
@@ -634,11 +655,37 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
|
|
|
634
655
|
// 处理 SSE 事件
|
|
635
656
|
handleSSEEvent(event)
|
|
636
657
|
|
|
637
|
-
//
|
|
638
|
-
if (event.event === '
|
|
658
|
+
// 特殊处理:会话过期(error 事件且 type 为 validation_error)
|
|
659
|
+
if (event.event === 'error' && event.data.type === 'validation_error') {
|
|
660
|
+
sessionExpiredDetected = true
|
|
639
661
|
clearSession()
|
|
640
662
|
}
|
|
641
663
|
})
|
|
664
|
+
|
|
665
|
+
// 如果检测到会话过期且不是重试,自动创建新会话并重试
|
|
666
|
+
if (sessionExpiredDetected && !isRetry) {
|
|
667
|
+
console.log('[LiveChatWidget] Session expired (validation_error), creating new session and retrying...')
|
|
668
|
+
|
|
669
|
+
// 移除 thinking 消息
|
|
670
|
+
const messagesWithoutThinking = messages.filter(msg => msg.id !== thinkingMessage.id)
|
|
671
|
+
setMessages(messagesWithoutThinking)
|
|
672
|
+
|
|
673
|
+
// 创建新会话
|
|
674
|
+
const response = await createSession({
|
|
675
|
+
user_id: userId,
|
|
676
|
+
site: site,
|
|
677
|
+
channel_code: channelCode,
|
|
678
|
+
real_user_id: loginUserId,
|
|
679
|
+
})
|
|
680
|
+
|
|
681
|
+
if (response.success) {
|
|
682
|
+
saveSession(response.sessionId)
|
|
683
|
+
// 重试发送消息
|
|
684
|
+
await handleSendMessage(sanitized, true)
|
|
685
|
+
} else {
|
|
686
|
+
throw new Error('Failed to recreate session after expiration')
|
|
687
|
+
}
|
|
688
|
+
}
|
|
642
689
|
} catch (error) {
|
|
643
690
|
console.error('[LiveChatWidget] Failed to send message:', error)
|
|
644
691
|
onError?.(error as Error)
|
|
@@ -654,8 +701,10 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
|
|
|
654
701
|
{
|
|
655
702
|
type: 'error',
|
|
656
703
|
data: {
|
|
657
|
-
message:
|
|
658
|
-
|
|
704
|
+
message: sessionExpiredDetected
|
|
705
|
+
? 'Your session has expired. We tried to reconnect but failed. Please try again.'
|
|
706
|
+
: 'Failed to send message. Please check your network connection and try again.',
|
|
707
|
+
code: sessionExpiredDetected ? 'SESSION_EXPIRED' : 'NETWORK_ERROR',
|
|
659
708
|
},
|
|
660
709
|
},
|
|
661
710
|
],
|
|
@@ -104,6 +104,8 @@ export const ChatInput: React.FC<ChatInputProps> = ({
|
|
|
104
104
|
className = '',
|
|
105
105
|
}) => {
|
|
106
106
|
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
|
107
|
+
// 跟踪输入法状态(是否正在使用拼音输入等)
|
|
108
|
+
const isComposingRef = useRef(false)
|
|
107
109
|
|
|
108
110
|
/**
|
|
109
111
|
* 自动调整 textarea 高度
|
|
@@ -121,12 +123,33 @@ export const ChatInput: React.FC<ChatInputProps> = ({
|
|
|
121
123
|
textarea.style.height = `${newHeight}px`
|
|
122
124
|
}, [value])
|
|
123
125
|
|
|
126
|
+
/**
|
|
127
|
+
* 处理输入法开始
|
|
128
|
+
*/
|
|
129
|
+
const handleCompositionStart = () => {
|
|
130
|
+
isComposingRef.current = true
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 处理输入法结束
|
|
135
|
+
*/
|
|
136
|
+
const handleCompositionEnd = () => {
|
|
137
|
+
isComposingRef.current = false
|
|
138
|
+
}
|
|
139
|
+
|
|
124
140
|
/**
|
|
125
141
|
* 处理键盘事件
|
|
126
142
|
* Enter 发送,Shift+Enter 换行
|
|
143
|
+
* 注意:输入法激活时(如拼音输入中),回车键不应发送消息
|
|
127
144
|
*/
|
|
128
145
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
129
146
|
if (event.key === 'Enter' && !event.shiftKey) {
|
|
147
|
+
// 如果正在使用输入法(如拼音输入),则不发送消息
|
|
148
|
+
// 让输入法先完成字符选择
|
|
149
|
+
if (isComposingRef.current) {
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
130
153
|
event.preventDefault()
|
|
131
154
|
if (value.trim() && !disabled) {
|
|
132
155
|
onSend()
|
|
@@ -175,11 +198,13 @@ export const ChatInput: React.FC<ChatInputProps> = ({
|
|
|
175
198
|
value={value}
|
|
176
199
|
onChange={handleChange}
|
|
177
200
|
onKeyDown={handleKeyDown}
|
|
201
|
+
onCompositionStart={handleCompositionStart}
|
|
202
|
+
onCompositionEnd={handleCompositionEnd}
|
|
178
203
|
placeholder={placeholder}
|
|
179
204
|
disabled={disabled}
|
|
180
205
|
autoFocus={autoFocus}
|
|
181
206
|
rows={1}
|
|
182
|
-
className="flex-1 resize-none bg-transparent text-sm text-gray-900 outline-none placeholder:text-gray-400 disabled:cursor-not-allowed disabled:opacity-50"
|
|
207
|
+
className="flex-1 resize-none bg-transparent font-bold text-sm text-gray-900 outline-none placeholder:text-gray-400 disabled:cursor-not-allowed disabled:opacity-50"
|
|
183
208
|
style={{
|
|
184
209
|
resize: 'none',
|
|
185
210
|
}}
|
|
@@ -8,7 +8,7 @@ import React from 'react'
|
|
|
8
8
|
import type { Message, MessageRenderer } from '../types'
|
|
9
9
|
import { MessageContent } from './MessageContent'
|
|
10
10
|
import { MessageRendererRegistry } from '../utils/messageRenderers'
|
|
11
|
-
|
|
11
|
+
import cn from 'classnames'
|
|
12
12
|
export interface ChatMessageProps {
|
|
13
13
|
/**
|
|
14
14
|
* 消息数据
|
|
@@ -122,7 +122,7 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({
|
|
|
122
122
|
// 将内容分为两组:气泡内容 和 独立内容
|
|
123
123
|
const bubbleContent = message.content.filter(c => !structuredContentTypes.includes(c.type))
|
|
124
124
|
const standaloneContent = message.content.filter(c => structuredContentTypes.includes(c.type))
|
|
125
|
-
|
|
125
|
+
const isBubbleProductCard = bubbleContent.some(c => c.type === 'product_card')
|
|
126
126
|
// 用户/助手消息
|
|
127
127
|
return (
|
|
128
128
|
<div className={`flex ${isUser ? 'justify-end' : 'justify-start'} py-2 ${className}`}>
|
|
@@ -130,13 +130,20 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({
|
|
|
130
130
|
{/* 消息气泡(包含文本和产品卡片,按原始顺序) */}
|
|
131
131
|
{bubbleContent.length > 0 && (
|
|
132
132
|
<div
|
|
133
|
-
className={
|
|
133
|
+
className={cn(
|
|
134
|
+
'w-fit min-w-0 overflow-hidden rounded-2xl ',
|
|
135
|
+
isUser ? 'text-white' : 'text-[#1D1D1F]',
|
|
136
|
+
isBubbleProductCard ? 'p-4' : 'p-3'
|
|
137
|
+
)}
|
|
134
138
|
style={{
|
|
135
139
|
backgroundColor: isUser ? '#005D8E' : '#F5F6F7',
|
|
136
140
|
}}
|
|
137
141
|
>
|
|
138
142
|
{/* 按原始顺序渲染气泡内的所有内容(text 和 product_card 交替) */}
|
|
139
|
-
<div className=
|
|
143
|
+
<div className={cn(
|
|
144
|
+
'flex w-full min-w-0 flex-col',
|
|
145
|
+
isBubbleProductCard ?'gap-0':'gap-2'
|
|
146
|
+
)}>
|
|
140
147
|
{bubbleContent.map((content, index) => (
|
|
141
148
|
<MessageContent
|
|
142
149
|
key={index}
|
|
@@ -294,6 +294,8 @@ export const ChatWindow: React.FC<ChatWindowProps> = ({
|
|
|
294
294
|
style={
|
|
295
295
|
{
|
|
296
296
|
borderRadius: '16px',
|
|
297
|
+
borderBottomLeftRadius: isMobile && '0px',
|
|
298
|
+
borderBottomRightRadius: isMobile && '0px',
|
|
297
299
|
'--livechat-mobile-height': `${mobileHeight}px`,
|
|
298
300
|
} as React.CSSProperties
|
|
299
301
|
}
|
|
@@ -72,7 +72,7 @@ export const FAQList: React.FC<FAQListProps> = ({ content, onQuestionClick }) =>
|
|
|
72
72
|
>
|
|
73
73
|
<div className="flex-1">
|
|
74
74
|
<div className="flex items-center gap-2">
|
|
75
|
-
<span className="text-
|
|
75
|
+
<span className="text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#080A0F]">
|
|
76
76
|
{item.question}
|
|
77
77
|
</span>
|
|
78
78
|
</div>
|
|
@@ -114,7 +114,7 @@ const PolicyCard: React.FC<{
|
|
|
114
114
|
ul: ({ node, ...props }) => <ul {...props} className="mb-2 ml-4 list-disc" />,
|
|
115
115
|
ol: ({ node, ...props }) => <ol {...props} className="mb-2 ml-4 list-decimal" />,
|
|
116
116
|
li: ({ node, ...props }) => <li {...props} className="mb-1" />,
|
|
117
|
-
strong: ({ node, ...props }) => <strong {...props} className="font-
|
|
117
|
+
strong: ({ node, ...props }) => <strong {...props} className="font-bold" />,
|
|
118
118
|
}}
|
|
119
119
|
>
|
|
120
120
|
{content}
|
|
@@ -88,7 +88,7 @@ const CompactProductCard: React.FC<{
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
return (
|
|
91
|
-
<div className="block w-full overflow-hidden rounded-2xl bg-[#F5F6F7] transition-shadow">
|
|
91
|
+
<div className="block w-full overflow-hidden rounded-2xl bg-[#F5F6F7] transition-shadow mb-[32px]">
|
|
92
92
|
<div className="block">
|
|
93
93
|
<div className="flex gap-2 p-4 bg-white">
|
|
94
94
|
{/* 商品图片 */}
|
|
@@ -102,7 +102,7 @@ const CompactProductCard: React.FC<{
|
|
|
102
102
|
</div>
|
|
103
103
|
|
|
104
104
|
{/* 商品信息 */}
|
|
105
|
-
<div className="flex flex-1 flex-col justify-center
|
|
105
|
+
<div className="flex flex-1 flex-col justify-center">
|
|
106
106
|
{/* 折扣标签 */}
|
|
107
107
|
{discountLabel && (
|
|
108
108
|
<div
|
|
@@ -172,6 +172,7 @@ const CompactProductCard: React.FC<{
|
|
|
172
172
|
* - 显示折扣标签、价格、评分
|
|
173
173
|
* - 支持 Add to Cart 按钮
|
|
174
174
|
* - 独立成段展示
|
|
175
|
+
* - 支持自定义渲染函数
|
|
175
176
|
*
|
|
176
177
|
* 布局:
|
|
177
178
|
* ```
|
|
@@ -190,7 +191,8 @@ const CompactProductCard: React.FC<{
|
|
|
190
191
|
* type: 'product_card',
|
|
191
192
|
* data: {
|
|
192
193
|
* product: { ... },
|
|
193
|
-
* onAddToCart: (product) => { ... }
|
|
194
|
+
* onAddToCart: (product) => { ... },
|
|
195
|
+
* productCardRender: (product) => <CustomCard product={product} />
|
|
194
196
|
* }
|
|
195
197
|
* }
|
|
196
198
|
* <ProductCard.render(content, false, false) />
|
|
@@ -199,12 +201,18 @@ const CompactProductCard: React.FC<{
|
|
|
199
201
|
export const ProductCard: MessageRenderer = {
|
|
200
202
|
render: (content, isUser, isSystem) => {
|
|
201
203
|
const productContent = content as ProductCardContent
|
|
202
|
-
const { product, onAddToCart } = productContent.data
|
|
204
|
+
const { product, rawProduct, onAddToCart, productCardRender } = productContent.data
|
|
203
205
|
|
|
204
206
|
if (!product) {
|
|
205
207
|
return null
|
|
206
208
|
}
|
|
207
209
|
|
|
210
|
+
// 如果提供了自定义渲染函数,使用自定义渲染(传入原始后端数据)
|
|
211
|
+
if (productCardRender) {
|
|
212
|
+
return <>{productCardRender(rawProduct || product)}</>
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 否则使用默认渲染
|
|
208
216
|
return <CompactProductCard product={product} onAddToCart={onAddToCart} />
|
|
209
217
|
},
|
|
210
218
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import React, { useState } from 'react'
|
|
8
8
|
import type { Product, MessageRenderer, CommonText } from '../../types'
|
|
9
|
-
import { DEFAULT_COMMON_TEXT } from '../../constants'
|
|
9
|
+
import { DEFAULT_COMMON_TEXT, CURRENCY_SYMBOLS } from '../../constants'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* 对比维度数据结构
|
|
@@ -29,6 +29,7 @@ export interface ProductComparisonData {
|
|
|
29
29
|
variants?: ComparisonDimension
|
|
30
30
|
member_price?: ComparisonDimension
|
|
31
31
|
discount?: ComparisonDimension
|
|
32
|
+
reviews?: ComparisonDimension
|
|
32
33
|
[key: string]: ComparisonDimension | undefined
|
|
33
34
|
}
|
|
34
35
|
commonText?: CommonText
|
|
@@ -65,8 +66,8 @@ export interface ProductComparisonProps {
|
|
|
65
66
|
* 格式化价格显示
|
|
66
67
|
*/
|
|
67
68
|
const formatPrice = (amount: number, currency: string = 'USD'): string => {
|
|
68
|
-
const symbol = currency
|
|
69
|
-
return `${symbol}${amount
|
|
69
|
+
const symbol = CURRENCY_SYMBOLS[currency] || currency
|
|
70
|
+
return `${symbol}${amount}`
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
/**
|
|
@@ -170,11 +171,11 @@ export const ProductComparison: React.FC<ProductComparisonProps> = ({ data, onAd
|
|
|
170
171
|
// 根据不同维度类型渲染不同格式
|
|
171
172
|
if (dimensionLabel.toLowerCase().includes('price')) {
|
|
172
173
|
// 价格维度
|
|
173
|
-
const priceDisplay =
|
|
174
|
+
const priceDisplay = value?.available?
|
|
174
175
|
value.min === value.max
|
|
175
176
|
? formatPrice(value.min, value.currency)
|
|
176
|
-
: `${formatPrice(value.min, value.currency)} - ${formatPrice(value.max, value.currency)}
|
|
177
|
-
return <span className="text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]">{priceDisplay}</span>
|
|
177
|
+
: `${formatPrice(value.min, value.currency)} - ${formatPrice(value.max, value.currency)}`:'-'
|
|
178
|
+
return <span className="text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]">{priceDisplay||'-'}</span>
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
if (dimensionLabel.toLowerCase().includes('variant')) {
|
|
@@ -186,6 +187,22 @@ export const ProductComparison: React.FC<ProductComparisonProps> = ({ data, onAd
|
|
|
186
187
|
)
|
|
187
188
|
}
|
|
188
189
|
|
|
190
|
+
if (dimensionLabel.toLowerCase().includes('review')) {
|
|
191
|
+
// 评论维度:显示评分和数量
|
|
192
|
+
const rating = value.rating || 0
|
|
193
|
+
const count = value.count || 0
|
|
194
|
+
return (
|
|
195
|
+
<div className="flex items-center gap-1">
|
|
196
|
+
<span className="text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]">
|
|
197
|
+
⭐ {rating.toFixed(1)}
|
|
198
|
+
</span>
|
|
199
|
+
<span className="text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#86868C]">
|
|
200
|
+
({count})
|
|
201
|
+
</span>
|
|
202
|
+
</div>
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
189
206
|
// 默认显示
|
|
190
207
|
return (
|
|
191
208
|
<span className="text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]">
|