@anker-in/campaign-ui 0.4.0-beta.7 → 0.4.0

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 (43) hide show
  1. package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js +1 -1
  2. package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
  3. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductComparison.d.ts +1 -0
  4. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductComparison.js +1 -1
  5. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductComparison.js.map +3 -3
  6. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.d.ts +11 -2
  7. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js +1 -1
  8. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
  9. package/dist/cjs/components/LiveChatWidget/index.d.ts +1 -1
  10. package/dist/cjs/components/LiveChatWidget/index.js +1 -1
  11. package/dist/cjs/components/LiveChatWidget/index.js.map +2 -2
  12. package/dist/cjs/components/LiveChatWidget/types.d.ts +14 -0
  13. package/dist/cjs/components/LiveChatWidget/types.js.map +1 -1
  14. package/dist/cjs/components/LiveChatWidget/utils/userId.d.ts +7 -2
  15. package/dist/cjs/components/LiveChatWidget/utils/userId.js +1 -1
  16. package/dist/cjs/components/LiveChatWidget/utils/userId.js.map +3 -3
  17. package/dist/cjs/stories/LiveChatWidget.stories.js +1 -1
  18. package/dist/cjs/stories/LiveChatWidget.stories.js.map +2 -2
  19. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js +1 -1
  20. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
  21. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.d.ts +1 -0
  22. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.js +1 -1
  23. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductComparison.js.map +3 -3
  24. package/dist/esm/components/LiveChatWidget/hooks/useChatState.d.ts +11 -2
  25. package/dist/esm/components/LiveChatWidget/hooks/useChatState.js +1 -1
  26. package/dist/esm/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
  27. package/dist/esm/components/LiveChatWidget/index.d.ts +1 -1
  28. package/dist/esm/components/LiveChatWidget/index.js +1 -1
  29. package/dist/esm/components/LiveChatWidget/index.js.map +3 -3
  30. package/dist/esm/components/LiveChatWidget/types.d.ts +14 -0
  31. package/dist/esm/components/LiveChatWidget/utils/userId.d.ts +7 -2
  32. package/dist/esm/components/LiveChatWidget/utils/userId.js +1 -1
  33. package/dist/esm/components/LiveChatWidget/utils/userId.js.map +3 -3
  34. package/dist/esm/stories/LiveChatWidget.stories.js +1 -1
  35. package/dist/esm/stories/LiveChatWidget.stories.js.map +2 -2
  36. package/package.json +1 -1
  37. package/src/components/LiveChatWidget/LiveChatWidget.tsx +44 -8
  38. package/src/components/LiveChatWidget/components/MessageContent/ProductComparison.tsx +6 -13
  39. package/src/components/LiveChatWidget/hooks/useChatState.ts +26 -7
  40. package/src/components/LiveChatWidget/index.tsx +1 -1
  41. package/src/components/LiveChatWidget/types.ts +15 -0
  42. package/src/components/LiveChatWidget/utils/userId.ts +13 -62
  43. package/src/stories/LiveChatWidget.stories.tsx +1 -1
@@ -5,13 +5,18 @@
5
5
  * 策略:
6
6
  * 1. 优先从 localStorage 读取
7
7
  * 2. 尝试获取 Google Analytics ID (GAID)
8
- * 3. 兜底:时间戳 + 随机数哈希
8
+ * 3. 返回空字符串,由后端生成
9
9
  */
10
10
  /**
11
11
  * 获取用户唯一标识符(异步版本)
12
- * @returns userId (GAID 或哈希值)
12
+ * @returns userId (GAID 或空字符串,空字符串由后端生成)
13
13
  */
14
14
  export declare function getUserId(): Promise<string>;
15
+ /**
16
+ * 保存后端返回的 userId 到 localStorage
17
+ * @param id 后端生成的 userId
18
+ */
19
+ export declare function saveUserId(id: string): void;
15
20
  /**
16
21
  * 清除保存的 userId(用于测试或重置)
17
22
  */
@@ -1,2 +1,2 @@
1
- const a="livechat_user_id";async function g(){if(typeof window<"u"){const t=localStorage.getItem(a);if(t)return t}const e=d();if(e)return typeof window<"u"&&localStorage.setItem(a,e),e;try{const t=await l();return typeof window<"u"&&localStorage.setItem(a,t),t}catch(t){console.warn("[LiveChat userId] SHA-256 failed, using sync fallback:",t);const n=f();return typeof window<"u"&&localStorage.setItem(a,n),n}}function d(){if(typeof window>"u")return null;if(typeof window.gtag!="function")return console.warn("[LiveChat userId] Google Analytics gtag is not available"),null;try{const e=window.dataLayer||[];for(const t of e)if(t&&t[1]&&typeof t[1]=="object"){const n=t[1].client_id||t[1].clientId;if(n&&typeof n=="string")return`G-${n}`}return null}catch(e){return console.error("[LiveChat userId] Failed to get GAID:",e),null}}async function l(){const e=Date.now(),t=Math.random().toString(36).substring(2,15),n=`${e}${t}`,i=new TextEncoder().encode(n),o=await crypto.subtle.digest("SHA-256",i);return`user-${Array.from(new Uint8Array(o)).map(c=>c.toString(16).padStart(2,"0")).join("").substring(0,16)}`}function f(){const e=Date.now(),t=Math.random().toString(36).substring(2,15),n=`${e}${t}`;let r=0;for(let o=0;o<n.length;o++){const s=n.charCodeAt(o);r=(r<<5)-r+s,r=r&r}return`user-${Math.abs(r).toString(16).padStart(8,"0")}`}function h(){typeof window<"u"&&localStorage.removeItem(a)}export{h as clearUserId,g as getUserId};
1
+ const n="livechat_user_id";async function i(){if(typeof window<"u"){const t=localStorage.getItem(n);if(t)return t}const e=r();return e?(typeof window<"u"&&localStorage.setItem(n,e),e):""}function a(e){e&&typeof window<"u"&&localStorage.setItem(n,e)}function r(){if(typeof window>"u")return null;if(typeof window.gtag!="function")return console.warn("[LiveChat userId] Google Analytics gtag is not available"),null;try{const e=window.dataLayer||[];for(const t of e)if(t&&t[1]&&typeof t[1]=="object"){const o=t[1].client_id||t[1].clientId;if(o&&typeof o=="string")return`G-${o}`}return null}catch(e){return console.error("[LiveChat userId] Failed to get GAID:",e),null}}function d(){typeof window<"u"&&localStorage.removeItem(n)}export{d as clearUserId,i as getUserId,a as saveUserId};
2
2
  //# sourceMappingURL=userId.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/components/LiveChatWidget/utils/userId.ts"],
