@anker-in/campaign-ui 0.3.2 → 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 +8 -1
  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,253 @@
1
+ /**
2
+ * 聊天输入框组件
3
+ * 提供文本输入和发送按钮
4
+ * 基于 specs/livechat-widget/plan.md 的输入框设计
5
+ */
6
+
7
+ import React, { useRef, useEffect } from 'react'
8
+
9
+ export interface ChatInputProps {
10
+ /**
11
+ * 输入框当前值
12
+ */
13
+ value: string
14
+
15
+ /**
16
+ * 值变化回调
17
+ */
18
+ onChange: (value: string) => void
19
+
20
+ /**
21
+ * 发送消息回调
22
+ */
23
+ onSend: () => void
24
+
25
+ /**
26
+ * 占位符文本
27
+ * @default "输入消息..."
28
+ */
29
+ placeholder?: string
30
+
31
+ /**
32
+ * 是否禁用输入
33
+ * @default false
34
+ */
35
+ disabled?: boolean
36
+
37
+ /**
38
+ * 是否自动聚焦
39
+ * @default false
40
+ */
41
+ autoFocus?: boolean
42
+
43
+ /**
44
+ * 最大字符数
45
+ * @default 5000
46
+ */
47
+ maxLength?: number
48
+
49
+ /**
50
+ * 自定义样式类名
51
+ */
52
+ className?: string
53
+
54
+ /**
55
+ * 底部提示文本
56
+ * 不传入则不显示
57
+ */
58
+ bottomTips?: string
59
+ }
60
+
61
+ /**
62
+ * 发送图标 (向上箭头)
63
+ */
64
+ const SendIcon: React.FC = () => (
65
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
66
+ <path d="M10.2069 15.6997V4.69971" stroke="white" stroke-width="2" stroke-linecap="round" />
67
+ <path
68
+ d="M15.3995 8.50341L10.9506 4.05446C10.534 3.6379 9.85866 3.63791 9.4421 4.05446L5.00005 8.49651"
69
+ stroke="white"
70
+ stroke-width="2"
71
+ stroke-linecap="round"
72
+ stroke-linejoin="round"
73
+ />
74
+ </svg>
75
+ )
76
+
77
+ /**
78
+ * 聊天输入框组件
79
+ *
80
+ * 功能:
81
+ * - 多行文本输入(自动增长)
82
+ * - Enter 发送,Shift+Enter 换行
83
+ * - 字符数限制(5000)
84
+ * - 禁用状态(发送中)
85
+ * - 发送按钮(空内容时禁用)
86
+ *
87
+ * 样式:
88
+ * - 固定底部
89
+ * - 边框分隔上方内容
90
+ * - 自适应高度(1-5 行)
91
+ *
92
+ * @example
93
+ * ```tsx
94
+ * <ChatInput
95
+ * value={inputValue}
96
+ * onChange={setInputValue}
97
+ * onSend={handleSend}
98
+ * disabled={isStreaming}
99
+ * />
100
+ * ```
101
+ */
102
+ export const ChatInput: React.FC<ChatInputProps> = ({
103
+ value,
104
+ onChange,
105
+ onSend,
106
+ placeholder = '',
107
+ disabled = false,
108
+ autoFocus = false,
109
+ maxLength = 5000,
110
+ bottomTips,
111
+ className = '',
112
+ }) => {
113
+ const textareaRef = useRef<HTMLTextAreaElement>(null)
114
+ // 跟踪输入法状态(是否正在使用拼音输入等)
115
+ const isComposingRef = useRef(false)
116
+
117
+ /**
118
+ * 自动调整 textarea 高度
119
+ */
120
+ useEffect(() => {
121
+ const textarea = textareaRef.current
122
+ if (!textarea) return
123
+
124
+ // 重置高度以获取正确的 scrollHeight
125
+ textarea.style.height = 'auto'
126
+
127
+ // 设置高度,最大 5 行(约 120px)
128
+ const maxHeight = 120
129
+ const newHeight = Math.min(textarea.scrollHeight, maxHeight)
130
+ textarea.style.height = `${newHeight}px`
131
+ }, [value])
132
+
133
+ /**
134
+ * 处理输入法开始
135
+ */
136
+ const handleCompositionStart = () => {
137
+ isComposingRef.current = true
138
+ }
139
+
140
+ /**
141
+ * 处理输入法结束
142
+ */
143
+ const handleCompositionEnd = () => {
144
+ isComposingRef.current = false
145
+ }
146
+
147
+ /**
148
+ * 处理键盘事件
149
+ * Enter 发送,Shift+Enter 换行
150
+ * 注意:输入法激活时(如拼音输入中),回车键不应发送消息
151
+ */
152
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
153
+ if (event.key === 'Enter' && !event.shiftKey) {
154
+ // 如果正在使用输入法(如拼音输入),则不发送消息
155
+ // 让输入法先完成字符选择
156
+ if (isComposingRef.current) {
157
+ return
158
+ }
159
+
160
+ event.preventDefault()
161
+ if (value.trim() && !disabled) {
162
+ onSend()
163
+ }
164
+ }
165
+ }
166
+
167
+ /**
168
+ * 处理输入变化
169
+ */
170
+ const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
171
+ const newValue = event.target.value
172
+
173
+ // 字符数限制
174
+ if (newValue.length <= maxLength) {
175
+ onChange(newValue)
176
+ }
177
+ }
178
+
179
+ /**
180
+ * 处理发送按钮点击
181
+ */
182
+ const handleSendClick = () => {
183
+ if (value.trim() && !disabled) {
184
+ onSend()
185
+ }
186
+ }
187
+
188
+ const canSend = value.trim().length > 0 && !disabled
189
+
190
+ return (
191
+ <div className={`flex flex-col gap-2 bg-white px-4 py-4 ${className}`}>
192
+ {/* 输入框容器 - 带渐变边框 */}
193
+ <div
194
+ className="flex items-center gap-2 rounded-2xl"
195
+ style={{
196
+ background: 'linear-gradient(to right, #7687F3, #7687F3, #4DA8F5, #A3DFCE)',
197
+ padding: '2px',
198
+ }}
199
+ >
200
+ {/* 内部白色背景 */}
201
+ <div className="flex flex-1 items-center gap-2 bg-white px-3 py-2" style={{ borderRadius: '14px' }}>
202
+ {/* 文本输入框 */}
203
+ <textarea
204
+ ref={textareaRef}
205
+ value={value}
206
+ onChange={handleChange}
207
+ onKeyDown={handleKeyDown}
208
+ onCompositionStart={handleCompositionStart}
209
+ onCompositionEnd={handleCompositionEnd}
210
+ placeholder={placeholder}
211
+ disabled={disabled}
212
+ autoFocus={autoFocus}
213
+ rows={1}
214
+ className="flex-1 resize-none bg-transparent font-bold text-sm text-gray-900 outline-none placeholder:text-gray-400 disabled:cursor-not-allowed disabled:opacity-50"
215
+ style={{
216
+ resize: 'none',
217
+ }}
218
+ />
219
+
220
+ {/* 字符数提示 (接近上限时显示) */}
221
+ {value.length > maxLength * 0.8 && (
222
+ <span className="text-xs text-gray-400">
223
+ {value.length}/{maxLength}
224
+ </span>
225
+ )}
226
+
227
+ {/* 发送按钮 */}
228
+ <button
229
+ type="button"
230
+ onClick={handleSendClick}
231
+ disabled={!canSend}
232
+ className={`shrink-0 rounded-full p-2 transition-all ${canSend ? ' active:scale-95' : 'cursor-not-allowed '}`}
233
+ style={
234
+ canSend
235
+ ? {
236
+ background: 'linear-gradient(to bottom, #A3DFCE, #4DA8F5, #7687F3)',
237
+ }
238
+ : { background: '#BEBEBE' }
239
+ }
240
+ aria-label="Send message"
241
+ >
242
+ <SendIcon />
243
+ </button>
244
+ </div>
245
+ </div>
246
+ {bottomTips && (
247
+ <p className="px-3 font-bold text-[12px] text-[#767880] tracking-tight leading-[1.4]">
248
+ {bottomTips}
249
+ </p>
250
+ )}
251
+ </div>
252
+ )
253
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * 单条聊天消息组件
3
+ * 显示消息气泡、发送者、时间戳
4
+ * 基于 specs/livechat-widget/plan.md 的消息展示设计
5
+ */
6
+
7
+ import React from 'react'
8
+ import type { Message, MessageRenderer } from '../types'
9
+ import { MessageContent } from './MessageContent'
10
+ import { MessageRendererRegistry } from '../utils/messageRenderers'
11
+ import cn from 'classnames'
12
+ export interface ChatMessageProps {
13
+ /**
14
+ * 消息数据
15
+ */
16
+ message: Message
17
+
18
+ /**
19
+ * 自定义渲染器注册表
20
+ */
21
+ rendererRegistry?: MessageRendererRegistry
22
+
23
+ /**
24
+ * 默认渲染器
25
+ */
26
+ defaultRenderer?: MessageRenderer
27
+
28
+ /**
29
+ * 是否显示时间戳
30
+ * @default true
31
+ */
32
+ showTimestamp?: boolean
33
+
34
+ /**
35
+ * 自定义样式类名
36
+ */
37
+ className?: string
38
+
39
+ /**
40
+ * 商品添加到购物车回调
41
+ */
42
+ onAddToCart?: (product: any) => void
43
+ }
44
+
45
+ /**
46
+ * 格式化时间戳为可读格式
47
+ * @param timestamp Unix 时间戳(毫秒)
48
+ * @returns 格式化后的时间字符串 (如 "14:30")
49
+ */
50
+ function formatTimestamp(timestamp: number): string {
51
+ const date = new Date(timestamp)
52
+ const hours = date.getHours().toString().padStart(2, '0')
53
+ const minutes = date.getMinutes().toString().padStart(2, '0')
54
+ return `${hours}:${minutes}`
55
+ }
56
+
57
+ /**
58
+ * 单条聊天消息组件
59
+ *
60
+ * 功能:
61
+ * - 显示消息气泡(用户 vs AI 助手不同样式)
62
+ * - 支持多个 content 块(一条消息可包含多种内容类型)
63
+ * - 显示时间戳
64
+ * - 系统消息居中显示
65
+ *
66
+ * 样式规则:
67
+ * - 用户消息:右对齐,灰色背景
68
+ * - AI 助手消息:左对齐,白色背景
69
+ * - 系统消息:居中,黄色背景
70
+ *
71
+ * @example
72
+ * ```tsx
73
+ * <ChatMessage
74
+ * message={message}
75
+ * rendererRegistry={customRegistry}
76
+ * showTimestamp={true}
77
+ * />
78
+ * ```
79
+ */
80
+ export const ChatMessage: React.FC<ChatMessageProps> = ({
81
+ message,
82
+ rendererRegistry,
83
+ defaultRenderer,
84
+ showTimestamp = true,
85
+ className = '',
86
+ onAddToCart,
87
+ }) => {
88
+ const isUser = message.role === 'user'
89
+ const isAssistant = message.role === 'assistant'
90
+ const isSystem = message.role === 'system'
91
+ const isTool = message.role === 'tool'
92
+
93
+ // 系统消息特殊处理(居中显示)
94
+ if (isSystem) {
95
+ return (
96
+ <div className={`flex justify-center py-2 ${className}`}>
97
+ <div className="">
98
+ {message.content.map((content, index) => (
99
+ <MessageContent
100
+ key={index}
101
+ content={content}
102
+ isUser={false}
103
+ isSystem={true}
104
+ rendererRegistry={rendererRegistry}
105
+ defaultRenderer={defaultRenderer}
106
+ />
107
+ ))}
108
+ </div>
109
+ </div>
110
+ )
111
+ }
112
+
113
+ // 工具消息(通常不显示,或显示为系统消息)
114
+ if (isTool) {
115
+ return null
116
+ }
117
+
118
+ // 分离内容类型:需要独立渲染的结构化内容
119
+ // 注意:product_card 不在此列表中,它会在气泡内渲染
120
+ const structuredContentTypes = ['product_list', 'product_comparison', 'faq_list', 'promotion_list', 'cart']
121
+
122
+ // 将内容分为两组:气泡内容 和 独立内容
123
+ const bubbleContent = message.content.filter(c => !structuredContentTypes.includes(c.type))
124
+ const standaloneContent = message.content.filter(c => structuredContentTypes.includes(c.type))
125
+ const isBubbleProductCard = bubbleContent.some(c => c.type === 'product_card')
126
+ // 用户/助手消息
127
+ return (
128
+ <div className={`flex ${isUser ? 'justify-end' : 'justify-start'} py-2 ${className}`}>
129
+ <div className="flex w-fit max-w-full flex-col gap-2">
130
+ {/* 消息气泡(包含文本和产品卡片,按原始顺序) */}
131
+ {bubbleContent.length > 0 && (
132
+ <div
133
+ className={cn(
134
+ 'w-fit min-w-0 overflow-hidden rounded-2xl ',
135
+ isUser ? 'text-white' : 'text-[#1D1D1F]',
136
+ isBubbleProductCard ? 'p-4' : 'p-3'
137
+ )}
138
+ style={{
139
+ backgroundColor: isUser ? '#005D8E' : '#F5F6F7',
140
+ }}
141
+ >
142
+ {/* 按原始顺序渲染气泡内的所有内容(text 和 product_card 交替) */}
143
+ <div className={cn(
144
+ 'flex w-full min-w-0 flex-col',
145
+ isBubbleProductCard ?'gap-0':'gap-2'
146
+ )}>
147
+ {bubbleContent.map((content, index) => (
148
+ <MessageContent
149
+ key={index}
150
+ content={content}
151
+ isUser={isUser}
152
+ isSystem={false}
153
+ rendererRegistry={rendererRegistry}
154
+ defaultRenderer={defaultRenderer}
155
+ onAddToCart={onAddToCart}
156
+ />
157
+ ))}
158
+ </div>
159
+ </div>
160
+ )}
161
+
162
+ {/* 独立渲染的结构化内容(不在气泡内) */}
163
+ {standaloneContent.map((content, index) => (
164
+ <div key={`standalone-${index}`} className="w-full">
165
+ <MessageContent
166
+ content={content}
167
+ isUser={isUser}
168
+ isSystem={false}
169
+ rendererRegistry={rendererRegistry}
170
+ defaultRenderer={defaultRenderer}
171
+ onAddToCart={onAddToCart}
172
+ />
173
+ </div>
174
+ ))}
175
+
176
+ {/* 时间戳 */}
177
+ {showTimestamp && (
178
+ <span
179
+ className={`
180
+ px-2 text-xs text-gray-400
181
+ ${isUser ? 'text-right' : 'text-left'}
182
+ `}
183
+ >
184
+ {formatTimestamp(message.timestamp)}
185
+ </span>
186
+ )}
187
+ </div>
188
+ </div>
189
+ )
190
+ }