@anker-in/campaign-ui 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/dist/cjs/components/LiveChatWidget/LiveChatWidget.d.ts +21 -1
  2. package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js +1 -1
  3. package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
  4. package/dist/cjs/components/LiveChatWidget/api/chat.d.ts +23 -2
  5. package/dist/cjs/components/LiveChatWidget/api/chat.js +2 -2
  6. package/dist/cjs/components/LiveChatWidget/api/chat.js.map +3 -3
  7. package/dist/cjs/components/LiveChatWidget/components/ChatHeader.js +1 -1
  8. package/dist/cjs/components/LiveChatWidget/components/ChatHeader.js.map +2 -2
  9. package/dist/cjs/components/LiveChatWidget/components/ChatInput.d.ts +5 -0
  10. package/dist/cjs/components/LiveChatWidget/components/ChatInput.js +1 -1
  11. package/dist/cjs/components/LiveChatWidget/components/ChatInput.js.map +3 -3
  12. package/dist/cjs/components/LiveChatWidget/components/ChatMessage.js +2 -2
  13. package/dist/cjs/components/LiveChatWidget/components/ChatMessage.js.map +3 -3
  14. package/dist/cjs/components/LiveChatWidget/components/ChatWindow.d.ts +5 -0
  15. package/dist/cjs/components/LiveChatWidget/components/ChatWindow.js +1 -1
  16. package/dist/cjs/components/LiveChatWidget/components/ChatWindow.js.map +3 -3
  17. package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.d.ts +51 -0
  18. package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js +33 -0
  19. package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js.map +7 -0
  20. package/dist/cjs/components/LiveChatWidget/components/MessageContent/CartCard.js +1 -1
  21. package/dist/cjs/components/LiveChatWidget/components/MessageContent/CartCard.js.map +3 -3
  22. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ErrorBlock.js +1 -1
  23. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ErrorBlock.js.map +2 -2
  24. package/dist/cjs/components/LiveChatWidget/components/MessageContent/FAQList.js +1 -1
  25. package/dist/cjs/components/LiveChatWidget/components/MessageContent/FAQList.js.map +3 -3
  26. package/dist/cjs/components/LiveChatWidget/components/MessageContent/PolicyBlock.js +2 -2
  27. package/dist/cjs/components/LiveChatWidget/components/MessageContent/PolicyBlock.js.map +3 -3
  28. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.d.ts +17 -24
  29. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.js +1 -4
  30. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.js.map +3 -3
  31. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductComparison.d.ts +7 -1
  32. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductComparison.js +1 -1
  33. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductComparison.js.map +3 -3
  34. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductList.js +1 -1
  35. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductList.js.map +3 -3
  36. package/dist/cjs/components/LiveChatWidget/components/MessageContent/PromotionList.d.ts +4 -1
  37. package/dist/cjs/components/LiveChatWidget/components/MessageContent/PromotionList.js +1 -1
  38. package/dist/cjs/components/LiveChatWidget/components/MessageContent/PromotionList.js.map +3 -3
  39. package/dist/cjs/components/LiveChatWidget/components/MessageContent/QuickReplies.js +1 -1
  40. package/dist/cjs/components/LiveChatWidget/components/MessageContent/QuickReplies.js.map +2 -2
  41. package/dist/cjs/components/LiveChatWidget/components/MessageContent/TextBlock.js +1 -1
  42. package/dist/cjs/components/LiveChatWidget/components/MessageContent/TextBlock.js.map +3 -3
  43. package/dist/cjs/components/LiveChatWidget/components/MessageContent.js +1 -1
  44. package/dist/cjs/components/LiveChatWidget/components/MessageContent.js.map +2 -2
  45. package/dist/cjs/components/LiveChatWidget/components/MessageList.js +2 -2
  46. package/dist/cjs/components/LiveChatWidget/components/MessageList.js.map +2 -2
  47. package/dist/cjs/components/LiveChatWidget/constants.d.ts +5 -0
  48. package/dist/cjs/components/LiveChatWidget/constants.js +1 -1
  49. package/dist/cjs/components/LiveChatWidget/constants.js.map +3 -3
  50. package/dist/cjs/components/LiveChatWidget/hooks/useChatAPI.d.ts +9 -0
  51. package/dist/cjs/components/LiveChatWidget/hooks/useChatAPI.js +1 -1
  52. package/dist/cjs/components/LiveChatWidget/hooks/useChatAPI.js.map +3 -3
  53. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.d.ts +35 -2
  54. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js +1 -1
  55. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
  56. package/dist/cjs/components/LiveChatWidget/index.d.ts +1 -1
  57. package/dist/cjs/components/LiveChatWidget/index.js +1 -1
  58. package/dist/cjs/components/LiveChatWidget/index.js.map +2 -2
  59. package/dist/cjs/components/LiveChatWidget/types.d.ts +212 -3
  60. package/dist/cjs/components/LiveChatWidget/types.js +1 -1
  61. package/dist/cjs/components/LiveChatWidget/types.js.map +1 -1
  62. package/dist/cjs/components/LiveChatWidget/utils/fetcher.d.ts +42 -0
  63. package/dist/cjs/components/LiveChatWidget/utils/fetcher.js +2 -0
  64. package/dist/cjs/components/LiveChatWidget/utils/fetcher.js.map +7 -0
  65. package/dist/cjs/components/chat/markdown.js +1 -1
  66. package/dist/cjs/components/chat/markdown.js.map +2 -2
  67. package/dist/cjs/components/credits/creditsBanner/index.js +2 -2
  68. package/dist/cjs/components/credits/creditsBanner/index.js.map +2 -2
  69. package/dist/cjs/components/index.d.ts +2 -0
  70. package/dist/cjs/components/index.js +1 -1
  71. package/dist/cjs/components/index.js.map +3 -3
  72. package/dist/cjs/stories/LiveChatWidget.stories.d.ts +1 -79
  73. package/dist/cjs/stories/LiveChatWidget.stories.js +8 -47
  74. package/dist/cjs/stories/LiveChatWidget.stories.js.map +3 -3
  75. package/dist/esm/components/LiveChatWidget/LiveChatWidget.d.ts +21 -1
  76. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js +1 -1
  77. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
  78. package/dist/esm/components/LiveChatWidget/api/chat.d.ts +23 -2
  79. package/dist/esm/components/LiveChatWidget/api/chat.js +2 -2
  80. package/dist/esm/components/LiveChatWidget/api/chat.js.map +3 -3
  81. package/dist/esm/components/LiveChatWidget/components/ChatHeader.js +1 -1
  82. package/dist/esm/components/LiveChatWidget/components/ChatHeader.js.map +2 -2
  83. package/dist/esm/components/LiveChatWidget/components/ChatInput.d.ts +5 -0
  84. package/dist/esm/components/LiveChatWidget/components/ChatInput.js +1 -1
  85. package/dist/esm/components/LiveChatWidget/components/ChatInput.js.map +3 -3
  86. package/dist/esm/components/LiveChatWidget/components/ChatMessage.js +2 -2
  87. package/dist/esm/components/LiveChatWidget/components/ChatMessage.js.map +3 -3
  88. package/dist/esm/components/LiveChatWidget/components/ChatWindow.d.ts +5 -0
  89. package/dist/esm/components/LiveChatWidget/components/ChatWindow.js +1 -1
  90. package/dist/esm/components/LiveChatWidget/components/ChatWindow.js.map +3 -3
  91. package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.d.ts +51 -0
  92. package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js +33 -0
  93. package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js.map +7 -0
  94. package/dist/esm/components/LiveChatWidget/components/MessageContent/CartCard.js +1 -1
  95. package/dist/esm/components/LiveChatWidget/components/MessageContent/CartCard.js.map +3 -3
  96. package/dist/esm/components/LiveChatWidget/components/MessageContent/ErrorBlock.js +1 -1
  97. package/dist/esm/components/LiveChatWidget/components/MessageContent/ErrorBlock.js.map +2 -2
  98. package/dist/esm/components/LiveChatWidget/components/MessageContent/FAQList.js +1 -1
  99. package/dist/esm/components/LiveChatWidget/components/MessageContent/FAQList.js.map +3 -3
  100. package/dist/esm/components/LiveChatWidget/components/MessageContent/PolicyBlock.js +2 -2
  101. package/dist/esm/components/LiveChatWidget/components/MessageContent/PolicyBlock.js.map +3 -3
  102. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.d.ts +17 -24
  103. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.js +1 -4
  104. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.js.map +3 -3
  105. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.d.ts +7 -1
  106. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.js +1 -1
  107. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.js.map +3 -3
  108. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductList.js +1 -1
  109. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductList.js.map +3 -3
  110. package/dist/esm/components/LiveChatWidget/components/MessageContent/PromotionList.d.ts +4 -1
  111. package/dist/esm/components/LiveChatWidget/components/MessageContent/PromotionList.js +1 -1
  112. package/dist/esm/components/LiveChatWidget/components/MessageContent/PromotionList.js.map +3 -3
  113. package/dist/esm/components/LiveChatWidget/components/MessageContent/QuickReplies.js +1 -1
  114. package/dist/esm/components/LiveChatWidget/components/MessageContent/QuickReplies.js.map +2 -2
  115. package/dist/esm/components/LiveChatWidget/components/MessageContent/TextBlock.js +1 -1
  116. package/dist/esm/components/LiveChatWidget/components/MessageContent/TextBlock.js.map +3 -3
  117. package/dist/esm/components/LiveChatWidget/components/MessageContent.js +1 -1
  118. package/dist/esm/components/LiveChatWidget/components/MessageContent.js.map +2 -2
  119. package/dist/esm/components/LiveChatWidget/components/MessageList.js +2 -2
  120. package/dist/esm/components/LiveChatWidget/components/MessageList.js.map +2 -2
  121. package/dist/esm/components/LiveChatWidget/constants.d.ts +5 -0
  122. package/dist/esm/components/LiveChatWidget/constants.js +1 -1
  123. package/dist/esm/components/LiveChatWidget/constants.js.map +3 -3
  124. package/dist/esm/components/LiveChatWidget/hooks/useChatAPI.d.ts +9 -0
  125. package/dist/esm/components/LiveChatWidget/hooks/useChatAPI.js +1 -1
  126. package/dist/esm/components/LiveChatWidget/hooks/useChatAPI.js.map +3 -3
  127. package/dist/esm/components/LiveChatWidget/hooks/useChatState.d.ts +35 -2
  128. package/dist/esm/components/LiveChatWidget/hooks/useChatState.js +1 -1
  129. package/dist/esm/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
  130. package/dist/esm/components/LiveChatWidget/index.d.ts +1 -1
  131. package/dist/esm/components/LiveChatWidget/index.js +1 -1
  132. package/dist/esm/components/LiveChatWidget/index.js.map +2 -2
  133. package/dist/esm/components/LiveChatWidget/types.d.ts +212 -3
  134. package/dist/esm/components/LiveChatWidget/utils/fetcher.d.ts +42 -0
  135. package/dist/esm/components/LiveChatWidget/utils/fetcher.js +2 -0
  136. package/dist/esm/components/LiveChatWidget/utils/fetcher.js.map +7 -0
  137. package/dist/esm/components/chat/markdown.js +1 -1
  138. package/dist/esm/components/chat/markdown.js.map +2 -2
  139. package/dist/esm/components/credits/creditsBanner/index.js +2 -2
  140. package/dist/esm/components/credits/creditsBanner/index.js.map +2 -2
  141. package/dist/esm/components/index.d.ts +2 -0
  142. package/dist/esm/components/index.js +1 -1
  143. package/dist/esm/components/index.js.map +3 -3
  144. package/dist/esm/stories/LiveChatWidget.stories.d.ts +1 -79
  145. package/dist/esm/stories/LiveChatWidget.stories.js +8 -47
  146. package/dist/esm/stories/LiveChatWidget.stories.js.map +3 -3
  147. package/dist/index.d.mts +1305 -0
  148. package/dist/index.d.ts +1305 -0
  149. package/dist/index.js +26656 -0
  150. package/dist/index.js.map +1 -0
  151. package/dist/index.mjs +26641 -0
  152. package/dist/index.mjs.map +1 -0
  153. package/package.json +9 -2
  154. package/src/components/LiveChatWidget/LiveChatWidget.tsx +887 -0
  155. package/src/components/LiveChatWidget/api/chat.ts +175 -0
  156. package/src/components/LiveChatWidget/components/ChatBubble.tsx +152 -0
  157. package/src/components/LiveChatWidget/components/ChatHeader.tsx +150 -0
  158. package/src/components/LiveChatWidget/components/ChatInput.tsx +253 -0
  159. package/src/components/LiveChatWidget/components/ChatMessage.tsx +190 -0
  160. package/src/components/LiveChatWidget/components/ChatWindow.tsx +363 -0
  161. package/src/components/LiveChatWidget/components/ComplianceDialog.tsx +216 -0
  162. package/src/components/LiveChatWidget/components/MessageContent/CartCard.tsx +202 -0
  163. package/src/components/LiveChatWidget/components/MessageContent/ErrorBlock.tsx +75 -0
  164. package/src/components/LiveChatWidget/components/MessageContent/FAQList.tsx +128 -0
  165. package/src/components/LiveChatWidget/components/MessageContent/PolicyBlock.tsx +152 -0
  166. package/src/components/LiveChatWidget/components/MessageContent/ProductCard.tsx +227 -0
  167. package/src/components/LiveChatWidget/components/MessageContent/ProductComparison.tsx +377 -0
  168. package/src/components/LiveChatWidget/components/MessageContent/ProductList.tsx +293 -0
  169. package/src/components/LiveChatWidget/components/MessageContent/PromotionList.tsx +170 -0
  170. package/src/components/LiveChatWidget/components/MessageContent/QuickReplies.tsx +91 -0
  171. package/src/components/LiveChatWidget/components/MessageContent/TextBlock.tsx +110 -0
  172. package/src/components/LiveChatWidget/components/MessageContent/ThinkingBlock.tsx +53 -0
  173. package/src/components/LiveChatWidget/components/MessageContent/index.ts +16 -0
  174. package/src/components/LiveChatWidget/components/MessageContent.tsx +113 -0
  175. package/src/components/LiveChatWidget/components/MessageList.tsx +261 -0
  176. package/src/components/LiveChatWidget/components/ScrollAnchor.tsx +75 -0
  177. package/src/components/LiveChatWidget/constants.ts +36 -0
  178. package/src/components/LiveChatWidget/hooks/useChatAPI.ts +146 -0
  179. package/src/components/LiveChatWidget/hooks/useChatState.ts +1090 -0
  180. package/src/components/LiveChatWidget/hooks/useSession.ts +123 -0
  181. package/src/components/LiveChatWidget/index.tsx +63 -0
  182. package/src/components/LiveChatWidget/types.ts +1011 -0
  183. package/src/components/LiveChatWidget/utils/cartTransformers.ts +72 -0
  184. package/src/components/LiveChatWidget/utils/fetcher.ts +131 -0
  185. package/src/components/LiveChatWidget/utils/messageRenderers.ts +120 -0
  186. package/src/components/LiveChatWidget/utils/productTransformers.ts +149 -0
  187. package/src/components/LiveChatWidget/utils/userId.ts +140 -0
  188. package/src/components/LiveChatWidget/utils/validation.ts +99 -0
  189. package/src/components/chat/markdown.tsx +1 -1
  190. package/src/components/credits/creditsBanner/index.tsx +5 -5
  191. package/src/components/index.ts +23 -0
  192. package/src/stories/LiveChatWidget.stories.tsx +322 -0
  193. package/src/styles/livechat.css +317 -0
  194. package/dist/cjs/components/credits/context/hooks/useFunctionMemberPrice.d.ts +0 -7
  195. package/dist/cjs/components/credits/context/hooks/useFunctionMemberPrice.js +0 -2
  196. package/dist/cjs/components/credits/context/hooks/useFunctionMemberPrice.js.map +0 -7
  197. package/dist/cjs/components/credits/context/utils/atobID.d.ts +0 -1
  198. package/dist/cjs/components/credits/context/utils/atobID.js +0 -2
  199. package/dist/cjs/components/credits/context/utils/atobID.js.map +0 -7
  200. package/dist/cjs/components/credits/context/utils/functionDiscountCalculate.d.ts +0 -5
  201. package/dist/cjs/components/credits/context/utils/functionDiscountCalculate.js +0 -2
  202. package/dist/cjs/components/credits/context/utils/functionDiscountCalculate.js.map +0 -7
  203. package/dist/cjs/components/credits/context/utils/getFunctionMemberPrice.d.ts +0 -8
  204. package/dist/cjs/components/credits/context/utils/getFunctionMemberPrice.js +0 -2
  205. package/dist/cjs/components/credits/context/utils/getFunctionMemberPrice.js.map +0 -7
  206. package/dist/cjs/components/credits/context/utils/getFunctionMemberPriceDiscountConfig.d.ts +0 -9
  207. package/dist/cjs/components/credits/context/utils/getFunctionMemberPriceDiscountConfig.js +0 -2
  208. package/dist/cjs/components/credits/context/utils/getFunctionMemberPriceDiscountConfig.js.map +0 -7
  209. package/dist/cjs/components/credits/context/utils/variantGetCoupon.d.ts +0 -6
  210. package/dist/cjs/components/credits/context/utils/variantGetCoupon.js +0 -2
  211. package/dist/cjs/components/credits/context/utils/variantGetCoupon.js.map +0 -7
  212. package/dist/esm/components/credits/context/hooks/useFunctionMemberPrice.d.ts +0 -7
  213. package/dist/esm/components/credits/context/hooks/useFunctionMemberPrice.js +0 -2
  214. package/dist/esm/components/credits/context/hooks/useFunctionMemberPrice.js.map +0 -7
  215. package/dist/esm/components/credits/context/utils/atobID.d.ts +0 -1
  216. package/dist/esm/components/credits/context/utils/atobID.js +0 -2
  217. package/dist/esm/components/credits/context/utils/atobID.js.map +0 -7
  218. package/dist/esm/components/credits/context/utils/functionDiscountCalculate.d.ts +0 -5
  219. package/dist/esm/components/credits/context/utils/functionDiscountCalculate.js +0 -2
  220. package/dist/esm/components/credits/context/utils/functionDiscountCalculate.js.map +0 -7
  221. package/dist/esm/components/credits/context/utils/getFunctionMemberPrice.d.ts +0 -8
  222. package/dist/esm/components/credits/context/utils/getFunctionMemberPrice.js +0 -2
  223. package/dist/esm/components/credits/context/utils/getFunctionMemberPrice.js.map +0 -7
  224. package/dist/esm/components/credits/context/utils/getFunctionMemberPriceDiscountConfig.d.ts +0 -9
  225. package/dist/esm/components/credits/context/utils/getFunctionMemberPriceDiscountConfig.js +0 -2
  226. package/dist/esm/components/credits/context/utils/getFunctionMemberPriceDiscountConfig.js.map +0 -7
  227. package/dist/esm/components/credits/context/utils/variantGetCoupon.d.ts +0 -6
  228. package/dist/esm/components/credits/context/utils/variantGetCoupon.js +0 -2
  229. package/dist/esm/components/credits/context/utils/variantGetCoupon.js.map +0 -7
