@anker-in/campaign-ui 0.3.5 → 0.3.6
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/hooks/useChatState.d.ts +6 -2
- package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js +1 -1
- package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js.map +2 -2
- 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 +2 -0
- package/dist/cjs/components/LiveChatWidget/types.js.map +1 -1
- package/dist/cjs/components/LiveChatWidget/utils/userId.d.ts +7 -2
- package/dist/cjs/components/LiveChatWidget/utils/userId.js +1 -1
- package/dist/cjs/components/LiveChatWidget/utils/userId.js.map +3 -3
- package/dist/cjs/stories/LiveChatWidget.stories.js +1 -1
- package/dist/cjs/stories/LiveChatWidget.stories.js.map +2 -2
- package/dist/esm/components/LiveChatWidget/LiveChatWidget.js +1 -1
- package/dist/esm/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/hooks/useChatState.d.ts +6 -2
- package/dist/esm/components/LiveChatWidget/hooks/useChatState.js +1 -1
- package/dist/esm/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/index.d.ts +1 -1
- package/dist/esm/components/LiveChatWidget/index.js +1 -1
- package/dist/esm/components/LiveChatWidget/index.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/types.d.ts +2 -0
- package/dist/esm/components/LiveChatWidget/utils/userId.d.ts +7 -2
- package/dist/esm/components/LiveChatWidget/utils/userId.js +1 -1
- package/dist/esm/components/LiveChatWidget/utils/userId.js.map +3 -3
- package/dist/esm/stories/LiveChatWidget.stories.js +1 -1
- package/dist/esm/stories/LiveChatWidget.stories.js.map +2 -2
- package/dist/index.d.mts +140 -13
- package/dist/index.d.ts +140 -13
- package/dist/index.js +2109 -6424
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1901 -6216
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/LiveChatWidget/LiveChatWidget.tsx +40 -7
- package/src/components/LiveChatWidget/hooks/useChatState.ts +18 -7
- package/src/components/LiveChatWidget/index.tsx +1 -1
- package/src/components/LiveChatWidget/types.ts +2 -0
- package/src/components/LiveChatWidget/utils/userId.ts +13 -62
- package/src/stories/LiveChatWidget.stories.tsx +1 -1
package/package.json
CHANGED
|
@@ -23,6 +23,7 @@ import { useChatState } from './hooks/useChatState'
|
|
|
23
23
|
import { useChatAPI } from './hooks/useChatAPI'
|
|
24
24
|
import { MessageRendererRegistry } from './utils/messageRenderers'
|
|
25
25
|
import { sanitizeInput } from './utils/validation'
|
|
26
|
+
import { saveUserId } from './utils/userId'
|
|
26
27
|
import { transformProducts } from './utils/productTransformers.js'
|
|
27
28
|
import { transformCartData } from './utils/cartTransformers.js'
|
|
28
29
|
import Cookies from 'js-cookie'
|
|
@@ -175,6 +176,7 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
|
|
|
175
176
|
openChat,
|
|
176
177
|
closeChat,
|
|
177
178
|
setInputValue,
|
|
179
|
+
setUserId,
|
|
178
180
|
addMessage,
|
|
179
181
|
setMessages,
|
|
180
182
|
clearMessages,
|
|
@@ -232,6 +234,9 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
|
|
|
232
234
|
return registry
|
|
233
235
|
}, [customRenderers])
|
|
234
236
|
|
|
237
|
+
// 标记是否已经初始化过会话,防止重复调用
|
|
238
|
+
const sessionInitializedRef = React.useRef(false)
|
|
239
|
+
|
|
235
240
|
/**
|
|
236
241
|
* T043: 打开聊天窗口时初始化会话
|
|
237
242
|
* 使用 API v2.0.0 的统一接口:
|
|
@@ -239,7 +244,18 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
|
|
|
239
244
|
* - 如果有 sessionId,恢复会话并加载历史消息
|
|
240
245
|
*/
|
|
241
246
|
useEffect(() => {
|
|
242
|
-
|
|
247
|
+
// 窗口关闭时重置初始化标记
|
|
248
|
+
if (!isOpen) {
|
|
249
|
+
sessionInitializedRef.current = false
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// userId 为 undefined 表示尚未初始化,等待初始化完成
|
|
254
|
+
if (userId === undefined) return
|
|
255
|
+
|
|
256
|
+
// 如果已经初始化过,不再重复调用
|
|
257
|
+
if (sessionInitializedRef.current) return
|
|
258
|
+
sessionInitializedRef.current = true
|
|
243
259
|
|
|
244
260
|
const currentSessionId = sessionId
|
|
245
261
|
|
|
@@ -372,7 +388,8 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
|
|
|
372
388
|
* 创建新会话
|
|
373
389
|
*/
|
|
374
390
|
const handleCreateNewSession = useCallback(async () => {
|
|
375
|
-
|
|
391
|
+
// userId 为 undefined 表示尚未初始化,等待初始化完成
|
|
392
|
+
if (userId === undefined) return
|
|
376
393
|
|
|
377
394
|
// 如果用户没有配置欢迎语,显示 loading 状态
|
|
378
395
|
if (!welcomeMessage) {
|
|
@@ -381,16 +398,23 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
|
|
|
381
398
|
|
|
382
399
|
try {
|
|
383
400
|
const response = await createSession({
|
|
384
|
-
user_id: userId,
|
|
401
|
+
user_id: userId ?? '',
|
|
385
402
|
site: site,
|
|
386
403
|
channel_code: channelCode,
|
|
387
404
|
real_user_id: loginUserId,
|
|
405
|
+
page_url: typeof window !== 'undefined' ? window.location.href : undefined,
|
|
388
406
|
})
|
|
389
407
|
|
|
390
408
|
if (response.success) {
|
|
391
409
|
// 保存新会话 ID
|
|
392
410
|
saveSession(response.sessionId)
|
|
393
411
|
|
|
412
|
+
// 保存后端返回的 userId(如果有)
|
|
413
|
+
if (response.userId) {
|
|
414
|
+
saveUserId(response.userId)
|
|
415
|
+
setUserId(response.userId)
|
|
416
|
+
}
|
|
417
|
+
|
|
394
418
|
// 清空消息列表
|
|
395
419
|
clearMessages()
|
|
396
420
|
|
|
@@ -488,16 +512,23 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
|
|
|
488
512
|
|
|
489
513
|
try {
|
|
490
514
|
const response = await createSession({
|
|
491
|
-
user_id: userId,
|
|
515
|
+
user_id: userId ?? '',
|
|
492
516
|
session_id: existingSessionId,
|
|
493
517
|
site: site,
|
|
494
518
|
channel_code: channelCode,
|
|
495
519
|
real_user_id: loginUserId,
|
|
520
|
+
page_url: typeof window !== 'undefined' ? window.location.href : undefined,
|
|
496
521
|
})
|
|
497
522
|
|
|
498
523
|
if (response.success && response.resumed) {
|
|
499
524
|
// 会话恢复成功
|
|
500
525
|
|
|
526
|
+
// 保存后端返回的 userId(如果有)
|
|
527
|
+
if (response.userId) {
|
|
528
|
+
saveUserId(response.userId)
|
|
529
|
+
setUserId(response.userId)
|
|
530
|
+
}
|
|
531
|
+
|
|
501
532
|
// 准备欢迎消息(无论是否有历史消息都需要)
|
|
502
533
|
const messageText = response.welcomeMessage || welcomeMessage
|
|
503
534
|
const welcomeContent: MessageContent[] = messageText ? [{ type: 'text', text: messageText }] : []
|
|
@@ -683,10 +714,11 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
|
|
|
683
714
|
if (!currentSessionId) {
|
|
684
715
|
// 没有会话,创建新会话
|
|
685
716
|
const response = await createSession({
|
|
686
|
-
user_id: userId,
|
|
717
|
+
user_id: userId ?? '',
|
|
687
718
|
site: site,
|
|
688
719
|
channel_code: channelCode,
|
|
689
720
|
real_user_id: loginUserId,
|
|
721
|
+
page_url: typeof window !== 'undefined' ? window.location.href : undefined,
|
|
690
722
|
})
|
|
691
723
|
if (response.success) {
|
|
692
724
|
currentSessionId = response.sessionId
|
|
@@ -699,7 +731,7 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
|
|
|
699
731
|
// 构建请求参数(session_id 现在是必填的)
|
|
700
732
|
const requestPayload: ChatStreamRequest = {
|
|
701
733
|
message: sanitized,
|
|
702
|
-
user_id: userId,
|
|
734
|
+
user_id: userId ?? '',
|
|
703
735
|
session_id: currentSessionId,
|
|
704
736
|
context: {
|
|
705
737
|
cartId: cartId,
|
|
@@ -730,10 +762,11 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
|
|
|
730
762
|
|
|
731
763
|
// 创建新会话
|
|
732
764
|
const response = await createSession({
|
|
733
|
-
user_id: userId,
|
|
765
|
+
user_id: userId ?? '',
|
|
734
766
|
site: site,
|
|
735
767
|
channel_code: channelCode,
|
|
736
768
|
real_user_id: loginUserId,
|
|
769
|
+
page_url: typeof window !== 'undefined' ? window.location.href : undefined,
|
|
737
770
|
})
|
|
738
771
|
|
|
739
772
|
if (response.success) {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { useState, useCallback, useRef, useEffect } from 'react'
|
|
8
8
|
import type { Message, MessageContent, SSEEvent, StatusData, ErrorData, MessageStartData, BackendCartData, Product, TextContent, ProductCardContent, ProductListContent } from '../types'
|
|
9
|
-
import { getUserId } from '../utils/userId'
|
|
9
|
+
import { getUserId, saveUserId } from '../utils/userId'
|
|
10
10
|
import { useSession } from './useSession'
|
|
11
11
|
import { transformProducts } from '../utils/productTransformers'
|
|
12
12
|
import { transformCartData } from '../utils/cartTransformers'
|
|
@@ -391,9 +391,9 @@ export interface UseChatStateReturn {
|
|
|
391
391
|
isOpen: boolean
|
|
392
392
|
|
|
393
393
|
/**
|
|
394
|
-
* 用户 ID
|
|
394
|
+
* 用户 ID(undefined 表示尚未初始化,空字符串表示由后端生成)
|
|
395
395
|
*/
|
|
396
|
-
userId: string
|
|
396
|
+
userId: string | undefined
|
|
397
397
|
|
|
398
398
|
/**
|
|
399
399
|
* 会话 ID
|
|
@@ -430,6 +430,11 @@ export interface UseChatStateReturn {
|
|
|
430
430
|
*/
|
|
431
431
|
setInputValue: (value: string) => void
|
|
432
432
|
|
|
433
|
+
/**
|
|
434
|
+
* 设置用户 ID(用于保存后端返回的 userId)
|
|
435
|
+
*/
|
|
436
|
+
setUserId: (id: string) => void
|
|
437
|
+
|
|
433
438
|
/**
|
|
434
439
|
* 添加消息到列表
|
|
435
440
|
*/
|
|
@@ -495,8 +500,8 @@ export function useChatState(options: UseChatStateOptions = {}): UseChatStateRet
|
|
|
495
500
|
// 会话管理
|
|
496
501
|
const { sessionId, saveSession, clearSession } = useSession()
|
|
497
502
|
|
|
498
|
-
// 用户 ID (
|
|
499
|
-
const [userId, setUserId] = useState<string>(
|
|
503
|
+
// 用户 ID (初始化时异步生成,undefined 表示尚未初始化)
|
|
504
|
+
const [userId, setUserId] = useState<string | undefined>(undefined)
|
|
500
505
|
|
|
501
506
|
// 初始化 userId
|
|
502
507
|
useEffect(() => {
|
|
@@ -646,11 +651,16 @@ export function useChatState(options: UseChatStateOptions = {}): UseChatStateRet
|
|
|
646
651
|
// 重置卡片缓存队列
|
|
647
652
|
pendingCardsRef.current = []
|
|
648
653
|
|
|
649
|
-
// T039: 保存 sessionId(如果后端返回)
|
|
650
|
-
const messageStartData = data as MessageStartData
|
|
654
|
+
// T039: 保存 sessionId 和 userId(如果后端返回)
|
|
655
|
+
const messageStartData = data as MessageStartData & { userId?: string }
|
|
651
656
|
if (messageStartData.sessionId && messageStartData.sessionId !== sessionId) {
|
|
652
657
|
saveSession(messageStartData.sessionId)
|
|
653
658
|
}
|
|
659
|
+
// 保存后端返回的 userId(如果有)
|
|
660
|
+
if (messageStartData.userId) {
|
|
661
|
+
saveUserId(messageStartData.userId)
|
|
662
|
+
setUserId(messageStartData.userId)
|
|
663
|
+
}
|
|
654
664
|
|
|
655
665
|
// 检查最后一条消息是否是 thinking 消息(用户发送消息时已添加)
|
|
656
666
|
setMessagesState(prev => {
|
|
@@ -1081,6 +1091,7 @@ export function useChatState(options: UseChatStateOptions = {}): UseChatStateRet
|
|
|
1081
1091
|
closeChat,
|
|
1082
1092
|
toggleChat,
|
|
1083
1093
|
setInputValue,
|
|
1094
|
+
setUserId,
|
|
1084
1095
|
addMessage,
|
|
1085
1096
|
setMessages,
|
|
1086
1097
|
clearMessages,
|
|
@@ -44,7 +44,7 @@ export { useSession } from './hooks/useSession'
|
|
|
44
44
|
|
|
45
45
|
// 导出工具类
|
|
46
46
|
export { MessageRendererRegistry } from './utils/messageRenderers'
|
|
47
|
-
export { getUserId } from './utils/userId'
|
|
47
|
+
export { getUserId, saveUserId, clearUserId } from './utils/userId'
|
|
48
48
|
export { sanitizeInput, isValidUrl, isValidUUID, isValidMessageContent, escapeHtml } from './utils/validation'
|
|
49
49
|
|
|
50
50
|
// 导出消息渲染器(供自定义使用)
|
|
@@ -637,11 +637,13 @@ export interface NewSessionRequest {
|
|
|
637
637
|
site?: string
|
|
638
638
|
channel_code?: string
|
|
639
639
|
real_user_id?: string
|
|
640
|
+
page_url?: string
|
|
640
641
|
}
|
|
641
642
|
|
|
642
643
|
export interface NewSessionResponse {
|
|
643
644
|
success: boolean
|
|
644
645
|
sessionId: string
|
|
646
|
+
userId?: string // 后端生成的 userId(当请求的 user_id 为空时返回)
|
|
645
647
|
message: string
|
|
646
648
|
resumed?: boolean
|
|
647
649
|
messages?: Message[]
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
* 策略:
|
|
6
6
|
* 1. 优先从 localStorage 读取
|
|
7
7
|
* 2. 尝试获取 Google Analytics ID (GAID)
|
|
8
|
-
* 3.
|
|
8
|
+
* 3. 返回空字符串,由后端生成
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const STORAGE_KEY = 'livechat_user_id'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* 获取用户唯一标识符(异步版本)
|
|
15
|
-
* @returns userId (GAID
|
|
15
|
+
* @returns userId (GAID 或空字符串,空字符串由后端生成)
|
|
16
16
|
*/
|
|
17
17
|
export async function getUserId(): Promise<string> {
|
|
18
18
|
// 1. 尝试从 localStorage 读取
|
|
@@ -30,21 +30,17 @@ export async function getUserId(): Promise<string> {
|
|
|
30
30
|
return gaid
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
// 3.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (typeof window !== 'undefined') {
|
|
45
|
-
localStorage.setItem(STORAGE_KEY, fallback)
|
|
46
|
-
}
|
|
47
|
-
return fallback
|
|
33
|
+
// 3. 返回空字符串,由后端生成
|
|
34
|
+
return ''
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 保存后端返回的 userId 到 localStorage
|
|
39
|
+
* @param id 后端生成的 userId
|
|
40
|
+
*/
|
|
41
|
+
export function saveUserId(id: string): void {
|
|
42
|
+
if (id && typeof window !== 'undefined') {
|
|
43
|
+
localStorage.setItem(STORAGE_KEY, id)
|
|
48
44
|
}
|
|
49
45
|
}
|
|
50
46
|
|
|
@@ -85,51 +81,6 @@ function getGAID(): string | null {
|
|
|
85
81
|
}
|
|
86
82
|
}
|
|
87
83
|
|
|
88
|
-
/**
|
|
89
|
-
* 生成哈希用户 ID(兜底方案)
|
|
90
|
-
* @returns 格式为 user-{hash} 的用户 ID,hash 由 timestamp + random 通过 SHA-256 生成
|
|
91
|
-
*/
|
|
92
|
-
async function generateHashedUserId(): Promise<string> {
|
|
93
|
-
const timestamp = Date.now()
|
|
94
|
-
const random = Math.random().toString(36).substring(2, 15) // 生成随机字符串
|
|
95
|
-
|
|
96
|
-
// 将 timestamp 和 random 组合成字符串
|
|
97
|
-
const rawString = `${timestamp}${random}`
|
|
98
|
-
|
|
99
|
-
// 使用 Web Crypto API 生成 SHA-256 哈希
|
|
100
|
-
const encoder = new TextEncoder()
|
|
101
|
-
const data = encoder.encode(rawString)
|
|
102
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', data)
|
|
103
|
-
|
|
104
|
-
// 将 ArrayBuffer 转换为 16 进制字符串
|
|
105
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
|
106
|
-
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
|
|
107
|
-
|
|
108
|
-
// 取前 16 位作为用户 ID(保持简洁)
|
|
109
|
-
return `user-${hashHex.substring(0, 16)}`
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* 同步版本的哈希用户 ID 生成(兜底的兜底)
|
|
114
|
-
* 当 Web Crypto API 不可用时使用
|
|
115
|
-
*/
|
|
116
|
-
function generateHashedUserIdSync(): string {
|
|
117
|
-
const timestamp = Date.now()
|
|
118
|
-
const random = Math.random().toString(36).substring(2, 15)
|
|
119
|
-
const rawString = `${timestamp}${random}`
|
|
120
|
-
|
|
121
|
-
// 使用简单的哈希算法作为兜底
|
|
122
|
-
let hash = 0
|
|
123
|
-
for (let i = 0; i < rawString.length; i++) {
|
|
124
|
-
const char = rawString.charCodeAt(i)
|
|
125
|
-
hash = (hash << 5) - hash + char
|
|
126
|
-
hash = hash & hash
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const hashString = Math.abs(hash).toString(16).padStart(8, '0')
|
|
130
|
-
return `user-${hashString}`
|
|
131
|
-
}
|
|
132
|
-
|
|
133
84
|
/**
|
|
134
85
|
* 清除保存的 userId(用于测试或重置)
|
|
135
86
|
*/
|
|
@@ -148,7 +148,7 @@ export const Default: Story = {
|
|
|
148
148
|
args: {
|
|
149
149
|
// 基础配置
|
|
150
150
|
loginUserId: 'test_test1',
|
|
151
|
-
apiBaseUrl: '
|
|
151
|
+
apiBaseUrl: 'https://beta-api-v2-livechat.anker.com',
|
|
152
152
|
site: 'beta.eufy.com',
|
|
153
153
|
channelCode: 'dtc',
|
|
154
154
|
title: 'eufy AI Assistant',
|