4
- "sourcesContent": ["/**\n * userId \u751F\u6210\u548C\u7BA1\u7406\u5DE5\u5177\n * \u57FA\u4E8E specs/livechat-widget/research.md \u7684\u51B3\u7B56\n *\n * \u7B56\u7565\uFF1A\n * 1. \u4F18\u5148\u4ECE localStorage \u8BFB\u53D6\n * 2. \u5C1D\u8BD5\u83B7\u53D6 Google Analytics ID (GAID)\n * 3. \u515C\u5E95\uFF1A\u65F6\u95F4\u6233 + \u968F\u673A\u6570\u54C8\u5E0C\n */\n\nconst STORAGE_KEY = 'livechat_user_id'\n\n/**\n * \u83B7\u53D6\u7528\u6237\u552F\u4E00\u6807\u8BC6\u7B26\uFF08\u5F02\u6B65\u7248\u672C\uFF09\n * @returns userId (GAID \u6216\u54C8\u5E0C\u503C)\n */\nexport async function getUserId(): Promise<string> {\n // 1. \u5C1D\u8BD5\u4ECE localStorage \u8BFB\u53D6\n if (typeof window !== 'undefined') {\n const stored = localStorage.getItem(STORAGE_KEY)\n if (stored) return stored\n }\n\n // 2. \u5C1D\u8BD5\u83B7\u53D6 GAID\n const gaid = getGAID()\n if (gaid) {\n if (typeof window !== 'undefined') {\n localStorage.setItem(STORAGE_KEY, gaid)\n }\n return gaid\n }\n\n // 3. \u515C\u5E95\uFF1A\u4F7F\u7528 SHA-256 \u751F\u6210\u54C8\u5E0C\n try {\n const fallback = await generateHashedUserId()\n if (typeof window !== 'undefined') {\n localStorage.setItem(STORAGE_KEY, fallback)\n }\n return fallback\n } catch (error) {\n // \u5982\u679C SHA-256 \u5931\u8D25\uFF0C\u4F7F\u7528\u540C\u6B65\u515C\u5E95\u65B9\u6848\n console.warn('[LiveChat userId] SHA-256 failed, using sync fallback:', error)\n const fallback = generateHashedUserIdSync()\n if (typeof window !== 'undefined') {\n localStorage.setItem(STORAGE_KEY, fallback)\n }\n return fallback\n }\n}\n\n/**\n * \u4ECE Google Analytics \u83B7\u53D6 Client ID (GAID)\n * @returns GAID \u6216 null\n */\nfunction getGAID(): string | null {\n if (typeof window === 'undefined') return null\n\n // \u68C0\u67E5 gtag \u662F\u5426\u53EF\u7528\n if (typeof (window as any).gtag !== 'function') {\n console.warn('[LiveChat userId] Google Analytics gtag is not available')\n return null\n }\n\n try {\n // Google Analytics 4 \u65B9\u6CD5\n // \u6CE8\u610F\uFF1Agtag \u7684 'get' \u547D\u4EE4\u662F\u5F02\u6B65\u7684\uFF0C\u8FD9\u91CC\u4F7F\u7528\u540C\u6B65 fallback\n // \u5728\u5B9E\u9645\u9879\u76EE\u4E2D\uFF0C\u5982\u679C GA \u5DF2\u521D\u59CB\u5316\uFF0C\u53EF\u4EE5\u4ECE dataLayer \u8BFB\u53D6\n const dataLayer = (window as any).dataLayer || []\n\n // \u5C1D\u8BD5\u4ECE dataLayer \u4E2D\u67E5\u627E\u5DF2\u5B58\u5728\u7684 client_id\n for (const item of dataLayer) {\n if (item && item[1] && typeof item[1] === 'object') {\n const clientId = item[1].client_id || item[1].clientId\n if (clientId && typeof clientId === 'string') {\n return `G-${clientId}`\n }\n }\n }\n\n // \u5982\u679C\u6CA1\u6709\u627E\u5230\uFF0C\u8FD4\u56DE null\uFF0C\u4F7F\u7528\u515C\u5E95\u65B9\u6848\n return null\n } catch (error) {\n console.error('[LiveChat userId] Failed to get GAID:', error)\n return null\n }\n}\n\n/**\n * \u751F\u6210\u54C8\u5E0C\u7528\u6237 ID\uFF08\u515C\u5E95\u65B9\u6848\uFF09\n * @returns \u683C\u5F0F\u4E3A user-{hash} \u7684\u7528\u6237 ID\uFF0Chash \u7531 timestamp + random \u901A\u8FC7 SHA-256 \u751F\u6210\n */\nasync function generateHashedUserId(): Promise<string> {\n const timestamp = Date.now()\n const random = Math.random().toString(36).substring(2, 15) // \u751F\u6210\u968F\u673A\u5B57\u7B26\u4E32\n\n // \u5C06 timestamp \u548C random \u7EC4\u5408\u6210\u5B57\u7B26\u4E32\n const rawString = `${timestamp}${random}`\n\n // \u4F7F\u7528 Web Crypto API \u751F\u6210 SHA-256 \u54C8\u5E0C\n const encoder = new TextEncoder()\n const data = encoder.encode(rawString)\n const hashBuffer = await crypto.subtle.digest('SHA-256', data)\n\n // \u5C06 ArrayBuffer \u8F6C\u6362\u4E3A 16 \u8FDB\u5236\u5B57\u7B26\u4E32\n const hashArray = Array.from(new Uint8Array(hashBuffer))\n const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')\n\n // \u53D6\u524D 16 \u4F4D\u4F5C\u4E3A\u7528\u6237 ID\uFF08\u4FDD\u6301\u7B80\u6D01\uFF09\n return `user-${hashHex.substring(0, 16)}`\n}\n\n/**\n * \u540C\u6B65\u7248\u672C\u7684\u54C8\u5E0C\u7528\u6237 ID \u751F\u6210\uFF08\u515C\u5E95\u7684\u515C\u5E95\uFF09\n * \u5F53 Web Crypto API \u4E0D\u53EF\u7528\u65F6\u4F7F\u7528\n */\nfunction generateHashedUserIdSync(): string {\n const timestamp = Date.now()\n const random = Math.random().toString(36).substring(2, 15)\n const rawString = `${timestamp}${random}`\n\n // \u4F7F\u7528\u7B80\u5355\u7684\u54C8\u5E0C\u7B97\u6CD5\u4F5C\u4E3A\u515C\u5E95\n let hash = 0\n for (let i = 0; i < rawString.length; i++) {\n const char = rawString.charCodeAt(i)\n hash = (hash << 5) - hash + char\n hash = hash & hash\n }\n\n const hashString = Math.abs(hash).toString(16).padStart(8, '0')\n return `user-${hashString}`\n}\n\n/**\n * \u6E05\u9664\u4FDD\u5B58\u7684 userId\uFF08\u7528\u4E8E\u6D4B\u8BD5\u6216\u91CD\u7F6E\uFF09\n */\nexport function clearUserId(): void {\n if (typeof window !== 'undefined') {\n localStorage.removeItem(STORAGE_KEY)\n }\n}\n"],
5
- "mappings": "AAUA,MAAMA,EAAc,mBAMpB,eAAsBC,GAA6B,CAEjD,GAAI,OAAO,OAAW,IAAa,CACjC,MAAMC,EAAS,aAAa,QAAQF,CAAW,EAC/C,GAAIE,EAAQ,OAAOA,CACrB,CAGA,MAAMC,EAAOC,EAAQ,EACrB,GAAID,EACF,OAAI,OAAO,OAAW,KACpB,aAAa,QAAQH,EAAaG,CAAI,EAEjCA,EAIT,GAAI,CACF,MAAME,EAAW,MAAMC,EAAqB,EAC5C,OAAI,OAAO,OAAW,KACpB,aAAa,QAAQN,EAAaK,CAAQ,EAErCA,CACT,OAASE,EAAO,CAEd,QAAQ,KAAK,yDAA0DA,CAAK,EAC5E,MAAMF,EAAWG,EAAyB,EAC1C,OAAI,OAAO,OAAW,KACpB,aAAa,QAAQR,EAAaK,CAAQ,EAErCA,CACT,CACF,CAMA,SAASD,GAAyB,CAChC,GAAI,OAAO,OAAW,IAAa,OAAO,KAG1C,GAAI,OAAQ,OAAe,MAAS,WAClC,eAAQ,KAAK,0DAA0D,EAChE,KAGT,GAAI,CAIF,MAAMK,EAAa,OAAe,WAAa,CAAC,EAGhD,UAAWC,KAAQD,EACjB,GAAIC,GAAQA,EAAK,CAAC,GAAK,OAAOA,EAAK,CAAC,GAAM,SAAU,CAClD,MAAMC,EAAWD,EAAK,CAAC,EAAE,WAAaA,EAAK,CAAC,EAAE,SAC9C,GAAIC,GAAY,OAAOA,GAAa,SAClC,MAAO,KAAKA,CAAQ,EAExB,CAIF,OAAO,IACT,OAASJ,EAAO,CACd,eAAQ,MAAM,wCAAyCA,CAAK,EACrD,IACT,CACF,CAMA,eAAeD,GAAwC,CACrD,MAAMM,EAAY,KAAK,IAAI,EACrBC,EAAS,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,EAAG,EAAE,EAGnDC,EAAY,GAAGF,CAAS,GAAGC,CAAM,GAIjCE,EADU,IAAI,YAAY,EACX,OAAOD,CAAS,EAC/BE,EAAa,MAAM,OAAO,OAAO,OAAO,UAAWD,CAAI,EAO7D,MAAO,QAJW,MAAM,KAAK,IAAI,WAAWC,CAAU,CAAC,EAC7B,IAAIC,GAAKA,EAAE,SAAS,EAAE,EAAE,SAAS,EAAG,GAAG,CAAC,EAAE,KAAK,EAAE,EAGpD,UAAU,EAAG,EAAE,CAAC,EACzC,CAMA,SAAST,GAAmC,CAC1C,MAAMI,EAAY,KAAK,IAAI,EACrBC,EAAS,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,EAAG,EAAE,EACnDC,EAAY,GAAGF,CAAS,GAAGC,CAAM,GAGvC,IAAIK,EAAO,EACX,QAASC,EAAI,EAAGA,EAAIL,EAAU,OAAQK,IAAK,CACzC,MAAMC,EAAON,EAAU,WAAWK,CAAC,EACnCD,GAAQA,GAAQ,GAAKA,EAAOE,EAC5BF,EAAOA,EAAOA,CAChB,CAGA,MAAO,QADY,KAAK,IAAIA,CAAI,EAAE,SAAS,EAAE,EAAE,SAAS,EAAG,GAAG,CACrC,EAC3B,CAKO,SAASG,GAAoB,CAC9B,OAAO,OAAW,KACpB,aAAa,WAAWrB,CAAW,CAEvC",
6
- "names": ["STORAGE_KEY", "getUserId", "stored", "gaid", "getGAID", "fallback", "generateHashedUserId", "error", "generateHashedUserIdSync", "dataLayer", "item", "clientId", "timestamp", "random", "rawString", "data", "hashBuffer", "b", "hash", "i", "char", "clearUserId"]
4
+ "sourcesContent": ["/**\n * userId \u751F\u6210\u548C\u7BA1\u7406\u5DE5\u5177\n * \u57FA\u4E8E specs/livechat-widget/research.md \u7684\u51B3\u7B56\n *\n * \u7B56\u7565\uFF1A\n * 1. \u4F18\u5148\u4ECE localStorage \u8BFB\u53D6\n * 2. \u5C1D\u8BD5\u83B7\u53D6 Google Analytics ID (GAID)\n * 3. \u8FD4\u56DE\u7A7A\u5B57\u7B26\u4E32\uFF0C\u7531\u540E\u7AEF\u751F\u6210\n */\n\nconst STORAGE_KEY = 'livechat_user_id'\n\n/**\n * \u83B7\u53D6\u7528\u6237\u552F\u4E00\u6807\u8BC6\u7B26\uFF08\u5F02\u6B65\u7248\u672C\uFF09\n * @returns userId (GAID \u6216\u7A7A\u5B57\u7B26\u4E32\uFF0C\u7A7A\u5B57\u7B26\u4E32\u7531\u540E\u7AEF\u751F\u6210)\n */\nexport async function getUserId(): Promise<string> {\n // 1. \u5C1D\u8BD5\u4ECE localStorage \u8BFB\u53D6\n if (typeof window !== 'undefined') {\n const stored = localStorage.getItem(STORAGE_KEY)\n if (stored) return stored\n }\n\n // 2. \u5C1D\u8BD5\u83B7\u53D6 GAID\n const gaid = getGAID()\n if (gaid) {\n if (typeof window !== 'undefined') {\n localStorage.setItem(STORAGE_KEY, gaid)\n }\n return gaid\n }\n\n // 3. \u8FD4\u56DE\u7A7A\u5B57\u7B26\u4E32\uFF0C\u7531\u540E\u7AEF\u751F\u6210\n return ''\n}\n\n/**\n * \u4FDD\u5B58\u540E\u7AEF\u8FD4\u56DE\u7684 userId \u5230 localStorage\n * @param id \u540E\u7AEF\u751F\u6210\u7684 userId\n */\nexport function saveUserId(id: string): void {\n if (id && typeof window !== 'undefined') {\n localStorage.setItem(STORAGE_KEY, id)\n }\n}\n\n/**\n * \u4ECE Google Analytics \u83B7\u53D6 Client ID (GAID)\n * @returns GAID \u6216 null\n */\nfunction getGAID(): string | null {\n if (typeof window === 'undefined') return null\n\n // \u68C0\u67E5 gtag \u662F\u5426\u53EF\u7528\n if (typeof (window as any).gtag !== 'function') {\n console.warn('[LiveChat userId] Google Analytics gtag is not available')\n return null\n }\n\n try {\n // Google Analytics 4 \u65B9\u6CD5\n // \u6CE8\u610F\uFF1Agtag \u7684 'get' \u547D\u4EE4\u662F\u5F02\u6B65\u7684\uFF0C\u8FD9\u91CC\u4F7F\u7528\u540C\u6B65 fallback\n // \u5728\u5B9E\u9645\u9879\u76EE\u4E2D\uFF0C\u5982\u679C GA \u5DF2\u521D\u59CB\u5316\uFF0C\u53EF\u4EE5\u4ECE dataLayer \u8BFB\u53D6\n const dataLayer = (window as any).dataLayer || []\n\n // \u5C1D\u8BD5\u4ECE dataLayer \u4E2D\u67E5\u627E\u5DF2\u5B58\u5728\u7684 client_id\n for (const item of dataLayer) {\n if (item && item[1] && typeof item[1] === 'object') {\n const clientId = item[1].client_id || item[1].clientId\n if (clientId && typeof clientId === 'string') {\n return `G-${clientId}`\n }\n }\n }\n\n // \u5982\u679C\u6CA1\u6709\u627E\u5230\uFF0C\u8FD4\u56DE null\uFF0C\u4F7F\u7528\u515C\u5E95\u65B9\u6848\n return null\n } catch (error) {\n console.error('[LiveChat userId] Failed to get GAID:', error)\n return null\n }\n}\n\n/**\n * \u6E05\u9664\u4FDD\u5B58\u7684 userId\uFF08\u7528\u4E8E\u6D4B\u8BD5\u6216\u91CD\u7F6E\uFF09\n */\nexport function clearUserId(): void {\n if (typeof window !== 'undefined') {\n localStorage.removeItem(STORAGE_KEY)\n }\n}\n"],
5
+ "mappings": "AAUA,MAAMA,EAAc,mBAMpB,eAAsBC,GAA6B,CAEjD,GAAI,OAAO,OAAW,IAAa,CACjC,MAAMC,EAAS,aAAa,QAAQF,CAAW,EAC/C,GAAIE,EAAQ,OAAOA,CACrB,CAGA,MAAMC,EAAOC,EAAQ,EACrB,OAAID,GACE,OAAO,OAAW,KACpB,aAAa,QAAQH,EAAaG,CAAI,EAEjCA,GAIF,EACT,CAMO,SAASE,EAAWC,EAAkB,CACvCA,GAAM,OAAO,OAAW,KAC1B,aAAa,QAAQN,EAAaM,CAAE,CAExC,CAMA,SAASF,GAAyB,CAChC,GAAI,OAAO,OAAW,IAAa,OAAO,KAG1C,GAAI,OAAQ,OAAe,MAAS,WAClC,eAAQ,KAAK,0DAA0D,EAChE,KAGT,GAAI,CAIF,MAAMG,EAAa,OAAe,WAAa,CAAC,EAGhD,UAAWC,KAAQD,EACjB,GAAIC,GAAQA,EAAK,CAAC,GAAK,OAAOA,EAAK,CAAC,GAAM,SAAU,CAClD,MAAMC,EAAWD,EAAK,CAAC,EAAE,WAAaA,EAAK,CAAC,EAAE,SAC9C,GAAIC,GAAY,OAAOA,GAAa,SAClC,MAAO,KAAKA,CAAQ,EAExB,CAIF,OAAO,IACT,OAASC,EAAO,CACd,eAAQ,MAAM,wCAAyCA,CAAK,EACrD,IACT,CACF,CAKO,SAASC,GAAoB,CAC9B,OAAO,OAAW,KACpB,aAAa,WAAWX,CAAW,CAEvC",
6
+ "names": ["STORAGE_KEY", "getUserId", "stored", "gaid", "getGAID", "saveUserId", "id", "dataLayer", "item", "clientId", "error", "clearUserId"]
7
7
  }
