@anker-in/campaign-ui 0.2.11-beta.40 → 0.2.11-beta.42
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/api/chat.js +2 -2
- package/dist/cjs/components/LiveChatWidget/api/chat.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js +6 -6
- package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js.map +2 -2
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.d.ts +1 -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/MessageList.js +3 -3
- package/dist/cjs/components/LiveChatWidget/components/MessageList.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/hooks/useChatState.d.ts +4 -2
- 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 +18 -11
- package/dist/cjs/components/LiveChatWidget/types.js.map +1 -1
- package/dist/cjs/components/index.d.ts +1 -1
- package/dist/cjs/components/index.js +1 -1
- package/dist/cjs/components/index.js.map +2 -2
- package/dist/cjs/stories/LiveChatWidget.stories.d.ts +1 -109
- package/dist/cjs/stories/LiveChatWidget.stories.js +7 -166
- 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/api/chat.js +2 -2
- package/dist/esm/components/LiveChatWidget/api/chat.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js +7 -7
- package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js.map +2 -2
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.d.ts +1 -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/MessageList.js +2 -2
- package/dist/esm/components/LiveChatWidget/components/MessageList.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/hooks/useChatState.d.ts +4 -2
- 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 +18 -11
- package/dist/esm/components/index.d.ts +1 -1
- package/dist/esm/components/index.js +1 -1
- package/dist/esm/components/index.js.map +2 -2
- package/dist/esm/stories/LiveChatWidget.stories.d.ts +1 -109
- package/dist/esm/stories/LiveChatWidget.stories.js +7 -166
- package/dist/esm/stories/LiveChatWidget.stories.js.map +3 -3
- package/package.json +1 -1
- package/src/components/LiveChatWidget/LiveChatWidget.tsx +65 -23
- package/src/components/LiveChatWidget/api/chat.ts +8 -7
- package/src/components/LiveChatWidget/components/ComplianceDialog.tsx +1 -5
- package/src/components/LiveChatWidget/components/MessageContent/ProductCard.tsx +17 -8
- package/src/components/LiveChatWidget/components/MessageList.tsx +11 -17
- package/src/components/LiveChatWidget/hooks/useChatState.ts +64 -98
- package/src/components/LiveChatWidget/types.ts +18 -11
- package/src/components/index.ts +2 -1
- package/src/stories/LiveChatWidget.stories.tsx +95 -660
- package/src/styles/livechat.css +29 -0
|
@@ -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 welcomeMessage: '',\n bottomTips: '',\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 * \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\n *\n * \u6F14\u793A\u5982\u4F55\u914D\u7F6E\u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\uFF0C\u5728\u7528\u6237\u9996\u6B21\u70B9\u51FB\u804A\u5929\u6C14\u6CE1\u65F6\u663E\u793A\u3002\n *\n * \u7528\u6237\u5FC5\u987B\u540C\u610F\u534F\u8BAE\u540E\u624D\u80FD\u6253\u5F00\u804A\u5929\u7A97\u53E3\u3002\n */\nexport const WithComplianceDialog: Story = {\n args: {\n loginUserId: 'test_test',\n apiBaseUrl: 'http://172.16.38.183:3003',\n site: 'beta.eufy.com',\n channelCode: 'dtc',\n title: 'eufy AI Assistant',\n\n // \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u914D\u7F6E\n complianceConfig: {\n title: \"Hi! I'm your eufy AI assistant.\",\n content: \"AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data\",\n 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>.',\n agreeButtonText: \"Agree\"\n },\n },\n parameters: {\n docs: {\n description: {\n story: `\n### \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u529F\u80FD\n\n\u4F7F\u7528 \\`complianceConfig\\` \u5C5E\u6027\u53EF\u4EE5\u5728\u7528\u6237\u9996\u6B21\u70B9\u51FB\u804A\u5929\u6C14\u6CE1\u65F6\u663E\u793A\u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u3002\n\n**\u529F\u80FD\u7279\u6027\uFF1A**\n\n- \u2705 \u7528\u6237\u5FC5\u987B\u52FE\u9009\u590D\u9009\u6846\u624D\u80FD\u70B9\u51FB\"\u540C\u610F\"\u6309\u94AE\n- \u2705 \u652F\u6301\u5728\u52FE\u9009\u6846\u6587\u672C\u4E2D\u5D4C\u5165\u94FE\u63A5\uFF08\u4F7F\u7528 \\`{link}\\` \u5360\u4F4D\u7B26\uFF09\n- \u2705 \u652F\u6301 HTML \u683C\u5F0F\u7684\u5185\u5BB9\u6587\u672C\n- \u2705 \u540C\u610F\u540E\u81EA\u52A8\u6253\u5F00\u804A\u5929\u7A97\u53E3\n- \u2705 \u5173\u95ED\u5F39\u7A97\u4E0D\u4F1A\u6253\u5F00\u804A\u5929\u7A97\u53E3\n- \u2705 \u540C\u610F\u540E\u518D\u6B21\u70B9\u51FB\u6C14\u6CE1\u76F4\u63A5\u6253\u5F00\u804A\u5929\uFF08\u65E0\u9700\u91CD\u590D\u540C\u610F\uFF09\n- \u2705 \u54CD\u5E94\u5F0F\u8BBE\u8BA1\uFF0C\u79FB\u52A8\u7AEF\u53CB\u597D\n- \u2705 **\u4E0E\u804A\u5929\u7A97\u53E3\u4F4D\u7F6E\u4E00\u81F4**\uFF08\u53F3\u4E0B\u89D2\u6216\u5E95\u90E8\u56FA\u5B9A\uFF09\n- \u2705 **\u65E0\u906E\u7F69\u5C42\u8BBE\u8BA1**\uFF0C\u4E0D\u906E\u6321\u9875\u9762\u5185\u5BB9\n\n**\u6D4B\u8BD5\u6B65\u9AA4\uFF1A**\n\n1. \u70B9\u51FB\u804A\u5929\u6C14\u6CE1\u6309\u94AE\n2. \u67E5\u770B\u5F39\u51FA\u7684\u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\n3. \u5C1D\u8BD5\u4E0D\u52FE\u9009\u590D\u9009\u6846\u65F6\u70B9\u51FB\"Agree\"\u6309\u94AE\uFF08\u6309\u94AE\u5E94\u8BE5\u662F\u7981\u7528\u72B6\u6001\uFF09\n4. \u52FE\u9009\u590D\u9009\u6846\u540E\u70B9\u51FB\"Agree\"\n5. \u89C2\u5BDF\u5F39\u7A97\u5173\u95ED\u5E76\u81EA\u52A8\u6253\u5F00\u804A\u5929\u7A97\u53E3\n6. \u5173\u95ED\u804A\u5929\u7A97\u53E3\u540E\u518D\u6B21\u70B9\u51FB\u6C14\u6CE1\uFF08\u5E94\u8BE5\u76F4\u63A5\u6253\u5F00\u804A\u5929\uFF0C\u4E0D\u518D\u663E\u793A\u6CD5\u89C4\u5F39\u7A97\uFF09\n\n**\u914D\u7F6E\u9879\u8BF4\u660E\uFF1A**\n\n\\`\\`\\`typescript\ncomplianceConfig: {\n title: string // \u5F39\u7A97\u6807\u9898\n content: string // \u5185\u5BB9\u6587\u672C\uFF08\u652F\u6301 HTML\uFF09\n checkboxText: string // \u52FE\u9009\u6846\u6587\u672C\uFF08\u652F\u6301\u5B8C\u6574 HTML\uFF0C\u5305\u62EC <a> \u6807\u7B7E\uFF09\n agreeButtonText?: string // \u540C\u610F\u6309\u94AE\u6587\u6848\uFF08\u9ED8\u8BA4 \"Agree\"\uFF09\n}\n\\`\\`\\`\n\n**\u793A\u4F8B\uFF1A**\n\n\\`\\`\\`tsx\ncheckboxText: 'By starting to use \"Live Chat\", you agree to <a href=\"https://example.com/privacy\" target=\"_blank\">PRIVACY NOTICE</a>.'\n\\`\\`\\`\n\n**\u6837\u5F0F\u5B9A\u5236\uFF1A**\n\n\u6240\u6709\u6837\u5F0F\u901A\u8FC7 CSS \u7C7B\u540D\u66B4\u9732\uFF0C\u53EF\u4EE5\u5168\u5C40\u8986\u76D6\uFF1A\n\n\\`\\`\\`css\n.livechat-compliance-content { /* \u5F39\u7A97\u4E3B\u4F53 */ }\n.livechat-compliance-title { /* \u6807\u9898 */ }\n.livechat-compliance-text { /* \u5185\u5BB9\u6587\u672C */ }\n.livechat-compliance-checkbox { /* \u590D\u9009\u6846 */ }\n.livechat-compliance-link { /* \u94FE\u63A5 */ }\n.livechat-compliance-button { /* \u540C\u610F\u6309\u94AE */ }\n\\`\\`\\`\n `,\n },\n },\n },\n}\n\n/**\n * \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97 - \u65E0\u94FE\u63A5\u7248\u672C\n *\n * \u6F14\u793A\u4E0D\u5305\u542B\u94FE\u63A5\u7684\u7B80\u5316\u7248\u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u3002\n */\nexport const WithComplianceDialogNoLink: Story = {\n args: {\n loginUserId: 'test_test',\n apiBaseUrl: 'http://172.16.38.183:3003',\n site: 'beta.eufy.com',\n channelCode: 'dtc',\n title: 'AI Assistant',\n\n // \u7B80\u5316\u7248\u6CD5\u89C4\u534F\u8BAE\uFF08\u4E0D\u5305\u542B\u94FE\u63A5\uFF09\n complianceConfig: {\n title: \"Welcome to AI Chat Support\",\n content: \"By using this service, you agree to our terms of service and privacy policy. AI responses may contain errors. Please verify important information.\",\n checkboxText: \"I have read and agree to the terms and conditions\",\n agreeButtonText: \"Continue\"\n },\n },\n parameters: {\n docs: {\n description: {\n story: `\n### \u7B80\u5316\u7248\u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\n\n\u5982\u679C\u4E0D\u9700\u8981\u5728\u52FE\u9009\u6846\u4E2D\u663E\u793A\u94FE\u63A5\uFF0C\u53EF\u4EE5\u7701\u7565 \\`linkText\\` \u548C \\`linkUrl\\` \u5C5E\u6027\u3002\n\n\u8FD9\u4E2A\u7248\u672C\u9002\u7528\u4E8E\uFF1A\n- \u7B80\u5355\u7684\u7528\u6237\u534F\u8BAE\u786E\u8BA4\n- \u514D\u8D23\u58F0\u660E\n- \u670D\u52A1\u6761\u6B3E\u540C\u610F\n\n**\u4E0E\u5B8C\u6574\u7248\u7684\u533A\u522B\uFF1A**\n- \u4E0D\u5305\u542B\u53EF\u70B9\u51FB\u7684\u94FE\u63A5\n- \u52FE\u9009\u6846\u6587\u672C\u4E3A\u7EAF\u6587\u672C\n- \u914D\u7F6E\u66F4\u7B80\u5355\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 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": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,
|
|
6
|
-
"names": ["LiveChatWidget_stories_exports", "__export", "
|
|
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 - \u5C55\u793A\u6240\u6709\u529F\u80FD\n */\nexport const Default: Story = {\n args: {\n // \u57FA\u7840\u914D\u7F6E\n loginUserId: 'test_test',\n apiBaseUrl: 'http://172.16.38.183:3003',\n site: 'beta.eufy.com',\n channelCode: 'dtc',\n title: 'eufy AI Assistant',\n cartId: 'gid://shopify/Cart/hWN7wB3Pa12gh78d8hPOAUBI?key=0e73db1d3fb5ac21da19099c45033253',\n accessToken: '47b1aa2c0797043f9baba39388029d70',\n\n // \u81EA\u5B9A\u4E49\u4F4D\u7F6E\n position: { bottom: '24px', right: '30px' },\n\n // \u6B22\u8FCE\u6D88\u606F\n welcomeMessage: `Welcome to eufy AI Assistant!\n\nI can help you with:\n- Product recommendations\n- Order tracking\n- FAQs and support\n\nHow can I assist you today?`,\n\n // \u5FEB\u6377\u56DE\u590D\n quickReplies: [\n { id: '1', label: 'Product Info', value: 'Tell me about your products', icon: '\uD83D\uDCE6' },\n { id: '2', label: 'Track Order', value: 'I want to track my order', icon: '\uD83D\uDE9A' },\n { id: '3', label: 'Support', value: 'I need help with my device', icon: '\uD83D\uDD27' },\n { id: '4', label: 'Recommendations', value: 'Recommend products for me', icon: '\u2B50' },\n ],\n\n // \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\n complianceConfig: {\n title: \"Hi! I'm your eufy AI assistant.\",\n content: \"AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data.\",\n 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>.',\n agreeButtonText: \"Agree\"\n },\n\n // reCAPTCHA \u914D\u7F6E\uFF08\u53D6\u6D88\u6CE8\u91CA\u4EE5\u542F\u7528\uFF09\n // recaptchaSitekey: '6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14',\n recaptchaAction: 'livechat',\n\n // \u663E\u793A\u65B0\u4F1A\u8BDD\u6309\u94AE\n showNewSessionButton: true,\n\n // \u81EA\u5B9A\u4E49\u6587\u6848\n commonText: {\n learnMore: 'Learn More',\n total: 'Total',\n },\n\n // \u81EA\u5B9A\u4E49\u6D88\u606F\u6E32\u67D3\u5668\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 Your browser does not support video playback\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 image_gallery: {\n render: (content: MessageContent) => {\n const galleryContent = content as any\n const images = galleryContent.images || []\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 // \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\n productCardRender: (product, productHandle) => {\n // product \u53EF\u80FD\u4E3A undefined\uFF0C\u6B64\u65F6\u53EF\u7528 productHandle \u67E5\u8BE2\n if (!product) {\n return (\n <div style={{ padding: '16px', border: '1px dashed #ccc', borderRadius: '8px', textAlign: 'center' }}>\n <p>Product loading... (handle: {productHandle})</p>\n </div>\n )\n }\n\n const imageUrl = product?.featured_image || ''\n const title = product?.title || ''\n const description = product?.description || ''\n const averageRating = product?.average_rating\n\n return (\n <div\n style={{\n border: '2px solid #4CAF50',\n borderRadius: '16px',\n padding: '16px',\n margin: '12px 0',\n backgroundColor: '#f0f9ff',\n boxShadow: '0 4px 12px rgba(76, 175, 80, 0.15)',\n }}\n >\n <div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>\n {imageUrl && (\n <img\n src={imageUrl}\n alt={title}\n style={{ width: '60px', height: '60px', borderRadius: '8px', objectFit: 'cover' }}\n />\n )}\n <div style={{ flex: 1 }}>\n <h4 style={{ margin: '0 0 4px 0', fontSize: '16px', fontWeight: 'bold' }}>{title}</h4>\n {description && (\n <p style={{ margin: 0, fontSize: '12px', color: '#666', lineHeight: 1.4 }}>\n {description.slice(0, 80)}...\n </p>\n )}\n {averageRating && (\n <span style={{ fontSize: '12px', color: '#FFB800' }}>Rating: {averageRating.toFixed(1)}</span>\n )}\n </div>\n <button\n style={{\n padding: '8px 16px',\n backgroundColor: '#4CAF50',\n color: 'white',\n borderRadius: '8px',\n border: 'none',\n cursor: 'pointer',\n }}\n onClick={() => console.log('View product:', productHandle, product)}\n >\n View\n </button>\n </div>\n </div>\n )\n },\n },\n\n render: args => (\n <LiveChatWidget\n {...args}\n // \u6240\u6709\u4E8B\u4EF6\u56DE\u8C03\n onOpen={() => console.log('[LiveChat] Chat opened')}\n onClose={() => console.log('[LiveChat] Chat closed')}\n onMessageSend={(message: string) => console.log('[LiveChat] Message sent:', message)}\n onError={(error: Error) => console.error('[LiveChat] Error:', error)}\n onTextMessage={() => console.log('[LiveChat] AI text message received')}\n onProductList={() => console.log('[LiveChat] Product list received')}\n onPromotionList={() => console.log('[LiveChat] Promotion list received')}\n onAddToCart={(product: any) => {\n console.log('[LiveChat] Add to cart:', product)\n alert(`Added \"${product.title}\" to cart!`)\n }}\n onCart={(cartId: string, checkoutUrl?: string) => {\n console.log('[LiveChat] Cart clicked:', { cartId, checkoutUrl })\n alert(`Cart ID: ${cartId}`)\n }}\n />\n ),\n}\n"],
|
|
5
|
+
"mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,aAAAE,EAAA,YAAAC,IAAA,eAAAC,EAAAJ,GA6MY,IAAAK,EAAA,6BAvMZC,EAA+B,wCAE/BC,EAAO,kCAEP,MAAMC,EAAoC,CACxC,MAAO,0BACP,UAAW,iBACX,WAAY,CACV,OAAQ,aACR,KAAM,CACJ,MAAO,CACL,OAAQ,GACR,aAAc,GAChB,EACA,YAAa,CACX,UAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAmDb,CACF,CACF,EACA,KAAM,CAAC,UAAU,EACjB,SAAU,CACR,WAAY,CACV,QAAS,OACT,YAAa,sBACf,EACA,QAAS,CACP,QAAS,SACT,YAAa,wGACb,MAAO,CACL,aAAc,CAAE,QAAS,WAAY,CACvC,CACF,EACA,iBAAkB,CAChB,QAAS,OACT,YAAa,2HACb,MAAO,CACL,aAAc,CAAE,QAAS,WAAY,CACvC,CACF,EACA,gBAAiB,CACf,QAAS,OACT,YAAa,wGACb,MAAO,CACL,aAAc,CAAE,QAAS,YAAa,CACxC,CACF,EACA,KAAM,CACJ,QAAS,OACT,YAAa,0BACf,EACA,YAAa,CACX,QAAS,OACT,YAAa,iFACb,MAAO,CACL,aAAc,CAAE,QAAS,WAAY,CACvC,CACF,EACA,eAAgB,CACd,QAAS,OACT,YAAa,2BACb,MAAO,CACL,aAAc,CAAE,QAAS,wHAA0B,CACrD,CACF,EACA,QAAS,CACP,QAAS,OACT,YAAa,UACf,EACA,SAAU,CACR,QAAS,SACT,YAAa,mDACb,MAAO,CACL,aAAc,CAAE,QAAS,uCAAwC,CACnE,CACF,CACF,EACA,KAAM,CACJ,WAAY,4BACZ,KAAM,eACN,YAAa,YACb,eAAgB,wHAClB,CACF,EAEA,IAAOL,EAAQK,EAMR,MAAMN,EAAiB,CAC5B,KAAM,CAEJ,YAAa,YACb,WAAY,4BACZ,KAAM,gBACN,YAAa,MACb,MAAO,oBACP,OAAQ,mFACR,YAAa,mCAGb,SAAU,CAAE,OAAQ,OAAQ,MAAO,MAAO,EAG1C,eAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAUhB,aAAc,CACZ,CAAE,GAAI,IAAK,MAAO,eAAgB,MAAO,8BAA+B,KAAM,WAAK,EACnF,CAAE,GAAI,IAAK,MAAO,cAAe,MAAO,2BAA4B,KAAM,WAAK,EAC/E,CAAE,GAAI,IAAK,MAAO,UAAW,MAAO,6BAA8B,KAAM,WAAK,EAC7E,CAAE,GAAI,IAAK,MAAO,kBAAmB,MAAO,4BAA6B,KAAM,QAAI,CACrF,EAGA,iBAAkB,CAChB,MAAO,kCACP,QAAS,gHACT,aAAc,wNACd,gBAAiB,OACnB,EAIA,gBAAiB,WAGjB,qBAAsB,GAGtB,WAAY,CACV,UAAW,aACX,MAAO,OACT,EAGA,gBAAiB,CACf,MAAO,CACL,OAASO,GAA4B,CACnC,MAAMC,EAAeD,EACrB,SACE,QAAC,OAAI,UAAU,SACb,oBAAC,SAAM,IAAKC,EAAa,IAAK,SAAQ,GAAC,UAAU,oBAAoB,OAAQA,EAAa,OAAQ,wDAElG,EACCA,EAAa,UAAS,OAAC,KAAE,UAAU,6BAA8B,SAAAA,EAAa,MAAM,GACvF,CAEJ,CACF,EACA,cAAe,CACb,OAASD,GAA4B,CAEnC,MAAME,EADiBF,EACO,QAAU,CAAC,EACzC,SACE,OAAC,OAAI,UAAU,yBACZ,SAAAE,EAAO,IAAI,CAACC,EAAYC,OACvB,OAAC,OAAgB,UAAU,yBACzB,mBAAC,OACC,IAAKD,EAAM,IACX,IAAKA,EAAM,KAAO,SAASC,EAAQ,CAAC,GACpC,UAAU,oCACZ,GALQA,CAMV,CACD,EACH,CAEJ,CACF,CACF,EAGA,kBAAmB,CAACC,EAASC,IAAkB,CAE7C,GAAI,CAACD,EACH,SACE,OAAC,OAAI,MAAO,CAAE,QAAS,OAAQ,OAAQ,kBAAmB,aAAc,MAAO,UAAW,QAAS,EACjG,oBAAC,KAAE,yCAA6BC,EAAc,KAAC,EACjD,EAIJ,MAAMC,EAAWF,GAAS,gBAAkB,GACtCG,EAAQH,GAAS,OAAS,GAC1BI,EAAcJ,GAAS,aAAe,GACtCK,EAAgBL,GAAS,eAE/B,SACE,OAAC,OACC,MAAO,CACL,OAAQ,oBACR,aAAc,OACd,QAAS,OACT,OAAQ,SACR,gBAAiB,UACjB,UAAW,oCACb,EAEA,oBAAC,OAAI,MAAO,CAAE,QAAS,OAAQ,IAAK,OAAQ,WAAY,QAAS,EAC9D,UAAAE,MACC,OAAC,OACC,IAAKA,EACL,IAAKC,EACL,MAAO,CAAE,MAAO,OAAQ,OAAQ,OAAQ,aAAc,MAAO,UAAW,OAAQ,EAClF,KAEF,QAAC,OAAI,MAAO,CAAE,KAAM,CAAE,EACpB,oBAAC,MAAG,MAAO,CAAE,OAAQ,YAAa,SAAU,OAAQ,WAAY,MAAO,EAAI,SAAAA,EAAM,EAChFC,MACC,QAAC,KAAE,MAAO,CAAE,OAAQ,EAAG,SAAU,OAAQ,MAAO,OAAQ,WAAY,GAAI,EACrE,UAAAA,EAAY,MAAM,EAAG,EAAE,EAAE,OAC5B,EAEDC,MACC,QAAC,QAAK,MAAO,CAAE,SAAU,OAAQ,MAAO,SAAU,EAAG,qBAASA,EAAc,QAAQ,CAAC,GAAE,GAE3F,KACA,OAAC,UACC,MAAO,CACL,QAAS,WACT,gBAAiB,UACjB,MAAO,QACP,aAAc,MACd,OAAQ,OACR,OAAQ,SACV,EACA,QAAS,IAAM,QAAQ,IAAI,gBAAiBJ,EAAeD,CAAO,EACnE,gBAED,GACF,EACF,CAEJ,CACF,EAEA,OAAQM,MACN,OAAC,kBACE,GAAGA,EAEJ,OAAQ,IAAM,QAAQ,IAAI,wBAAwB,EAClD,QAAS,IAAM,QAAQ,IAAI,wBAAwB,EACnD,cAAgBC,GAAoB,QAAQ,IAAI,2BAA4BA,CAAO,EACnF,QAAUC,GAAiB,QAAQ,MAAM,oBAAqBA,CAAK,EACnE,cAAe,IAAM,QAAQ,IAAI,qCAAqC,EACtE,cAAe,IAAM,QAAQ,IAAI,kCAAkC,EACnE,gBAAiB,IAAM,QAAQ,IAAI,oCAAoC,EACvE,YAAcR,GAAiB,CAC7B,QAAQ,IAAI,0BAA2BA,CAAO,EAC9C,MAAM,UAAUA,EAAQ,KAAK,YAAY,CAC3C,EACA,OAAQ,CAACS,EAAgBC,IAAyB,CAChD,QAAQ,IAAI,2BAA4B,CAAE,OAAAD,EAAQ,YAAAC,CAAY,CAAC,EAC/D,MAAM,YAAYD,CAAM,EAAE,CAC5B,EACF,CAEJ",
|
|
6
|
+
"names": ["LiveChatWidget_stories_exports", "__export", "Default", "LiveChatWidget_stories_default", "__toCommonJS", "import_jsx_runtime", "import_LiveChatWidget", "import_livechat", "meta", "content", "videoContent", "images", "image", "index", "product", "productHandle", "imageUrl", "title", "description", "averageRating", "args", "message", "error", "cartId", "checkoutUrl"]
|
|
7
7
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{Fragment as
|
|
1
|
+
import{Fragment as Ze,jsx as R,jsxs as je}from"react/jsx-runtime";import w,{useEffect as Ae,useCallback as y}from"react";import*as H from"@radix-ui/react-dialog";import{DEFAULT_COMMON_TEXT as ve}from"./constants";import{ChatBubble as qe}from"./components/ChatBubble";import{ChatWindow as Oe}from"./components/ChatWindow";import{ComplianceDialog as Fe}from"./components/ComplianceDialog";import{useChatState as Be}from"./hooks/useChatState";import{useChatAPI as We}from"./hooks/useChatAPI";import{MessageRendererRegistry as Ie}from"./utils/messageRenderers";import{sanitizeInput as Le}from"./utils/validation";import{transformProducts as te}from"./utils/productTransformers.js";import{transformCartData as Ne}from"./utils/cartTransformers.js";import se from"js-cookie";import{TextBlock as $e,ProductCard as ke,ProductList as Qe,ProductComparisonRenderer as ze,PolicyBlock as He,createQuickRepliesRenderer as Ye,ThinkingBlock as Ge,ErrorBlock as Ve,FAQListRenderer as Xe,PromotionListRenderer as Ke,CartCard as Je}from"./components/MessageContent/index.js";const lt=({apiBaseUrl:ae,headers:re,recaptchaSitekey:Y,recaptchaAction:ne,site:o,channelCode:m,loginUserId:l,cartId:G,accessToken:V,position:oe,welcomeMessage:x,quickReplies:u,customRenderers:B,logoUrl:ie,title:ce,chatBubbleIcon:de,open:pe,onOpenChange:le,onOpen:ue,onClose:me,onMessageSend:W,onError:g,onTextMessage:ge,onProductList:he,onPromotionList:fe,onAddToCart:T,onCart:I,showNewSessionButton:ye,commonText:X,productCardRender:_e,bottomTips:Ce,complianceConfig:_})=>{const L=_?.cookieName||"livechat_compliance_agreed",[K,N]=w.useState(!1),[J,we]=w.useState(()=>_?se.get(L)!==void 0:!0),D=w.useMemo(()=>({...ve,...X}),[X]),Re=Be({welcomeMessage:x,site:o,open:pe,onOpenChange:le,onOpen:ue,onClose:me,onMessageSend:W,onError:g,onTextMessage:ge,onProductList:he,onPromotionList:fe,onAddToCart:T,onCart:I,productCardRender:_e}),{messages:A,isOpen:v,userId:i,sessionId:$,inputValue:k,isStreaming:xe,openChat:S,closeChat:Z,setInputValue:Q,addMessage:p,setMessages:C,clearMessages:q,handleSSEEvent:j,saveSession:E,clearSession:M}=Re,{sendMessageStream:U,createSession:h}=We({apiBaseUrl:ae,headers:re,recaptchaConfig:{needRecaptcha:!!Y,recaptchaSitekey:Y,recaptchaAction:ne},onError:g}),b=w.useRef(async t=>{}),De=w.useMemo(()=>{const t=new Ie;t.register("text",$e),t.register("product_card",ke),t.register("product_list",Qe),t.register("product_comparison",ze),t.register("policy",He),t.register("thinking",Ge),t.register("error",Ve),t.register("faq_list",Xe),t.register("promotion_list",Ke),t.register("cart",Je);const s=Ye(r=>{b.current(r.value)});return t.register("quick_replies",s),B&&t.registerMany(B),t},[B]);Ae(()=>{if(!v||!i)return;const t=$;t?Se(t):P()},[v,i]);const ee=y(t=>{if(Array.isArray(t.content))return t;const s=[];typeof t.content=="string"&&t.content.trim()&&s.push({type:"text",text:t.content});const r=t.structuredContent||t.structured_content;return Array.isArray(r)&&r.forEach(e=>{if(e.type==="product_list"&&Array.isArray(e.data))s.push({type:"product_list",data:{products:te(e.data,o),title:void 0,commonText:D}});else if(e.type==="quick_replies"&&e.data?.replies)s.push({type:"quick_replies",data:{replies:e.data.replies}});else if(e.type==="policy"&&e.data?.title&&e.data?.content)s.push({type:"policy",data:{title:e.data.title,content:e.data.content}});else if(e.type==="product_comparison"&&e.data?.products&&e.data?.dimensions)s.push({type:"product_comparison",data:{products:te(e.data.products,o),dimensions:e.data.dimensions,onAddToCart:T,commonText:D}});else if(e.type==="faq_list"&&e.data?.found!==void 0)s.push({type:"faq_list",data:e.data});else if(e.type==="promotion_list"&&e.data?.found!==void 0)s.push({type:"promotion_list",data:{...e.data,commonText:D}});else if(e.type==="cart"&&e.data?.id!==void 0){const c=Ne(e.data);s.push({type:"cart",data:{...c,onCart:I,commonText:D}})}else s.push(e)}),s.length===0&&s.push({type:"text",text:""}),{...t,content:s}},[o,I,T,D]),P=y(async()=>{if(i)try{const t=await h({user_id:i,site:o,channel_code:m,real_user_id:l});if(t.success){E(t.sessionId),q();const s=t.welcomeMessage||x;if(s){const r=[{type:"text",text:s}],e=t.quickQuestions;if(e&&e.length>0){const c=e.map((d,a)=>({id:`quick-${a}`,label:d,value:d}));r.push({type:"quick_replies",data:{replies:c}})}else u&&u.length>0&&r.push({type:"quick_replies",data:{replies:u}});p({id:`welcome-${Date.now()}`,role:"assistant",content:r,timestamp:Date.now()})}}}catch(t){console.error("[LiveChatWidget] Failed to create new session:",t),g?.(t);const s=t?.type==="GoogleRecaptchaError";p({id:`error-${Date.now()}`,role:"system",content:[{type:"error",data:{message:s?"Your session has expired. Please refresh the page and try again.":"Failed to create session. Please refresh the page and try again.",code:s?"RECAPTCHA_ERROR":"SESSION_CREATE_ERROR"}}],timestamp:Date.now()})}},[i,o,m,l,h,E,q,x,u,p,g]),Se=y(async t=>{try{const s=await h({user_id:i,session_id:t,site:o,channel_code:m,real_user_id:l});if(s.success&&s.resumed){const r=s.welcomeMessage||x,e=r?[{type:"text",text:r}]:[],c=s.quickQuestions;if(c&&c.length>0){const d=c.map((a,F)=>({id:`quick-${F}`,label:a,value:a}));e.push({type:"quick_replies",data:{replies:d}})}else u&&u.length>0&&e.push({type:"quick_replies",data:{replies:u}});if(s.messages&&s.messages.length>0){const d=s.messages.filter(a=>a!=null).map(ee).filter(a=>a.content&&a.content.length>0&&!(a.content.length===1&&a.content[0].type==="text"&&!a.content[0].text));if(e.length>0){const a={id:`welcome-${Date.now()}`,role:"assistant",content:e,timestamp:Date.now()};C([a,...d])}else C(d)}else q(),e.length>0&&p({id:`welcome-${Date.now()}`,role:"assistant",content:e,timestamp:Date.now()})}else s.resumed||(M(),P())}catch(s){console.error("[LiveChatWidget] Failed to resume session:",s),s?.type==="GoogleRecaptchaError"?p({id:`error-${Date.now()}`,role:"system",content:[{type:"error",data:{message:"Your session has expired. Please refresh the page and try again.",code:"RECAPTCHA_ERROR"}}],timestamp:Date.now()}):(p({id:`error-${Date.now()}`,role:"system",content:[{type:"error",data:{message:"Failed to resume session. Creating a new session...",code:"SESSION_RESUME_ERROR"}}],timestamp:Date.now()}),M(),P())}},[i,o,m,l,h,C,M,q,ee,P,x,u,p]),O=y(async(t,s=!1)=>{const r=t||k.trim();if(!r)return;!t&&!s&&Q("");const e=Le(r);if(!e){g?.(new Error("Invalid message"));return}if(!s){const a={id:`user-${Date.now()}`,role:"user",content:[{type:"text",text:e}],timestamp:Date.now()};p(a)}const c={id:`thinking-${Date.now()}`,role:"assistant",content:[{type:"thinking",data:{status:"thinking"}}],timestamp:Date.now()};p(c),s||W?.(e);let d=!1;try{let a=$;if(!a){const n=await h({user_id:i,site:o,channel_code:m,real_user_id:l});if(n.success)a=n.sessionId,E(a);else throw new Error("Failed to create session")}if(await U({message:e,user_id:i,session_id:a,context:{cartId:G,accessToken:V,real_user_id:l}},n=>{j(n),n.event==="error"&&n.data.type==="validation_error"&&(d=!0,M())}),d&&!s){console.log("[LiveChatWidget] Session expired (validation_error), creating new session and retrying...");const n=A.filter(z=>z.id!==c.id);C(n);const f=await h({user_id:i,site:o,channel_code:m,real_user_id:l});if(f.success)E(f.sessionId),await O(e,!0);else throw new Error("Failed to recreate session after expiration")}}catch(a){console.error("[LiveChatWidget] Failed to send message:",a),g?.(a);const F=a?.type==="GoogleRecaptchaError";let n,f;F?(n="Your session has expired. Please refresh the page and try again.",f="RECAPTCHA_ERROR"):d?(n="Your session has expired. We tried to reconnect but failed. Please try again.",f="SESSION_EXPIRED"):(n="Failed to send message. Please check your network connection and try again.",f="NETWORK_ERROR");const z=A.filter(Te=>Te.id!==c.id);C([...z,{id:`error-${Date.now()}`,role:"system",content:[{type:"error",data:{message:n,code:f}}],timestamp:Date.now()}])}},[k,i,$,o,m,l,G,V,A,Q,p,C,h,U,j,E,M,W,g]);w.useEffect(()=>{b.current=O},[O]);const Ee=y(()=>{_&&!J?N(!0):S()},[_,J,S]),Me=y(()=>{we(!0),N(!1),se.set(L,"true",{expires:365}),S()},[S,L]),Pe=y(()=>{N(!1)},[]);return je(Ze,{children:[_&&R(Fe,{open:K,config:_,onAgree:Me,onClose:Pe}),R(qe,{position:oe,onClick:Ee,visible:!v&&!K,iconImageUrl:de}),R(H.Root,{open:v,onOpenChange:t=>t?S():Z(),children:R(H.Portal,{children:R(H.Content,{className:"livechat-window-enter",style:{position:"fixed",zIndex:9998},children:R(Oe,{messages:A,inputValue:k,onInputChange:Q,onSend:()=>O(),onClose:Z,onNewSession:P,title:ce,logoUrl:ie,isSending:xe,rendererRegistry:De,inputPlaceholder:"",onAddToCart:T,showNewSessionButton:ye,bottomTips:Ce})})})})]})};export{lt as LiveChatWidget};
|
|
2
2
|
//# sourceMappingURL=LiveChatWidget.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/components/LiveChatWidget/LiveChatWidget.tsx"],
|
|
4
|
-
"sourcesContent": ["/**\n * LiveChat \u4E3B\u7EC4\u4EF6\n * \u96C6\u6210\u6240\u6709\u5B50\u7EC4\u4EF6\uFF0C\u63D0\u4F9B\u5B8C\u6574\u7684\u804A\u5929\u529F\u80FD\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u4E09\u5C42\u67B6\u6784\u8BBE\u8BA1\n */\n\nimport React, { useEffect, useCallback } from 'react'\nimport * as Dialog from '@radix-ui/react-dialog'\nimport type {\n LiveChatWidgetProps,\n QuickReply,\n Message,\n MessageContent,\n ChatStreamRequest,\n BackendCartData,\n CommonText,\n} from './types'\nimport { DEFAULT_COMMON_TEXT } from './constants'\nimport { ChatBubble } from './components/ChatBubble'\nimport { ChatWindow } from './components/ChatWindow'\nimport { ComplianceDialog } from './components/ComplianceDialog'\nimport { useChatState } from './hooks/useChatState'\nimport { useChatAPI } from './hooks/useChatAPI'\nimport { MessageRendererRegistry } from './utils/messageRenderers'\nimport { sanitizeInput } from './utils/validation'\nimport { transformProducts } from './utils/productTransformers.js'\nimport { transformCartData } from './utils/cartTransformers.js'\nimport Cookies from 'js-cookie'\nimport {\n TextBlock,\n ProductCard,\n ProductList,\n ProductComparisonRenderer,\n PolicyBlock,\n createQuickRepliesRenderer,\n ThinkingBlock,\n ErrorBlock,\n FAQListRenderer,\n PromotionListRenderer,\n CartCard,\n} from './components/MessageContent/index.js'\n\n/**\n * LiveChat \u804A\u5929\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u6C14\u6CE1\u5F39\u7A97\u804A\u5929\u754C\u9762\n * - SSE \u6D41\u5F0F\u6D88\u606F\u63A5\u6536\n * - \u4F1A\u8BDD\u7BA1\u7406\uFF08userId, sessionId\uFF09\n * - \u5386\u53F2\u6D88\u606F\u52A0\u8F7D\n * - \u591A\u79CD\u6D88\u606F\u7C7B\u578B\u6E32\u67D3\n * - \u81EA\u5B9A\u4E49\u6269\u5C55\u673A\u5236\n * - reCAPTCHA v3 \u5B89\u5168\u9632\u62A4\n *\n * \u67B6\u6784\uFF1A\n * - UI Layer: ChatBubble, ChatWindow, MessageList, etc.\n * - Logic Layer: useChatState, useChatAPI, useSession\n * - Core Layer: MessageRendererRegistry, \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\n *\n * @example\n * ```tsx\n * // \u57FA\u7840\u4F7F\u7528\uFF08\u4F7F\u7528\u9ED8\u8BA4\u4F4D\u7F6E\uFF09\n * <LiveChatWidget\n * apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n * site=\"www.eufy.com\"\n * welcomeMessage=\"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\"\n * onMessageSend={(msg) => console.log('Sent:', msg)}\n * />\n *\n * // \u4F7F\u7528\u81EA\u5B9A\u4E49\u4F4D\u7F6E\n * <LiveChatWidget\n * apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n * site=\"www.eufy.com\"\n * position={{ bottom: \"20px\", right: \"30px\" }}\n * onMessageSend={(msg) => console.log('Sent:', msg)}\n * />\n *\n * // \u542F\u7528 reCAPTCHA v3 \u5B89\u5168\u9632\u62A4\uFF08\u63D0\u4F9B sitekey \u5373\u81EA\u52A8\u542F\u7528\uFF09\n * <LiveChatWidget\n * apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n * site=\"www.eufy.com\"\n * recaptchaSitekey=\"6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14\"\n * recaptchaAction=\"livechat\"\n * />\n *\n * // \u4F7F\u7528\u81EA\u5B9A\u4E49 headers \u548C reCAPTCHA\n * <LiveChatWidget\n * apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n * site=\"www.eufy.com\"\n * headers={{\n * \"Authorization\": \"Bearer your-token\",\n * \"X-Custom-Header\": \"value\"\n * }}\n * recaptchaSitekey=\"your-site-key\"\n * />\n * ```\n */\nexport const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({\n apiBaseUrl,\n headers,\n recaptchaSitekey,\n recaptchaAction,\n site,\n channelCode,\n loginUserId,\n cartId,\n accessToken,\n position,\n welcomeMessage,\n quickReplies,\n customRenderers,\n logoUrl,\n title,\n chatBubbleIcon,\n open,\n onOpenChange,\n onOpen,\n onClose,\n onMessageSend,\n onError,\n onTextMessage,\n onProductList,\n onPromotionList,\n onAddToCart,\n onCart,\n showNewSessionButton,\n commonText,\n productCardRender,\n bottomTips,\n complianceConfig,\n}) => {\n // \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u72B6\u6001\n // \u4ECE Cookie \u8BFB\u53D6\u7528\u6237\u662F\u5426\u5DF2\u540C\u610F\uFF08\u6709\u6548\u671F 365 \u5929\uFF09\n const cookieName = complianceConfig?.cookieName || 'livechat_compliance_agreed'\n const [showComplianceDialog, setShowComplianceDialog] = React.useState(false)\n const [hasAgreedCompliance, setHasAgreedCompliance] = React.useState(() => {\n // \u521D\u59CB\u5316\u65F6\u68C0\u67E5 Cookie\n return complianceConfig ? Cookies.get(cookieName) !== undefined : true\n })\n\n // \u5408\u5E76\u9ED8\u8BA4\u6587\u6848\u548C\u81EA\u5B9A\u4E49\u6587\u6848\n const mergedText: Required<CommonText> = React.useMemo(\n () => ({\n ...DEFAULT_COMMON_TEXT,\n ...commonText,\n }),\n [commonText]\n )\n\n // \u72B6\u6001\u7BA1\u7406\n const chatState = useChatState({\n welcomeMessage,\n site,\n open,\n onOpenChange,\n onOpen,\n onClose,\n onMessageSend,\n onError,\n onTextMessage,\n onProductList,\n onPromotionList,\n onAddToCart,\n onCart,\n productCardRender,\n })\n\n const {\n messages,\n isOpen,\n userId,\n sessionId,\n inputValue,\n isStreaming,\n openChat,\n closeChat,\n setInputValue,\n addMessage,\n setMessages,\n clearMessages,\n handleSSEEvent,\n saveSession,\n clearSession,\n } = chatState\n\n // API \u8C03\u7528\n const { sendMessageStream, createSession } = useChatAPI({\n apiBaseUrl,\n headers,\n recaptchaConfig: {\n needRecaptcha: !!recaptchaSitekey, // \u6839\u636E sitekey \u81EA\u52A8\u5224\u65AD\u662F\u5426\u542F\u7528\n recaptchaSitekey,\n recaptchaAction,\n },\n onError,\n })\n\n // \u4F7F\u7528 ref \u5B58\u50A8\u6700\u65B0\u7684 handleSendMessage\uFF0C\u907F\u514D\u5FAA\u73AF\u4F9D\u8D56\n const handleSendMessageRef = React.useRef<(_message?: string) => Promise<void>>(async (_message?: string) => {})\n\n // \u6D88\u606F\u6E32\u67D3\u5668\u6CE8\u518C\u8868\n const rendererRegistry = React.useMemo(() => {\n const registry = new MessageRendererRegistry()\n\n // \u6CE8\u518C\u9ED8\u8BA4\u6E32\u67D3\u5668\n registry.register('text', TextBlock)\n registry.register('product_card', ProductCard)\n registry.register('product_list', ProductList)\n registry.register('product_comparison', ProductComparisonRenderer)\n registry.register('policy', PolicyBlock)\n registry.register('thinking', ThinkingBlock)\n registry.register('error', ErrorBlock)\n registry.register('faq_list', FAQListRenderer)\n registry.register('promotion_list', PromotionListRenderer)\n registry.register('cart', CartCard)\n\n // \u6CE8\u518C\u5FEB\u6377\u56DE\u590D\u6E32\u67D3\u5668\uFF08\u5E26\u56DE\u8C03\uFF09\n const quickRepliesRenderer = createQuickRepliesRenderer((reply: QuickReply) => {\n // \u4F7F\u7528 ref \u8C03\u7528\u6700\u65B0\u7684 handleSendMessage\n handleSendMessageRef.current(reply.value)\n })\n registry.register('quick_replies', quickRepliesRenderer)\n\n // \u6CE8\u518C\u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\n if (customRenderers) {\n registry.registerMany(customRenderers)\n }\n\n return registry\n }, [customRenderers])\n\n /**\n * T043: \u6253\u5F00\u804A\u5929\u7A97\u53E3\u65F6\u521D\u59CB\u5316\u4F1A\u8BDD\n * \u4F7F\u7528 API v2.0.0 \u7684\u7EDF\u4E00\u63A5\u53E3\uFF1A\n * - \u5982\u679C\u6CA1\u6709 sessionId\uFF0C\u521B\u5EFA\u65B0\u4F1A\u8BDD\n * - \u5982\u679C\u6709 sessionId\uFF0C\u6062\u590D\u4F1A\u8BDD\u5E76\u52A0\u8F7D\u5386\u53F2\u6D88\u606F\n */\n useEffect(() => {\n if (!isOpen || !userId) return\n\n const currentSessionId = sessionId\n\n if (!currentSessionId) {\n // \u6CA1\u6709\u4F1A\u8BDD\uFF0C\u521B\u5EFA\u65B0\u4F1A\u8BDD\n handleCreateNewSession()\n } else {\n // \u6709\u4F1A\u8BDD\uFF0C\u5C1D\u8BD5\u6062\u590D\u4F1A\u8BDD\u548C\u5386\u53F2\u6D88\u606F\n handleResumeSession(currentSessionId)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isOpen, userId])\n\n /**\n * \u89C4\u8303\u5316\u6D88\u606F\u683C\u5F0F\uFF08\u786E\u4FDD content \u662F\u6570\u7EC4\uFF09\n * \u540E\u7AEF\u8FD4\u56DE\u683C\u5F0F\uFF1A\n * - content: \u5B57\u7B26\u4E32\uFF08\u6587\u672C\u5185\u5BB9\uFF09\n * - structuredContent: \u6570\u7EC4\uFF08\u7ED3\u6784\u5316\u5185\u5BB9\uFF0C\u5982\u4EA7\u54C1\u5217\u8868\u3001\u653F\u7B56\u7B49\uFF09\n * \u9700\u8981\u5408\u5E76\u4E3A\u7EDF\u4E00\u7684 content \u6570\u7EC4\u683C\u5F0F\n */\n const normalizeMessage = useCallback(\n (message: any): Message => {\n // \u5982\u679C content \u5DF2\u7ECF\u662F\u6570\u7EC4\uFF0C\u76F4\u63A5\u8FD4\u56DE\n if (Array.isArray(message.content)) {\n return message as Message\n }\n\n const contentBlocks: MessageContent[] = []\n\n // \u5904\u7406\u6587\u672C\u5185\u5BB9\n if (typeof message.content === 'string' && message.content.trim()) {\n contentBlocks.push({\n type: 'text',\n text: message.content,\n })\n }\n\n // \u5904\u7406\u7ED3\u6784\u5316\u5185\u5BB9\n // \u5386\u53F2\u6D88\u606F\u683C\u5F0F: structured_content: [{type, data}]\n const structuredData = message.structuredContent || message.structured_content\n\n if (Array.isArray(structuredData)) {\n structuredData.forEach((block: any) => {\n if (block.type === 'product_list' && Array.isArray(block.data)) {\n // \u8F6C\u6362\u4EA7\u54C1\u5217\u8868\u6570\u636E\u7ED3\u6784 - data \u76F4\u63A5\u662F\u4EA7\u54C1\u6570\u7EC4\n contentBlocks.push({\n type: 'product_list',\n data: {\n products: transformProducts(block.data, site),\n title: undefined, // \u5386\u53F2\u6D88\u606F\u4E0D\u5305\u542B title\n commonText: mergedText,\n },\n })\n } else if (block.type === 'quick_replies' && block.data?.replies) {\n contentBlocks.push({\n type: 'quick_replies',\n data: {\n replies: block.data.replies,\n },\n })\n } else if (block.type === 'policy' && block.data?.title && block.data?.content) {\n contentBlocks.push({\n type: 'policy',\n data: {\n title: block.data.title,\n content: block.data.content,\n },\n })\n } else if (block.type === 'product_comparison' && block.data?.products && block.data?.dimensions) {\n // \u8F6C\u6362\u4EA7\u54C1\u5BF9\u6BD4\u6570\u636E\u7ED3\u6784\u5E76\u6CE8\u5165 onAddToCart \u56DE\u8C03\n contentBlocks.push({\n type: 'product_comparison',\n data: {\n products: transformProducts(block.data.products, site),\n dimensions: block.data.dimensions,\n onAddToCart: onAddToCart,\n commonText: mergedText,\n },\n })\n } else if (block.type === 'faq_list' && block.data?.found !== undefined) {\n // FAQ \u5217\u8868\u5361\u7247 - \u76F4\u63A5\u4F7F\u7528\u540E\u7AEF\u6570\u636E\n contentBlocks.push({\n type: 'faq_list',\n data: block.data,\n })\n } else if (block.type === 'promotion_list' && block.data?.found !== undefined) {\n // \u4FC3\u9500\u6D3B\u52A8\u5217\u8868\u5361\u7247 - \u76F4\u63A5\u4F7F\u7528\u540E\u7AEF\u6570\u636E\n contentBlocks.push({\n type: 'promotion_list',\n data: {\n ...block.data,\n commonText: mergedText,\n },\n })\n } else if (block.type === 'cart' && block.data?.id !== undefined) {\n // \u8D2D\u7269\u8F66\u5361\u7247 - \u8F6C\u6362\u540E\u7AEF\u6570\u636E\u683C\u5F0F\u5E76\u6CE8\u5165 onCart \u56DE\u8C03\n const transformedData = transformCartData(block.data as BackendCartData)\n contentBlocks.push({\n type: 'cart',\n data: {\n ...transformedData,\n onCart: onCart,\n commonText: mergedText,\n },\n })\n } else {\n // \u5176\u4ED6\u7C7B\u578B\u76F4\u63A5\u6DFB\u52A0\n contentBlocks.push(block)\n }\n })\n }\n\n // \u5982\u679C\u6CA1\u6709\u4EFB\u4F55\u5185\u5BB9\u5757\uFF0C\u8FD4\u56DE\u7A7A\u6587\u672C\u5757\n if (contentBlocks.length === 0) {\n contentBlocks.push({\n type: 'text',\n text: '',\n })\n }\n\n return {\n ...message,\n content: contentBlocks,\n } as Message\n },\n [site, onCart, onAddToCart, mergedText]\n )\n\n /**\n * \u521B\u5EFA\u65B0\u4F1A\u8BDD\n */\n const handleCreateNewSession = useCallback(async () => {\n if (!userId) return\n\n try {\n const response = await createSession({\n user_id: userId,\n site: site,\n channel_code: channelCode,\n real_user_id: loginUserId,\n })\n\n if (response.success) {\n // \u4FDD\u5B58\u65B0\u4F1A\u8BDD ID\n saveSession(response.sessionId)\n\n // \u6E05\u7A7A\u6D88\u606F\u5217\u8868\n clearMessages()\n\n // \u4F7F\u7528\u540E\u7AEF\u8FD4\u56DE\u7684\u6B22\u8FCE\u6D88\u606F\uFF0C\u5982\u679C\u6CA1\u6709\u5219\u4F7F\u7528 props \u4E2D\u7684\u9ED8\u8BA4\u503C\n const messageText = response.welcomeMessage || welcomeMessage\n\n if (messageText) {\n const welcomeContent: MessageContent[] = [{ type: 'text', text: messageText }]\n\n // \u4F7F\u7528\u540E\u7AEF\u8FD4\u56DE\u7684\u5FEB\u6377\u95EE\u9898\uFF0C\u5982\u679C\u6CA1\u6709\u5219\u4F7F\u7528 props \u4E2D\u7684\u5FEB\u6377\u56DE\u590D\n const questions = response.quickQuestions\n if (questions && questions.length > 0) {\n // \u5C06\u540E\u7AEF\u7684 quickQuestions (\u5B57\u7B26\u4E32\u6570\u7EC4) \u8F6C\u6362\u4E3A QuickReply \u683C\u5F0F\n const quickRepliesFromBackend = questions.map((question, index) => ({\n id: `quick-${index}`,\n label: question,\n value: question,\n }))\n\n welcomeContent.push({\n type: 'quick_replies',\n data: {\n replies: quickRepliesFromBackend,\n },\n })\n } else if (quickReplies && quickReplies.length > 0) {\n // \u5982\u679C\u540E\u7AEF\u6CA1\u6709\u8FD4\u56DE\uFF0C\u4F7F\u7528 props \u4E2D\u7684\u5FEB\u6377\u56DE\u590D\n welcomeContent.push({\n type: 'quick_replies',\n data: {\n replies: quickReplies,\n },\n })\n }\n\n addMessage({\n id: `welcome-${Date.now()}`,\n role: 'assistant',\n content: welcomeContent,\n timestamp: Date.now(),\n })\n }\n }\n } catch (error) {\n console.error('[LiveChatWidget] Failed to create new session:', error)\n onError?.(error as Error)\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: 'Failed to create session. Please refresh the page and try again.',\n code: 'SESSION_CREATE_ERROR',\n },\n },\n ],\n timestamp: Date.now(),\n })\n }\n }, [\n userId,\n site,\n channelCode,\n loginUserId,\n createSession,\n saveSession,\n clearMessages,\n welcomeMessage,\n quickReplies,\n addMessage,\n onError,\n ])\n\n /**\n * \u6062\u590D\u4F1A\u8BDD\u548C\u5386\u53F2\u6D88\u606F\uFF08\u4F7F\u7528\u65B0\u7684 API v2.0.0\uFF09\n */\n const handleResumeSession = useCallback(\n async (existingSessionId: string) => {\n try {\n const response = await createSession({\n user_id: userId,\n session_id: existingSessionId,\n site: site,\n channel_code: channelCode,\n real_user_id: loginUserId,\n })\n\n if (response.success && response.resumed) {\n // \u4F1A\u8BDD\u6062\u590D\u6210\u529F\n\n // \u51C6\u5907\u6B22\u8FCE\u6D88\u606F\uFF08\u65E0\u8BBA\u662F\u5426\u6709\u5386\u53F2\u6D88\u606F\u90FD\u9700\u8981\uFF09\n const messageText = response.welcomeMessage || welcomeMessage\n const welcomeContent: MessageContent[] = messageText ? [{ type: 'text', text: messageText }] : []\n\n // \u6DFB\u52A0\u5FEB\u6377\u56DE\u590D\u5230\u6B22\u8FCE\u6D88\u606F\n const questions = response.quickQuestions\n if (questions && questions.length > 0) {\n const quickRepliesFromBackend = questions.map((question, index) => ({\n id: `quick-${index}`,\n label: question,\n value: question,\n }))\n\n welcomeContent.push({\n type: 'quick_replies',\n data: {\n replies: quickRepliesFromBackend,\n },\n })\n } else if (quickReplies && quickReplies.length > 0) {\n welcomeContent.push({\n type: 'quick_replies',\n data: {\n replies: quickReplies,\n },\n })\n }\n\n if (response.messages && response.messages.length > 0) {\n // \u6709\u5386\u53F2\u6D88\u606F\uFF0C\u89C4\u8303\u5316\u5E76\u52A0\u8F7D\uFF08\u8FC7\u6EE4\u6389 null/undefined \u503C\u548C\u7A7A\u5185\u5BB9\u6D88\u606F\uFF09\n const normalizedMessages = response.messages\n .filter((msg: any) => msg != null)\n .map(normalizeMessage)\n .filter((msg: Message) => msg.content && msg.content.length > 0 && !(msg.content.length === 1 && msg.content[0].type === 'text' && !msg.content[0].text))\n\n // \u5982\u679C\u6709\u6B22\u8FCE\u6D88\u606F\uFF0C\u5C06\u5176\u6DFB\u52A0\u5230\u5386\u53F2\u6D88\u606F\u7684\u5F00\u5934\n if (welcomeContent.length > 0) {\n const welcomeMsg: Message = {\n id: `welcome-${Date.now()}`,\n role: 'assistant',\n content: welcomeContent,\n timestamp: Date.now(),\n }\n setMessages([welcomeMsg, ...normalizedMessages])\n } else {\n setMessages(normalizedMessages)\n }\n } else {\n // \u6CA1\u6709\u5386\u53F2\u6D88\u606F\uFF0C\u4EC5\u663E\u793A\u6B22\u8FCE\u6D88\u606F\n clearMessages()\n\n if (welcomeContent.length > 0) {\n addMessage({\n id: `welcome-${Date.now()}`,\n role: 'assistant',\n content: welcomeContent,\n timestamp: Date.now(),\n })\n }\n }\n } else if (!response.resumed) {\n // \u4F1A\u8BDD\u65E0\u6548\u6216\u8FC7\u671F\uFF0C\u521B\u5EFA\u65B0\u4F1A\u8BDD\n clearSession()\n handleCreateNewSession()\n }\n } catch (error) {\n console.error('[LiveChatWidget] Failed to resume session:', error)\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: 'Failed to resume session. Creating a new session...',\n code: 'SESSION_RESUME_ERROR',\n },\n },\n ],\n timestamp: Date.now(),\n })\n\n // \u6062\u590D\u5931\u8D25\uFF0C\u6E05\u7A7A\u4F1A\u8BDD\u5E76\u521B\u5EFA\u65B0\u7684\n clearSession()\n handleCreateNewSession()\n }\n },\n [\n userId,\n site,\n channelCode,\n loginUserId,\n createSession,\n setMessages,\n clearSession,\n clearMessages,\n normalizeMessage,\n handleCreateNewSession,\n welcomeMessage,\n quickReplies,\n addMessage,\n ]\n )\n\n /**\n * \u53D1\u9001\u6D88\u606F\n */\n const handleSendMessage = useCallback(\n async (message?: string, isRetry: boolean = false) => {\n const textToSend = message || inputValue.trim()\n\n if (!textToSend) return\n\n // \u6E05\u7A7A\u8F93\u5165\u6846\uFF08\u4EC5\u5728\u975E\u91CD\u8BD5\u65F6\uFF09\n if (!message && !isRetry) {\n setInputValue('')\n }\n\n // \u8F93\u5165\u9A8C\u8BC1\n const sanitized = sanitizeInput(textToSend)\n if (!sanitized) {\n onError?.(new Error('Invalid message'))\n return\n }\n\n // \u6DFB\u52A0\u7528\u6237\u6D88\u606F\u5230\u754C\u9762\uFF08\u4EC5\u5728\u975E\u91CD\u8BD5\u65F6\uFF09\n if (!isRetry) {\n const userMessage = {\n id: `user-${Date.now()}`,\n role: 'user' as const,\n content: [{ type: 'text' as const, text: sanitized }],\n timestamp: Date.now(),\n }\n addMessage(userMessage)\n }\n\n // \u7ACB\u5373\u6DFB\u52A0\u601D\u8003\u72B6\u6001\u6D88\u606F\uFF0C\u63D0\u5347\u7528\u6237\u4F53\u9A8C\n const thinkingMessage = {\n id: `thinking-${Date.now()}`,\n role: 'assistant' as const,\n content: [{ type: 'thinking' as const, data: { status: 'thinking' } }],\n timestamp: Date.now(),\n }\n addMessage(thinkingMessage)\n\n // \u89E6\u53D1\u6D88\u606F\u53D1\u9001\u56DE\u8C03\uFF08\u4EC5\u5728\u975E\u91CD\u8BD5\u65F6\uFF09\n if (!isRetry) {\n onMessageSend?.(sanitized)\n }\n\n // \u6807\u8BB0\u662F\u5426\u68C0\u6D4B\u5230\u4F1A\u8BDD\u8FC7\u671F\n let sessionExpiredDetected = false\n\n try {\n // \u786E\u4FDD\u6709 sessionId\uFF0C\u5982\u679C\u6CA1\u6709\u5219\u5148\u521B\u5EFA\u4F1A\u8BDD\n let currentSessionId = sessionId\n if (!currentSessionId) {\n // \u6CA1\u6709\u4F1A\u8BDD\uFF0C\u521B\u5EFA\u65B0\u4F1A\u8BDD\n const response = await createSession({\n user_id: userId,\n site: site,\n channel_code: channelCode,\n real_user_id: loginUserId,\n })\n if (response.success) {\n currentSessionId = response.sessionId\n saveSession(currentSessionId)\n } else {\n throw new Error('Failed to create session')\n }\n }\n\n // \u6784\u5EFA\u8BF7\u6C42\u53C2\u6570\uFF08session_id \u73B0\u5728\u662F\u5FC5\u586B\u7684\uFF09\n const requestPayload: ChatStreamRequest = {\n message: sanitized,\n user_id: userId,\n session_id: currentSessionId,\n context: {\n cartId: cartId,\n accessToken: accessToken,\n real_user_id: loginUserId,\n },\n }\n\n // \u53D1\u9001\u6D88\u606F\u5230\u540E\u7AEF\n await sendMessageStream(requestPayload, event => {\n // \u5904\u7406 SSE \u4E8B\u4EF6\n handleSSEEvent(event)\n\n // \u7279\u6B8A\u5904\u7406\uFF1A\u4F1A\u8BDD\u8FC7\u671F\uFF08error \u4E8B\u4EF6\u4E14 type \u4E3A validation_error\uFF09\n if (event.event === 'error' && event.data.type === 'validation_error') {\n sessionExpiredDetected = true\n clearSession()\n }\n })\n\n // \u5982\u679C\u68C0\u6D4B\u5230\u4F1A\u8BDD\u8FC7\u671F\u4E14\u4E0D\u662F\u91CD\u8BD5\uFF0C\u81EA\u52A8\u521B\u5EFA\u65B0\u4F1A\u8BDD\u5E76\u91CD\u8BD5\n if (sessionExpiredDetected && !isRetry) {\n console.log('[LiveChatWidget] Session expired (validation_error), creating new session and retrying...')\n\n // \u79FB\u9664 thinking \u6D88\u606F\n const messagesWithoutThinking = messages.filter(msg => msg.id !== thinkingMessage.id)\n setMessages(messagesWithoutThinking)\n\n // \u521B\u5EFA\u65B0\u4F1A\u8BDD\n const response = await createSession({\n user_id: userId,\n site: site,\n channel_code: channelCode,\n real_user_id: loginUserId,\n })\n\n if (response.success) {\n saveSession(response.sessionId)\n // \u91CD\u8BD5\u53D1\u9001\u6D88\u606F\n await handleSendMessage(sanitized, true)\n } else {\n throw new Error('Failed to recreate session after expiration')\n }\n }\n } catch (error) {\n console.error('[LiveChatWidget] Failed to send message:', error)\n onError?.(error as Error)\n\n // \u79FB\u9664\u521A\u624D\u6DFB\u52A0\u7684 thinking \u6D88\u606F\uFF0C\u5E76\u6DFB\u52A0\u9519\u8BEF\u6D88\u606F\n const messagesWithoutThinking = messages.filter(msg => msg.id !== thinkingMessage.id)\n setMessages([\n ...messagesWithoutThinking,\n {\n id: `error-${Date.now()}`,\n role: 'system',\n content: [\n {\n type: 'error',\n data: {\n message: sessionExpiredDetected\n ? 'Your session has expired. We tried to reconnect but failed. Please try again.'\n : 'Failed to send message. Please check your network connection and try again.',\n code: sessionExpiredDetected ? 'SESSION_EXPIRED' : 'NETWORK_ERROR',\n },\n },\n ],\n timestamp: Date.now(),\n },\n ])\n }\n },\n [\n inputValue,\n userId,\n sessionId,\n site,\n channelCode,\n loginUserId,\n cartId,\n accessToken,\n messages,\n setInputValue,\n addMessage,\n setMessages,\n createSession,\n sendMessageStream,\n handleSSEEvent,\n saveSession,\n clearSession,\n onMessageSend,\n onError,\n ]\n )\n\n // \u66F4\u65B0 ref \u4EE5\u4FDD\u6301\u6700\u65B0\u7684 handleSendMessage\n React.useEffect(() => {\n handleSendMessageRef.current = handleSendMessage\n }, [handleSendMessage])\n\n /**\n * \u5904\u7406\u6C14\u6CE1\u6309\u94AE\u70B9\u51FB\n * \u5982\u679C\u914D\u7F6E\u4E86\u6CD5\u89C4\u534F\u8BAE\u4E14\u7528\u6237\u672A\u540C\u610F\uFF0C\u5148\u663E\u793A\u6CD5\u89C4\u5F39\u7A97\n * \u5426\u5219\u76F4\u63A5\u6253\u5F00\u804A\u5929\u7A97\u53E3\n */\n const handleBubbleClick = useCallback(() => {\n if (complianceConfig && !hasAgreedCompliance) {\n // \u663E\u793A\u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\n setShowComplianceDialog(true)\n } else {\n // \u76F4\u63A5\u6253\u5F00\u804A\u5929\u7A97\u53E3\n openChat()\n }\n }, [complianceConfig, hasAgreedCompliance, openChat])\n\n /**\n * \u5904\u7406\u7528\u6237\u540C\u610F\u6CD5\u89C4\u534F\u8BAE\n */\n const handleComplianceAgree = useCallback(() => {\n // \u8BBE\u7F6E\u540C\u610F\u72B6\u6001\n setHasAgreedCompliance(true)\n setShowComplianceDialog(false)\n\n // \u4FDD\u5B58\u5230 Cookie\uFF08\u6709\u6548\u671F 365 \u5929\uFF09\n Cookies.set(cookieName, 'true', { expires: 365 })\n\n // \u540C\u610F\u540E\u7ACB\u5373\u6253\u5F00\u804A\u5929\u7A97\u53E3\n openChat()\n }, [openChat, cookieName])\n\n /**\n * \u5904\u7406\u6CD5\u89C4\u5F39\u7A97\u5173\u95ED\n */\n const handleComplianceClose = useCallback(() => {\n setShowComplianceDialog(false)\n }, [])\n\n return (\n <>\n {/* \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97 */}\n {complianceConfig && (\n <ComplianceDialog\n open={showComplianceDialog}\n config={complianceConfig}\n onAgree={handleComplianceAgree}\n onClose={handleComplianceClose}\n />\n )}\n\n {/* \u6C14\u6CE1\u6309\u94AE */}\n <ChatBubble\n position={position}\n onClick={handleBubbleClick}\n visible={!isOpen && !showComplianceDialog}\n iconImageUrl={chatBubbleIcon}\n />\n\n {/* \u804A\u5929\u7A97\u53E3\uFF08\u4F7F\u7528 Radix UI Dialog\uFF09 */}\n <Dialog.Root open={isOpen} onOpenChange={open => (open ? openChat() : closeChat())}>\n <Dialog.Portal>\n <Dialog.Content\n className=\"livechat-window-enter\"\n style={{\n position: 'fixed',\n zIndex: 9998,\n }}\n >\n <ChatWindow\n messages={messages}\n inputValue={inputValue}\n onInputChange={setInputValue}\n onSend={() => handleSendMessage()}\n onClose={closeChat}\n onNewSession={handleCreateNewSession}\n title={title}\n logoUrl={logoUrl}\n isSending={isStreaming}\n rendererRegistry={rendererRegistry}\n inputPlaceholder=\"\"\n onAddToCart={onAddToCart}\n showNewSessionButton={showNewSessionButton}\n bottomTips={bottomTips}\n />\n </Dialog.Content>\n </Dialog.Portal>\n </Dialog.Root>\n </>\n )\n}\n"],
|
|
5
|
-
"mappings": "
|
|
6
|
-
"names": ["Fragment", "jsx", "jsxs", "React", "useEffect", "useCallback", "Dialog", "DEFAULT_COMMON_TEXT", "ChatBubble", "ChatWindow", "ComplianceDialog", "useChatState", "useChatAPI", "MessageRendererRegistry", "sanitizeInput", "transformProducts", "transformCartData", "Cookies", "TextBlock", "ProductCard", "ProductList", "ProductComparisonRenderer", "PolicyBlock", "createQuickRepliesRenderer", "ThinkingBlock", "ErrorBlock", "FAQListRenderer", "PromotionListRenderer", "CartCard", "LiveChatWidget", "apiBaseUrl", "headers", "recaptchaSitekey", "recaptchaAction", "site", "channelCode", "loginUserId", "cartId", "accessToken", "position", "welcomeMessage", "quickReplies", "customRenderers", "logoUrl", "title", "chatBubbleIcon", "open", "onOpenChange", "onOpen", "onClose", "onMessageSend", "onError", "onTextMessage", "onProductList", "onPromotionList", "onAddToCart", "onCart", "showNewSessionButton", "commonText", "productCardRender", "bottomTips", "complianceConfig", "cookieName", "showComplianceDialog", "setShowComplianceDialog", "hasAgreedCompliance", "setHasAgreedCompliance", "mergedText", "chatState", "messages", "isOpen", "userId", "sessionId", "inputValue", "isStreaming", "openChat", "closeChat", "setInputValue", "addMessage", "setMessages", "clearMessages", "handleSSEEvent", "saveSession", "clearSession", "sendMessageStream", "createSession", "handleSendMessageRef", "_message", "rendererRegistry", "registry", "quickRepliesRenderer", "reply", "currentSessionId", "handleResumeSession", "handleCreateNewSession", "normalizeMessage", "message", "contentBlocks", "structuredData", "block", "transformedData", "response", "messageText", "welcomeContent", "questions", "quickRepliesFromBackend", "question", "index", "error", "existingSessionId", "normalizedMessages", "msg", "welcomeMsg", "handleSendMessage", "isRetry", "textToSend", "sanitized", "userMessage", "thinkingMessage", "sessionExpiredDetected", "event", "messagesWithoutThinking", "handleBubbleClick", "handleComplianceAgree", "handleComplianceClose"]
|
|
4
|
+
"sourcesContent": ["/**\n * LiveChat \u4E3B\u7EC4\u4EF6\n * \u96C6\u6210\u6240\u6709\u5B50\u7EC4\u4EF6\uFF0C\u63D0\u4F9B\u5B8C\u6574\u7684\u804A\u5929\u529F\u80FD\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u4E09\u5C42\u67B6\u6784\u8BBE\u8BA1\n */\n\nimport React, { useEffect, useCallback } from 'react'\nimport * as Dialog from '@radix-ui/react-dialog'\nimport type {\n LiveChatWidgetProps,\n QuickReply,\n Message,\n MessageContent,\n ChatStreamRequest,\n BackendCartData,\n CommonText,\n} from './types'\nimport { DEFAULT_COMMON_TEXT } from './constants'\nimport { ChatBubble } from './components/ChatBubble'\nimport { ChatWindow } from './components/ChatWindow'\nimport { ComplianceDialog } from './components/ComplianceDialog'\nimport { useChatState } from './hooks/useChatState'\nimport { useChatAPI } from './hooks/useChatAPI'\nimport { MessageRendererRegistry } from './utils/messageRenderers'\nimport { sanitizeInput } from './utils/validation'\nimport { transformProducts } from './utils/productTransformers.js'\nimport { transformCartData } from './utils/cartTransformers.js'\nimport Cookies from 'js-cookie'\nimport {\n TextBlock,\n ProductCard,\n ProductList,\n ProductComparisonRenderer,\n PolicyBlock,\n createQuickRepliesRenderer,\n ThinkingBlock,\n ErrorBlock,\n FAQListRenderer,\n PromotionListRenderer,\n CartCard,\n} from './components/MessageContent/index.js'\n\n/**\n * LiveChat \u804A\u5929\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u6C14\u6CE1\u5F39\u7A97\u804A\u5929\u754C\u9762\n * - SSE \u6D41\u5F0F\u6D88\u606F\u63A5\u6536\n * - \u4F1A\u8BDD\u7BA1\u7406\uFF08userId, sessionId\uFF09\n * - \u5386\u53F2\u6D88\u606F\u52A0\u8F7D\n * - \u591A\u79CD\u6D88\u606F\u7C7B\u578B\u6E32\u67D3\n * - \u81EA\u5B9A\u4E49\u6269\u5C55\u673A\u5236\n * - reCAPTCHA v3 \u5B89\u5168\u9632\u62A4\n *\n * \u67B6\u6784\uFF1A\n * - UI Layer: ChatBubble, ChatWindow, MessageList, etc.\n * - Logic Layer: useChatState, useChatAPI, useSession\n * - Core Layer: MessageRendererRegistry, \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\n *\n * @example\n * ```tsx\n * // \u57FA\u7840\u4F7F\u7528\uFF08\u4F7F\u7528\u9ED8\u8BA4\u4F4D\u7F6E\uFF09\n * <LiveChatWidget\n * apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n * site=\"www.eufy.com\"\n * welcomeMessage=\"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\"\n * onMessageSend={(msg) => console.log('Sent:', msg)}\n * />\n *\n * // \u4F7F\u7528\u81EA\u5B9A\u4E49\u4F4D\u7F6E\n * <LiveChatWidget\n * apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n * site=\"www.eufy.com\"\n * position={{ bottom: \"20px\", right: \"30px\" }}\n * onMessageSend={(msg) => console.log('Sent:', msg)}\n * />\n *\n * // \u542F\u7528 reCAPTCHA v3 \u5B89\u5168\u9632\u62A4\uFF08\u63D0\u4F9B sitekey \u5373\u81EA\u52A8\u542F\u7528\uFF09\n * <LiveChatWidget\n * apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n * site=\"www.eufy.com\"\n * recaptchaSitekey=\"6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14\"\n * recaptchaAction=\"livechat\"\n * />\n *\n * // \u4F7F\u7528\u81EA\u5B9A\u4E49 headers \u548C reCAPTCHA\n * <LiveChatWidget\n * apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n * site=\"www.eufy.com\"\n * headers={{\n * \"Authorization\": \"Bearer your-token\",\n * \"X-Custom-Header\": \"value\"\n * }}\n * recaptchaSitekey=\"your-site-key\"\n * />\n * ```\n */\nexport const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({\n apiBaseUrl,\n headers,\n recaptchaSitekey,\n recaptchaAction,\n site,\n channelCode,\n loginUserId,\n cartId,\n accessToken,\n position,\n welcomeMessage,\n quickReplies,\n customRenderers,\n logoUrl,\n title,\n chatBubbleIcon,\n open,\n onOpenChange,\n onOpen,\n onClose,\n onMessageSend,\n onError,\n onTextMessage,\n onProductList,\n onPromotionList,\n onAddToCart,\n onCart,\n showNewSessionButton,\n commonText,\n productCardRender,\n bottomTips,\n complianceConfig,\n}) => {\n // \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u72B6\u6001\n // \u4ECE Cookie \u8BFB\u53D6\u7528\u6237\u662F\u5426\u5DF2\u540C\u610F\uFF08\u6709\u6548\u671F 365 \u5929\uFF09\n const cookieName = complianceConfig?.cookieName || 'livechat_compliance_agreed'\n const [showComplianceDialog, setShowComplianceDialog] = React.useState(false)\n const [hasAgreedCompliance, setHasAgreedCompliance] = React.useState(() => {\n // \u521D\u59CB\u5316\u65F6\u68C0\u67E5 Cookie\n return complianceConfig ? Cookies.get(cookieName) !== undefined : true\n })\n\n // \u5408\u5E76\u9ED8\u8BA4\u6587\u6848\u548C\u81EA\u5B9A\u4E49\u6587\u6848\n const mergedText: Required<CommonText> = React.useMemo(\n () => ({\n ...DEFAULT_COMMON_TEXT,\n ...commonText,\n }),\n [commonText]\n )\n\n // \u72B6\u6001\u7BA1\u7406\n const chatState = useChatState({\n welcomeMessage,\n site,\n open,\n onOpenChange,\n onOpen,\n onClose,\n onMessageSend,\n onError,\n onTextMessage,\n onProductList,\n onPromotionList,\n onAddToCart,\n onCart,\n productCardRender,\n })\n\n const {\n messages,\n isOpen,\n userId,\n sessionId,\n inputValue,\n isStreaming,\n openChat,\n closeChat,\n setInputValue,\n addMessage,\n setMessages,\n clearMessages,\n handleSSEEvent,\n saveSession,\n clearSession,\n } = chatState\n\n // API \u8C03\u7528\n const { sendMessageStream, createSession } = useChatAPI({\n apiBaseUrl,\n headers,\n recaptchaConfig: {\n needRecaptcha: !!recaptchaSitekey, // \u6839\u636E sitekey \u81EA\u52A8\u5224\u65AD\u662F\u5426\u542F\u7528\n recaptchaSitekey,\n recaptchaAction,\n },\n onError,\n })\n\n // \u4F7F\u7528 ref \u5B58\u50A8\u6700\u65B0\u7684 handleSendMessage\uFF0C\u907F\u514D\u5FAA\u73AF\u4F9D\u8D56\n const handleSendMessageRef = React.useRef<(_message?: string) => Promise<void>>(async (_message?: string) => {})\n\n // \u6D88\u606F\u6E32\u67D3\u5668\u6CE8\u518C\u8868\n const rendererRegistry = React.useMemo(() => {\n const registry = new MessageRendererRegistry()\n\n // \u6CE8\u518C\u9ED8\u8BA4\u6E32\u67D3\u5668\n registry.register('text', TextBlock)\n registry.register('product_card', ProductCard)\n registry.register('product_list', ProductList)\n registry.register('product_comparison', ProductComparisonRenderer)\n registry.register('policy', PolicyBlock)\n registry.register('thinking', ThinkingBlock)\n registry.register('error', ErrorBlock)\n registry.register('faq_list', FAQListRenderer)\n registry.register('promotion_list', PromotionListRenderer)\n registry.register('cart', CartCard)\n\n // \u6CE8\u518C\u5FEB\u6377\u56DE\u590D\u6E32\u67D3\u5668\uFF08\u5E26\u56DE\u8C03\uFF09\n const quickRepliesRenderer = createQuickRepliesRenderer((reply: QuickReply) => {\n // \u4F7F\u7528 ref \u8C03\u7528\u6700\u65B0\u7684 handleSendMessage\n handleSendMessageRef.current(reply.value)\n })\n registry.register('quick_replies', quickRepliesRenderer)\n\n // \u6CE8\u518C\u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\n if (customRenderers) {\n registry.registerMany(customRenderers)\n }\n\n return registry\n }, [customRenderers])\n\n /**\n * T043: \u6253\u5F00\u804A\u5929\u7A97\u53E3\u65F6\u521D\u59CB\u5316\u4F1A\u8BDD\n * \u4F7F\u7528 API v2.0.0 \u7684\u7EDF\u4E00\u63A5\u53E3\uFF1A\n * - \u5982\u679C\u6CA1\u6709 sessionId\uFF0C\u521B\u5EFA\u65B0\u4F1A\u8BDD\n * - \u5982\u679C\u6709 sessionId\uFF0C\u6062\u590D\u4F1A\u8BDD\u5E76\u52A0\u8F7D\u5386\u53F2\u6D88\u606F\n */\n useEffect(() => {\n if (!isOpen || !userId) return\n\n const currentSessionId = sessionId\n\n if (!currentSessionId) {\n // \u6CA1\u6709\u4F1A\u8BDD\uFF0C\u521B\u5EFA\u65B0\u4F1A\u8BDD\n handleCreateNewSession()\n } else {\n // \u6709\u4F1A\u8BDD\uFF0C\u5C1D\u8BD5\u6062\u590D\u4F1A\u8BDD\u548C\u5386\u53F2\u6D88\u606F\n handleResumeSession(currentSessionId)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isOpen, userId])\n\n /**\n * \u89C4\u8303\u5316\u6D88\u606F\u683C\u5F0F\uFF08\u786E\u4FDD content \u662F\u6570\u7EC4\uFF09\n * \u540E\u7AEF\u8FD4\u56DE\u683C\u5F0F\uFF1A\n * - content: \u5B57\u7B26\u4E32\uFF08\u6587\u672C\u5185\u5BB9\uFF09\n * - structuredContent: \u6570\u7EC4\uFF08\u7ED3\u6784\u5316\u5185\u5BB9\uFF0C\u5982\u4EA7\u54C1\u5217\u8868\u3001\u653F\u7B56\u7B49\uFF09\n * \u9700\u8981\u5408\u5E76\u4E3A\u7EDF\u4E00\u7684 content \u6570\u7EC4\u683C\u5F0F\n */\n const normalizeMessage = useCallback(\n (message: any): Message => {\n // \u5982\u679C content \u5DF2\u7ECF\u662F\u6570\u7EC4\uFF0C\u76F4\u63A5\u8FD4\u56DE\n if (Array.isArray(message.content)) {\n return message as Message\n }\n\n const contentBlocks: MessageContent[] = []\n\n // \u5904\u7406\u6587\u672C\u5185\u5BB9\n if (typeof message.content === 'string' && message.content.trim()) {\n contentBlocks.push({\n type: 'text',\n text: message.content,\n })\n }\n\n // \u5904\u7406\u7ED3\u6784\u5316\u5185\u5BB9\n // \u5386\u53F2\u6D88\u606F\u683C\u5F0F: structured_content: [{type, data}]\n const structuredData = message.structuredContent || message.structured_content\n\n if (Array.isArray(structuredData)) {\n structuredData.forEach((block: any) => {\n if (block.type === 'product_list' && Array.isArray(block.data)) {\n // \u8F6C\u6362\u4EA7\u54C1\u5217\u8868\u6570\u636E\u7ED3\u6784 - data \u76F4\u63A5\u662F\u4EA7\u54C1\u6570\u7EC4\n contentBlocks.push({\n type: 'product_list',\n data: {\n products: transformProducts(block.data, site),\n title: undefined, // \u5386\u53F2\u6D88\u606F\u4E0D\u5305\u542B title\n commonText: mergedText,\n },\n })\n } else if (block.type === 'quick_replies' && block.data?.replies) {\n contentBlocks.push({\n type: 'quick_replies',\n data: {\n replies: block.data.replies,\n },\n })\n } else if (block.type === 'policy' && block.data?.title && block.data?.content) {\n contentBlocks.push({\n type: 'policy',\n data: {\n title: block.data.title,\n content: block.data.content,\n },\n })\n } else if (block.type === 'product_comparison' && block.data?.products && block.data?.dimensions) {\n // \u8F6C\u6362\u4EA7\u54C1\u5BF9\u6BD4\u6570\u636E\u7ED3\u6784\u5E76\u6CE8\u5165 onAddToCart \u56DE\u8C03\n contentBlocks.push({\n type: 'product_comparison',\n data: {\n products: transformProducts(block.data.products, site),\n dimensions: block.data.dimensions,\n onAddToCart: onAddToCart,\n commonText: mergedText,\n },\n })\n } else if (block.type === 'faq_list' && block.data?.found !== undefined) {\n // FAQ \u5217\u8868\u5361\u7247 - \u76F4\u63A5\u4F7F\u7528\u540E\u7AEF\u6570\u636E\n contentBlocks.push({\n type: 'faq_list',\n data: block.data,\n })\n } else if (block.type === 'promotion_list' && block.data?.found !== undefined) {\n // \u4FC3\u9500\u6D3B\u52A8\u5217\u8868\u5361\u7247 - \u76F4\u63A5\u4F7F\u7528\u540E\u7AEF\u6570\u636E\n contentBlocks.push({\n type: 'promotion_list',\n data: {\n ...block.data,\n commonText: mergedText,\n },\n })\n } else if (block.type === 'cart' && block.data?.id !== undefined) {\n // \u8D2D\u7269\u8F66\u5361\u7247 - \u8F6C\u6362\u540E\u7AEF\u6570\u636E\u683C\u5F0F\u5E76\u6CE8\u5165 onCart \u56DE\u8C03\n const transformedData = transformCartData(block.data as BackendCartData)\n contentBlocks.push({\n type: 'cart',\n data: {\n ...transformedData,\n onCart: onCart,\n commonText: mergedText,\n },\n })\n } else {\n // \u5176\u4ED6\u7C7B\u578B\u76F4\u63A5\u6DFB\u52A0\n contentBlocks.push(block)\n }\n })\n }\n\n // \u5982\u679C\u6CA1\u6709\u4EFB\u4F55\u5185\u5BB9\u5757\uFF0C\u8FD4\u56DE\u7A7A\u6587\u672C\u5757\n if (contentBlocks.length === 0) {\n contentBlocks.push({\n type: 'text',\n text: '',\n })\n }\n\n return {\n ...message,\n content: contentBlocks,\n } as Message\n },\n [site, onCart, onAddToCart, mergedText]\n )\n\n /**\n * \u521B\u5EFA\u65B0\u4F1A\u8BDD\n */\n const handleCreateNewSession = useCallback(async () => {\n if (!userId) return\n\n try {\n const response = await createSession({\n user_id: userId,\n site: site,\n channel_code: channelCode,\n real_user_id: loginUserId,\n })\n\n if (response.success) {\n // \u4FDD\u5B58\u65B0\u4F1A\u8BDD ID\n saveSession(response.sessionId)\n\n // \u6E05\u7A7A\u6D88\u606F\u5217\u8868\n clearMessages()\n\n // \u4F7F\u7528\u540E\u7AEF\u8FD4\u56DE\u7684\u6B22\u8FCE\u6D88\u606F\uFF0C\u5982\u679C\u6CA1\u6709\u5219\u4F7F\u7528 props \u4E2D\u7684\u9ED8\u8BA4\u503C\n const messageText = response.welcomeMessage || welcomeMessage\n\n if (messageText) {\n const welcomeContent: MessageContent[] = [{ type: 'text', text: messageText }]\n\n // \u4F7F\u7528\u540E\u7AEF\u8FD4\u56DE\u7684\u5FEB\u6377\u95EE\u9898\uFF0C\u5982\u679C\u6CA1\u6709\u5219\u4F7F\u7528 props \u4E2D\u7684\u5FEB\u6377\u56DE\u590D\n const questions = response.quickQuestions\n if (questions && questions.length > 0) {\n // \u5C06\u540E\u7AEF\u7684 quickQuestions (\u5B57\u7B26\u4E32\u6570\u7EC4) \u8F6C\u6362\u4E3A QuickReply \u683C\u5F0F\n const quickRepliesFromBackend = questions.map((question, index) => ({\n id: `quick-${index}`,\n label: question,\n value: question,\n }))\n\n welcomeContent.push({\n type: 'quick_replies',\n data: {\n replies: quickRepliesFromBackend,\n },\n })\n } else if (quickReplies && quickReplies.length > 0) {\n // \u5982\u679C\u540E\u7AEF\u6CA1\u6709\u8FD4\u56DE\uFF0C\u4F7F\u7528 props \u4E2D\u7684\u5FEB\u6377\u56DE\u590D\n welcomeContent.push({\n type: 'quick_replies',\n data: {\n replies: quickReplies,\n },\n })\n }\n\n addMessage({\n id: `welcome-${Date.now()}`,\n role: 'assistant',\n content: welcomeContent,\n timestamp: Date.now(),\n })\n }\n }\n } catch (error) {\n console.error('[LiveChatWidget] Failed to create new session:', error)\n onError?.(error as Error)\n\n // Check if it's a reCAPTCHA error\n const isRecaptcha = (error as any)?.type === 'GoogleRecaptchaError'\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: isRecaptcha\n ? 'Your session has expired. Please refresh the page and try again.'\n : 'Failed to create session. Please refresh the page and try again.',\n code: isRecaptcha ? 'RECAPTCHA_ERROR' : 'SESSION_CREATE_ERROR',\n },\n },\n ],\n timestamp: Date.now(),\n })\n }\n }, [\n userId,\n site,\n channelCode,\n loginUserId,\n createSession,\n saveSession,\n clearMessages,\n welcomeMessage,\n quickReplies,\n addMessage,\n onError,\n ])\n\n /**\n * \u6062\u590D\u4F1A\u8BDD\u548C\u5386\u53F2\u6D88\u606F\uFF08\u4F7F\u7528\u65B0\u7684 API v2.0.0\uFF09\n */\n const handleResumeSession = useCallback(\n async (existingSessionId: string) => {\n try {\n const response = await createSession({\n user_id: userId,\n session_id: existingSessionId,\n site: site,\n channel_code: channelCode,\n real_user_id: loginUserId,\n })\n\n if (response.success && response.resumed) {\n // \u4F1A\u8BDD\u6062\u590D\u6210\u529F\n\n // \u51C6\u5907\u6B22\u8FCE\u6D88\u606F\uFF08\u65E0\u8BBA\u662F\u5426\u6709\u5386\u53F2\u6D88\u606F\u90FD\u9700\u8981\uFF09\n const messageText = response.welcomeMessage || welcomeMessage\n const welcomeContent: MessageContent[] = messageText ? [{ type: 'text', text: messageText }] : []\n\n // \u6DFB\u52A0\u5FEB\u6377\u56DE\u590D\u5230\u6B22\u8FCE\u6D88\u606F\n const questions = response.quickQuestions\n if (questions && questions.length > 0) {\n const quickRepliesFromBackend = questions.map((question, index) => ({\n id: `quick-${index}`,\n label: question,\n value: question,\n }))\n\n welcomeContent.push({\n type: 'quick_replies',\n data: {\n replies: quickRepliesFromBackend,\n },\n })\n } else if (quickReplies && quickReplies.length > 0) {\n welcomeContent.push({\n type: 'quick_replies',\n data: {\n replies: quickReplies,\n },\n })\n }\n\n if (response.messages && response.messages.length > 0) {\n // \u6709\u5386\u53F2\u6D88\u606F\uFF0C\u89C4\u8303\u5316\u5E76\u52A0\u8F7D\uFF08\u8FC7\u6EE4\u6389 null/undefined \u503C\u548C\u7A7A\u5185\u5BB9\u6D88\u606F\uFF09\n const normalizedMessages = response.messages\n .filter((msg: any) => msg != null)\n .map(normalizeMessage)\n .filter((msg: Message) => msg.content && msg.content.length > 0 && !(msg.content.length === 1 && msg.content[0].type === 'text' && !msg.content[0].text))\n\n // \u5982\u679C\u6709\u6B22\u8FCE\u6D88\u606F\uFF0C\u5C06\u5176\u6DFB\u52A0\u5230\u5386\u53F2\u6D88\u606F\u7684\u5F00\u5934\n if (welcomeContent.length > 0) {\n const welcomeMsg: Message = {\n id: `welcome-${Date.now()}`,\n role: 'assistant',\n content: welcomeContent,\n timestamp: Date.now(),\n }\n setMessages([welcomeMsg, ...normalizedMessages])\n } else {\n setMessages(normalizedMessages)\n }\n } else {\n // \u6CA1\u6709\u5386\u53F2\u6D88\u606F\uFF0C\u4EC5\u663E\u793A\u6B22\u8FCE\u6D88\u606F\n clearMessages()\n\n if (welcomeContent.length > 0) {\n addMessage({\n id: `welcome-${Date.now()}`,\n role: 'assistant',\n content: welcomeContent,\n timestamp: Date.now(),\n })\n }\n }\n } else if (!response.resumed) {\n // \u4F1A\u8BDD\u65E0\u6548\u6216\u8FC7\u671F\uFF0C\u521B\u5EFA\u65B0\u4F1A\u8BDD\n clearSession()\n handleCreateNewSession()\n }\n } catch (error) {\n console.error('[LiveChatWidget] Failed to resume session:', error)\n\n // Check if it's a reCAPTCHA error\n const isRecaptcha = (error as any)?.type === 'GoogleRecaptchaError'\n\n if (isRecaptcha) {\n // reCAPTCHA error - show refresh page message, don't retry\n addMessage({\n id: `error-${Date.now()}`,\n role: 'system',\n content: [\n {\n type: 'error',\n data: {\n message: 'Your session has expired. Please refresh the page and try again.',\n code: 'RECAPTCHA_ERROR',\n },\n },\n ],\n timestamp: Date.now(),\n })\n } else {\n // Other errors - show message and try to create new session\n addMessage({\n id: `error-${Date.now()}`,\n role: 'system',\n content: [\n {\n type: 'error',\n data: {\n message: 'Failed to resume session. Creating a new session...',\n code: 'SESSION_RESUME_ERROR',\n },\n },\n ],\n timestamp: Date.now(),\n })\n\n // \u6062\u590D\u5931\u8D25\uFF0C\u6E05\u7A7A\u4F1A\u8BDD\u5E76\u521B\u5EFA\u65B0\u7684\n clearSession()\n handleCreateNewSession()\n }\n }\n },\n [\n userId,\n site,\n channelCode,\n loginUserId,\n createSession,\n setMessages,\n clearSession,\n clearMessages,\n normalizeMessage,\n handleCreateNewSession,\n welcomeMessage,\n quickReplies,\n addMessage,\n ]\n )\n\n /**\n * \u53D1\u9001\u6D88\u606F\n */\n const handleSendMessage = useCallback(\n async (message?: string, isRetry: boolean = false) => {\n const textToSend = message || inputValue.trim()\n\n if (!textToSend) return\n\n // \u6E05\u7A7A\u8F93\u5165\u6846\uFF08\u4EC5\u5728\u975E\u91CD\u8BD5\u65F6\uFF09\n if (!message && !isRetry) {\n setInputValue('')\n }\n\n // \u8F93\u5165\u9A8C\u8BC1\n const sanitized = sanitizeInput(textToSend)\n if (!sanitized) {\n onError?.(new Error('Invalid message'))\n return\n }\n\n // \u6DFB\u52A0\u7528\u6237\u6D88\u606F\u5230\u754C\u9762\uFF08\u4EC5\u5728\u975E\u91CD\u8BD5\u65F6\uFF09\n if (!isRetry) {\n const userMessage = {\n id: `user-${Date.now()}`,\n role: 'user' as const,\n content: [{ type: 'text' as const, text: sanitized }],\n timestamp: Date.now(),\n }\n addMessage(userMessage)\n }\n\n // \u7ACB\u5373\u6DFB\u52A0\u601D\u8003\u72B6\u6001\u6D88\u606F\uFF0C\u63D0\u5347\u7528\u6237\u4F53\u9A8C\n const thinkingMessage = {\n id: `thinking-${Date.now()}`,\n role: 'assistant' as const,\n content: [{ type: 'thinking' as const, data: { status: 'thinking' } }],\n timestamp: Date.now(),\n }\n addMessage(thinkingMessage)\n\n // \u89E6\u53D1\u6D88\u606F\u53D1\u9001\u56DE\u8C03\uFF08\u4EC5\u5728\u975E\u91CD\u8BD5\u65F6\uFF09\n if (!isRetry) {\n onMessageSend?.(sanitized)\n }\n\n // \u6807\u8BB0\u662F\u5426\u68C0\u6D4B\u5230\u4F1A\u8BDD\u8FC7\u671F\n let sessionExpiredDetected = false\n\n try {\n // \u786E\u4FDD\u6709 sessionId\uFF0C\u5982\u679C\u6CA1\u6709\u5219\u5148\u521B\u5EFA\u4F1A\u8BDD\n let currentSessionId = sessionId\n if (!currentSessionId) {\n // \u6CA1\u6709\u4F1A\u8BDD\uFF0C\u521B\u5EFA\u65B0\u4F1A\u8BDD\n const response = await createSession({\n user_id: userId,\n site: site,\n channel_code: channelCode,\n real_user_id: loginUserId,\n })\n if (response.success) {\n currentSessionId = response.sessionId\n saveSession(currentSessionId)\n } else {\n throw new Error('Failed to create session')\n }\n }\n\n // \u6784\u5EFA\u8BF7\u6C42\u53C2\u6570\uFF08session_id \u73B0\u5728\u662F\u5FC5\u586B\u7684\uFF09\n const requestPayload: ChatStreamRequest = {\n message: sanitized,\n user_id: userId,\n session_id: currentSessionId,\n context: {\n cartId: cartId,\n accessToken: accessToken,\n real_user_id: loginUserId,\n },\n }\n\n // \u53D1\u9001\u6D88\u606F\u5230\u540E\u7AEF\n await sendMessageStream(requestPayload, event => {\n // \u5904\u7406 SSE \u4E8B\u4EF6\n handleSSEEvent(event)\n\n // \u7279\u6B8A\u5904\u7406\uFF1A\u4F1A\u8BDD\u8FC7\u671F\uFF08error \u4E8B\u4EF6\u4E14 type \u4E3A validation_error\uFF09\n if (event.event === 'error' && event.data.type === 'validation_error') {\n sessionExpiredDetected = true\n clearSession()\n }\n })\n\n // \u5982\u679C\u68C0\u6D4B\u5230\u4F1A\u8BDD\u8FC7\u671F\u4E14\u4E0D\u662F\u91CD\u8BD5\uFF0C\u81EA\u52A8\u521B\u5EFA\u65B0\u4F1A\u8BDD\u5E76\u91CD\u8BD5\n if (sessionExpiredDetected && !isRetry) {\n console.log('[LiveChatWidget] Session expired (validation_error), creating new session and retrying...')\n\n // \u79FB\u9664 thinking \u6D88\u606F\n const messagesWithoutThinking = messages.filter(msg => msg.id !== thinkingMessage.id)\n setMessages(messagesWithoutThinking)\n\n // \u521B\u5EFA\u65B0\u4F1A\u8BDD\n const response = await createSession({\n user_id: userId,\n site: site,\n channel_code: channelCode,\n real_user_id: loginUserId,\n })\n\n if (response.success) {\n saveSession(response.sessionId)\n // \u91CD\u8BD5\u53D1\u9001\u6D88\u606F\n await handleSendMessage(sanitized, true)\n } else {\n throw new Error('Failed to recreate session after expiration')\n }\n }\n } catch (error) {\n console.error('[LiveChatWidget] Failed to send message:', error)\n onError?.(error as Error)\n\n // Check if it's a reCAPTCHA error\n const isRecaptcha = (error as any)?.type === 'GoogleRecaptchaError'\n\n // Determine error message based on error type\n let errorMessage: string\n let errorCode: string\n\n if (isRecaptcha) {\n errorMessage = 'Your session has expired. Please refresh the page and try again.'\n errorCode = 'RECAPTCHA_ERROR'\n } else if (sessionExpiredDetected) {\n errorMessage = 'Your session has expired. We tried to reconnect but failed. Please try again.'\n errorCode = 'SESSION_EXPIRED'\n } else {\n errorMessage = 'Failed to send message. Please check your network connection and try again.'\n errorCode = 'NETWORK_ERROR'\n }\n\n // \u79FB\u9664\u521A\u624D\u6DFB\u52A0\u7684 thinking \u6D88\u606F\uFF0C\u5E76\u6DFB\u52A0\u9519\u8BEF\u6D88\u606F\n const messagesWithoutThinking = messages.filter(msg => msg.id !== thinkingMessage.id)\n setMessages([\n ...messagesWithoutThinking,\n {\n id: `error-${Date.now()}`,\n role: 'system',\n content: [\n {\n type: 'error',\n data: {\n message: errorMessage,\n code: errorCode,\n },\n },\n ],\n timestamp: Date.now(),\n },\n ])\n }\n },\n [\n inputValue,\n userId,\n sessionId,\n site,\n channelCode,\n loginUserId,\n cartId,\n accessToken,\n messages,\n setInputValue,\n addMessage,\n setMessages,\n createSession,\n sendMessageStream,\n handleSSEEvent,\n saveSession,\n clearSession,\n onMessageSend,\n onError,\n ]\n )\n\n // \u66F4\u65B0 ref \u4EE5\u4FDD\u6301\u6700\u65B0\u7684 handleSendMessage\n React.useEffect(() => {\n handleSendMessageRef.current = handleSendMessage\n }, [handleSendMessage])\n\n /**\n * \u5904\u7406\u6C14\u6CE1\u6309\u94AE\u70B9\u51FB\n * \u5982\u679C\u914D\u7F6E\u4E86\u6CD5\u89C4\u534F\u8BAE\u4E14\u7528\u6237\u672A\u540C\u610F\uFF0C\u5148\u663E\u793A\u6CD5\u89C4\u5F39\u7A97\n * \u5426\u5219\u76F4\u63A5\u6253\u5F00\u804A\u5929\u7A97\u53E3\n */\n const handleBubbleClick = useCallback(() => {\n if (complianceConfig && !hasAgreedCompliance) {\n // \u663E\u793A\u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\n setShowComplianceDialog(true)\n } else {\n // \u76F4\u63A5\u6253\u5F00\u804A\u5929\u7A97\u53E3\n openChat()\n }\n }, [complianceConfig, hasAgreedCompliance, openChat])\n\n /**\n * \u5904\u7406\u7528\u6237\u540C\u610F\u6CD5\u89C4\u534F\u8BAE\n */\n const handleComplianceAgree = useCallback(() => {\n // \u8BBE\u7F6E\u540C\u610F\u72B6\u6001\n setHasAgreedCompliance(true)\n setShowComplianceDialog(false)\n\n // \u4FDD\u5B58\u5230 Cookie\uFF08\u6709\u6548\u671F 365 \u5929\uFF09\n Cookies.set(cookieName, 'true', { expires: 365 })\n\n // \u540C\u610F\u540E\u7ACB\u5373\u6253\u5F00\u804A\u5929\u7A97\u53E3\n openChat()\n }, [openChat, cookieName])\n\n /**\n * \u5904\u7406\u6CD5\u89C4\u5F39\u7A97\u5173\u95ED\n */\n const handleComplianceClose = useCallback(() => {\n setShowComplianceDialog(false)\n }, [])\n\n return (\n <>\n {/* \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97 */}\n {complianceConfig && (\n <ComplianceDialog\n open={showComplianceDialog}\n config={complianceConfig}\n onAgree={handleComplianceAgree}\n onClose={handleComplianceClose}\n />\n )}\n\n {/* \u6C14\u6CE1\u6309\u94AE */}\n <ChatBubble\n position={position}\n onClick={handleBubbleClick}\n visible={!isOpen && !showComplianceDialog}\n iconImageUrl={chatBubbleIcon}\n />\n\n {/* \u804A\u5929\u7A97\u53E3\uFF08\u4F7F\u7528 Radix UI Dialog\uFF09 */}\n <Dialog.Root open={isOpen} onOpenChange={open => (open ? openChat() : closeChat())}>\n <Dialog.Portal>\n <Dialog.Content\n className=\"livechat-window-enter\"\n style={{\n position: 'fixed',\n zIndex: 9998,\n }}\n >\n <ChatWindow\n messages={messages}\n inputValue={inputValue}\n onInputChange={setInputValue}\n onSend={() => handleSendMessage()}\n onClose={closeChat}\n onNewSession={handleCreateNewSession}\n title={title}\n logoUrl={logoUrl}\n isSending={isStreaming}\n rendererRegistry={rendererRegistry}\n inputPlaceholder=\"\"\n onAddToCart={onAddToCart}\n showNewSessionButton={showNewSessionButton}\n bottomTips={bottomTips}\n />\n </Dialog.Content>\n </Dialog.Portal>\n </Dialog.Root>\n </>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["Fragment", "jsx", "jsxs", "React", "useEffect", "useCallback", "Dialog", "DEFAULT_COMMON_TEXT", "ChatBubble", "ChatWindow", "ComplianceDialog", "useChatState", "useChatAPI", "MessageRendererRegistry", "sanitizeInput", "transformProducts", "transformCartData", "Cookies", "TextBlock", "ProductCard", "ProductList", "ProductComparisonRenderer", "PolicyBlock", "createQuickRepliesRenderer", "ThinkingBlock", "ErrorBlock", "FAQListRenderer", "PromotionListRenderer", "CartCard", "LiveChatWidget", "apiBaseUrl", "headers", "recaptchaSitekey", "recaptchaAction", "site", "channelCode", "loginUserId", "cartId", "accessToken", "position", "welcomeMessage", "quickReplies", "customRenderers", "logoUrl", "title", "chatBubbleIcon", "open", "onOpenChange", "onOpen", "onClose", "onMessageSend", "onError", "onTextMessage", "onProductList", "onPromotionList", "onAddToCart", "onCart", "showNewSessionButton", "commonText", "productCardRender", "bottomTips", "complianceConfig", "cookieName", "showComplianceDialog", "setShowComplianceDialog", "hasAgreedCompliance", "setHasAgreedCompliance", "mergedText", "chatState", "messages", "isOpen", "userId", "sessionId", "inputValue", "isStreaming", "openChat", "closeChat", "setInputValue", "addMessage", "setMessages", "clearMessages", "handleSSEEvent", "saveSession", "clearSession", "sendMessageStream", "createSession", "handleSendMessageRef", "_message", "rendererRegistry", "registry", "quickRepliesRenderer", "reply", "currentSessionId", "handleResumeSession", "handleCreateNewSession", "normalizeMessage", "message", "contentBlocks", "structuredData", "block", "transformedData", "response", "messageText", "welcomeContent", "questions", "quickRepliesFromBackend", "question", "index", "error", "isRecaptcha", "existingSessionId", "normalizedMessages", "msg", "welcomeMsg", "handleSendMessage", "isRetry", "textToSend", "sanitized", "userMessage", "thinkingMessage", "sessionExpiredDetected", "event", "messagesWithoutThinking", "errorMessage", "errorCode", "handleBubbleClick", "handleComplianceAgree", "handleComplianceClose"]
|
|
7
7
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{fetcher as
|
|
2
|
-
`);d=
|
|
1
|
+
import{fetcher as u}from"../utils/fetcher";async function g(i,p,n,a,e){const t=await u({url:`${i}/api/chat/stream`,method:"POST",headers:{Accept:"text/event-stream",...a},body:p,needRecaptcha:e?.needRecaptcha,recaptchaSitekey:e?.recaptchaSitekey,recaptchaAction:e?.recaptchaAction||"send_message"});if(!t.ok){const r=await t.json().catch(()=>null),c=new Error(r?.message||`HTTP ${t.status}`);throw c.type=r?.type,c}const s=t.body?.getReader();if(!s)throw new Error("Response body is not readable");const y=new TextDecoder;let d="",h=null;try{for(;;){const{done:r,value:c}=await s.read();if(r)break;d+=y.decode(c,{stream:!0});const S=d.split(`
|
|
2
|
+
`);d=S.pop()||"";for(const R of S){const o=R.trim();if(o===""){h=null;continue}if(o.startsWith("event:"))h=o.substring(6).trim();else if(o.startsWith("data:")){const m=o.substring(5).trim();try{const l=JSON.parse(m);n({event:h||"message",data:l})}catch(l){console.error("[LiveChat API] Failed to parse SSE data:",m,l),n({event:"error",data:{message:"Failed to parse SSE data",code:"PARSE_ERROR"}})}}}}}catch(r){throw console.error("[LiveChat API] SSE stream error:",r),r}finally{s.releaseLock()}}async function f(i,p,n,a){const e=await u({url:`${i}/api/chat/new-session`,method:"POST",headers:n,body:p,needRecaptcha:a?.needRecaptcha,recaptchaSitekey:a?.recaptchaSitekey,recaptchaAction:a?.recaptchaAction||"new_session"});if(!e.ok){const t=await e.json().catch(()=>null),s=new Error(t?.message||`HTTP ${e.status}`);throw s.type=t?.type,s}return e.json()}export{f as createNewSession,g as sendMessage};
|
|
3
3
|
//# sourceMappingURL=chat.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/components/LiveChatWidget/api/chat.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * LiveChat API \u5C42\n * \u5904\u7406 SSE \u6D41\u5F0F\u901A\u4FE1\u548C HTTP \u8BF7\u6C42\n * \u57FA\u4E8E specs/livechat-widget/contracts/*.yaml\n */\n\nimport type { ChatStreamRequest, SSEEvent, NewSessionRequest, NewSessionResponse, ErrorResponse } from '../types'\nimport { fetcher } from '../utils/fetcher'\n\n/**\n * Recaptcha \u914D\u7F6E\n */\nexport interface RecaptchaConfig {\n /**\n * \u662F\u5426\u542F\u7528 reCAPTCHA\n */\n needRecaptcha?: boolean\n /**\n * reCAPTCHA site key\n */\n recaptchaSitekey?: string\n /**\n * reCAPTCHA action \u540D\u79F0\n */\n recaptchaAction?: string\n}\n\n// ============================================================================\n// SSE \u6D41\u5F0F\u6D88\u606F\u5904\u7406 (T006)\n// ============================================================================\n\n/**\n * \u53D1\u9001\u6D88\u606F\u5E76\u63A5\u6536 SSE \u6D41\u5F0F\u54CD\u5E94\n * @param apiBaseUrl API \u57FA\u7840 URL\n * @param request \u8BF7\u6C42\u53C2\u6570\n * @param onEvent SSE \u4E8B\u4EF6\u56DE\u8C03\n * @param customHeaders \u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\n * @param recaptchaConfig reCAPTCHA \u914D\u7F6E\n */\nexport async function sendMessage(\n apiBaseUrl: string,\n request: ChatStreamRequest,\n onEvent: (event: SSEEvent) => void,\n customHeaders?: Record<string, string>,\n recaptchaConfig?: RecaptchaConfig\n): Promise<void> {\n // \u4F7F\u7528 fetcher \u5904\u7406 reCAPTCHA \u548C\u81EA\u5B9A\u4E49 headers\n const response = await fetcher({\n url: `${apiBaseUrl}/api/chat/stream`,\n method: 'POST',\n headers: {\n Accept: 'text/event-stream',\n ...customHeaders,\n },\n body: request,\n needRecaptcha: recaptchaConfig?.needRecaptcha,\n recaptchaSitekey: recaptchaConfig?.recaptchaSitekey,\n recaptchaAction: recaptchaConfig?.recaptchaAction || 'send_message',\n })\n\n if (!response.ok) {\n
|
|
5
|
-
"mappings": "AAOA,OAAS,WAAAA,MAAe,mBAgCxB,eAAsBC,EACpBC,EACAC,EACAC,EACAC,EACAC,EACe,CAEf,MAAMC,EAAW,MAAMP,EAAQ,CAC7B,IAAK,GAAGE,CAAU,mBAClB,OAAQ,OACR,QAAS,CACP,OAAQ,oBACR,GAAGG,CACL,EACA,KAAMF,EACN,cAAeG,GAAiB,cAChC,iBAAkBA,GAAiB,iBACnC,gBAAiBA,GAAiB,iBAAmB,cACvD,CAAC,EAED,GAAI,CAACC,EAAS,
|
|
6
|
-
"names": ["fetcher", "sendMessage", "apiBaseUrl", "request", "onEvent", "customHeaders", "recaptchaConfig", "response", "reader", "decoder", "buffer", "currentEvent", "done", "value", "lines", "line", "trimmed", "dataStr", "data", "err", "
|
|
4
|
+
"sourcesContent": ["/**\n * LiveChat API \u5C42\n * \u5904\u7406 SSE \u6D41\u5F0F\u901A\u4FE1\u548C HTTP \u8BF7\u6C42\n * \u57FA\u4E8E specs/livechat-widget/contracts/*.yaml\n */\n\nimport type { ChatStreamRequest, SSEEvent, NewSessionRequest, NewSessionResponse, ErrorResponse } from '../types'\nimport { fetcher } from '../utils/fetcher'\n\n/**\n * Recaptcha \u914D\u7F6E\n */\nexport interface RecaptchaConfig {\n /**\n * \u662F\u5426\u542F\u7528 reCAPTCHA\n */\n needRecaptcha?: boolean\n /**\n * reCAPTCHA site key\n */\n recaptchaSitekey?: string\n /**\n * reCAPTCHA action \u540D\u79F0\n */\n recaptchaAction?: string\n}\n\n// ============================================================================\n// SSE \u6D41\u5F0F\u6D88\u606F\u5904\u7406 (T006)\n// ============================================================================\n\n/**\n * \u53D1\u9001\u6D88\u606F\u5E76\u63A5\u6536 SSE \u6D41\u5F0F\u54CD\u5E94\n * @param apiBaseUrl API \u57FA\u7840 URL\n * @param request \u8BF7\u6C42\u53C2\u6570\n * @param onEvent SSE \u4E8B\u4EF6\u56DE\u8C03\n * @param customHeaders \u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\n * @param recaptchaConfig reCAPTCHA \u914D\u7F6E\n */\nexport async function sendMessage(\n apiBaseUrl: string,\n request: ChatStreamRequest,\n onEvent: (event: SSEEvent) => void,\n customHeaders?: Record<string, string>,\n recaptchaConfig?: RecaptchaConfig\n): Promise<void> {\n // \u4F7F\u7528 fetcher \u5904\u7406 reCAPTCHA \u548C\u81EA\u5B9A\u4E49 headers\n const response = await fetcher({\n url: `${apiBaseUrl}/api/chat/stream`,\n method: 'POST',\n headers: {\n Accept: 'text/event-stream',\n ...customHeaders,\n },\n body: request,\n needRecaptcha: recaptchaConfig?.needRecaptcha,\n recaptchaSitekey: recaptchaConfig?.recaptchaSitekey,\n recaptchaAction: recaptchaConfig?.recaptchaAction || 'send_message',\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => null)\n const error = new Error(errorData?.message || `HTTP ${response.status}`) as Error & { type?: string }\n error.type = errorData?.type\n throw error\n }\n\n const reader = response.body?.getReader()\n if (!reader) {\n throw new Error('Response body is not readable')\n }\n\n const decoder = new TextDecoder()\n let buffer = ''\n let currentEvent: string | null = null\n\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n // \u89E3\u7801\u5E76\u8FFD\u52A0\u5230\u7F13\u51B2\u533A\n buffer += decoder.decode(value, { stream: true })\n\n // \u6309\u884C\u5206\u5272\n const lines = buffer.split('\\n')\n\n // \u4FDD\u7559\u6700\u540E\u4E00\u4E2A\u4E0D\u5B8C\u6574\u7684\u884C\n buffer = lines.pop() || ''\n\n for (const line of lines) {\n const trimmed = line.trim()\n\n if (trimmed === '') {\n // \u7A7A\u884C\u8868\u793A\u4E8B\u4EF6\u8FB9\u754C\uFF0C\u91CD\u7F6E\u5F53\u524D\u4E8B\u4EF6\u7C7B\u578B\n currentEvent = null\n continue\n }\n\n if (trimmed.startsWith('event:')) {\n // \u63D0\u53D6\u4E8B\u4EF6\u7C7B\u578B\n currentEvent = trimmed.substring(6).trim()\n } else if (trimmed.startsWith('data:')) {\n // \u63D0\u53D6\u6570\u636E\u5E76\u89E3\u6790 JSON\n const dataStr = trimmed.substring(5).trim()\n try {\n const data = JSON.parse(dataStr)\n onEvent({\n event: (currentEvent as any) || 'message',\n data,\n })\n } catch (err) {\n console.error('[LiveChat API] Failed to parse SSE data:', dataStr, err)\n onEvent({\n event: 'error',\n data: {\n message: 'Failed to parse SSE data',\n code: 'PARSE_ERROR',\n },\n })\n }\n }\n }\n }\n } catch (error) {\n console.error('[LiveChat API] SSE stream error:', error)\n throw error\n } finally {\n reader.releaseLock()\n }\n}\n\n// ============================================================================\n// \u521B\u5EFA/\u6062\u590D\u4F1A\u8BDD (T009)\n// ============================================================================\n\n/**\n * \u521B\u5EFA\u65B0\u4F1A\u8BDD\u6216\u6062\u590D\u73B0\u6709\u4F1A\u8BDD\n *\n * \u4F7F\u7528\u573A\u666F\uFF1A\n * 1. \u521B\u5EFA\u65B0\u4F1A\u8BDD\uFF1A\u4E0D\u4F20 session_id\uFF0C\u8FD4\u56DE\u65B0\u7684 sessionId\n * 2. \u6062\u590D\u4F1A\u8BDD\uFF1A\u4F20\u5165 session_id\uFF0C\u9A8C\u8BC1\u4F1A\u8BDD\u6709\u6548\u6027\u5E76\u8FD4\u56DE\u5386\u53F2\u6D88\u606F\uFF08resumed: true, messages: [...]\uFF09\n *\n * @param apiBaseUrl API \u57FA\u7840 URL\n * @param request \u8BF7\u6C42\u53C2\u6570\n * @param customHeaders \u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\n * @param recaptchaConfig reCAPTCHA \u914D\u7F6E\n * @returns \u4F1A\u8BDD\u4FE1\u606F\uFF0C\u53EF\u80FD\u5305\u542B\u5386\u53F2\u6D88\u606F\n */\nexport async function createNewSession(\n apiBaseUrl: string,\n request: NewSessionRequest,\n customHeaders?: Record<string, string>,\n recaptchaConfig?: RecaptchaConfig\n): Promise<NewSessionResponse> {\n // \u4F7F\u7528 fetcher \u5904\u7406 reCAPTCHA \u548C\u81EA\u5B9A\u4E49 headers\n const response = await fetcher({\n url: `${apiBaseUrl}/api/chat/new-session`,\n method: 'POST',\n headers: customHeaders,\n body: request,\n needRecaptcha: recaptchaConfig?.needRecaptcha,\n recaptchaSitekey: recaptchaConfig?.recaptchaSitekey,\n recaptchaAction: recaptchaConfig?.recaptchaAction || 'new_session',\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => null)\n const error = new Error(errorData?.message || `HTTP ${response.status}`) as Error & { type?: string }\n error.type = errorData?.type\n throw error\n }\n\n return response.json()\n}\n"],
|
|
5
|
+
"mappings": "AAOA,OAAS,WAAAA,MAAe,mBAgCxB,eAAsBC,EACpBC,EACAC,EACAC,EACAC,EACAC,EACe,CAEf,MAAMC,EAAW,MAAMP,EAAQ,CAC7B,IAAK,GAAGE,CAAU,mBAClB,OAAQ,OACR,QAAS,CACP,OAAQ,oBACR,GAAGG,CACL,EACA,KAAMF,EACN,cAAeG,GAAiB,cAChC,iBAAkBA,GAAiB,iBACnC,gBAAiBA,GAAiB,iBAAmB,cACvD,CAAC,EAED,GAAI,CAACC,EAAS,GAAI,CAChB,MAAMC,EAAY,MAAMD,EAAS,KAAK,EAAE,MAAM,IAAM,IAAI,EAClDE,EAAQ,IAAI,MAAMD,GAAW,SAAW,QAAQD,EAAS,MAAM,EAAE,EACvE,MAAAE,EAAM,KAAOD,GAAW,KAClBC,CACR,CAEA,MAAMC,EAASH,EAAS,MAAM,UAAU,EACxC,GAAI,CAACG,EACH,MAAM,IAAI,MAAM,+BAA+B,EAGjD,MAAMC,EAAU,IAAI,YACpB,IAAIC,EAAS,GACTC,EAA8B,KAElC,GAAI,CACF,OAAa,CACX,KAAM,CAAE,KAAAC,EAAM,MAAAC,CAAM,EAAI,MAAML,EAAO,KAAK,EAC1C,GAAII,EAAM,MAGVF,GAAUD,EAAQ,OAAOI,EAAO,CAAE,OAAQ,EAAK,CAAC,EAGhD,MAAMC,EAAQJ,EAAO,MAAM;AAAA,CAAI,EAG/BA,EAASI,EAAM,IAAI,GAAK,GAExB,UAAWC,KAAQD,EAAO,CACxB,MAAME,EAAUD,EAAK,KAAK,EAE1B,GAAIC,IAAY,GAAI,CAElBL,EAAe,KACf,QACF,CAEA,GAAIK,EAAQ,WAAW,QAAQ,EAE7BL,EAAeK,EAAQ,UAAU,CAAC,EAAE,KAAK,UAChCA,EAAQ,WAAW,OAAO,EAAG,CAEtC,MAAMC,EAAUD,EAAQ,UAAU,CAAC,EAAE,KAAK,EAC1C,GAAI,CACF,MAAME,EAAO,KAAK,MAAMD,CAAO,EAC/Bf,EAAQ,CACN,MAAQS,GAAwB,UAChC,KAAAO,CACF,CAAC,CACH,OAASC,EAAK,CACZ,QAAQ,MAAM,2CAA4CF,EAASE,CAAG,EACtEjB,EAAQ,CACN,MAAO,QACP,KAAM,CACJ,QAAS,2BACT,KAAM,aACR,CACF,CAAC,CACH,CACF,CACF,CACF,CACF,OAASK,EAAO,CACd,cAAQ,MAAM,mCAAoCA,CAAK,EACjDA,CACR,QAAE,CACAC,EAAO,YAAY,CACrB,CACF,CAmBA,eAAsBY,EACpBpB,EACAC,EACAE,EACAC,EAC6B,CAE7B,MAAMC,EAAW,MAAMP,EAAQ,CAC7B,IAAK,GAAGE,CAAU,wBAClB,OAAQ,OACR,QAASG,EACT,KAAMF,EACN,cAAeG,GAAiB,cAChC,iBAAkBA,GAAiB,iBACnC,gBAAiBA,GAAiB,iBAAmB,aACvD,CAAC,EAED,GAAI,CAACC,EAAS,GAAI,CAChB,MAAMC,EAAY,MAAMD,EAAS,KAAK,EAAE,MAAM,IAAM,IAAI,EAClDE,EAAQ,IAAI,MAAMD,GAAW,SAAW,QAAQD,EAAS,MAAM,EAAE,EACvE,MAAAE,EAAM,KAAOD,GAAW,KAClBC,CACR,CAEA,OAAOF,EAAS,KAAK,CACvB",
|
|
6
|
+
"names": ["fetcher", "sendMessage", "apiBaseUrl", "request", "onEvent", "customHeaders", "recaptchaConfig", "response", "errorData", "error", "reader", "decoder", "buffer", "currentEvent", "done", "value", "lines", "line", "trimmed", "dataStr", "data", "err", "createNewSession"]
|
|
7
7
|
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import{jsx as e,jsxs as
|
|
1
|
+
import{jsx as e,jsxs as i}from"react/jsx-runtime";import{useState as m,useCallback as l}from"react";import*as n from"@radix-ui/react-dialog";const h=({open:c,config:a,onAgree:r,onClose:s})=>{const[o,p]=m(!0),d=l(()=>{o&&r()},[o,r]),x=l(t=>{t||s()},[s]),g=l(t=>{t.target.tagName==="A"&&t.stopPropagation()},[]);return e(n.Root,{open:c,onOpenChange:x,children:e(n.Portal,{children:i(n.Content,{className:"livechat-compliance-dialog",onInteractOutside:t=>t.preventDefault(),onEscapeKeyDown:t=>t.preventDefault(),children:[e("div",{className:"flex items-center justify-end px-4 pt-4 pb-2 flex-shrink-0 max-md:px-6 max-sm:px-5",children:e(n.Close,{asChild:!0,children:e("button",{type:"button",className:`
|
|
2
2
|
w-6 h-6
|
|
3
3
|
flex items-center justify-center
|
|
4
4
|
border-0 bg-transparent text-gray-500
|
|
5
5
|
cursor-pointer
|
|
6
|
-
`,"aria-label":"\u5173\u95ED",children:
|
|
6
|
+
`,"aria-label":"\u5173\u95ED",children:i("svg",{width:"20",height:"20",className:"text-[#3C3C3B]",viewBox:"0 0 20 20",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[e("line",{x1:"15",y1:"5",x2:"5",y2:"15"}),e("line",{x1:"5",y1:"5",x2:"15",y2:"15"})]})})})}),i("div",{className:"flex-1 overflow-y-auto px-4 pb-4 max-md:px-6 max-md:pb-6 max-sm:px-5 max-sm:pb-5",children:[e(n.Title,{className:`
|
|
7
7
|
text-[18px] font-bold text-[#080A0F]
|
|
8
8
|
mb-1 leading-[1.4] tracking-tight
|
|
9
|
-
`,children:
|
|
9
|
+
`,children:a.title}),e("div",{className:`
|
|
10
10
|
text-[16px] font-bold leading-[1.4] text-[#080A0F]
|
|
11
11
|
tracking-tight
|
|
12
12
|
mb-2
|
|
13
|
-
`,dangerouslySetInnerHTML:{__html:
|
|
13
|
+
`,dangerouslySetInnerHTML:{__html:a.content}}),i("label",{className:`
|
|
14
14
|
flex items-center gap-2 mb-4
|
|
15
15
|
cursor-pointer select-none
|
|
16
|
-
`,onClick:g,children:[e("input",{type:"checkbox",className:"absolute opacity-0 w-0 h-0",checked:o,onChange:t=>
|
|
16
|
+
`,onClick:g,children:[e("input",{type:"checkbox",className:"absolute opacity-0 w-0 h-0",checked:o,onChange:t=>p(t.target.checked)}),e("span",{className:`
|
|
17
17
|
flex-shrink-0 w-4 h-4 mt-0.5
|
|
18
18
|
border-[1px] rounded-[2px] flex items-center justify-center
|
|
19
19
|
transition-all duration-200 ease-in-out
|
|
@@ -22,12 +22,12 @@ import{jsx as e,jsxs as l}from"react/jsx-runtime";import{useState as m,useCallba
|
|
|
22
22
|
flex-1 text-[14px] leading-[1.4] text-[#080A0F]
|
|
23
23
|
tracking-tight font-bold
|
|
24
24
|
[&_a]:underline
|
|
25
|
-
`,dangerouslySetInnerHTML:{__html:
|
|
25
|
+
`,dangerouslySetInnerHTML:{__html:a.checkboxText}})]}),e("button",{type:"button",className:`
|
|
26
26
|
w-full px-5 py-[10px]
|
|
27
27
|
border-0 rounded-[19px]
|
|
28
28
|
text-[14px] leading-[1.2]
|
|
29
29
|
tracking-tighter font-bold
|
|
30
30
|
transition-all duration-200 ease-in-out text-white
|
|
31
31
|
${o?"bg-[#080A0F] cursor-pointer ":"bg-[#767880] cursor-not-allowed"}
|
|
32
|
-
`,onClick:d,disabled:!o,children:
|
|
32
|
+
`,onClick:d,disabled:!o,children:a.agreeButtonText||"Agree"})]})]})})})};export{h as ComplianceDialog};
|
|
33
33
|
//# sourceMappingURL=ComplianceDialog.js.map
|