@anker-in/campaign-ui 0.2.11-beta.34 → 0.2.11-beta.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js +1 -1
- package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.d.ts +51 -0
- package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js +41 -0
- package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js.map +7 -0
- package/dist/cjs/components/LiveChatWidget/index.d.ts +1 -1
- package/dist/cjs/components/LiveChatWidget/index.js +1 -1
- package/dist/cjs/components/LiveChatWidget/index.js.map +2 -2
- package/dist/cjs/components/LiveChatWidget/types.d.ts +38 -0
- package/dist/cjs/components/LiveChatWidget/types.js.map +1 -1
- package/dist/cjs/stories/LiveChatWidget.stories.d.ts +14 -0
- package/dist/cjs/stories/LiveChatWidget.stories.js +76 -7
- package/dist/cjs/stories/LiveChatWidget.stories.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/LiveChatWidget.js +1 -1
- package/dist/esm/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.d.ts +51 -0
- package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js +41 -0
- package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js.map +7 -0
- package/dist/esm/components/LiveChatWidget/index.d.ts +1 -1
- package/dist/esm/components/LiveChatWidget/index.js +1 -1
- package/dist/esm/components/LiveChatWidget/index.js.map +2 -2
- package/dist/esm/components/LiveChatWidget/types.d.ts +38 -0
- package/dist/esm/stories/LiveChatWidget.stories.d.ts +14 -0
- package/dist/esm/stories/LiveChatWidget.stories.js +76 -7
- package/dist/esm/stories/LiveChatWidget.stories.js.map +3 -3
- package/package.json +5 -3
- package/src/components/LiveChatWidget/LiveChatWidget.tsx +65 -1
- package/src/components/LiveChatWidget/components/ComplianceDialog.tsx +229 -0
- package/src/components/LiveChatWidget/index.tsx +1 -0
- package/src/components/LiveChatWidget/types.ts +48 -0
- package/src/stories/LiveChatWidget.stories.tsx +131 -0
|
@@ -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 { 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 {\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}) => {\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 return (\n <>\n {/* \u6C14\u6CE1\u6309\u94AE */}\n <ChatBubble position={position} onClick={openChat} visible={!isOpen} iconImageUrl={chatBubbleIcon} />\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", "useChatState", "useChatAPI", "MessageRendererRegistry", "sanitizeInput", "transformProducts", "transformCartData", "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", "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"]
|
|
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": "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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", "existingSessionId", "normalizedMessages", "msg", "welcomeMsg", "handleSendMessage", "isRetry", "textToSend", "sanitized", "userMessage", "thinkingMessage", "sessionExpiredDetected", "event", "messagesWithoutThinking", "handleBubbleClick", "handleComplianceAgree", "handleComplianceClose"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 法规协议弹窗组件
|
|
3
|
+
* 在用户首次点击聊天气泡时显示,用户同意后才打开聊天窗口
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import type { ComplianceDialogConfig } from '../types';
|
|
7
|
+
export interface ComplianceDialogProps {
|
|
8
|
+
/**
|
|
9
|
+
* 是否打开弹窗
|
|
10
|
+
*/
|
|
11
|
+
open: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* 弹窗配置
|
|
14
|
+
*/
|
|
15
|
+
config: ComplianceDialogConfig;
|
|
16
|
+
/**
|
|
17
|
+
* 用户点击同意按钮的回调
|
|
18
|
+
*/
|
|
19
|
+
onAgree: () => void;
|
|
20
|
+
/**
|
|
21
|
+
* 用户关闭弹窗的回调(点击遮罩或关闭按钮)
|
|
22
|
+
*/
|
|
23
|
+
onClose: () => void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 法规协议弹窗组件
|
|
27
|
+
*
|
|
28
|
+
* 功能:
|
|
29
|
+
* - 显示法规协议内容
|
|
30
|
+
* - 必须勾选复选框才能点击同意按钮
|
|
31
|
+
* - 支持在文本中嵌入链接
|
|
32
|
+
* - 响应式设计,移动端友好
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* <ComplianceDialog
|
|
37
|
+
* open={showCompliance}
|
|
38
|
+
* config={{
|
|
39
|
+
* title: "Hi! I'm your eufy AI assistant.",
|
|
40
|
+
* content: "AI-generated responses can be inaccurate...",
|
|
41
|
+
* checkboxText: "By starting to use \"Live Chat\", you agree to Anker's {link}.",
|
|
42
|
+
* linkText: "LIVE CHAT PRIVACY NOTICE",
|
|
43
|
+
* linkUrl: "https://www.anker.com/privacy",
|
|
44
|
+
* agreeButtonText: "Agree"
|
|
45
|
+
* }}
|
|
46
|
+
* onAgree={() => setShowCompliance(false)}
|
|
47
|
+
* onClose={() => setShowCompliance(false)}
|
|
48
|
+
* />
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare const ComplianceDialog: React.FC<ComplianceDialogProps>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import{jsx as e,jsxs as n}from"react/jsx-runtime";import{useState as g,useCallback as r}from"react";import*as a from"@radix-ui/react-dialog";const u=({open:m,config:l,onAgree:s,onClose:d})=>{const[t,i]=g(!1),x=r(()=>{t&&(s(),i(!1))},[t,s]),c=r(o=>{o||(d(),i(!1))},[d]),p=r(o=>{o.target.tagName==="A"&&o.stopPropagation()},[]);return e(a.Root,{open:m,onOpenChange:c,children:n(a.Portal,{children:[e(a.Overlay,{className:"hidden"}),n(a.Content,{className:`
|
|
2
|
+
fixed z-[9998] bg-white overflow-hidden flex flex-col
|
|
3
|
+
animate-in slide-in-from-bottom-4 fade-in duration-300
|
|
4
|
+
max-md:bottom-0 max-md:left-0 max-md:right-0 max-md:w-screen max-md:max-h-[80%]
|
|
5
|
+
max-md:rounded-t-2xl max-md:shadow-[0_-4px_20px_rgba(0,0,0,0.15)]
|
|
6
|
+
md:bottom-4 md:right-4 md:w-[390px] md:max-h-[70vh]
|
|
7
|
+
md:rounded-2xl md:shadow-xl
|
|
8
|
+
2xl:max-h-[720px]
|
|
9
|
+
`,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(a.Close,{asChild:!0,children:e("button",{type:"button",className:`
|
|
10
|
+
w-6 h-6
|
|
11
|
+
flex items-center justify-center
|
|
12
|
+
border-0 bg-transparent text-gray-500
|
|
13
|
+
cursor-pointer
|
|
14
|
+
`,"aria-label":"\u5173\u95ED",children:n("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"})]})})})}),n("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(a.Title,{className:`
|
|
15
|
+
text-[18px] font-bold text-[#080A0F]
|
|
16
|
+
mb-1 leading-[1.4] tracking-tight
|
|
17
|
+
`,children:l.title}),e("div",{className:`
|
|
18
|
+
text-[16px] font-bold leading-[1.4] text-[#080A0F]
|
|
19
|
+
tracking-tight
|
|
20
|
+
mb-2
|
|
21
|
+
`,dangerouslySetInnerHTML:{__html:l.content}}),n("label",{className:`
|
|
22
|
+
flex items-center gap-2 mb-4
|
|
23
|
+
cursor-pointer select-none
|
|
24
|
+
`,onClick:p,children:[e("input",{type:"checkbox",className:"absolute opacity-0 w-0 h-0",checked:t,onChange:o=>i(o.target.checked)}),e("span",{className:`
|
|
25
|
+
flex-shrink-0 w-4 h-4 mt-0.5
|
|
26
|
+
border-[1px] rounded-[2px] flex items-center justify-center
|
|
27
|
+
transition-all duration-200 ease-in-out
|
|
28
|
+
${t?"bg-[#080A0F] border-[#080A0F] text-white":"bg-white border-[#2A2C32]"}
|
|
29
|
+
`,children:t&&e("svg",{width:"12",height:"10",viewBox:"0 0 12 10",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:e("polyline",{points:"1.5 5 4.5 8 10.5 2"})})}),e("span",{className:`
|
|
30
|
+
flex-1 text-[14px] leading-[1.4] text-[#080A0F]
|
|
31
|
+
tracking-tight font-bold
|
|
32
|
+
[&_a]:underline
|
|
33
|
+
`,dangerouslySetInnerHTML:{__html:l.checkboxText}})]}),e("button",{type:"button",className:`
|
|
34
|
+
w-full px-5 py-[10px]
|
|
35
|
+
border-0 rounded-[19px]
|
|
36
|
+
text-[14px] leading-[1.2]
|
|
37
|
+
tracking-tighter font-bold
|
|
38
|
+
transition-all duration-200 ease-in-out text-white
|
|
39
|
+
${t?"bg-[#080A0F] cursor-pointer ":"bg-[#767880] cursor-not-allowed"}
|
|
40
|
+
`,onClick:x,disabled:!t,children:l.agreeButtonText||"Agree"})]})]})]})})};export{u as ComplianceDialog};
|
|
41
|
+
//# sourceMappingURL=ComplianceDialog.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/components/LiveChatWidget/components/ComplianceDialog.tsx"],
|
|
4
|
+
"sourcesContent": ["/**\n * \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u7EC4\u4EF6\n * \u5728\u7528\u6237\u9996\u6B21\u70B9\u51FB\u804A\u5929\u6C14\u6CE1\u65F6\u663E\u793A\uFF0C\u7528\u6237\u540C\u610F\u540E\u624D\u6253\u5F00\u804A\u5929\u7A97\u53E3\n */\n\nimport React, { useState, useCallback } from 'react'\nimport * as Dialog from '@radix-ui/react-dialog'\nimport type { ComplianceDialogConfig } from '../types'\n\nexport interface ComplianceDialogProps {\n /**\n * \u662F\u5426\u6253\u5F00\u5F39\u7A97\n */\n open: boolean\n\n /**\n * \u5F39\u7A97\u914D\u7F6E\n */\n config: ComplianceDialogConfig\n\n /**\n * \u7528\u6237\u70B9\u51FB\u540C\u610F\u6309\u94AE\u7684\u56DE\u8C03\n */\n onAgree: () => void\n\n /**\n * \u7528\u6237\u5173\u95ED\u5F39\u7A97\u7684\u56DE\u8C03\uFF08\u70B9\u51FB\u906E\u7F69\u6216\u5173\u95ED\u6309\u94AE\uFF09\n */\n onClose: () => void\n}\n\n/**\n * \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u663E\u793A\u6CD5\u89C4\u534F\u8BAE\u5185\u5BB9\n * - \u5FC5\u987B\u52FE\u9009\u590D\u9009\u6846\u624D\u80FD\u70B9\u51FB\u540C\u610F\u6309\u94AE\n * - \u652F\u6301\u5728\u6587\u672C\u4E2D\u5D4C\u5165\u94FE\u63A5\n * - \u54CD\u5E94\u5F0F\u8BBE\u8BA1\uFF0C\u79FB\u52A8\u7AEF\u53CB\u597D\n *\n * @example\n * ```tsx\n * <ComplianceDialog\n * open={showCompliance}\n * config={{\n * title: \"Hi! I'm your eufy AI assistant.\",\n * content: \"AI-generated responses can be inaccurate...\",\n * checkboxText: \"By starting to use \\\"Live Chat\\\", you agree to Anker's {link}.\",\n * linkText: \"LIVE CHAT PRIVACY NOTICE\",\n * linkUrl: \"https://www.anker.com/privacy\",\n * agreeButtonText: \"Agree\"\n * }}\n * onAgree={() => setShowCompliance(false)}\n * onClose={() => setShowCompliance(false)}\n * />\n * ```\n */\nexport const ComplianceDialog: React.FC<ComplianceDialogProps> = ({\n open,\n config,\n onAgree,\n onClose,\n}) => {\n const [isChecked, setIsChecked] = useState(false)\n\n const handleAgree = useCallback(() => {\n if (isChecked) {\n onAgree()\n // \u91CD\u7F6E\u52FE\u9009\u72B6\u6001\uFF0C\u4EE5\u4FBF\u4E0B\u6B21\u6253\u5F00\u65F6\u91CD\u65B0\u52FE\u9009\n setIsChecked(false)\n }\n }, [isChecked, onAgree])\n\n const handleOpenChange = useCallback((open: boolean) => {\n if (!open) {\n onClose()\n // \u91CD\u7F6E\u52FE\u9009\u72B6\u6001\n setIsChecked(false)\n }\n }, [onClose])\n\n // \u5904\u7406\u70B9\u51FB\u4E8B\u4EF6\uFF0C\u9632\u6B62\u70B9\u51FB\u94FE\u63A5\u65F6\u89E6\u53D1 checkbox\n const handleCheckboxLabelClick = useCallback((e: React.MouseEvent) => {\n // \u5982\u679C\u70B9\u51FB\u7684\u662F\u94FE\u63A5\uFF0C\u963B\u6B62\u5192\u6CE1\n if ((e.target as HTMLElement).tagName === 'A') {\n e.stopPropagation()\n }\n }, [])\n\n return (\n <Dialog.Root open={open} onOpenChange={handleOpenChange}>\n <Dialog.Portal>\n {/* \u906E\u7F69\u5C42 - \u9690\u85CF */}\n <Dialog.Overlay className=\"hidden\" />\n\n {/* \u5F39\u7A97\u5185\u5BB9 */}\n <Dialog.Content\n className=\"\n fixed z-[9998] bg-white overflow-hidden flex flex-col\n animate-in slide-in-from-bottom-4 fade-in duration-300\n max-md:bottom-0 max-md:left-0 max-md:right-0 max-md:w-screen max-md:max-h-[80%]\n max-md:rounded-t-2xl max-md:shadow-[0_-4px_20px_rgba(0,0,0,0.15)]\n md:bottom-4 md:right-4 md:w-[390px] md:max-h-[70vh]\n md:rounded-2xl md:shadow-xl\n 2xl:max-h-[720px]\n \"\n >\n {/* Header - \u5305\u542B\u5173\u95ED\u6309\u94AE\uFF08\u53F3\u4E0A\u89D2\uFF09 */}\n <div className=\"flex items-center justify-end px-4 pt-4 pb-2 flex-shrink-0 max-md:px-6 max-sm:px-5\">\n <Dialog.Close asChild>\n <button\n type=\"button\"\n className=\"\n w-6 h-6\n flex items-center justify-center\n border-0 bg-transparent text-gray-500\n cursor-pointer\n \"\n aria-label=\"\u5173\u95ED\"\n >\n <svg\n width=\"20\"\n height=\"20\"\n className=\"text-[#3C3C3B]\"\n viewBox=\"0 0 20 20\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <line x1=\"15\" y1=\"5\" x2=\"5\" y2=\"15\" />\n <line x1=\"5\" y1=\"5\" x2=\"15\" y2=\"15\" />\n </svg>\n </button>\n </Dialog.Close>\n </div>\n\n {/* Body - \u53EF\u6EDA\u52A8\u5185\u5BB9\u533A\u57DF */}\n <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\">\n {/* \u6807\u9898 */}\n <Dialog.Title\n className=\"\n text-[18px] font-bold text-[#080A0F]\n mb-1 leading-[1.4] tracking-tight\n \"\n >\n {config.title}\n </Dialog.Title>\n\n {/* \u5185\u5BB9 */}\n <div\n className=\"\n text-[16px] font-bold leading-[1.4] text-[#080A0F]\n tracking-tight\n mb-2\n \"\n dangerouslySetInnerHTML={{ __html: config.content }}\n />\n\n {/* \u52FE\u9009\u6846 */}\n <label\n className=\"\n flex items-center gap-2 mb-4\n cursor-pointer select-none\n \"\n onClick={handleCheckboxLabelClick}\n >\n <input\n type=\"checkbox\"\n className=\"absolute opacity-0 w-0 h-0\"\n checked={isChecked}\n onChange={e => setIsChecked(e.target.checked)}\n />\n <span\n className={`\n flex-shrink-0 w-4 h-4 mt-0.5\n border-[1px] rounded-[2px] flex items-center justify-center\n transition-all duration-200 ease-in-out\n ${isChecked ? 'bg-[#080A0F] border-[#080A0F] text-white' : 'bg-white border-[#2A2C32]'}\n `}\n >\n {isChecked && (\n <svg\n width=\"12\"\n height=\"10\"\n viewBox=\"0 0 12 10\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <polyline points=\"1.5 5 4.5 8 10.5 2\" />\n </svg>\n )}\n </span>\n <span\n className=\"\n flex-1 text-[14px] leading-[1.4] text-[#080A0F]\n tracking-tight font-bold\n [&_a]:underline\n \"\n dangerouslySetInnerHTML={{ __html: config.checkboxText }}\n />\n </label>\n\n {/* \u540C\u610F\u6309\u94AE */}\n <button\n type=\"button\"\n className={`\n w-full px-5 py-[10px]\n border-0 rounded-[19px]\n text-[14px] leading-[1.2] \n tracking-tighter font-bold\n transition-all duration-200 ease-in-out text-white\n ${isChecked ? 'bg-[#080A0F] cursor-pointer ' : 'bg-[#767880] cursor-not-allowed'}\n `}\n onClick={handleAgree}\n disabled={!isChecked}\n >\n {config.agreeButtonText || 'Agree'}\n </button>\n </div>\n </Dialog.Content>\n </Dialog.Portal>\n </Dialog.Root>\n )\n}\n"],
|
|
5
|
+
"mappings": "AA6FQ,cAAAA,EA2BQ,QAAAC,MA3BR,oBAxFR,OAAgB,YAAAC,EAAU,eAAAC,MAAmB,QAC7C,UAAYC,MAAY,yBAmDjB,MAAMC,EAAoD,CAAC,CAChE,KAAAC,EACA,OAAAC,EACA,QAAAC,EACA,QAAAC,CACF,IAAM,CACJ,KAAM,CAACC,EAAWC,CAAY,EAAIT,EAAS,EAAK,EAE1CU,EAAcT,EAAY,IAAM,CAChCO,IACFF,EAAQ,EAERG,EAAa,EAAK,EAEtB,EAAG,CAACD,EAAWF,CAAO,CAAC,EAEjBK,EAAmBV,EAAaG,GAAkB,CACjDA,IACHG,EAAQ,EAERE,EAAa,EAAK,EAEtB,EAAG,CAACF,CAAO,CAAC,EAGNK,EAA2BX,EAAaY,GAAwB,CAE/DA,EAAE,OAAuB,UAAY,KACxCA,EAAE,gBAAgB,CAEtB,EAAG,CAAC,CAAC,EAEL,OACEf,EAACI,EAAO,KAAP,CAAY,KAAME,EAAM,aAAcO,EACrC,SAAAZ,EAACG,EAAO,OAAP,CAEC,UAAAJ,EAACI,EAAO,QAAP,CAAe,UAAU,SAAS,EAGnCH,EAACG,EAAO,QAAP,CACC,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAWV,UAAAJ,EAAC,OAAI,UAAU,sFACb,SAAAA,EAACI,EAAO,MAAP,CAAa,QAAO,GACnB,SAAAJ,EAAC,UACC,KAAK,SACL,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,kBAMV,aAAW,eAEX,SAAAC,EAAC,OACC,MAAM,KACN,OAAO,KACP,UAAU,iBACV,QAAQ,YACR,KAAK,OACL,OAAO,eACP,YAAY,IACZ,cAAc,QACd,eAAe,QAEf,UAAAD,EAAC,QAAK,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,EACpCA,EAAC,QAAK,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,GACtC,EACF,EACF,EACF,EAGAC,EAAC,OAAI,UAAU,mFAEb,UAAAD,EAACI,EAAO,MAAP,CACC,UAAU;AAAA;AAAA;AAAA,gBAKT,SAAAG,EAAO,MACV,EAGAP,EAAC,OACC,UAAU;AAAA;AAAA;AAAA;AAAA,gBAKV,wBAAyB,CAAE,OAAQO,EAAO,OAAQ,EACpD,EAGAN,EAAC,SACC,UAAU;AAAA;AAAA;AAAA,gBAIV,QAASa,EAET,UAAAd,EAAC,SACC,KAAK,WACL,UAAU,6BACV,QAASU,EACT,SAAUK,GAAKJ,EAAaI,EAAE,OAAO,OAAO,EAC9C,EACAf,EAAC,QACC,UAAW;AAAA;AAAA;AAAA;AAAA,oBAIPU,EAAY,2CAA6C,2BAA2B;AAAA,kBAGvF,SAAAA,GACCV,EAAC,OACC,MAAM,KACN,OAAO,KACP,QAAQ,YACR,KAAK,OACL,OAAO,eACP,YAAY,IACZ,cAAc,QACd,eAAe,QAEf,SAAAA,EAAC,YAAS,OAAO,qBAAqB,EACxC,EAEJ,EACAA,EAAC,QACC,UAAU;AAAA;AAAA;AAAA;AAAA,kBAKV,wBAAyB,CAAE,OAAQO,EAAO,YAAa,EACzD,GACF,EAGAP,EAAC,UACC,KAAK,SACL,UAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAMPU,EAAY,gCAAkC,iCAAiC;AAAA,gBAEnF,QAASE,EACT,SAAU,CAACF,EAEV,SAAAH,EAAO,iBAAmB,QAC7B,GACF,GACF,GACF,EACF,CAEJ",
|
|
6
|
+
"names": ["jsx", "jsxs", "useState", "useCallback", "Dialog", "ComplianceDialog", "open", "config", "onAgree", "onClose", "isChecked", "setIsChecked", "handleAgree", "handleOpenChange", "handleCheckboxLabelClick", "e"]
|
|
7
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* LiveChatWidget 组件入口
|
|
3
3
|
*/
|
|
4
4
|
export { LiveChatWidget } from './LiveChatWidget';
|
|
5
|
-
export type { LiveChatWidgetProps, Message, MessageContent, MessageRole, MessageContentType, MessageMetadata, Product, QuickReply, Session, BubblePosition, MessageRenderer, ProductCardContent, ProductListContent, PolicyContent, QuickRepliesContent, ThinkingContent, ErrorContent, FAQListContent, FAQItem, FAQCategory, PromotionListContent, PromotionItem, CartContent, CartData, CartLine, CartCost, CartAmount, CartDiscountCode, SSEEvent, ChatStreamRequest, } from './types';
|
|
5
|
+
export type { LiveChatWidgetProps, Message, MessageContent, MessageRole, MessageContentType, MessageMetadata, Product, QuickReply, Session, BubblePosition, MessageRenderer, ProductCardContent, ProductListContent, PolicyContent, QuickRepliesContent, ThinkingContent, ErrorContent, FAQListContent, FAQItem, FAQCategory, PromotionListContent, PromotionItem, CartContent, CartData, CartLine, CartCost, CartAmount, CartDiscountCode, SSEEvent, ChatStreamRequest, ComplianceDialogConfig, } from './types';
|
|
6
6
|
export { useChatState } from './hooks/useChatState';
|
|
7
7
|
export { useChatAPI } from './hooks/useChatAPI';
|
|
8
8
|
export { useSession } from './hooks/useSession';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{LiveChatWidget as o}from"./LiveChatWidget";import{useChatState as
|
|
1
|
+
import{LiveChatWidget as o}from"./LiveChatWidget";import{useChatState as n}from"./hooks/useChatState";import{useChatAPI as i}from"./hooks/useChatAPI";import{useSession as C}from"./hooks/useSession";import{MessageRendererRegistry as c}from"./utils/messageRenderers";import{getUserId as l}from"./utils/userId";import{sanitizeInput as g,isValidUrl as u,isValidUUID as P,isValidMessageContent as f,escapeHtml as k}from"./utils/validation";import{TextBlock as R,ProductCard as L,ProductList as M,PolicyBlock as y,QuickReplies as Q,ThinkingBlock as h,ErrorBlock as A,FAQList as I,PromotionList as S,CartCard as B,createQuickRepliesRenderer as D}from"./components/MessageContent/index.js";export{B as CartCard,A as ErrorBlock,I as FAQList,o as LiveChatWidget,c as MessageRendererRegistry,y as PolicyBlock,L as ProductCard,M as ProductList,S as PromotionList,Q as QuickReplies,R as TextBlock,h as ThinkingBlock,D as createQuickRepliesRenderer,k as escapeHtml,l as getUserId,f as isValidMessageContent,P as isValidUUID,u as isValidUrl,g as sanitizeInput,i as useChatAPI,n as useChatState,C as useSession};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/components/LiveChatWidget/index.tsx"],
|
|
4
|
-
"sourcesContent": ["/**\n * LiveChatWidget \u7EC4\u4EF6\u5165\u53E3\n */\n\nexport { LiveChatWidget } from './LiveChatWidget'\nexport type {\n LiveChatWidgetProps,\n Message,\n MessageContent,\n MessageRole,\n MessageContentType,\n MessageMetadata,\n Product,\n QuickReply,\n Session,\n BubblePosition,\n MessageRenderer,\n ProductCardContent,\n ProductListContent,\n PolicyContent,\n QuickRepliesContent,\n ThinkingContent,\n ErrorContent,\n FAQListContent,\n FAQItem,\n FAQCategory,\n PromotionListContent,\n PromotionItem,\n CartContent,\n CartData,\n CartLine,\n CartCost,\n CartAmount,\n CartDiscountCode,\n SSEEvent,\n ChatStreamRequest,\n} from './types'\n\n// \u5BFC\u51FA Hook (\u4F9B\u9AD8\u7EA7\u7528\u6237\u81EA\u5B9A\u4E49\u4F7F\u7528)\nexport { useChatState } from './hooks/useChatState'\nexport { useChatAPI } from './hooks/useChatAPI'\nexport { useSession } from './hooks/useSession'\n\n// \u5BFC\u51FA\u5DE5\u5177\u7C7B\nexport { MessageRendererRegistry } from './utils/messageRenderers'\nexport { getUserId } from './utils/userId'\nexport { sanitizeInput, isValidUrl, isValidUUID, isValidMessageContent, escapeHtml } from './utils/validation'\n\n// \u5BFC\u51FA\u6D88\u606F\u6E32\u67D3\u5668\uFF08\u4F9B\u81EA\u5B9A\u4E49\u4F7F\u7528\uFF09\nexport {\n TextBlock,\n ProductCard,\n ProductList,\n PolicyBlock,\n QuickReplies,\n ThinkingBlock,\n ErrorBlock,\n FAQList,\n PromotionList,\n CartCard,\n createQuickRepliesRenderer,\n} from './components/MessageContent/index.js'\n"],
|
|
5
|
-
"mappings": "AAIA,OAAS,kBAAAA,MAAsB,
|
|
4
|
+
"sourcesContent": ["/**\n * LiveChatWidget \u7EC4\u4EF6\u5165\u53E3\n */\n\nexport { LiveChatWidget } from './LiveChatWidget'\nexport type {\n LiveChatWidgetProps,\n Message,\n MessageContent,\n MessageRole,\n MessageContentType,\n MessageMetadata,\n Product,\n QuickReply,\n Session,\n BubblePosition,\n MessageRenderer,\n ProductCardContent,\n ProductListContent,\n PolicyContent,\n QuickRepliesContent,\n ThinkingContent,\n ErrorContent,\n FAQListContent,\n FAQItem,\n FAQCategory,\n PromotionListContent,\n PromotionItem,\n CartContent,\n CartData,\n CartLine,\n CartCost,\n CartAmount,\n CartDiscountCode,\n SSEEvent,\n ChatStreamRequest,\n ComplianceDialogConfig,\n} from './types'\n\n// \u5BFC\u51FA Hook (\u4F9B\u9AD8\u7EA7\u7528\u6237\u81EA\u5B9A\u4E49\u4F7F\u7528)\nexport { useChatState } from './hooks/useChatState'\nexport { useChatAPI } from './hooks/useChatAPI'\nexport { useSession } from './hooks/useSession'\n\n// \u5BFC\u51FA\u5DE5\u5177\u7C7B\nexport { MessageRendererRegistry } from './utils/messageRenderers'\nexport { getUserId } from './utils/userId'\nexport { sanitizeInput, isValidUrl, isValidUUID, isValidMessageContent, escapeHtml } from './utils/validation'\n\n// \u5BFC\u51FA\u6D88\u606F\u6E32\u67D3\u5668\uFF08\u4F9B\u81EA\u5B9A\u4E49\u4F7F\u7528\uFF09\nexport {\n TextBlock,\n ProductCard,\n ProductList,\n PolicyBlock,\n QuickReplies,\n ThinkingBlock,\n ErrorBlock,\n FAQList,\n PromotionList,\n CartCard,\n createQuickRepliesRenderer,\n} from './components/MessageContent/index.js'\n"],
|
|
5
|
+
"mappings": "AAIA,OAAS,kBAAAA,MAAsB,mBAoC/B,OAAS,gBAAAC,MAAoB,uBAC7B,OAAS,cAAAC,MAAkB,qBAC3B,OAAS,cAAAC,MAAkB,qBAG3B,OAAS,2BAAAC,MAA+B,2BACxC,OAAS,aAAAC,MAAiB,iBAC1B,OAAS,iBAAAC,EAAe,cAAAC,EAAY,eAAAC,EAAa,yBAAAC,EAAuB,cAAAC,MAAkB,qBAG1F,OACE,aAAAC,EACA,eAAAC,EACA,eAAAC,EACA,eAAAC,EACA,gBAAAC,EACA,iBAAAC,EACA,cAAAC,EACA,WAAAC,EACA,iBAAAC,EACA,YAAAC,EACA,8BAAAC,MACK",
|
|
6
6
|
"names": ["LiveChatWidget", "useChatState", "useChatAPI", "useSession", "MessageRendererRegistry", "getUserId", "sanitizeInput", "isValidUrl", "isValidUUID", "isValidMessageContent", "escapeHtml", "TextBlock", "ProductCard", "ProductList", "PolicyBlock", "QuickReplies", "ThinkingBlock", "ErrorBlock", "FAQList", "PromotionList", "CartCard", "createQuickRepliesRenderer"]
|
|
7
7
|
}
|
|
@@ -559,6 +559,38 @@ export interface CommonText {
|
|
|
559
559
|
*/
|
|
560
560
|
total?: string;
|
|
561
561
|
}
|
|
562
|
+
/**
|
|
563
|
+
* 法规协议弹窗配置
|
|
564
|
+
*/
|
|
565
|
+
export interface ComplianceDialogConfig {
|
|
566
|
+
/**
|
|
567
|
+
* 弹窗标题
|
|
568
|
+
* @example "Hi! I'm your eufy AI assistant."
|
|
569
|
+
*/
|
|
570
|
+
title: string;
|
|
571
|
+
/**
|
|
572
|
+
* 弹窗内容文本(支持 HTML)
|
|
573
|
+
* @example "AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data"
|
|
574
|
+
*/
|
|
575
|
+
content: string;
|
|
576
|
+
/**
|
|
577
|
+
* 勾选框文本(支持完整 HTML,包括链接)
|
|
578
|
+
* 可以直接包含 <a> 标签等 HTML 元素
|
|
579
|
+
* @example "By starting to use \"Live Chat\", you agree to Anker's <a href=\"https://www.anker.com/privacy\" target=\"_blank\">LIVE CHAT PRIVACY NOTICE</a>."
|
|
580
|
+
*/
|
|
581
|
+
checkboxText: string;
|
|
582
|
+
/**
|
|
583
|
+
* 同意按钮文本
|
|
584
|
+
* @default "Agree"
|
|
585
|
+
*/
|
|
586
|
+
agreeButtonText?: string;
|
|
587
|
+
/**
|
|
588
|
+
* Cookie 名称,用于记录用户同意状态
|
|
589
|
+
* Cookie 有效期为 365 天
|
|
590
|
+
* @default "livechat_compliance_agreed"
|
|
591
|
+
*/
|
|
592
|
+
cookieName?: string;
|
|
593
|
+
}
|
|
562
594
|
export interface LiveChatWidgetProps {
|
|
563
595
|
/**
|
|
564
596
|
* API 基础 URL
|
|
@@ -757,6 +789,12 @@ export interface LiveChatWidgetProps {
|
|
|
757
789
|
* @default "The above content is provided by AI, for reference."
|
|
758
790
|
*/
|
|
759
791
|
bottomTips?: string;
|
|
792
|
+
/**
|
|
793
|
+
* 法规协议弹窗配置
|
|
794
|
+
* 提供此参数将在用户首次点击聊天气泡时显示法规协议弹窗
|
|
795
|
+
* 用户同意后才会打开聊天窗口
|
|
796
|
+
*/
|
|
797
|
+
complianceConfig?: ComplianceDialogConfig;
|
|
760
798
|
}
|
|
761
799
|
export interface MessageRenderer {
|
|
762
800
|
render: (content: MessageContent, isUser: boolean, isSystem: boolean) => React.ReactNode;
|
|
@@ -98,6 +98,20 @@ export declare const FAQListMessage: Story;
|
|
|
98
98
|
* reCAPTCHA 会在后台自动验证用户行为,无需用户手动操作。
|
|
99
99
|
*/
|
|
100
100
|
export declare const WithRecaptcha: Story;
|
|
101
|
+
/**
|
|
102
|
+
* 法规协议弹窗
|
|
103
|
+
*
|
|
104
|
+
* 演示如何配置法规协议弹窗,在用户首次点击聊天气泡时显示。
|
|
105
|
+
*
|
|
106
|
+
* 用户必须同意协议后才能打开聊天窗口。
|
|
107
|
+
*/
|
|
108
|
+
export declare const WithComplianceDialog: Story;
|
|
109
|
+
/**
|
|
110
|
+
* 法规协议弹窗 - 无链接版本
|
|
111
|
+
*
|
|
112
|
+
* 演示不包含链接的简化版法规协议弹窗。
|
|
113
|
+
*/
|
|
114
|
+
export declare const WithComplianceDialogNoLink: Story;
|
|
101
115
|
/**
|
|
102
116
|
* 自定义产品卡片渲染
|
|
103
117
|
*
|