@@ -48,5 +48,5 @@ const customRenderers = {
48
48
  customRenderers={customRenderers}
49
49
  />
50
50
  \`\`\`
51
- `}}},tags:["autodocs"],argTypes:{apiBaseUrl:{control:"text",description:"API \u57FA\u7840 URL"},headers:{control:"object",description:"\u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\uFF0C\u5C06\u5728\u6240\u6709 API \u8BF7\u6C42\u4E2D\u6DFB\u52A0",table:{defaultValue:{summary:"undefined"}}},recaptchaSitekey:{control:"text",description:"Google reCAPTCHA v3 site key\uFF0C\u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u81EA\u52A8\u542F\u7528 reCAPTCHA v3 \u9A8C\u8BC1",table:{defaultValue:{summary:"undefined"}}},recaptchaAction:{control:"text",description:"reCAPTCHA action \u540D\u79F0\uFF0C\u7528\u4E8E\u533A\u5206\u4E0D\u540C\u7684\u9A8C\u8BC1\u573A\u666F",table:{defaultValue:{summary:'"activity"'}}},site:{control:"text",description:"Shopify \u5E97\u94FA URL"},channelCode:{control:"text",description:"\u6E20\u9053\u7F16\u7801\uFF0C\u7528\u4E8E\u6807\u8BC6\u6765\u6E90\u6E20\u9053",table:{defaultValue:{summary:"undefined"}}},welcomeMessage:{control:"text",description:"\u6B22\u8FCE\u6D88\u606F",table:{defaultValue:{summary:"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F"}}},logoUrl:{control:"text",description:"Logo URL"},position:{control:"object",description:"\u6C14\u6CE1\u6309\u94AE\u4F4D\u7F6E\u5BF9\u8C61",table:{defaultValue:{summary:'{ bottom: "1.5rem", right: "1.5rem" }'}}}},args:{apiBaseUrl:"http://172.16.38.183:3003",site:"www.eufy.com",loginUserId:"test_test",welcomeMessage:"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F"}};var g=c;const m={args:{loginUserId:"test_test1",apiBaseUrl:"http://172.16.38.183:3003",site:"beta.eufy.com",channelCode:"dtc",title:"eufy AI Assistant",cartId:"gid://shopify/Cart/hWN7wB3Pa12gh78d8hPOAUBI?key=0e73db1d3fb5ac21da19099c45033253",accessToken:"47b1aa2c0797043f9baba39388029d70",position:{bottom:"24px",right:"30px"},welcomeMessage:"",quickReplies:[{id:"1",label:"Product Info",value:"Tell me about your products",icon:"\u{1F4E6}"},{id:"2",label:"Track Order",value:"I want to track my order",icon:"\u{1F69A}"},{id:"3",label:"Support",value:"I need help with my device",icon:"\u{1F527}"},{id:"4",label:"Recommendations",value:"Recommend products for me",icon:"\u2B50"}],complianceConfig:{title:"Hi! I'm your eufy AI assistant.",content:"AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data.",checkboxText:`By starting to use "Live Chat", you agree to Anker's <a href="https://www.anker.com/pages/privacy-policy" target="_blank" rel="noopener noreferrer" style="text-decoration: underline;">LIVE CHAT PRIVACY NOTICE</a>.`,agreeButtonText:"Agree"},recaptchaAction:"livechat",showNewSessionButton:!0,commonText:{learnMore:"Learn More",total:"Total"},customRenderers:{video:{render:t=>{const e=t;return r("div",{className:"w-full",children:[o("video",{src:e.url,controls:!0,className:"w-full rounded-lg",poster:e.poster,children:"Your browser does not support video playback"}),e.title&&o("p",{className:"mt-2 text-sm text-gray-600",children:e.title})]})}},image_gallery:{render:t=>{const a=t.images||[];return o("div",{className:"grid grid-cols-2 gap-2",children:a.map((i,n)=>o("div",{className:"relative aspect-square",children:o("img",{src:i.url,alt:i.alt||`Image ${n+1}`,className:"size-full rounded-lg object-cover"})},n))})}}},productCardRender:(t,e)=>{if(!t)return o("div",{style:{padding:"16px",border:"1px dashed #ccc",borderRadius:"8px",textAlign:"center"},children:r("p",{children:["Product loading... (handle: ",e,")"]})});const a=t?.featured_image||"",i=t?.title||"",n=t?.description||"",s=t?.average_rating;return o("div",{style:{border:"2px solid #4CAF50",borderRadius:"16px",padding:"16px",margin:"12px 0",backgroundColor:"#f0f9ff",boxShadow:"0 4px 12px rgba(76, 175, 80, 0.15)"},children:r("div",{style:{display:"flex",gap:"12px",alignItems:"center"},children:[a&&o("img",{src:a,alt:i,style:{width:"60px",height:"60px",borderRadius:"8px",objectFit:"cover"}}),r("div",{style:{flex:1},children:[o("h4",{style:{margin:"0 0 4px 0",fontSize:"16px",fontWeight:"bold"},children:i}),n&&r("p",{style:{margin:0,fontSize:"12px",color:"#666",lineHeight:1.4},children:[n.slice(0,80),"..."]}),s&&r("span",{style:{fontSize:"12px",color:"#FFB800"},children:["Rating: ",s.toFixed(1)]})]}),o("button",{style:{padding:"8px 16px",backgroundColor:"#4CAF50",color:"white",borderRadius:"8px",border:"none",cursor:"pointer"},onClick:()=>console.log("View product:",e,t),children:"View"})]})})}},render:t=>o(l,{...t,onOpen:()=>console.log("[LiveChat] Chat opened"),onClose:()=>console.log("[LiveChat] Chat closed"),onMessageSend:e=>console.log("[LiveChat] Message sent:",e),onError:e=>console.error("[LiveChat] Error:",e),onTextMessage:()=>console.log("[LiveChat] AI text message received"),onProductList:()=>console.log("[LiveChat] Product list received"),onPromotionList:()=>console.log("[LiveChat] Promotion list received"),onAddToCart:e=>{console.log("[LiveChat] Add to cart:",e),alert(`Added "${e.title}" to cart!`)},onCart:(e,a)=>{console.log("[LiveChat] Cart clicked:",{cartId:e,checkoutUrl:a}),alert(`Cart ID: ${e}`)}})};export{m as Default,g as default};
51
+ `}}},tags:["autodocs"],argTypes:{apiBaseUrl:{control:"text",description:"API \u57FA\u7840 URL"},headers:{control:"object",description:"\u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\uFF0C\u5C06\u5728\u6240\u6709 API \u8BF7\u6C42\u4E2D\u6DFB\u52A0",table:{defaultValue:{summary:"undefined"}}},recaptchaSitekey:{control:"text",description:"Google reCAPTCHA v3 site key\uFF0C\u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u81EA\u52A8\u542F\u7528 reCAPTCHA v3 \u9A8C\u8BC1",table:{defaultValue:{summary:"undefined"}}},recaptchaAction:{control:"text",description:"reCAPTCHA action \u540D\u79F0\uFF0C\u7528\u4E8E\u533A\u5206\u4E0D\u540C\u7684\u9A8C\u8BC1\u573A\u666F",table:{defaultValue:{summary:'"activity"'}}},site:{control:"text",description:"Shopify \u5E97\u94FA URL"},channelCode:{control:"text",description:"\u6E20\u9053\u7F16\u7801\uFF0C\u7528\u4E8E\u6807\u8BC6\u6765\u6E90\u6E20\u9053",table:{defaultValue:{summary:"undefined"}}},welcomeMessage:{control:"text",description:"\u6B22\u8FCE\u6D88\u606F",table:{defaultValue:{summary:"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F"}}},logoUrl:{control:"text",description:"Logo URL"},position:{control:"object",description:"\u6C14\u6CE1\u6309\u94AE\u4F4D\u7F6E\u5BF9\u8C61",table:{defaultValue:{summary:'{ bottom: "1.5rem", right: "1.5rem" }'}}}},args:{apiBaseUrl:"http://172.16.38.183:3003",site:"www.eufy.com",loginUserId:"test_test",welcomeMessage:"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F"}};var g=c;const m={args:{loginUserId:"test_test1",apiBaseUrl:"https://beta-api-v2-livechat.anker.com",site:"beta.eufy.com",channelCode:"dtc",title:"eufy AI Assistant",cartId:"gid://shopify/Cart/hWN7wB3Pa12gh78d8hPOAUBI?key=0e73db1d3fb5ac21da19099c45033253",accessToken:"47b1aa2c0797043f9baba39388029d70",position:{bottom:"24px",right:"30px"},welcomeMessage:"",quickReplies:[{id:"1",label:"Product Info",value:"Tell me about your products",icon:"\u{1F4E6}"},{id:"2",label:"Track Order",value:"I want to track my order",icon:"\u{1F69A}"},{id:"3",label:"Support",value:"I need help with my device",icon:"\u{1F527}"},{id:"4",label:"Recommendations",value:"Recommend products for me",icon:"\u2B50"}],complianceConfig:{title:"Hi! I'm your eufy AI assistant.",content:"AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data.",checkboxText:`By starting to use "Live Chat", you agree to Anker's <a href="https://www.anker.com/pages/privacy-policy" target="_blank" rel="noopener noreferrer" style="text-decoration: underline;">LIVE CHAT PRIVACY NOTICE</a>.`,agreeButtonText:"Agree"},recaptchaAction:"livechat",showNewSessionButton:!0,commonText:{learnMore:"Learn More",total:"Total"},customRenderers:{video:{render:t=>{const e=t;return r("div",{className:"w-full",children:[o("video",{src:e.url,controls:!0,className:"w-full rounded-lg",poster:e.poster,children:"Your browser does not support video playback"}),e.title&&o("p",{className:"mt-2 text-sm text-gray-600",children:e.title})]})}},image_gallery:{render:t=>{const a=t.images||[];return o("div",{className:"grid grid-cols-2 gap-2",children:a.map((i,n)=>o("div",{className:"relative aspect-square",children:o("img",{src:i.url,alt:i.alt||`Image ${n+1}`,className:"size-full rounded-lg object-cover"})},n))})}}},productCardRender:(t,e)=>{if(!t)return o("div",{style:{padding:"16px",border:"1px dashed #ccc",borderRadius:"8px",textAlign:"center"},children:r("p",{children:["Product loading... (handle: ",e,")"]})});const a=t?.featured_image||"",i=t?.title||"",n=t?.description||"",s=t?.average_rating;return o("div",{style:{border:"2px solid #4CAF50",borderRadius:"16px",padding:"16px",margin:"12px 0",backgroundColor:"#f0f9ff",boxShadow:"0 4px 12px rgba(76, 175, 80, 0.15)"},children:r("div",{style:{display:"flex",gap:"12px",alignItems:"center"},children:[a&&o("img",{src:a,alt:i,style:{width:"60px",height:"60px",borderRadius:"8px",objectFit:"cover"}}),r("div",{style:{flex:1},children:[o("h4",{style:{margin:"0 0 4px 0",fontSize:"16px",fontWeight:"bold"},children:i}),n&&r("p",{style:{margin:0,fontSize:"12px",color:"#666",lineHeight:1.4},children:[n.slice(0,80),"..."]}),s&&r("span",{style:{fontSize:"12px",color:"#FFB800"},children:["Rating: ",s.toFixed(1)]})]}),o("button",{style:{padding:"8px 16px",backgroundColor:"#4CAF50",color:"white",borderRadius:"8px",border:"none",cursor:"pointer"},onClick:()=>console.log("View product:",e,t),children:"View"})]})})}},render:t=>o(l,{...t,onOpen:()=>console.log("[LiveChat] Chat opened"),onClose:()=>console.log("[LiveChat] Chat closed"),onMessageSend:e=>console.log("[LiveChat] Message sent:",e),onError:e=>console.error("[LiveChat] Error:",e),onTextMessage:()=>console.log("[LiveChat] AI text message received"),onProductList:()=>console.log("[LiveChat] Product list received"),onPromotionList:()=>console.log("[LiveChat] Promotion list received"),onAddToCart:e=>{console.log("[LiveChat] Add to cart:",e),alert(`Added "${e.title}" to cart!`)},onCart:(e,a)=>{console.log("[LiveChat] Cart clicked:",{cartId:e,checkoutUrl:a}),alert(`Cart ID: ${e}`)}})};export{m as Default,g as default};
52
52
  //# sourceMappingURL=LiveChatWidget.stories.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/stories/LiveChatWidget.stories.tsx"],