@@ -0,0 +1,887 @@
1
+ /**
2
+ * LiveChat 主组件
3
+ * 集成所有子组件,提供完整的聊天功能
4
+ * 基于 specs/livechat-widget/plan.md 的三层架构设计
5
+ */
6
+
7
+ import React, { useEffect, useCallback } from 'react'
8
+ import * as Dialog from '@radix-ui/react-dialog'
9
+ import type {
10
+ LiveChatWidgetProps,
11
+ QuickReply,
12
+ Message,
13
+ MessageContent,
14
+ ChatStreamRequest,
15
+ BackendCartData,
16
+ CommonText,
17
+ } from './types'
18
+ import { DEFAULT_COMMON_TEXT } from './constants'
19
+ import { ChatBubble } from './components/ChatBubble'
20
+ import { ChatWindow } from './components/ChatWindow'
21
+ import { ComplianceDialog } from './components/ComplianceDialog'
22
+ import { useChatState } from './hooks/useChatState'
23
+ import { useChatAPI } from './hooks/useChatAPI'
24
+ import { MessageRendererRegistry } from './utils/messageRenderers'
25
+ import { sanitizeInput } from './utils/validation'
26
+ import { transformProducts } from './utils/productTransformers.js'
27
+ import { transformCartData } from './utils/cartTransformers.js'
28
+ import Cookies from 'js-cookie'
29
+ import {
30
+ TextBlock,
31
+ ProductCard,
32
+ ProductList,
33
+ ProductComparisonRenderer,
34
+ PolicyBlock,
35
+ createQuickRepliesRenderer,
36
+ ThinkingBlock,
37
+ ErrorBlock,
38
+ FAQListRenderer,
39
+ PromotionListRenderer,
40
+ CartCard,
41
+ } from './components/MessageContent/index.js'
42
+
43
+ /**
44
+ * LiveChat 聊天组件
45
+ *
46
+ * 功能:
47
+ * - 气泡弹窗聊天界面
48
+ * - SSE 流式消息接收
49
+ * - 会话管理(userId, sessionId)
50
+ * - 历史消息加载
51
+ * - 多种消息类型渲染
52
+ * - 自定义扩展机制
53
+ * - reCAPTCHA v3 安全防护
54
+ *
55
+ * 架构:
56
+ * - UI Layer: ChatBubble, ChatWindow, MessageList, etc.
57
+ * - Logic Layer: useChatState, useChatAPI, useSession
58
+ * - Core Layer: MessageRendererRegistry, 自定义渲染器
59
+ *
60
+ * @example
61
+ * ```tsx
62
+ * // 基础使用(使用默认位置)
63
+ * <LiveChatWidget
64
+ * apiBaseUrl="https://beta-api-livechat.anker.com"
65
+ * site="www.eufy.com"
66
+ * welcomeMessage="你好!我是 AI 助手"
67
+ * onMessageSend={(msg) => console.log('Sent:', msg)}
68
+ * />
69
+ *
70
+ * // 使用自定义位置
71
+ * <LiveChatWidget
72
+ * apiBaseUrl="https://beta-api-livechat.anker.com"
73
+ * site="www.eufy.com"
74
+ * position={{ bottom: "20px", right: "30px" }}
75
+ * onMessageSend={(msg) => console.log('Sent:', msg)}
76
+ * />
77
+ *
78
+ * // 启用 reCAPTCHA v3 安全防护(提供 sitekey 即自动启用)
79
+ * <LiveChatWidget
80
+ * apiBaseUrl="https://beta-api-livechat.anker.com"
81
+ * site="www.eufy.com"
82
+ * recaptchaSitekey="6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14"
83
+ * recaptchaAction="livechat"
84
+ * />
85
+ *
86
+ * // 使用自定义 headers 和 reCAPTCHA
87
+ * <LiveChatWidget
88
+ * apiBaseUrl="https://beta-api-livechat.anker.com"
89
+ * site="www.eufy.com"
90
+ * headers={{
91
+ * "Authorization": "Bearer your-token",
92
+ * "X-Custom-Header": "value"
93
+ * }}
94
+ * recaptchaSitekey="your-site-key"
95
+ * />
96
+ * ```
97
+ */
98
+ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
99
+ apiBaseUrl,
100
+ headers,
101
+ recaptchaSitekey,
102
+ recaptchaAction,
103
+ site,
104
+ channelCode,
105
+ loginUserId,
106
+ cartId,
107
+ accessToken,
108
+ position,
109
+ welcomeMessage,
110
+ quickReplies,
111
+ customRenderers,
112
+ logoUrl,
113
+ title,
114
+ chatBubbleIcon,
115
+ open,
116
+ onOpenChange,
117
+ onOpen,
118
+ onClose,
119
+ onMessageSend,
120
+ onError,
121
+ onTextMessage,
122
+ onProductList,
123
+ onPromotionList,
124
+ onAddToCart,
125
+ onCart,
126
+ showNewSessionButton,
127
+ commonText,
128
+ productCardRender,
129
+ bottomTips,
130
+ complianceConfig,
131
+ }) => {
132
+ // 法规协议弹窗状态
133
+ // 从 Cookie 读取用户是否已同意(有效期 365 天)
134
+ const cookieName = complianceConfig?.cookieName || 'livechat_compliance_agreed'
135
+ const [showComplianceDialog, setShowComplianceDialog] = React.useState(false)
136
+ const [hasAgreedCompliance, setHasAgreedCompliance] = React.useState(() => {
137
+ // 初始化时检查 Cookie
138
+ return complianceConfig ? Cookies.get(cookieName) !== undefined : true
139
+ })
140
+
141
+ // 合并默认文案和自定义文案
142
+ const mergedText: Required<CommonText> = React.useMemo(
143
+ () => ({
144
+ ...DEFAULT_COMMON_TEXT,
145
+ ...commonText,
146
+ }),
147
+ [commonText]
148
+ )
149
+
150
+ // 状态管理
151
+ const chatState = useChatState({
152
+ welcomeMessage,
153
+ site,
154
+ open,
155
+ onOpenChange,
156
+ onOpen,
157
+ onClose,
158
+ onMessageSend,
159
+ onError,
160
+ onTextMessage,
161
+ onProductList,
162
+ onPromotionList,
163
+ onAddToCart,
164
+ onCart,
165
+ productCardRender,
166
+ })
167
+
168
+ const {
169
+ messages,
170
+ isOpen,
171
+ userId,
172
+ sessionId,
173
+ inputValue,
174
+ isStreaming,
175
+ openChat,
176
+ closeChat,
177
+ setInputValue,
178
+ addMessage,
179
+ setMessages,
180
+ clearMessages,
181
+ handleSSEEvent,
182
+ saveSession,
183
+ clearSession,
184
+ } = chatState
185
+
186
+ // API 调用
187
+ const { sendMessageStream, createSession } = useChatAPI({
188
+ apiBaseUrl,
189
+ headers,
190
+ recaptchaConfig: {
191
+ needRecaptcha: !!recaptchaSitekey, // 根据 sitekey 自动判断是否启用
192
+ recaptchaSitekey,
193
+ recaptchaAction,
194
+ },
195
+ onError,
196
+ })
197
+
198
+ // 使用 ref 存储最新的 handleSendMessage,避免循环依赖
199
+ const handleSendMessageRef = React.useRef<(_message?: string) => Promise<void>>(async (_message?: string) => {})
200
+
201
+ // 消息渲染器注册表
202
+ const rendererRegistry = React.useMemo(() => {
203
+ const registry = new MessageRendererRegistry()
204
+
205
+ // 注册默认渲染器
206
+ registry.register('text', TextBlock)
207
+ registry.register('product_card', ProductCard)
208
+ registry.register('product_list', ProductList)
209
+ registry.register('product_comparison', ProductComparisonRenderer)
210
+ registry.register('policy', PolicyBlock)
211
+ registry.register('thinking', ThinkingBlock)
212
+ registry.register('error', ErrorBlock)
213
+ registry.register('faq_list', FAQListRenderer)
214
+ registry.register('promotion_list', PromotionListRenderer)
215
+ registry.register('cart', CartCard)
216
+
217
+ // 注册快捷回复渲染器(带回调)
218
+ const quickRepliesRenderer = createQuickRepliesRenderer((reply: QuickReply) => {
219
+ // 使用 ref 调用最新的 handleSendMessage
220
+ handleSendMessageRef.current(reply.value)
221
+ })
222
+ registry.register('quick_replies', quickRepliesRenderer)
223
+
224
+ // 注册自定义渲染器
225
+ if (customRenderers) {
226
+ registry.registerMany(customRenderers)
227
+ }
228
+
229
+ return registry
230
+ }, [customRenderers])
231
+
232
+ /**
233
+ * T043: 打开聊天窗口时初始化会话
234
+ * 使用 API v2.0.0 的统一接口:
235
+ * - 如果没有 sessionId,创建新会话
236
+ * - 如果有 sessionId,恢复会话并加载历史消息
237
+ */
238
+ useEffect(() => {
239
+ if (!isOpen || !userId) return
240
+
241
+ const currentSessionId = sessionId
242
+
243
+ if (!currentSessionId) {
244
+ // 没有会话,创建新会话
245
+ handleCreateNewSession()
246
+ } else {
247
+ // 有会话,尝试恢复会话和历史消息
248
+ handleResumeSession(currentSessionId)
249
+ }
250
+ // eslint-disable-next-line react-hooks/exhaustive-deps
251
+ }, [isOpen, userId])
252
+
253
+ /**
254
+ * 规范化消息格式(确保 content 是数组)
255
+ * 后端返回格式:
256
+ * - content: 字符串(文本内容)
257
+ * - structuredContent: 数组(结构化内容,如产品列表、政策等)
258
+ * 需要合并为统一的 content 数组格式
259
+ */
260
+ const normalizeMessage = useCallback(
261
+ (message: any): Message => {
262
+ // 如果 content 已经是数组,直接返回
263
+ if (Array.isArray(message.content)) {
264
+ return message as Message
265
+ }
266
+
267
+ const contentBlocks: MessageContent[] = []
268
+
269
+ // 处理文本内容
270
+ if (typeof message.content === 'string' && message.content.trim()) {
271
+ contentBlocks.push({
272
+ type: 'text',
273
+ text: message.content,
274
+ })
275
+ }
276
+
277
+ // 处理结构化内容
278
+ // 历史消息格式: structured_content: [{type, data}]
279
+ const structuredData = message.structuredContent || message.structured_content
280
+
281
+ if (Array.isArray(structuredData)) {
282
+ structuredData.forEach((block: any) => {
283
+ if (block.type === 'product_list' && Array.isArray(block.data)) {
284
+ // 转换产品列表数据结构 - data 直接是产品数组
285
+ contentBlocks.push({
286
+ type: 'product_list',
287
+ data: {
288
+ products: transformProducts(block.data, site),
289
+ title: undefined, // 历史消息不包含 title
290
+ commonText: mergedText,
291
+ },
292
+ })
293
+ } else if (block.type === 'quick_replies' && block.data?.replies) {
294
+ contentBlocks.push({
295
+ type: 'quick_replies',
296
+ data: {
297
+ replies: block.data.replies,
298
+ },
299
+ })
300
+ } else if (block.type === 'policy' && block.data?.title && block.data?.content) {
301
+ contentBlocks.push({
302
+ type: 'policy',
303
+ data: {
304
+ title: block.data.title,
305
+ content: block.data.content,
306
+ },
307
+ })
308
+ } else if (block.type === 'product_comparison' && block.data?.products && block.data?.dimensions) {
309
+ // 转换产品对比数据结构并注入 onAddToCart 回调
310
+ contentBlocks.push({
311
+ type: 'product_comparison',
312
+ data: {
313
+ products: transformProducts(block.data.products, site),
314
+ dimensions: block.data.dimensions,
315
+ onAddToCart: onAddToCart,
316
+ commonText: mergedText,
317
+ },
318
+ })
319
+ } else if (block.type === 'faq_list' && block.data?.found !== undefined) {
320
+ // FAQ 列表卡片 - 直接使用后端数据
321
+ contentBlocks.push({
322
+ type: 'faq_list',
323
+ data: block.data,
324
+ })
325
+ } else if (block.type === 'promotion_list' && block.data?.found !== undefined) {
326
+ // 促销活动列表卡片 - 直接使用后端数据
327
+ contentBlocks.push({
328
+ type: 'promotion_list',
329
+ data: {
330
+ ...block.data,
331
+ commonText: mergedText,
332
+ },
333
+ })
334
+ } else if (block.type === 'cart' && block.data?.id !== undefined) {
335
+ // 购物车卡片 - 转换后端数据格式并注入 onCart 回调
336
+ const transformedData = transformCartData(block.data as BackendCartData)
337
+ contentBlocks.push({
338
+ type: 'cart',
339
+ data: {
340
+ ...transformedData,
341
+ onCart: onCart,
342
+ commonText: mergedText,
343
+ },
344
+ })
345
+ } else {
346
+ // 其他类型直接添加
347
+ contentBlocks.push(block)
348
+ }
349
+ })
350
+ }
351
+
352
+ // 如果没有任何内容块,返回空文本块
353
+ if (contentBlocks.length === 0) {
354
+ contentBlocks.push({
355
+ type: 'text',
356
+ text: '',
357
+ })
358
+ }
359
+
360
+ return {
361
+ ...message,
362
+ content: contentBlocks,
363
+ } as Message
364
+ },
365
+ [site, onCart, onAddToCart, mergedText]
366
+ )
367
+
368
+ /**
369
+ * 创建新会话
370
+ */
371
+ const handleCreateNewSession = useCallback(async () => {
372
+ if (!userId) return
373
+
374
+ try {
375
+ const response = await createSession({
376
+ user_id: userId,
377
+ site: site,
378
+ channel_code: channelCode,
379
+ real_user_id: loginUserId,
380
+ })
381
+
382
+ if (response.success) {
383
+ // 保存新会话 ID
384
+ saveSession(response.sessionId)
385
+
386
+ // 清空消息列表
387
+ clearMessages()
388
+
389
+ // 使用后端返回的欢迎消息,如果没有则使用 props 中的默认值
390
+ const messageText = response.welcomeMessage || welcomeMessage
391
+
392
+ if (messageText) {
393
+ const welcomeContent: MessageContent[] = [{ type: 'text', text: messageText }]
394
+
395
+ // 使用后端返回的快捷问题,如果没有则使用 props 中的快捷回复
396
+ const questions = response.quickQuestions
397
+ if (questions && questions.length > 0) {
398
+ // 将后端的 quickQuestions (字符串数组) 转换为 QuickReply 格式
399
+ const quickRepliesFromBackend = questions.map((question, index) => ({
400
+ id: `quick-${index}`,
401
+ label: question,
402
+ value: question,
403
+ }))
404
+
405
+ welcomeContent.push({
406
+ type: 'quick_replies',
407
+ data: {
408
+ replies: quickRepliesFromBackend,
409
+ },
410
+ })
411
+ } else if (quickReplies && quickReplies.length > 0) {
412
+ // 如果后端没有返回,使用 props 中的快捷回复
413
+ welcomeContent.push({
414
+ type: 'quick_replies',
415
+ data: {
416
+ replies: quickReplies,
417
+ },
418
+ })
419
+ }
420
+
421
+ addMessage({
422
+ id: `welcome-${Date.now()}`,
423
+ role: 'assistant',
424
+ content: welcomeContent,
425
+ timestamp: Date.now(),
426
+ })
427
+ }
428
+ }
429
+ } catch (error) {
430
+ console.error('[LiveChatWidget] Failed to create new session:', error)
431
+ onError?.(error as Error)
432
+
433
+ // Check if it's a reCAPTCHA error
434
+ const isRecaptcha = (error as any)?.type === 'GoogleRecaptchaError'
435
+
436
+ // 添加错误消息到界面
437
+ addMessage({
438
+ id: `error-${Date.now()}`,
439
+ role: 'system',
440
+ content: [
441
+ {
442
+ type: 'error',
443
+ data: {
444
+ message: isRecaptcha
445
+ ? 'Your session has expired. Please refresh the page and try again.'
446
+ : 'Failed to create session. Please refresh the page and try again.',
447
+ code: isRecaptcha ? 'RECAPTCHA_ERROR' : 'SESSION_CREATE_ERROR',
448
+ },
449
+ },
450
+ ],
451
+ timestamp: Date.now(),
452
+ })
453
+ }
454
+ }, [
455
+ userId,
456
+ site,
457
+ channelCode,
458
+ loginUserId,
459
+ createSession,
460
+ saveSession,
461
+ clearMessages,
462
+ welcomeMessage,
463
+ quickReplies,
464
+ addMessage,
465
+ onError,
466
+ ])
467
+
468
+ /**
469
+ * 恢复会话和历史消息(使用新的 API v2.0.0)
470
+ */
471
+ const handleResumeSession = useCallback(
472
+ async (existingSessionId: string) => {
473
+ try {
474
+ const response = await createSession({
475
+ user_id: userId,
476
+ session_id: existingSessionId,
477
+ site: site,
478
+ channel_code: channelCode,
479
+ real_user_id: loginUserId,
480
+ })
481
+
482
+ if (response.success && response.resumed) {
483
+ // 会话恢复成功
484
+
485
+ // 准备欢迎消息(无论是否有历史消息都需要)
486
+ const messageText = response.welcomeMessage || welcomeMessage
487
+ const welcomeContent: MessageContent[] = messageText ? [{ type: 'text', text: messageText }] : []
488
+
489
+ // 添加快捷回复到欢迎消息
490
+ const questions = response.quickQuestions
491
+ if (questions && questions.length > 0) {
492
+ const quickRepliesFromBackend = questions.map((question, index) => ({
493
+ id: `quick-${index}`,
494
+ label: question,
495
+ value: question,
496
+ }))
497
+
498
+ welcomeContent.push({
499
+ type: 'quick_replies',
500
+ data: {
501
+ replies: quickRepliesFromBackend,
502
+ },
503
+ })
504
+ } else if (quickReplies && quickReplies.length > 0) {
505
+ welcomeContent.push({
506
+ type: 'quick_replies',
507
+ data: {
508
+ replies: quickReplies,
509
+ },
510
+ })
511
+ }
512
+
513
+ if (response.messages && response.messages.length > 0) {
514
+ // 有历史消息,规范化并加载(过滤掉 null/undefined 值和空内容消息)
515
+ const normalizedMessages = response.messages
516
+ .filter((msg: any) => msg != null)
517
+ .map(normalizeMessage)
518
+ .filter((msg: Message) => msg.content && msg.content.length > 0 && !(msg.content.length === 1 && msg.content[0].type === 'text' && !msg.content[0].text))
519
+
520
+ // 如果有欢迎消息,将其添加到历史消息的开头
521
+ if (welcomeContent.length > 0) {
522
+ const welcomeMsg: Message = {
523
+ id: `welcome-${Date.now()}`,
524
+ role: 'assistant',
525
+ content: welcomeContent,
526
+ timestamp: Date.now(),
527
+ }
528
+ setMessages([welcomeMsg, ...normalizedMessages])
529
+ } else {
530
+ setMessages(normalizedMessages)
531
+ }
532
+ } else {
533
+ // 没有历史消息,仅显示欢迎消息
534
+ clearMessages()
535
+
536
+ if (welcomeContent.length > 0) {
537
+ addMessage({
538
+ id: `welcome-${Date.now()}`,
539
+ role: 'assistant',
540
+ content: welcomeContent,
541
+ timestamp: Date.now(),
542
+ })
543
+ }
544
+ }
545
+ } else if (!response.resumed) {
546
+ // 会话无效或过期,创建新会话
547
+ clearSession()
548
+ handleCreateNewSession()
549
+ }
550
+ } catch (error) {
551
+ console.error('[LiveChatWidget] Failed to resume session:', error)
552
+
553
+ // Check if it's a reCAPTCHA error
554
+ const isRecaptcha = (error as any)?.type === 'GoogleRecaptchaError'
555
+
556
+ if (isRecaptcha) {
557
+ // reCAPTCHA error - show refresh page message, don't retry
558
+ addMessage({
559
+ id: `error-${Date.now()}`,
560
+ role: 'system',
561
+ content: [
562
+ {
563
+ type: 'error',
564
+ data: {
565
+ message: 'Your session has expired. Please refresh the page and try again.',
566
+ code: 'RECAPTCHA_ERROR',
567
+ },
568
+ },
569
+ ],
570
+ timestamp: Date.now(),
571
+ })
572
+ } else {
573
+ // Other errors - show message and try to create new session
574
+ addMessage({
575
+ id: `error-${Date.now()}`,
576
+ role: 'system',
577
+ content: [
578
+ {
579
+ type: 'error',
580
+ data: {
581
+ message: 'Failed to resume session. Creating a new session...',
582
+ code: 'SESSION_RESUME_ERROR',
583
+ },
584
+ },
585
+ ],
586
+ timestamp: Date.now(),
587
+ })
588
+
589
+ // 恢复失败,清空会话并创建新的
590
+ clearSession()
591
+ handleCreateNewSession()
592
+ }
593
+ }
594
+ },
595
+ [
596
+ userId,
597
+ site,
598
+ channelCode,
599
+ loginUserId,
600
+ createSession,
601
+ setMessages,
602
+ clearSession,
603
+ clearMessages,
604
+ normalizeMessage,
605
+ handleCreateNewSession,
606
+ welcomeMessage,
607
+ quickReplies,
608
+ addMessage,
609
+ ]
610
+ )
611
+
612
+ /**
613
+ * 发送消息
614
+ */
615
+ const handleSendMessage = useCallback(
616
+ async (message?: string, isRetry: boolean = false) => {
617
+ const textToSend = message || inputValue.trim()
618
+
619
+ if (!textToSend) return
620
+
621
+ // 清空输入框(仅在非重试时)
622
+ if (!message && !isRetry) {
623
+ setInputValue('')
624
+ }
625
+
626
+ // 输入验证
627
+ const sanitized = sanitizeInput(textToSend)
628
+ if (!sanitized) {
629
+ onError?.(new Error('Invalid message'))
630
+ return
631
+ }
632
+
633
+ // 添加用户消息到界面(仅在非重试时)
634
+ if (!isRetry) {
635
+ const userMessage = {
636
+ id: `user-${Date.now()}`,
637
+ role: 'user' as const,
638
+ content: [{ type: 'text' as const, text: sanitized }],
639
+ timestamp: Date.now(),
640
+ }
641
+ addMessage(userMessage)
642
+ }
643
+
644
+ // 立即添加思考状态消息,提升用户体验
645
+ const thinkingMessage = {
646
+ id: `thinking-${Date.now()}`,
647
+ role: 'assistant' as const,
648
+ content: [{ type: 'thinking' as const, data: { status: 'thinking' } }],
649
+ timestamp: Date.now(),
650
+ }
651
+ addMessage(thinkingMessage)
652
+
653
+ // 触发消息发送回调(仅在非重试时)
654
+ if (!isRetry) {
655
+ onMessageSend?.(sanitized)
656
+ }
657
+
658
+ // 标记是否检测到会话过期
659
+ let sessionExpiredDetected = false
660
+
661
+ try {
662
+ // 确保有 sessionId,如果没有则先创建会话
663
+ let currentSessionId = sessionId
664
+ if (!currentSessionId) {
665
+ // 没有会话,创建新会话
666
+ const response = await createSession({
667
+ user_id: userId,
668
+ site: site,
669
+ channel_code: channelCode,
670
+ real_user_id: loginUserId,
671
+ })
672
+ if (response.success) {
673
+ currentSessionId = response.sessionId
674
+ saveSession(currentSessionId)
675
+ } else {
676
+ throw new Error('Failed to create session')
677
+ }
678
+ }
679
+
680
+ // 构建请求参数(session_id 现在是必填的)
681
+ const requestPayload: ChatStreamRequest = {
682
+ message: sanitized,
683
+ user_id: userId,
684
+ session_id: currentSessionId,
685
+ context: {
686
+ cartId: cartId,
687
+ accessToken: accessToken,
688
+ real_user_id: loginUserId,
689
+ },
690
+ }
691
+
692
+ // 发送消息到后端
693
+ await sendMessageStream(requestPayload, event => {
694
+ // 处理 SSE 事件
695
+ handleSSEEvent(event)
696
+
697
+ // 特殊处理:会话过期(error 事件且 type 为 validation_error)
698
+ if (event.event === 'error' && event.data.type === 'validation_error') {
699
+ sessionExpiredDetected = true
700
+ clearSession()
701
+ }
702
+ })
703
+
704
+ // 如果检测到会话过期且不是重试,自动创建新会话并重试
705
+ if (sessionExpiredDetected && !isRetry) {
706
+ console.log('[LiveChatWidget] Session expired (validation_error), creating new session and retrying...')
707
+
708
+ // 移除 thinking 消息
709
+ const messagesWithoutThinking = messages.filter(msg => msg.id !== thinkingMessage.id)
710
+ setMessages(messagesWithoutThinking)
711
+
712
+ // 创建新会话
713
+ const response = await createSession({
714
+ user_id: userId,
715
+ site: site,
716
+ channel_code: channelCode,
717
+ real_user_id: loginUserId,
718
+ })
719
+
720
+ if (response.success) {
721
+ saveSession(response.sessionId)
722
+ // 重试发送消息
723
+ await handleSendMessage(sanitized, true)
724
+ } else {
725
+ throw new Error('Failed to recreate session after expiration')
726
+ }
727
+ }
728
+ } catch (error) {
729
+ console.error('[LiveChatWidget] Failed to send message:', error)
730
+ onError?.(error as Error)
731
+
732
+ // Check if it's a reCAPTCHA error
733
+ const isRecaptcha = (error as any)?.type === 'GoogleRecaptchaError'
734
+
735
+ // Determine error message based on error type
736
+ let errorMessage: string
737
+ let errorCode: string
738
+
739
+ if (isRecaptcha) {
740
+ errorMessage = 'Your session has expired. Please refresh the page and try again.'
741
+ errorCode = 'RECAPTCHA_ERROR'
742
+ } else if (sessionExpiredDetected) {
743
+ errorMessage = 'Your session has expired. We tried to reconnect but failed. Please try again.'
744
+ errorCode = 'SESSION_EXPIRED'
745
+ } else {
746
+ errorMessage = 'Failed to send message. Please check your network connection and try again.'
747
+ errorCode = 'NETWORK_ERROR'
748
+ }
749
+
750
+ // 移除刚才添加的 thinking 消息,并添加错误消息
751
+ const messagesWithoutThinking = messages.filter(msg => msg.id !== thinkingMessage.id)
752
+ setMessages([
753
+ ...messagesWithoutThinking,
754
+ {
755
+ id: `error-${Date.now()}`,
756
+ role: 'system',
757
+ content: [
758
+ {
759
+ type: 'error',
760
+ data: {
761
+ message: errorMessage,
762
+ code: errorCode,
763
+ },
764
+ },
765
+ ],
766
+ timestamp: Date.now(),
767
+ },
768
+ ])
769
+ }
770
+ },
771
+ [
772
+ inputValue,
773
+ userId,
774
+ sessionId,
775
+ site,
776
+ channelCode,
777
+ loginUserId,
778
+ cartId,
779
+ accessToken,
780
+ messages,
781
+ setInputValue,
782
+ addMessage,
783
+ setMessages,
784
+ createSession,
785
+ sendMessageStream,
786
+ handleSSEEvent,
787
+ saveSession,
788
+ clearSession,
789
+ onMessageSend,
790
+ onError,
791
+ ]
792
+ )
793
+
794
+ // 更新 ref 以保持最新的 handleSendMessage
795
+ React.useEffect(() => {
796
+ handleSendMessageRef.current = handleSendMessage
797
+ }, [handleSendMessage])
798
+
799
+ /**
800
+ * 处理气泡按钮点击
801
+ * 如果配置了法规协议且用户未同意,先显示法规弹窗
802
+ * 否则直接打开聊天窗口
803
+ */
804
+ const handleBubbleClick = useCallback(() => {
805
+ if (complianceConfig && !hasAgreedCompliance) {
806
+ // 显示法规协议弹窗
807
+ setShowComplianceDialog(true)
808
+ } else {
809
+ // 直接打开聊天窗口
810
+ openChat()
811
+ }
812
+ }, [complianceConfig, hasAgreedCompliance, openChat])
813
+
814
+ /**
815
+ * 处理用户同意法规协议
816
+ */
817
+ const handleComplianceAgree = useCallback(() => {
818
+ // 设置同意状态
819
+ setHasAgreedCompliance(true)
820
+ setShowComplianceDialog(false)
821
+
822
+ // 保存到 Cookie(有效期 365 天)
823
+ Cookies.set(cookieName, 'true', { expires: 365 })
824
+
825
+ // 同意后立即打开聊天窗口
826
+ openChat()
827
+ }, [openChat, cookieName])
828
+
829
+ /**
830
+ * 处理法规弹窗关闭
831
+ */
832
+ const handleComplianceClose = useCallback(() => {
833
+ setShowComplianceDialog(false)
834
+ }, [])
835
+
836
+ return (
837
+ <>
838
+ {/* 法规协议弹窗 */}
839
+ {complianceConfig && (
840
+ <ComplianceDialog
841
+ open={showComplianceDialog}
842
+ config={complianceConfig}
843
+ onAgree={handleComplianceAgree}
844
+ onClose={handleComplianceClose}
845
+ />
846
+ )}
847
+
848
+ {/* 气泡按钮 */}
849
+ <ChatBubble
850
+ position={position}
851
+ onClick={handleBubbleClick}
852
+ visible={!isOpen && !showComplianceDialog}
853
+ iconImageUrl={chatBubbleIcon}
854
+ />
855
+
856
+ {/* 聊天窗口(使用 Radix UI Dialog) */}
857
+ <Dialog.Root open={isOpen} onOpenChange={open => (open ? openChat() : closeChat())}>
858
+ <Dialog.Portal>
859
+ <Dialog.Content
860
+ className="livechat-window-enter"
861
+ style={{
862
+ position: 'fixed',
863
+ zIndex: 9998,
864
+ }}
865
+ >
866
+ <ChatWindow
867
+ messages={messages}
868
+ inputValue={inputValue}
869
+ onInputChange={setInputValue}
870
+ onSend={() => handleSendMessage()}
871
+ onClose={closeChat}
872
+ onNewSession={handleCreateNewSession}
873
+ title={title}
874
+ logoUrl={logoUrl}
875
+ isSending={isStreaming}
876
+ rendererRegistry={rendererRegistry}
877
+ inputPlaceholder=""
878
+ onAddToCart={onAddToCart}
879
+ showNewSessionButton={showNewSessionButton}
880
+ bottomTips={bottomTips}
881
+ />
882
+ </Dialog.Content>
883
+ </Dialog.Portal>
884
+ </Dialog.Root>
885
+ </>
886
+ )
887
+ }