4
- "sourcesContent": ["/**\n * LiveChatWidget Storybook Stories\n * \u5C55\u793A LiveChat \u7EC4\u4EF6\u7684\u5404\u79CD\u4F7F\u7528\u573A\u666F\u548C\u914D\u7F6E\n */\n\nimport type { Meta, StoryObj } from '@storybook/react'\nimport { LiveChatWidget } from '../components/LiveChatWidget'\nimport type { MessageRenderer, MessageContent } from '../components/LiveChatWidget'\nimport '../styles/livechat.css'\n\nconst meta: Meta<typeof LiveChatWidget> = {\n title: 'Campaign/LiveChatWidget',\n component: LiveChatWidget,\n parameters: {\n layout: 'fullscreen',\n docs: {\n story: {\n inline: false,\n iframeHeight: 500,\n },\n description: {\n component: `\n# LiveChat \u804A\u5929\u7EC4\u4EF6\n\n\u53EF\u590D\u7528\u7684\u6C14\u6CE1\u5F39\u7A97\u804A\u5929\u7EC4\u4EF6\uFF0C\u652F\u6301 SSE \u6D41\u5F0F\u6D88\u606F\u3001\u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\u548C\u591A\u79CD\u6D88\u606F\u7C7B\u578B\u3002\n\n## \u529F\u80FD\u7279\u6027\n\n- \uD83C\uDF88 **\u6C14\u6CE1\u5F39\u7A97**: \u53EF\u81EA\u5B9A\u4E49\u4F4D\u7F6E\u7684\u60AC\u6D6E\u6C14\u6CE1\u6309\u94AE\n- \uD83D\uDCAC **\u6D41\u5F0F\u6D88\u606F**: \u57FA\u4E8E SSE \u7684\u5B9E\u65F6\u6D41\u5F0F\u54CD\u5E94\n- \uD83D\uDCE6 **\u591A\u79CD\u6D88\u606F\u7C7B\u578B**: \u6587\u672C\u3001\u5546\u54C1\u5361\u7247\u3001\u5546\u54C1\u5217\u8868\u3001\u653F\u7B56\u3001\u5FEB\u6377\u56DE\u590D\u7B49\n- \uD83C\uDFA8 **\u53EF\u5B9A\u5236**: \u652F\u6301\u81EA\u5B9A\u4E49\u54C1\u724C\u989C\u8272\u3001Logo\u3001\u6E32\u67D3\u5668\n- \uD83D\uDCF1 **\u54CD\u5E94\u5F0F**: \u79FB\u52A8\u7AEF\u5168\u5C4F\uFF0C\u684C\u9762\u7AEF\u56FA\u5B9A\u5C3A\u5BF8\n- \uD83D\uDCBE **\u4F1A\u8BDD\u7BA1\u7406**: \u81EA\u52A8\u7BA1\u7406 userId \u548C sessionId\n- \uD83D\uDD12 **\u5B89\u5168\u9632\u62A4**: \u5185\u7F6E XSS \u9632\u62A4\u548C\u8F93\u5165\u9A8C\u8BC1\n\n## \u57FA\u7840\u7528\u6CD5\n\n\\`\\`\\`tsx\nimport { LiveChatWidget } from '@anker-in/campaign-ui'\nimport '@anker-in/campaign-ui/livechat.css'\n\nfunction App() {\n return (\n <LiveChatWidget\n apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n site=\"www.eufy.com\"\n channel_code=\"web_homepage\"\n welcomeMessage=\"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\"\n />\n )\n}\n\\`\\`\\`\n\n## \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\n\n\\`\\`\\`tsx\nconst customRenderers = {\n video: {\n render: (content) => (\n <video src={content.url} controls className=\"w-full rounded\" />\n )\n }\n}\n\n<LiveChatWidget\n apiBaseUrl=\"...\"\n site=\"...\"\n customRenderers={customRenderers}\n/>\n\\`\\`\\`\n `,\n },\n },\n },\n tags: ['autodocs'],\n argTypes: {\n apiBaseUrl: {\n control: 'text',\n description: 'API \u57FA\u7840 URL',\n },\n headers: {\n control: 'object',\n description: '\u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\uFF0C\u5C06\u5728\u6240\u6709 API \u8BF7\u6C42\u4E2D\u6DFB\u52A0',\n table: {\n defaultValue: { summary: 'undefined' },\n },\n },\n recaptchaSitekey: {\n control: 'text',\n description: 'Google reCAPTCHA v3 site key\uFF0C\u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u81EA\u52A8\u542F\u7528 reCAPTCHA v3 \u9A8C\u8BC1',\n table: {\n defaultValue: { summary: 'undefined' },\n },\n },\n recaptchaAction: {\n control: 'text',\n description: 'reCAPTCHA action \u540D\u79F0\uFF0C\u7528\u4E8E\u533A\u5206\u4E0D\u540C\u7684\u9A8C\u8BC1\u573A\u666F',\n table: {\n defaultValue: { summary: '\"activity\"' },\n },\n },\n site: {\n control: 'text',\n description: 'Shopify \u5E97\u94FA URL',\n },\n channelCode: {\n control: 'text',\n description: '\u6E20\u9053\u7F16\u7801\uFF0C\u7528\u4E8E\u6807\u8BC6\u6765\u6E90\u6E20\u9053',\n table: {\n defaultValue: { summary: 'undefined' },\n },\n },\n welcomeMessage: {\n control: 'text',\n description: '\u6B22\u8FCE\u6D88\u606F',\n table: {\n defaultValue: { summary: '\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F' },\n },\n },\n logoUrl: {\n control: 'text',\n description: 'Logo URL',\n },\n position: {\n control: 'object',\n description: '\u6C14\u6CE1\u6309\u94AE\u4F4D\u7F6E\u5BF9\u8C61',\n table: {\n defaultValue: { summary: '{ bottom: \"1.5rem\", right: \"1.5rem\" }' },\n },\n },\n },\n args: {\n apiBaseUrl: 'http://172.16.38.183:3003',\n site: 'www.eufy.com',\n loginUserId: 'test_test',\n welcomeMessage: '\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F',\n },\n}\n\nexport default meta\ntype Story = StoryObj<typeof LiveChatWidget>\n\n/**\n * \u9ED8\u8BA4\u914D\u7F6E - \u5C55\u793A\u6240\u6709\u529F\u80FD\n */\nexport const Default: Story = {\n args: {\n // \u57FA\u7840\u914D\u7F6E\n loginUserId: 'test_test1',\n apiBaseUrl: 'http://172.16.38.183:3003',\n site: 'beta.eufy.com',\n channelCode: 'dtc',\n title: 'eufy AI Assistant',\n cartId: 'gid://shopify/Cart/hWN7wB3Pa12gh78d8hPOAUBI?key=0e73db1d3fb5ac21da19099c45033253',\n accessToken: '47b1aa2c0797043f9baba39388029d70',\n\n // \u81EA\u5B9A\u4E49\u4F4D\u7F6E\n position: { bottom: '24px', right: '30px' },\n\n // \u6B22\u8FCE\u6D88\u606F\n welcomeMessage: '',\n\n // \u5FEB\u6377\u56DE\u590D\n quickReplies: [\n { id: '1', label: 'Product Info', value: 'Tell me about your products', icon: '\uD83D\uDCE6' },\n { id: '2', label: 'Track Order', value: 'I want to track my order', icon: '\uD83D\uDE9A' },\n { id: '3', label: 'Support', value: 'I need help with my device', icon: '\uD83D\uDD27' },\n { id: '4', label: 'Recommendations', value: 'Recommend products for me', icon: '\u2B50' },\n ],\n\n // \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\n complianceConfig: {\n title: \"Hi! I'm your eufy AI assistant.\",\n content:\n 'AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data.',\n checkboxText:\n 'By starting to use \"Live Chat\", you agree to Anker\\'s <a href=\"https://www.anker.com/pages/privacy-policy\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"text-decoration: underline;\">LIVE CHAT PRIVACY NOTICE</a>.',\n agreeButtonText: 'Agree',\n },\n\n // reCAPTCHA \u914D\u7F6E\uFF08\u53D6\u6D88\u6CE8\u91CA\u4EE5\u542F\u7528\uFF09\n // recaptchaSitekey: '6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14',\n recaptchaAction: 'livechat',\n\n // \u663E\u793A\u65B0\u4F1A\u8BDD\u6309\u94AE\n showNewSessionButton: true,\n\n // \u81EA\u5B9A\u4E49\u6587\u6848\n commonText: {\n learnMore: 'Learn More',\n total: 'Total',\n },\n\n // \u81EA\u5B9A\u4E49\u6D88\u606F\u6E32\u67D3\u5668\n customRenderers: {\n video: {\n render: (content: MessageContent) => {\n const videoContent = content as any\n return (\n <div className=\"w-full\">\n <video src={videoContent.url} controls className=\"w-full rounded-lg\" poster={videoContent.poster}>\n Your browser does not support video playback\n </video>\n {videoContent.title && <p className=\"mt-2 text-sm text-gray-600\">{videoContent.title}</p>}\n </div>\n )\n },\n } as MessageRenderer,\n image_gallery: {\n render: (content: MessageContent) => {\n const galleryContent = content as any\n const images = galleryContent.images || []\n return (\n <div className=\"grid grid-cols-2 gap-2\">\n {images.map((image: any, index: number) => (\n <div key={index} className=\"relative aspect-square\">\n <img\n src={image.url}\n alt={image.alt || `Image ${index + 1}`}\n className=\"size-full rounded-lg object-cover\"\n />\n </div>\n ))}\n </div>\n )\n },\n } as MessageRenderer,\n },\n\n // \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\n productCardRender: (product, productHandle) => {\n // product \u53EF\u80FD\u4E3A undefined\uFF0C\u6B64\u65F6\u53EF\u7528 productHandle \u67E5\u8BE2\n if (!product) {\n return (\n <div style={{ padding: '16px', border: '1px dashed #ccc', borderRadius: '8px', textAlign: 'center' }}>\n <p>Product loading... (handle: {productHandle})</p>\n </div>\n )\n }\n\n const imageUrl = product?.featured_image || ''\n const title = product?.title || ''\n const description = product?.description || ''\n const averageRating = product?.average_rating\n\n return (\n <div\n style={{\n border: '2px solid #4CAF50',\n borderRadius: '16px',\n padding: '16px',\n margin: '12px 0',\n backgroundColor: '#f0f9ff',\n boxShadow: '0 4px 12px rgba(76, 175, 80, 0.15)',\n }}\n >\n <div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>\n {imageUrl && (\n <img\n src={imageUrl}\n alt={title}\n style={{ width: '60px', height: '60px', borderRadius: '8px', objectFit: 'cover' }}\n />\n )}\n <div style={{ flex: 1 }}>\n <h4 style={{ margin: '0 0 4px 0', fontSize: '16px', fontWeight: 'bold' }}>{title}</h4>\n {description && (\n <p style={{ margin: 0, fontSize: '12px', color: '#666', lineHeight: 1.4 }}>\n {description.slice(0, 80)}...\n </p>\n )}\n {averageRating && (\n <span style={{ fontSize: '12px', color: '#FFB800' }}>Rating: {averageRating.toFixed(1)}</span>\n )}\n </div>\n <button\n style={{\n padding: '8px 16px',\n backgroundColor: '#4CAF50',\n color: 'white',\n borderRadius: '8px',\n border: 'none',\n cursor: 'pointer',\n }}\n onClick={() => console.log('View product:', productHandle, product)}\n >\n View\n </button>\n </div>\n </div>\n )\n },\n },\n\n render: args => (\n <LiveChatWidget\n {...args}\n // \u6240\u6709\u4E8B\u4EF6\u56DE\u8C03\n onOpen={() => console.log('[LiveChat] Chat opened')}\n onClose={() => console.log('[LiveChat] Chat closed')}\n onMessageSend={(message: string) => console.log('[LiveChat] Message sent:', message)}\n onError={(error: Error) => console.error('[LiveChat] Error:', error)}\n onTextMessage={() => console.log('[LiveChat] AI text message received')}\n onProductList={() => console.log('[LiveChat] Product list received')}\n onPromotionList={() => console.log('[LiveChat] Promotion list received')}\n onAddToCart={(product: any) => {\n console.log('[LiveChat] Add to cart:', product)\n alert(`Added \"${product.title}\" to cart!`)\n }}\n onCart={(cartId: string, checkoutUrl?: string) => {\n console.log('[LiveChat] Cart clicked:', { cartId, checkoutUrl })\n alert(`Cart ID: ${cartId}`)\n }}\n />\n ),\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * LiveChatWidget Storybook Stories\n * \u5C55\u793A LiveChat \u7EC4\u4EF6\u7684\u5404\u79CD\u4F7F\u7528\u573A\u666F\u548C\u914D\u7F6E\n */\n\nimport type { Meta, StoryObj } from '@storybook/react'\nimport { LiveChatWidget } from '../components/LiveChatWidget'\nimport type { MessageRenderer, MessageContent } from '../components/LiveChatWidget'\nimport '../styles/livechat.css'\n\nconst meta: Meta<typeof LiveChatWidget> = {\n title: 'Campaign/LiveChatWidget',\n component: LiveChatWidget,\n parameters: {\n layout: 'fullscreen',\n docs: {\n story: {\n inline: false,\n iframeHeight: 500,\n },\n description: {\n component: `\n# LiveChat \u804A\u5929\u7EC4\u4EF6\n\n\u53EF\u590D\u7528\u7684\u6C14\u6CE1\u5F39\u7A97\u804A\u5929\u7EC4\u4EF6\uFF0C\u652F\u6301 SSE \u6D41\u5F0F\u6D88\u606F\u3001\u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\u548C\u591A\u79CD\u6D88\u606F\u7C7B\u578B\u3002\n\n## \u529F\u80FD\u7279\u6027\n\n- \uD83C\uDF88 **\u6C14\u6CE1\u5F39\u7A97**: \u53EF\u81EA\u5B9A\u4E49\u4F4D\u7F6E\u7684\u60AC\u6D6E\u6C14\u6CE1\u6309\u94AE\n- \uD83D\uDCAC **\u6D41\u5F0F\u6D88\u606F**: \u57FA\u4E8E SSE \u7684\u5B9E\u65F6\u6D41\u5F0F\u54CD\u5E94\n- \uD83D\uDCE6 **\u591A\u79CD\u6D88\u606F\u7C7B\u578B**: \u6587\u672C\u3001\u5546\u54C1\u5361\u7247\u3001\u5546\u54C1\u5217\u8868\u3001\u653F\u7B56\u3001\u5FEB\u6377\u56DE\u590D\u7B49\n- \uD83C\uDFA8 **\u53EF\u5B9A\u5236**: \u652F\u6301\u81EA\u5B9A\u4E49\u54C1\u724C\u989C\u8272\u3001Logo\u3001\u6E32\u67D3\u5668\n- \uD83D\uDCF1 **\u54CD\u5E94\u5F0F**: \u79FB\u52A8\u7AEF\u5168\u5C4F\uFF0C\u684C\u9762\u7AEF\u56FA\u5B9A\u5C3A\u5BF8\n- \uD83D\uDCBE **\u4F1A\u8BDD\u7BA1\u7406**: \u81EA\u52A8\u7BA1\u7406 userId \u548C sessionId\n- \uD83D\uDD12 **\u5B89\u5168\u9632\u62A4**: \u5185\u7F6E XSS \u9632\u62A4\u548C\u8F93\u5165\u9A8C\u8BC1\n\n## \u57FA\u7840\u7528\u6CD5\n\n\\`\\`\\`tsx\nimport { LiveChatWidget } from '@anker-in/campaign-ui'\nimport '@anker-in/campaign-ui/livechat.css'\n\nfunction App() {\n return (\n <LiveChatWidget\n apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n site=\"www.eufy.com\"\n channel_code=\"web_homepage\"\n welcomeMessage=\"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\"\n />\n )\n}\n\\`\\`\\`\n\n## \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\n\n\\`\\`\\`tsx\nconst customRenderers = {\n video: {\n render: (content) => (\n <video src={content.url} controls className=\"w-full rounded\" />\n )\n }\n}\n\n<LiveChatWidget\n apiBaseUrl=\"...\"\n site=\"...\"\n customRenderers={customRenderers}\n/>\n\\`\\`\\`\n `,\n },\n },\n },\n tags: ['autodocs'],\n argTypes: {\n apiBaseUrl: {\n control: 'text',\n description: 'API \u57FA\u7840 URL',\n },\n headers: {\n control: 'object',\n description: '\u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\uFF0C\u5C06\u5728\u6240\u6709 API \u8BF7\u6C42\u4E2D\u6DFB\u52A0',\n table: {\n defaultValue: { summary: 'undefined' },\n },\n },\n recaptchaSitekey: {\n control: 'text',\n description: 'Google reCAPTCHA v3 site key\uFF0C\u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u81EA\u52A8\u542F\u7528 reCAPTCHA v3 \u9A8C\u8BC1',\n table: {\n defaultValue: { summary: 'undefined' },\n },\n },\n recaptchaAction: {\n control: 'text',\n description: 'reCAPTCHA action \u540D\u79F0\uFF0C\u7528\u4E8E\u533A\u5206\u4E0D\u540C\u7684\u9A8C\u8BC1\u573A\u666F',\n table: {\n defaultValue: { summary: '\"activity\"' },\n },\n },\n site: {\n control: 'text',\n description: 'Shopify \u5E97\u94FA URL',\n },\n channelCode: {\n control: 'text',\n description: '\u6E20\u9053\u7F16\u7801\uFF0C\u7528\u4E8E\u6807\u8BC6\u6765\u6E90\u6E20\u9053',\n table: {\n defaultValue: { summary: 'undefined' },\n },\n },\n welcomeMessage: {\n control: 'text',\n description: '\u6B22\u8FCE\u6D88\u606F',\n table: {\n defaultValue: { summary: '\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F' },\n },\n },\n logoUrl: {\n control: 'text',\n description: 'Logo URL',\n },\n position: {\n control: 'object',\n description: '\u6C14\u6CE1\u6309\u94AE\u4F4D\u7F6E\u5BF9\u8C61',\n table: {\n defaultValue: { summary: '{ bottom: \"1.5rem\", right: \"1.5rem\" }' },\n },\n },\n },\n args: {\n apiBaseUrl: 'http://172.16.38.183:3003',\n site: 'www.eufy.com',\n loginUserId: 'test_test',\n welcomeMessage: '\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F',\n },\n}\n\nexport default meta\ntype Story = StoryObj<typeof LiveChatWidget>\n\n/**\n * \u9ED8\u8BA4\u914D\u7F6E - \u5C55\u793A\u6240\u6709\u529F\u80FD\n */\nexport const Default: Story = {\n args: {\n // \u57FA\u7840\u914D\u7F6E\n loginUserId: 'test_test1',\n apiBaseUrl: 'https://beta-api-v2-livechat.anker.com',\n site: 'beta.eufy.com',\n channelCode: 'dtc',\n title: 'eufy AI Assistant',\n cartId: 'gid://shopify/Cart/hWN7wB3Pa12gh78d8hPOAUBI?key=0e73db1d3fb5ac21da19099c45033253',\n accessToken: '47b1aa2c0797043f9baba39388029d70',\n\n // \u81EA\u5B9A\u4E49\u4F4D\u7F6E\n position: { bottom: '24px', right: '30px' },\n\n // \u6B22\u8FCE\u6D88\u606F\n welcomeMessage: '',\n\n // \u5FEB\u6377\u56DE\u590D\n quickReplies: [\n { id: '1', label: 'Product Info', value: 'Tell me about your products', icon: '\uD83D\uDCE6' },\n { id: '2', label: 'Track Order', value: 'I want to track my order', icon: '\uD83D\uDE9A' },\n { id: '3', label: 'Support', value: 'I need help with my device', icon: '\uD83D\uDD27' },\n { id: '4', label: 'Recommendations', value: 'Recommend products for me', icon: '\u2B50' },\n ],\n\n // \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\n complianceConfig: {\n title: \"Hi! I'm your eufy AI assistant.\",\n content:\n 'AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data.',\n checkboxText:\n 'By starting to use \"Live Chat\", you agree to Anker\\'s <a href=\"https://www.anker.com/pages/privacy-policy\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"text-decoration: underline;\">LIVE CHAT PRIVACY NOTICE</a>.',\n agreeButtonText: 'Agree',\n },\n\n // reCAPTCHA \u914D\u7F6E\uFF08\u53D6\u6D88\u6CE8\u91CA\u4EE5\u542F\u7528\uFF09\n // recaptchaSitekey: '6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14',\n recaptchaAction: 'livechat',\n\n // \u663E\u793A\u65B0\u4F1A\u8BDD\u6309\u94AE\n showNewSessionButton: true,\n\n // \u81EA\u5B9A\u4E49\u6587\u6848\n commonText: {\n learnMore: 'Learn More',\n total: 'Total',\n },\n\n // \u81EA\u5B9A\u4E49\u6D88\u606F\u6E32\u67D3\u5668\n customRenderers: {\n video: {\n render: (content: MessageContent) => {\n const videoContent = content as any\n return (\n <div className=\"w-full\">\n <video src={videoContent.url} controls className=\"w-full rounded-lg\" poster={videoContent.poster}>\n Your browser does not support video playback\n </video>\n {videoContent.title && <p className=\"mt-2 text-sm text-gray-600\">{videoContent.title}</p>}\n </div>\n )\n },\n } as MessageRenderer,\n image_gallery: {\n render: (content: MessageContent) => {\n const galleryContent = content as any\n const images = galleryContent.images || []\n return (\n <div className=\"grid grid-cols-2 gap-2\">\n {images.map((image: any, index: number) => (\n <div key={index} className=\"relative aspect-square\">\n <img\n src={image.url}\n alt={image.alt || `Image ${index + 1}`}\n className=\"size-full rounded-lg object-cover\"\n />\n </div>\n ))}\n </div>\n )\n },\n } as MessageRenderer,\n },\n\n // \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\n productCardRender: (product, productHandle) => {\n // product \u53EF\u80FD\u4E3A undefined\uFF0C\u6B64\u65F6\u53EF\u7528 productHandle \u67E5\u8BE2\n if (!product) {\n return (\n <div style={{ padding: '16px', border: '1px dashed #ccc', borderRadius: '8px', textAlign: 'center' }}>\n <p>Product loading... (handle: {productHandle})</p>\n </div>\n )\n }\n\n const imageUrl = product?.featured_image || ''\n const title = product?.title || ''\n const description = product?.description || ''\n const averageRating = product?.average_rating\n\n return (\n <div\n style={{\n border: '2px solid #4CAF50',\n borderRadius: '16px',\n padding: '16px',\n margin: '12px 0',\n backgroundColor: '#f0f9ff',\n boxShadow: '0 4px 12px rgba(76, 175, 80, 0.15)',\n }}\n >\n <div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>\n {imageUrl && (\n <img\n src={imageUrl}\n alt={title}\n style={{ width: '60px', height: '60px', borderRadius: '8px', objectFit: 'cover' }}\n />\n )}\n <div style={{ flex: 1 }}>\n <h4 style={{ margin: '0 0 4px 0', fontSize: '16px', fontWeight: 'bold' }}>{title}</h4>\n {description && (\n <p style={{ margin: 0, fontSize: '12px', color: '#666', lineHeight: 1.4 }}>\n {description.slice(0, 80)}...\n </p>\n )}\n {averageRating && (\n <span style={{ fontSize: '12px', color: '#FFB800' }}>Rating: {averageRating.toFixed(1)}</span>\n )}\n </div>\n <button\n style={{\n padding: '8px 16px',\n backgroundColor: '#4CAF50',\n color: 'white',\n borderRadius: '8px',\n border: 'none',\n cursor: 'pointer',\n }}\n onClick={() => console.log('View product:', productHandle, product)}\n >\n View\n </button>\n </div>\n </div>\n )\n },\n },\n\n render: args => (\n <LiveChatWidget\n {...args}\n // \u6240\u6709\u4E8B\u4EF6\u56DE\u8C03\n onOpen={() => console.log('[LiveChat] Chat opened')}\n onClose={() => console.log('[LiveChat] Chat closed')}\n onMessageSend={(message: string) => console.log('[LiveChat] Message sent:', message)}\n onError={(error: Error) => console.error('[LiveChat] Error:', error)}\n onTextMessage={() => console.log('[LiveChat] AI text message received')}\n onProductList={() => console.log('[LiveChat] Product list received')}\n onPromotionList={() => console.log('[LiveChat] Promotion list received')}\n onAddToCart={(product: any) => {\n console.log('[LiveChat] Add to cart:', product)\n alert(`Added \"${product.title}\" to cart!`)\n }}\n onCart={(cartId: string, checkoutUrl?: string) => {\n console.log('[LiveChat] Cart clicked:', { cartId, checkoutUrl })\n alert(`Cart ID: ${cartId}`)\n }}\n />\n ),\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["jsx", "jsxs", "LiveChatWidget", "meta", "LiveChatWidget_stories_default", "Default", "content", "videoContent", "images", "image", "index", "product", "productHandle", "imageUrl", "title", "description", "averageRating", "args", "message", "error", "cartId", "checkoutUrl"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anker-in/campaign-ui",
3
- "version": "0.4.0-beta.07",
3
+ "version": "0.4.0",
4
4
  "description": "Campaign UI components and utilities for Anker projects",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -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'
@@ -126,6 +127,7 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
126
127
  showNewSessionButton,
127
128
  commonText,
128
129
  productCardRender,
130
+ productComparisonRender,
129
131
  bottomTips,
130
132
  complianceConfig,
131
133
  }) => {
@@ -163,6 +165,7 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
163
165
  onAddToCart,
164
166
  onCart,
165
167
  productCardRender,
168
+ productComparisonRender,
166
169
  })
167
170
 
168
171
  const {
@@ -175,6 +178,7 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
175
178
  openChat,
176
179
  closeChat,
177
180
  setInputValue,
181
+ setUserId,
178
182
  addMessage,
179
183
  setMessages,
180
184
  clearMessages,
@@ -232,6 +236,9 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
232
236
  return registry
233
237
  }, [customRenderers])
234
238
 
239
+ // 标记是否已经初始化过会话,防止重复调用
240
+ const sessionInitializedRef = React.useRef(false)
241
+
235
242
  /**
236
243
  * T043: 打开聊天窗口时初始化会话
237
244
  * 使用 API v2.0.0 的统一接口:
@@ -239,7 +246,18 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
239
246
  * - 如果有 sessionId,恢复会话并加载历史消息
240
247
  */
241
248
  useEffect(() => {
242
- if (!isOpen || !userId) return
249
+ // 窗口关闭时重置初始化标记
250
+ if (!isOpen) {
251
+ sessionInitializedRef.current = false
252
+ return
253
+ }
254
+
255
+ // userId 为 undefined 表示尚未初始化,等待初始化完成
256
+ if (userId === undefined) return
257
+
258
+ // 如果已经初始化过,不再重复调用
259
+ if (sessionInitializedRef.current) return
260
+ sessionInitializedRef.current = true
243
261
 
244
262
  const currentSessionId = sessionId
245
263
 
@@ -309,7 +327,7 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
309
327
  },
310
328
  })
311
329
  } else if (block.type === 'product_comparison' && block.data?.products && block.data?.dimensions) {
312
- // 转换产品对比数据结构并注入 onAddToCart 回调
330
+ // 转换产品对比数据结构并注入 onAddToCart 回调和自定义渲染函数
313
331
  contentBlocks.push({
314
332
  type: 'product_comparison',
315
333
  data: {
@@ -317,6 +335,7 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
317
335
  dimensions: block.data.dimensions,
318
336
  onAddToCart: onAddToCart,
319
337
  commonText: mergedText,
338
+ productComparisonRender: productComparisonRender,
320
339
  },
321
340
  })
322
341
  } else if (block.type === 'faq_list' && block.data?.found !== undefined) {
@@ -372,7 +391,8 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
372
391
  * 创建新会话
373
392
  */
374
393
  const handleCreateNewSession = useCallback(async () => {
375
- if (!userId) return
394
+ // userId 为 undefined 表示尚未初始化,等待初始化完成
395
+ if (userId === undefined) return
376
396
 
377
397
  // 如果用户没有配置欢迎语,显示 loading 状态
378
398
  if (!welcomeMessage) {
@@ -381,16 +401,23 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
381
401
 
382
402
  try {
383
403
  const response = await createSession({
384
- user_id: userId,
404
+ user_id: userId ?? '',
385
405
  site: site,
386
406
  channel_code: channelCode,
387
407
  real_user_id: loginUserId,
408
+ page_url: typeof window !== 'undefined' ? window.location.href : undefined,
388
409
  })
389
410
 
390
411
  if (response.success) {
391
412
  // 保存新会话 ID
392
413
  saveSession(response.sessionId)
393
414
 
415
+ // 保存后端返回的 userId(如果有)
416
+ if (response.userId) {
417
+ saveUserId(response.userId)
418
+ setUserId(response.userId)
419
+ }
420
+
394
421
  // 清空消息列表
395
422
  clearMessages()
396
423
 
@@ -488,16 +515,23 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
488
515
 
489
516
  try {
490
517
  const response = await createSession({
491
- user_id: userId,
518
+ user_id: userId ?? '',
492
519
  session_id: existingSessionId,
493
520
  site: site,
494
521
  channel_code: channelCode,
495
522
  real_user_id: loginUserId,
523
+ page_url: typeof window !== 'undefined' ? window.location.href : undefined,
496
524
  })
497
525
 
498
526
  if (response.success && response.resumed) {
499
527
  // 会话恢复成功
500
528
 
529
+ // 保存后端返回的 userId(如果有)
530
+ if (response.userId) {
531
+ saveUserId(response.userId)
532
+ setUserId(response.userId)
533
+ }
534
+
501
535
  // 准备欢迎消息(无论是否有历史消息都需要)
502
536
  const messageText = response.welcomeMessage || welcomeMessage
503
537
  const welcomeContent: MessageContent[] = messageText ? [{ type: 'text', text: messageText }] : []
@@ -683,10 +717,11 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
683
717
  if (!currentSessionId) {
684
718
  // 没有会话,创建新会话
685
719
  const response = await createSession({
686
- user_id: userId,
720
+ user_id: userId ?? '',
687
721
  site: site,
688
722
  channel_code: channelCode,
689
723
  real_user_id: loginUserId,
724
+ page_url: typeof window !== 'undefined' ? window.location.href : undefined,
690
725
  })
691
726
  if (response.success) {
692
727
  currentSessionId = response.sessionId
@@ -699,7 +734,7 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
699
734
  // 构建请求参数(session_id 现在是必填的)
700
735
  const requestPayload: ChatStreamRequest = {
701
736
  message: sanitized,
702
- user_id: userId,
737
+ user_id: userId ?? '',
703
738
  session_id: currentSessionId,
704
739
  context: {
705
740
  cartId: cartId,
@@ -730,10 +765,11 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
730
765
 
731
766
  // 创建新会话
732
767
  const response = await createSession({
733
- user_id: userId,
768
+ user_id: userId ?? '',
734
769
  site: site,
735
770
  channel_code: channelCode,
736
771
  real_user_id: loginUserId,
772
+ page_url: typeof window !== 'undefined' ? window.location.href : undefined,
737
773
  })
738
774
 
739
775
  if (response.success) {
@@ -33,6 +33,7 @@ export interface ProductComparisonData {
33
33
  [key: string]: ComparisonDimension | undefined
34
34
  }
35
35
  commonText?: CommonText
36
+ productComparisonRender?: (data: ProductComparisonData) => React.ReactNode
36
37
  }
37
38
 
38
39
  export interface ProductComparisonProps {
@@ -323,19 +324,6 @@ export const ProductComparison: React.FC<ProductComparisonProps> = ({ data, onAd
323
324
  </button>
324
325
  )}
325
326
 
326
- {/* 颜色选项(如果有variants) */}
327
- {product.variants && product.variants.length > 1 && (
328
- <div className="flex gap-2">
329
- {product.variants.slice(0, 3).map((variant, idx) => (
330
- <div
331
- key={variant.id || idx}
332
- className="size-6 rounded-full border-2 border-[#DADCE0]"
333
- style={{ backgroundColor: variant.color || '#000' }}
334
- title={variant.title}
335
- />
336
- ))}
337
- </div>
338
- )}
339
327
  </div>
340
328
  )
341
329
  })}
@@ -364,6 +352,11 @@ export const ProductComparisonRenderer: MessageRenderer = {
364
352
 
365
353
  const comparisonData = content.data as ProductComparisonData & { onAddToCart?: (product: Product) => void }
366
354
 
355
+ // 如果提供了自定义渲染函数,使用自定义渲染
356
+ if (comparisonData.productComparisonRender) {
357
+ return <>{comparisonData.productComparisonRender(comparisonData)}</>
358
+ }
359
+
367
360
  return (
368
361
  <ProductComparison
369
362
  data={comparisonData}
@@ -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'
@@ -377,6 +377,12 @@ export interface UseChatStateOptions {
377
377
  * @param productHandle 文本占位符中的产品 ID,可用于应用层查询产品数据
378
378
  */
379
379
  productCardRender?: (product: any, productHandle: string) => React.ReactNode
380
+
381
+ /**
382
+ * 自定义产品对比卡片渲染函数
383
+ * @param data 产品对比数据(包含 products 和 dimensions)
384
+ */
385
+ productComparisonRender?: (data: any) => React.ReactNode
380
386
  }
381
387
 
382
388
  export interface UseChatStateReturn {
@@ -391,9 +397,9 @@ export interface UseChatStateReturn {
391
397
  isOpen: boolean
392
398
 
393
399
  /**
394
- * 用户 ID
400
+ * 用户 ID(undefined 表示尚未初始化,空字符串表示由后端生成)
395
401
  */
396
- userId: string
402
+ userId: string | undefined
397
403
 
398
404
  /**
399
405
  * 会话 ID
@@ -430,6 +436,11 @@ export interface UseChatStateReturn {
430
436
  */
431
437
  setInputValue: (value: string) => void
432
438
 
439
+ /**
440
+ * 设置用户 ID(用于保存后端返回的 userId)
441
+ */
442
+ setUserId: (id: string) => void
443
+
433
444
  /**
434
445
  * 添加消息到列表
435
446
  */
@@ -490,13 +501,14 @@ export function useChatState(options: UseChatStateOptions = {}): UseChatStateRet
490
501
  onAddToCart,
491
502
  onCart,
492
503
  productCardRender,
504
+ productComparisonRender,
493
505
  } = options
494
506
 
495
507
  // 会话管理
496
508
  const { sessionId, saveSession, clearSession } = useSession()
497
509
 
498
- // 用户 ID (初始化时异步生成)
499
- const [userId, setUserId] = useState<string>('')
510
+ // 用户 ID (初始化时异步生成,undefined 表示尚未初始化)
511
+ const [userId, setUserId] = useState<string | undefined>(undefined)
500
512
 
501
513
  // 初始化 userId
502
514
  useEffect(() => {
@@ -646,11 +658,16 @@ export function useChatState(options: UseChatStateOptions = {}): UseChatStateRet
646
658
  // 重置卡片缓存队列
647
659
  pendingCardsRef.current = []
648
660
 
649
- // T039: 保存 sessionId(如果后端返回)
650
- const messageStartData = data as MessageStartData
661
+ // T039: 保存 sessionId 和 userId(如果后端返回)
662
+ const messageStartData = data as MessageStartData & { userId?: string }
651
663
  if (messageStartData.sessionId && messageStartData.sessionId !== sessionId) {
652
664
  saveSession(messageStartData.sessionId)
653
665
  }
666
+ // 保存后端返回的 userId(如果有)
667
+ if (messageStartData.userId) {
668
+ saveUserId(messageStartData.userId)
669
+ setUserId(messageStartData.userId)
670
+ }
654
671
 
655
672
  // 检查最后一条消息是否是 thinking 消息(用户发送消息时已添加)
656
673
  setMessagesState(prev => {
@@ -820,6 +837,7 @@ export function useChatState(options: UseChatStateOptions = {}): UseChatStateRet
820
837
  data: {
821
838
  products: transformedProducts,
822
839
  dimensions: contentData.dimensions || {},
840
+ productComparisonRender: productComparisonRender,
823
841
  },
824
842
  } as MessageContent
825
843
  }
@@ -1081,6 +1099,7 @@ export function useChatState(options: UseChatStateOptions = {}): UseChatStateRet
1081
1099
  closeChat,
1082
1100
  toggleChat,
1083
1101
  setInputValue,
1102
+ setUserId,
1084
1103
  addMessage,
1085
1104
  setMessages,
1086
1105
  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
  // 导出消息渲染器(供自定义使用)
@@ -147,6 +147,10 @@ export interface ProductComparisonContent {
147
147
  }
148
148
  onAddToCart?: (product: Product) => void
149
149
  commonText?: CommonText
150
+ productComparisonRender?: (data: {
151
+ products: Product[]
152
+ dimensions: ProductComparisonContent['data']['dimensions']
153
+ }) => React.ReactNode
150
154
  }
151
155
  }
152
156
 
@@ -637,11 +641,13 @@ export interface NewSessionRequest {
637
641
  site?: string
638
642
  channel_code?: string
639
643
  real_user_id?: string
644
+ page_url?: string
640
645
  }
641
646
 
642
647
  export interface NewSessionResponse {
643
648
  success: boolean
644
649
  sessionId: string
650
+ userId?: string // 后端生成的 userId(当请求的 user_id 为空时返回)
645
651
  message: string
646
652
  resumed?: boolean
647
653
  messages?: Message[]
@@ -976,6 +982,15 @@ export interface LiveChatWidgetProps {
976
982
  */
977
983
  productCardRender?: (product: any, productHandle: string) => React.ReactNode
978
984
 
985
+ /**
986
+ * 自定义产品对比卡片渲染函数
987
+ * 用于自定义渲染 product_comparison 类型的产品对比卡片
988
+ * 当提供此函数时,将替代默认的产品对比卡片渲染逻辑
989
+ * @param data 产品对比数据(包含 products 和 dimensions)
990
+ * @returns React 可渲染的内容
991
+ */
992
+ productComparisonRender?: (data: ProductComparisonContent['data']) => React.ReactNode
993
+
979
994
  /**
980
995
  * 输入框底部提示文本
981
996
  * 不传入则不显示