@anker-in/campaign-ui 0.2.11-beta.40 → 0.2.11-beta.42

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 (54) 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/api/chat.js +2 -2
  4. package/dist/cjs/components/LiveChatWidget/api/chat.js.map +3 -3
  5. package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js +6 -6
  6. package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js.map +2 -2
  7. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.d.ts +1 -1
  8. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.js +1 -1
  9. package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.js.map +3 -3
  10. package/dist/cjs/components/LiveChatWidget/components/MessageList.js +3 -3
  11. package/dist/cjs/components/LiveChatWidget/components/MessageList.js.map +3 -3
  12. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.d.ts +4 -2
  13. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js +1 -1
  14. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
  15. package/dist/cjs/components/LiveChatWidget/types.d.ts +18 -11
  16. package/dist/cjs/components/LiveChatWidget/types.js.map +1 -1
  17. package/dist/cjs/components/index.d.ts +1 -1
  18. package/dist/cjs/components/index.js +1 -1
  19. package/dist/cjs/components/index.js.map +2 -2
  20. package/dist/cjs/stories/LiveChatWidget.stories.d.ts +1 -109
  21. package/dist/cjs/stories/LiveChatWidget.stories.js +7 -166
  22. package/dist/cjs/stories/LiveChatWidget.stories.js.map +3 -3
  23. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js +1 -1
  24. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
  25. package/dist/esm/components/LiveChatWidget/api/chat.js +2 -2
  26. package/dist/esm/components/LiveChatWidget/api/chat.js.map +3 -3
  27. package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js +7 -7
  28. package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js.map +2 -2
  29. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.d.ts +1 -1
  30. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.js +1 -1
  31. package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.js.map +3 -3
  32. package/dist/esm/components/LiveChatWidget/components/MessageList.js +2 -2
  33. package/dist/esm/components/LiveChatWidget/components/MessageList.js.map +3 -3
  34. package/dist/esm/components/LiveChatWidget/hooks/useChatState.d.ts +4 -2
  35. package/dist/esm/components/LiveChatWidget/hooks/useChatState.js +1 -1
  36. package/dist/esm/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
  37. package/dist/esm/components/LiveChatWidget/types.d.ts +18 -11
  38. package/dist/esm/components/index.d.ts +1 -1
  39. package/dist/esm/components/index.js +1 -1
  40. package/dist/esm/components/index.js.map +2 -2
  41. package/dist/esm/stories/LiveChatWidget.stories.d.ts +1 -109
  42. package/dist/esm/stories/LiveChatWidget.stories.js +7 -166
  43. package/dist/esm/stories/LiveChatWidget.stories.js.map +3 -3
  44. package/package.json +1 -1
  45. package/src/components/LiveChatWidget/LiveChatWidget.tsx +65 -23
  46. package/src/components/LiveChatWidget/api/chat.ts +8 -7
  47. package/src/components/LiveChatWidget/components/ComplianceDialog.tsx +1 -5
  48. package/src/components/LiveChatWidget/components/MessageContent/ProductCard.tsx +17 -8
  49. package/src/components/LiveChatWidget/components/MessageList.tsx +11 -17
  50. package/src/components/LiveChatWidget/hooks/useChatState.ts +64 -98
  51. package/src/components/LiveChatWidget/types.ts +18 -11
  52. package/src/components/index.ts +2 -1
  53. package/src/stories/LiveChatWidget.stories.tsx +95 -660
  54. package/src/styles/livechat.css +29 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/components/LiveChatWidget/components/ComplianceDialog.tsx"],
4
- "sourcesContent": ["/**\n * \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u7EC4\u4EF6\n * \u5728\u7528\u6237\u9996\u6B21\u70B9\u51FB\u804A\u5929\u6C14\u6CE1\u65F6\u663E\u793A\uFF0C\u7528\u6237\u540C\u610F\u540E\u624D\u6253\u5F00\u804A\u5929\u7A97\u53E3\n */\n\nimport React, { useState, useCallback } from 'react'\nimport * as Dialog from '@radix-ui/react-dialog'\nimport type { ComplianceDialogConfig } from '../types'\n\nexport interface ComplianceDialogProps {\n /**\n * \u662F\u5426\u6253\u5F00\u5F39\u7A97\n */\n open: boolean\n\n /**\n * \u5F39\u7A97\u914D\u7F6E\n */\n config: ComplianceDialogConfig\n\n /**\n * \u7528\u6237\u70B9\u51FB\u540C\u610F\u6309\u94AE\u7684\u56DE\u8C03\n */\n onAgree: () => void\n\n /**\n * \u7528\u6237\u5173\u95ED\u5F39\u7A97\u7684\u56DE\u8C03\uFF08\u70B9\u51FB\u906E\u7F69\u6216\u5173\u95ED\u6309\u94AE\uFF09\n */\n onClose: () => void\n}\n\n/**\n * \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u663E\u793A\u6CD5\u89C4\u534F\u8BAE\u5185\u5BB9\n * - \u5FC5\u987B\u52FE\u9009\u590D\u9009\u6846\u624D\u80FD\u70B9\u51FB\u540C\u610F\u6309\u94AE\n * - \u652F\u6301\u5728\u6587\u672C\u4E2D\u5D4C\u5165\u94FE\u63A5\n * - \u54CD\u5E94\u5F0F\u8BBE\u8BA1\uFF0C\u79FB\u52A8\u7AEF\u53CB\u597D\n *\n * @example\n * ```tsx\n * <ComplianceDialog\n * open={showCompliance}\n * config={{\n * title: \"Hi! I'm your eufy AI assistant.\",\n * content: \"AI-generated responses can be inaccurate...\",\n * checkboxText: \"By starting to use \\\"Live Chat\\\", you agree to Anker's {link}.\",\n * linkText: \"LIVE CHAT PRIVACY NOTICE\",\n * linkUrl: \"https://www.anker.com/privacy\",\n * agreeButtonText: \"Agree\"\n * }}\n * onAgree={() => setShowCompliance(false)}\n * onClose={() => setShowCompliance(false)}\n * />\n * ```\n */\nexport const ComplianceDialog: React.FC<ComplianceDialogProps> = ({\n open,\n config,\n onAgree,\n onClose,\n}) => {\n const [isChecked, setIsChecked] = useState(false)\n\n const handleAgree = useCallback(() => {\n if (isChecked) {\n onAgree()\n // \u91CD\u7F6E\u52FE\u9009\u72B6\u6001\uFF0C\u4EE5\u4FBF\u4E0B\u6B21\u6253\u5F00\u65F6\u91CD\u65B0\u52FE\u9009\n setIsChecked(false)\n }\n }, [isChecked, onAgree])\n\n const handleOpenChange = useCallback((open: boolean) => {\n if (!open) {\n onClose()\n // \u91CD\u7F6E\u52FE\u9009\u72B6\u6001\n setIsChecked(false)\n }\n }, [onClose])\n\n // \u5904\u7406\u70B9\u51FB\u4E8B\u4EF6\uFF0C\u9632\u6B62\u70B9\u51FB\u94FE\u63A5\u65F6\u89E6\u53D1 checkbox\n const handleCheckboxLabelClick = useCallback((e: React.MouseEvent) => {\n // \u5982\u679C\u70B9\u51FB\u7684\u662F\u94FE\u63A5\uFF0C\u963B\u6B62\u5192\u6CE1\n if ((e.target as HTMLElement).tagName === 'A') {\n e.stopPropagation()\n }\n }, [])\n\n return (\n <Dialog.Root open={open} onOpenChange={handleOpenChange}>\n <Dialog.Portal>\n {/* \u5F39\u7A97\u5185\u5BB9 - \u65E0\u906E\u7F69\u5C42\uFF08\u4E0E\u804A\u5929\u7A97\u53E3\u4E00\u81F4\uFF09 */}\n <Dialog.Content\n className=\"livechat-compliance-dialog\"\n onInteractOutside={(e) => e.preventDefault()}\n onEscapeKeyDown={(e) => e.preventDefault()}\n >\n {/* Header - \u5305\u542B\u5173\u95ED\u6309\u94AE\uFF08\u53F3\u4E0A\u89D2\uFF09 */}\n <div className=\"flex items-center justify-end px-4 pt-4 pb-2 flex-shrink-0 max-md:px-6 max-sm:px-5\">\n <Dialog.Close asChild>\n <button\n type=\"button\"\n className=\"\n w-6 h-6\n flex items-center justify-center\n border-0 bg-transparent text-gray-500\n cursor-pointer\n \"\n aria-label=\"\u5173\u95ED\"\n >\n <svg\n width=\"20\"\n height=\"20\"\n className=\"text-[#3C3C3B]\"\n viewBox=\"0 0 20 20\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <line x1=\"15\" y1=\"5\" x2=\"5\" y2=\"15\" />\n <line x1=\"5\" y1=\"5\" x2=\"15\" y2=\"15\" />\n </svg>\n </button>\n </Dialog.Close>\n </div>\n\n {/* Body - \u53EF\u6EDA\u52A8\u5185\u5BB9\u533A\u57DF */}\n <div className=\"flex-1 overflow-y-auto px-4 pb-4 max-md:px-6 max-md:pb-6 max-sm:px-5 max-sm:pb-5\">\n {/* \u6807\u9898 */}\n <Dialog.Title\n className=\"\n text-[18px] font-bold text-[#080A0F]\n mb-1 leading-[1.4] tracking-tight\n \"\n >\n {config.title}\n </Dialog.Title>\n\n {/* \u5185\u5BB9 */}\n <div\n className=\"\n text-[16px] font-bold leading-[1.4] text-[#080A0F]\n tracking-tight\n mb-2\n \"\n dangerouslySetInnerHTML={{ __html: config.content }}\n />\n\n {/* \u52FE\u9009\u6846 */}\n <label\n className=\"\n flex items-center gap-2 mb-4\n cursor-pointer select-none\n \"\n onClick={handleCheckboxLabelClick}\n >\n <input\n type=\"checkbox\"\n className=\"absolute opacity-0 w-0 h-0\"\n checked={isChecked}\n onChange={e => setIsChecked(e.target.checked)}\n />\n <span\n className={`\n flex-shrink-0 w-4 h-4 mt-0.5\n border-[1px] rounded-[2px] flex items-center justify-center\n transition-all duration-200 ease-in-out\n ${isChecked ? 'bg-[#080A0F] border-[#080A0F] text-white' : 'bg-white border-[#2A2C32]'}\n `}\n >\n {isChecked && (\n <svg\n width=\"12\"\n height=\"10\"\n viewBox=\"0 0 12 10\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <polyline points=\"1.5 5 4.5 8 10.5 2\" />\n </svg>\n )}\n </span>\n <span\n className=\"\n flex-1 text-[14px] leading-[1.4] text-[#080A0F]\n tracking-tight font-bold\n [&_a]:underline\n \"\n dangerouslySetInnerHTML={{ __html: config.checkboxText }}\n />\n </label>\n\n {/* \u540C\u610F\u6309\u94AE */}\n <button\n type=\"button\"\n className={`\n w-full px-5 py-[10px]\n border-0 rounded-[19px]\n text-[14px] leading-[1.2] \n tracking-tighter font-bold\n transition-all duration-200 ease-in-out text-white\n ${isChecked ? 'bg-[#080A0F] cursor-pointer ' : 'bg-[#767880] cursor-not-allowed'}\n `}\n onClick={handleAgree}\n disabled={!isChecked}\n >\n {config.agreeButtonText || 'Agree'}\n </button>\n </div>\n </Dialog.Content>\n </Dialog.Portal>\n </Dialog.Root>\n )\n}\n"],
5
- "mappings": "AA+GgB,OAWE,OAAAA,EAXF,QAAAC,MAAA,oBA1GhB,OAAgB,YAAAC,EAAU,eAAAC,MAAmB,QAC7C,UAAYC,MAAY,yBAmDjB,MAAMC,EAAoD,CAAC,CAChE,KAAAC,EACA,OAAAC,EACA,QAAAC,EACA,QAAAC,CACF,IAAM,CACJ,KAAM,CAACC,EAAWC,CAAY,EAAIT,EAAS,EAAK,EAE1CU,EAAcT,EAAY,IAAM,CAChCO,IACFF,EAAQ,EAERG,EAAa,EAAK,EAEtB,EAAG,CAACD,EAAWF,CAAO,CAAC,EAEjBK,EAAmBV,EAAaG,GAAkB,CACjDA,IACHG,EAAQ,EAERE,EAAa,EAAK,EAEtB,EAAG,CAACF,CAAO,CAAC,EAGNK,EAA2BX,EAAaY,GAAwB,CAE/DA,EAAE,OAAuB,UAAY,KACxCA,EAAE,gBAAgB,CAEtB,EAAG,CAAC,CAAC,EAEL,OACEf,EAACI,EAAO,KAAP,CAAY,KAAME,EAAM,aAAcO,EACrC,SAAAb,EAACI,EAAO,OAAP,CAEC,SAAAH,EAACG,EAAO,QAAP,CACC,UAAU,6BACV,kBAAoBW,GAAMA,EAAE,eAAe,EAC3C,gBAAkBA,GAAMA,EAAE,eAAe,EAGzC,UAAAf,EAAC,OAAI,UAAU,sFACb,SAAAA,EAACI,EAAO,MAAP,CAAa,QAAO,GACnB,SAAAJ,EAAC,UACC,KAAK,SACL,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,kBAMV,aAAW,eAEX,SAAAC,EAAC,OACC,MAAM,KACN,OAAO,KACP,UAAU,iBACV,QAAQ,YACR,KAAK,OACL,OAAO,eACP,YAAY,IACZ,cAAc,QACd,eAAe,QAEf,UAAAD,EAAC,QAAK,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,EACpCA,EAAC,QAAK,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,GACtC,EACF,EACF,EACF,EAGAC,EAAC,OAAI,UAAU,mFAEb,UAAAD,EAACI,EAAO,MAAP,CACC,UAAU;AAAA;AAAA;AAAA,gBAKT,SAAAG,EAAO,MACV,EAGAP,EAAC,OACC,UAAU;AAAA;AAAA;AAAA;AAAA,gBAKV,wBAAyB,CAAE,OAAQO,EAAO,OAAQ,EACpD,EAGAN,EAAC,SACC,UAAU;AAAA;AAAA;AAAA,gBAIV,QAASa,EAET,UAAAd,EAAC,SACC,KAAK,WACL,UAAU,6BACV,QAASU,EACT,SAAUK,GAAKJ,EAAaI,EAAE,OAAO,OAAO,EAC9C,EACAf,EAAC,QACC,UAAW;AAAA;AAAA;AAAA;AAAA,oBAIPU,EAAY,2CAA6C,2BAA2B;AAAA,kBAGvF,SAAAA,GACCV,EAAC,OACC,MAAM,KACN,OAAO,KACP,QAAQ,YACR,KAAK,OACL,OAAO,eACP,YAAY,IACZ,cAAc,QACd,eAAe,QAEf,SAAAA,EAAC,YAAS,OAAO,qBAAqB,EACxC,EAEJ,EACAA,EAAC,QACC,UAAU;AAAA;AAAA;AAAA;AAAA,kBAKV,wBAAyB,CAAE,OAAQO,EAAO,YAAa,EACzD,GACF,EAGAP,EAAC,UACC,KAAK,SACL,UAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAMPU,EAAY,gCAAkC,iCAAiC;AAAA,gBAEnF,QAASE,EACT,SAAU,CAACF,EAEV,SAAAH,EAAO,iBAAmB,QAC7B,GACF,GACF,EACF,EACF,CAEJ",
4
+ "sourcesContent": ["/**\n * \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u7EC4\u4EF6\n * \u5728\u7528\u6237\u9996\u6B21\u70B9\u51FB\u804A\u5929\u6C14\u6CE1\u65F6\u663E\u793A\uFF0C\u7528\u6237\u540C\u610F\u540E\u624D\u6253\u5F00\u804A\u5929\u7A97\u53E3\n */\n\nimport React, { useState, useCallback } from 'react'\nimport * as Dialog from '@radix-ui/react-dialog'\nimport type { ComplianceDialogConfig } from '../types'\n\nexport interface ComplianceDialogProps {\n /**\n * \u662F\u5426\u6253\u5F00\u5F39\u7A97\n */\n open: boolean\n\n /**\n * \u5F39\u7A97\u914D\u7F6E\n */\n config: ComplianceDialogConfig\n\n /**\n * \u7528\u6237\u70B9\u51FB\u540C\u610F\u6309\u94AE\u7684\u56DE\u8C03\n */\n onAgree: () => void\n\n /**\n * \u7528\u6237\u5173\u95ED\u5F39\u7A97\u7684\u56DE\u8C03\uFF08\u70B9\u51FB\u906E\u7F69\u6216\u5173\u95ED\u6309\u94AE\uFF09\n */\n onClose: () => void\n}\n\n/**\n * \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u663E\u793A\u6CD5\u89C4\u534F\u8BAE\u5185\u5BB9\n * - \u5FC5\u987B\u52FE\u9009\u590D\u9009\u6846\u624D\u80FD\u70B9\u51FB\u540C\u610F\u6309\u94AE\n * - \u652F\u6301\u5728\u6587\u672C\u4E2D\u5D4C\u5165\u94FE\u63A5\n * - \u54CD\u5E94\u5F0F\u8BBE\u8BA1\uFF0C\u79FB\u52A8\u7AEF\u53CB\u597D\n *\n * @example\n * ```tsx\n * <ComplianceDialog\n * open={showCompliance}\n * config={{\n * title: \"Hi! I'm your eufy AI assistant.\",\n * content: \"AI-generated responses can be inaccurate...\",\n * checkboxText: \"By starting to use \\\"Live Chat\\\", you agree to Anker's {link}.\",\n * linkText: \"LIVE CHAT PRIVACY NOTICE\",\n * linkUrl: \"https://www.anker.com/privacy\",\n * agreeButtonText: \"Agree\"\n * }}\n * onAgree={() => setShowCompliance(false)}\n * onClose={() => setShowCompliance(false)}\n * />\n * ```\n */\nexport const ComplianceDialog: React.FC<ComplianceDialogProps> = ({\n open,\n config,\n onAgree,\n onClose,\n}) => {\n const [isChecked, setIsChecked] = useState(true)\n\n const handleAgree = useCallback(() => {\n if (isChecked) {\n onAgree()\n }\n }, [isChecked, onAgree])\n\n const handleOpenChange = useCallback((open: boolean) => {\n if (!open) {\n onClose()\n }\n }, [onClose])\n\n // \u5904\u7406\u70B9\u51FB\u4E8B\u4EF6\uFF0C\u9632\u6B62\u70B9\u51FB\u94FE\u63A5\u65F6\u89E6\u53D1 checkbox\n const handleCheckboxLabelClick = useCallback((e: React.MouseEvent) => {\n // \u5982\u679C\u70B9\u51FB\u7684\u662F\u94FE\u63A5\uFF0C\u963B\u6B62\u5192\u6CE1\n if ((e.target as HTMLElement).tagName === 'A') {\n e.stopPropagation()\n }\n }, [])\n\n return (\n <Dialog.Root open={open} onOpenChange={handleOpenChange}>\n <Dialog.Portal>\n {/* \u5F39\u7A97\u5185\u5BB9 - \u65E0\u906E\u7F69\u5C42\uFF08\u4E0E\u804A\u5929\u7A97\u53E3\u4E00\u81F4\uFF09 */}\n <Dialog.Content\n className=\"livechat-compliance-dialog\"\n onInteractOutside={(e) => e.preventDefault()}\n onEscapeKeyDown={(e) => e.preventDefault()}\n >\n {/* Header - \u5305\u542B\u5173\u95ED\u6309\u94AE\uFF08\u53F3\u4E0A\u89D2\uFF09 */}\n <div className=\"flex items-center justify-end px-4 pt-4 pb-2 flex-shrink-0 max-md:px-6 max-sm:px-5\">\n <Dialog.Close asChild>\n <button\n type=\"button\"\n className=\"\n w-6 h-6\n flex items-center justify-center\n border-0 bg-transparent text-gray-500\n cursor-pointer\n \"\n aria-label=\"\u5173\u95ED\"\n >\n <svg\n width=\"20\"\n height=\"20\"\n className=\"text-[#3C3C3B]\"\n viewBox=\"0 0 20 20\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <line x1=\"15\" y1=\"5\" x2=\"5\" y2=\"15\" />\n <line x1=\"5\" y1=\"5\" x2=\"15\" y2=\"15\" />\n </svg>\n </button>\n </Dialog.Close>\n </div>\n\n {/* Body - \u53EF\u6EDA\u52A8\u5185\u5BB9\u533A\u57DF */}\n <div className=\"flex-1 overflow-y-auto px-4 pb-4 max-md:px-6 max-md:pb-6 max-sm:px-5 max-sm:pb-5\">\n {/* \u6807\u9898 */}\n <Dialog.Title\n className=\"\n text-[18px] font-bold text-[#080A0F]\n mb-1 leading-[1.4] tracking-tight\n \"\n >\n {config.title}\n </Dialog.Title>\n\n {/* \u5185\u5BB9 */}\n <div\n className=\"\n text-[16px] font-bold leading-[1.4] text-[#080A0F]\n tracking-tight\n mb-2\n \"\n dangerouslySetInnerHTML={{ __html: config.content }}\n />\n\n {/* \u52FE\u9009\u6846 */}\n <label\n className=\"\n flex items-center gap-2 mb-4\n cursor-pointer select-none\n \"\n onClick={handleCheckboxLabelClick}\n >\n <input\n type=\"checkbox\"\n className=\"absolute opacity-0 w-0 h-0\"\n checked={isChecked}\n onChange={e => setIsChecked(e.target.checked)}\n />\n <span\n className={`\n flex-shrink-0 w-4 h-4 mt-0.5\n border-[1px] rounded-[2px] flex items-center justify-center\n transition-all duration-200 ease-in-out\n ${isChecked ? 'bg-[#080A0F] border-[#080A0F] text-white' : 'bg-white border-[#2A2C32]'}\n `}\n >\n {isChecked && (\n <svg\n width=\"12\"\n height=\"10\"\n viewBox=\"0 0 12 10\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <polyline points=\"1.5 5 4.5 8 10.5 2\" />\n </svg>\n )}\n </span>\n <span\n className=\"\n flex-1 text-[14px] leading-[1.4] text-[#080A0F]\n tracking-tight font-bold\n [&_a]:underline\n \"\n dangerouslySetInnerHTML={{ __html: config.checkboxText }}\n />\n </label>\n\n {/* \u540C\u610F\u6309\u94AE */}\n <button\n type=\"button\"\n className={`\n w-full px-5 py-[10px]\n border-0 rounded-[19px]\n text-[14px] leading-[1.2] \n tracking-tighter font-bold\n transition-all duration-200 ease-in-out text-white\n ${isChecked ? 'bg-[#080A0F] cursor-pointer ' : 'bg-[#767880] cursor-not-allowed'}\n `}\n onClick={handleAgree}\n disabled={!isChecked}\n >\n {config.agreeButtonText || 'Agree'}\n </button>\n </div>\n </Dialog.Content>\n </Dialog.Portal>\n </Dialog.Root>\n )\n}\n"],
5
+ "mappings": "AA2GgB,OAWE,OAAAA,EAXF,QAAAC,MAAA,oBAtGhB,OAAgB,YAAAC,EAAU,eAAAC,MAAmB,QAC7C,UAAYC,MAAY,yBAmDjB,MAAMC,EAAoD,CAAC,CAChE,KAAAC,EACA,OAAAC,EACA,QAAAC,EACA,QAAAC,CACF,IAAM,CACJ,KAAM,CAACC,EAAWC,CAAY,EAAIT,EAAS,EAAI,EAEzCU,EAAcT,EAAY,IAAM,CAChCO,GACFF,EAAQ,CAEZ,EAAG,CAACE,EAAWF,CAAO,CAAC,EAEjBK,EAAmBV,EAAaG,GAAkB,CACjDA,GACHG,EAAQ,CAEZ,EAAG,CAACA,CAAO,CAAC,EAGNK,EAA2BX,EAAaY,GAAwB,CAE/DA,EAAE,OAAuB,UAAY,KACxCA,EAAE,gBAAgB,CAEtB,EAAG,CAAC,CAAC,EAEL,OACEf,EAACI,EAAO,KAAP,CAAY,KAAME,EAAM,aAAcO,EACrC,SAAAb,EAACI,EAAO,OAAP,CAEC,SAAAH,EAACG,EAAO,QAAP,CACC,UAAU,6BACV,kBAAoBW,GAAMA,EAAE,eAAe,EAC3C,gBAAkBA,GAAMA,EAAE,eAAe,EAGzC,UAAAf,EAAC,OAAI,UAAU,sFACb,SAAAA,EAACI,EAAO,MAAP,CAAa,QAAO,GACnB,SAAAJ,EAAC,UACC,KAAK,SACL,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,kBAMV,aAAW,eAEX,SAAAC,EAAC,OACC,MAAM,KACN,OAAO,KACP,UAAU,iBACV,QAAQ,YACR,KAAK,OACL,OAAO,eACP,YAAY,IACZ,cAAc,QACd,eAAe,QAEf,UAAAD,EAAC,QAAK,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,EACpCA,EAAC,QAAK,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,GACtC,EACF,EACF,EACF,EAGAC,EAAC,OAAI,UAAU,mFAEb,UAAAD,EAACI,EAAO,MAAP,CACC,UAAU;AAAA;AAAA;AAAA,gBAKT,SAAAG,EAAO,MACV,EAGAP,EAAC,OACC,UAAU;AAAA;AAAA;AAAA;AAAA,gBAKV,wBAAyB,CAAE,OAAQO,EAAO,OAAQ,EACpD,EAGAN,EAAC,SACC,UAAU;AAAA;AAAA;AAAA,gBAIV,QAASa,EAET,UAAAd,EAAC,SACC,KAAK,WACL,UAAU,6BACV,QAASU,EACT,SAAUK,GAAKJ,EAAaI,EAAE,OAAO,OAAO,EAC9C,EACAf,EAAC,QACC,UAAW;AAAA;AAAA;AAAA;AAAA,oBAIPU,EAAY,2CAA6C,2BAA2B;AAAA,kBAGvF,SAAAA,GACCV,EAAC,OACC,MAAM,KACN,OAAO,KACP,QAAQ,YACR,KAAK,OACL,OAAO,eACP,YAAY,IACZ,cAAc,QACd,eAAe,QAEf,SAAAA,EAAC,YAAS,OAAO,qBAAqB,EACxC,EAEJ,EACAA,EAAC,QACC,UAAU;AAAA;AAAA;AAAA;AAAA,kBAKV,wBAAyB,CAAE,OAAQO,EAAO,YAAa,EACzD,GACF,EAGAP,EAAC,UACC,KAAK,SACL,UAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAMPU,EAAY,gCAAkC,iCAAiC;AAAA,gBAEnF,QAASE,EACT,SAAU,CAACF,EAEV,SAAAH,EAAO,iBAAmB,QAC7B,GACF,GACF,EACF,EACF,CAEJ",
6
6
  "names": ["jsx", "jsxs", "useState", "useCallback", "Dialog", "ComplianceDialog", "open", "config", "onAgree", "onClose", "isChecked", "setIsChecked", "handleAgree", "handleOpenChange", "handleCheckboxLabelClick", "e"]
7
7
  }
@@ -32,7 +32,7 @@ import type { MessageRenderer } from '../../types';
32
32
  * data: {
33
33
  * product: { ... },
34
34
  * onAddToCart: (product) => { ... },
35
- * productCardRender: (product) => <CustomCard product={product} />
35
+ * productCardRender: (product, productHandle) => <CustomCard product={product} handle={productHandle} />
36
36
  * }
37
37
  * }
38
38
  * <ProductCard.render(content, false, false) />
@@ -1,2 +1,2 @@
1
- import{Fragment as F,jsx as e,jsxs as i}from"react/jsx-runtime";import{CURRENCY_SYMBOLS as b,DEFAULT_COMMON_TEXT as u}from"../../constants.js";function y(t){const{amount:n,currency:o}=t;return`${b[o]||o}${n.toFixed(2)}`}function w(t,n,o=u.off){if(!t.discount_type||t.discount_value===void 0)return"";const a=typeof t.discount_value=="string"?parseFloat(t.discount_value):t.discount_value;return isNaN(a)?"":t.discount_type==="percentage"?`${Math.round(a)}% ${o}`:t.discount_type==="fixed_amount"?`${b[n]||n}${Math.round(a)} ${o}`:""}const P=({product:t,onAddToCart:n,addToCartText:o=u.addToCart,offText:a=u.off})=>{const{title:r,description:m,price:s,imageUrl:d,stockStatus:C,averageRating:p,variants:N}=t,h=C==="out_of_stock",c=N?.[0],l=c?.discount?.has_discount,f=l?c?.discount?.discount_price:null,g=c?.discount,_=f?{amount:f,currency:s.currency}:s,x=g&&l?w(g,s.currency,a):"",k=v=>{v.preventDefault(),v.stopPropagation(),n&&n(t)};return e("div",{className:"block w-full overflow-hidden rounded-2xl bg-[#F5F6F7] transition-shadow mb-[32px]",children:e("div",{className:"block",children:i("div",{className:"flex gap-2 p-4 bg-white",children:[e("div",{className:" flex shrink-0 items-center overflow-hidden rounded-md ",style:{width:"40%"},children:e("img",{src:d,alt:r,className:`h-auto w-full object-cover ${h?"opacity-50":""}`,loading:"lazy"})}),i("div",{className:"flex flex-1 flex-col justify-center",children:[x&&e("div",{className:"mb-1 w-fit rounded-full px-2 text-sm font-bold leading-none tracking-[-0.04em] text-white",style:{backgroundColor:"#005D8E",paddingTop:"6px",paddingBottom:"4px"},children:x}),e("h4",{className:"line-clamp-2 text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#080A0F]",children:r}),i("div",{className:"mt-4 flex items-center gap-2",children:[i("div",{className:"flex items-center gap-1",children:[e("span",{className:"text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]",children:y(_)}),l&&e("span",{className:"text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#6D6D6F] line-through",children:y(s)})]}),p!==void 0&&i("div",{className:"flex items-center gap-0.5 text-xs text-gray-600",children:[e("span",{className:"text-yellow-500",children:"\u2B50"}),e("span",{children:p.toFixed(1)})]})]}),e("button",{type:"button",onClick:k,className:"mt-2 w-fit rounded-full px-[20px] py-[10px] text-center text-sm font-bold leading-[1.2] tracking-[-0.04em] text-white",style:{backgroundColor:"#1D1D1F"},children:o})]})]})})})},M={render:(t,n,o)=>{const a=t,{product:r,rawProduct:m,onAddToCart:s,productCardRender:d}=a.data;return r?d?e(F,{children:d(m||r)}):e(P,{product:r,onAddToCart:s}):null}};export{M as ProductCard};
1
+ import{Fragment as F,jsx as e,jsxs as c}from"react/jsx-runtime";import{CURRENCY_SYMBOLS as y,DEFAULT_COMMON_TEXT as p}from"../../constants.js";function C(t){const{amount:o,currency:n}=t;return`${y[n]||n}${o.toFixed(2)}`}function k(t,o,n=p.off){if(!t.discount_type||t.discount_value===void 0)return"";const r=typeof t.discount_value=="string"?parseFloat(t.discount_value):t.discount_value;return isNaN(r)?"":t.discount_type==="percentage"?`${Math.round(r)}% ${n}`:t.discount_type==="fixed_amount"?`${y[o]||o}${Math.round(r)} ${n}`:""}const w=({product:t,onAddToCart:o,addToCartText:n=p.addToCart,offText:r=p.off})=>{const{title:a,description:i,price:s,imageUrl:l,stockStatus:d,averageRating:f,variants:h}=t,N=d==="out_of_stock",u=h?.[0],m=u?.discount?.has_discount,g=m?u?.discount?.discount_price:null,x=u?.discount,P=g?{amount:g,currency:s.currency}:s,v=x&&m?k(x,s.currency,r):"",_=b=>{b.preventDefault(),b.stopPropagation(),o&&o(t)};return e("div",{className:"block w-full overflow-hidden rounded-2xl bg-[#F5F6F7] transition-shadow mb-[32px]",children:e("div",{className:"block",children:c("div",{className:"flex gap-2 p-4 bg-white",children:[e("div",{className:" flex shrink-0 items-center overflow-hidden rounded-md ",style:{width:"40%"},children:e("img",{src:l,alt:a,className:`h-auto w-full object-cover ${N?"opacity-50":""}`,loading:"lazy"})}),c("div",{className:"flex flex-1 flex-col justify-center",children:[v&&e("div",{className:"mb-1 w-fit rounded-full px-2 text-sm font-bold leading-none tracking-[-0.04em] text-white",style:{backgroundColor:"#005D8E",paddingTop:"6px",paddingBottom:"4px"},children:v}),e("h4",{className:"line-clamp-2 text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#080A0F]",children:a}),c("div",{className:"mt-4 flex items-center gap-2",children:[c("div",{className:"flex items-center gap-1",children:[e("span",{className:"text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]",children:C(P)}),m&&e("span",{className:"text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#6D6D6F] line-through",children:C(s)})]}),f!==void 0&&c("div",{className:"flex items-center gap-0.5 text-xs text-gray-600",children:[e("span",{className:"text-yellow-500",children:"\u2B50"}),e("span",{children:f.toFixed(1)})]})]}),e("button",{type:"button",onClick:_,className:"mt-2 w-fit rounded-full px-[20px] py-[10px] text-center text-sm font-bold leading-[1.2] tracking-[-0.04em] text-white",style:{backgroundColor:"#1D1D1F"},children:n})]})]})})})},M={render:(t,o,n)=>{const r=t,{product:a,rawProduct:i,productHandle:s,onAddToCart:l,productCardRender:d}=r.data;return console.log("[ProductCard] \u6E32\u67D3\u4EA7\u54C1\u5361\u7247:",{productHandle:s,hasProduct:!!a,hasRawProduct:!!i,hasCustomRender:!!d}),d?(console.log("[ProductCard] \u4F7F\u7528\u81EA\u5B9A\u4E49\u6E32\u67D3, productHandle:",s),e(F,{children:d(i||a,s)})):a?e(w,{product:a,onAddToCart:l}):null}};export{M as ProductCard};
2
2
  //# sourceMappingURL=ProductCard.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/components/LiveChatWidget/components/MessageContent/ProductCard.tsx"],
4
- "sourcesContent": ["/**\n * \u5546\u54C1\u5361\u7247\u6E32\u67D3\u5668 - \u7D27\u51D1\u578B\n * \u663E\u793A\u5355\u4E2A\u5546\u54C1\u7684\u8BE6\u7EC6\u4FE1\u606F\uFF08\u6A2A\u5411\u5E03\u5C40\uFF09\n * \u57FA\u4E8E specs/livechat-widget/data-model.md \u7684\u5546\u54C1\u6570\u636E\u6A21\u578B\n */\n\nimport React from 'react'\nimport type { MessageRenderer, ProductCardContent, Product, CommonText } from '../../types'\nimport { CURRENCY_SYMBOLS, DEFAULT_COMMON_TEXT } from '../../constants.js'\n\n/**\n * \u683C\u5F0F\u5316\u4EF7\u683C\n */\nfunction formatPrice(price: Product['price']): string {\n const { amount, currency } = price\n\n const symbol = CURRENCY_SYMBOLS[currency] || currency\n return `${symbol}${amount.toFixed(2)}`\n}\n\n/**\n * \u683C\u5F0F\u5316\u6298\u6263\u6807\u7B7E\u6587\u672C\n * @param discount \u6298\u6263\u5BF9\u8C61\n * @param currency \u8D27\u5E01\u4EE3\u7801\n * @param offText \"OFF\" \u6587\u6848\n * @returns \u683C\u5F0F\u5316\u540E\u7684\u6298\u6263\u6587\u672C\uFF08\u5982 \"$10 OFF\" \u6216 \"20% OFF\"\uFF09\n */\nfunction formatDiscountLabel(\n discount: { discount_type?: string; discount_value?: string | number },\n currency: string,\n offText: string = DEFAULT_COMMON_TEXT.off\n): string {\n if (!discount.discount_type || discount.discount_value === undefined) {\n return ''\n }\n\n // \u5C06 discount_value \u8F6C\u6362\u4E3A\u6570\u5B57\n const value =\n typeof discount.discount_value === 'string' ? parseFloat(discount.discount_value) : discount.discount_value\n\n if (isNaN(value)) {\n return ''\n }\n\n if (discount.discount_type === 'percentage') {\n return `${Math.round(value)}% ${offText}`\n }\n\n if (discount.discount_type === 'fixed_amount') {\n const symbol = CURRENCY_SYMBOLS[currency] || currency\n return `${symbol}${Math.round(value)} ${offText}`\n }\n\n return ''\n}\n\n/**\n * \u7D27\u51D1\u578B\u5546\u54C1\u5361\u7247\u7EC4\u4EF6\uFF08\u6A2A\u5411\u5E03\u5C40\uFF09\n */\nconst CompactProductCard: React.FC<{\n product: Product\n onAddToCart?: (product: Product) => void\n addToCartText?: string\n offText?: string\n}> = ({ product, onAddToCart, addToCartText = DEFAULT_COMMON_TEXT.addToCart, offText = DEFAULT_COMMON_TEXT.off }) => {\n const { title, description, price, imageUrl, stockStatus, averageRating, variants } = product\n\n const isOutOfStock = stockStatus === 'out_of_stock'\n\n // \u83B7\u53D6\u7B2C\u4E00\u4E2A\u53D8\u4F53\u7684\u6298\u6263\u4FE1\u606F\n const firstVariant = variants?.[0]\n const hasDiscount = firstVariant?.discount?.has_discount\n const discountPrice = hasDiscount ? firstVariant?.discount?.discount_price : null\n const discount = firstVariant?.discount\n\n // \u5F53\u524D\u663E\u793A\u4EF7\u683C\uFF1A\u6709\u6298\u6263\u65F6\u663E\u793A\u6298\u6263\u4EF7\uFF0C\u5426\u5219\u663E\u793A\u539F\u4EF7\n const currentPrice = discountPrice ? { amount: discountPrice, currency: price.currency } : price\n\n // \u683C\u5F0F\u5316\u6298\u6263\u6807\u7B7E\n const discountLabel = discount && hasDiscount ? formatDiscountLabel(discount, price.currency, offText) : ''\n\n const handleAddToCart = (e: React.MouseEvent) => {\n e.preventDefault()\n e.stopPropagation()\n if (onAddToCart) {\n onAddToCart(product)\n }\n }\n\n return (\n <div className=\"block w-full overflow-hidden rounded-2xl bg-[#F5F6F7] transition-shadow mb-[32px]\">\n <div className=\"block\">\n <div className=\"flex gap-2 p-4 bg-white\">\n {/* \u5546\u54C1\u56FE\u7247 */}\n <div className=\" flex shrink-0 items-center overflow-hidden rounded-md \" style={{ width: '40%' }}>\n <img\n src={imageUrl}\n alt={title}\n className={`h-auto w-full object-cover ${isOutOfStock ? 'opacity-50' : ''}`}\n loading=\"lazy\"\n />\n </div>\n\n {/* \u5546\u54C1\u4FE1\u606F */}\n <div className=\"flex flex-1 flex-col justify-center\">\n {/* \u6298\u6263\u6807\u7B7E */}\n {discountLabel && (\n <div\n className=\"mb-1 w-fit rounded-full px-2 text-sm font-bold leading-none tracking-[-0.04em] text-white\"\n style={{ backgroundColor: '#005D8E', paddingTop: '6px', paddingBottom: '4px' }}\n >\n {discountLabel}\n </div>\n )}\n\n {/* \u6807\u9898 */}\n <h4 className=\"line-clamp-2 text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#080A0F]\">\n {title}\n </h4>\n\n {/* \u63CF\u8FF0\uFF08\u53EF\u9009\uFF09 */}\n {/* {description && (\n <p className=\"line-clamp-2 text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\">\n {description}\n </p>\n )} */}\n\n {/* \u4EF7\u683C\u548C\u8BC4\u5206 */}\n <div className=\"mt-4 flex items-center gap-2\">\n <div className=\"flex items-center gap-1\">\n {/* \u5F53\u524D\u4EF7\u683C\uFF08\u6709\u6298\u6263\u65F6\u663E\u793A\u6298\u6263\u4EF7\uFF0C\u5426\u5219\u663E\u793A\u539F\u4EF7\uFF09 */}\n <span className=\"text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\">\n {formatPrice(currentPrice)}\n </span>\n {/* \u539F\u4EF7\uFF08\u5212\u7EBF\u4EF7\uFF09- \u4EC5\u5728\u6709\u6298\u6263\u65F6\u663E\u793A */}\n {hasDiscount && (\n <span className=\"text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#6D6D6F] line-through\">\n {formatPrice(price)}\n </span>\n )}\n </div>\n {/* \u8BC4\u5206\uFF08\u53EF\u9009\uFF09 */}\n {averageRating !== undefined && (\n <div className=\"flex items-center gap-0.5 text-xs text-gray-600\">\n <span className=\"text-yellow-500\">\u2B50</span>\n <span>{averageRating.toFixed(1)}</span>\n </div>\n )}\n </div>\n\n {/* Add to Cart \u6309\u94AE - \u5728\u4EF7\u683C\u4E0B\u65B9 */}\n <button\n type=\"button\"\n onClick={handleAddToCart}\n className=\"mt-2 w-fit rounded-full px-[20px] py-[10px] text-center text-sm font-bold leading-[1.2] tracking-[-0.04em] text-white\"\n style={{ backgroundColor: '#1D1D1F' }}\n >\n {addToCartText}\n </button>\n </div>\n </div>\n </div>\n </div>\n )\n}\n\n/**\n * \u5546\u54C1\u5361\u7247\u6E32\u67D3\u5668\n *\n * \u529F\u80FD\uFF1A\n * - \u7D27\u51D1\u578B\u6A2A\u5411\u5E03\u5C40\uFF08\u56FE\u7247\u5728\u5DE6\uFF0C\u4FE1\u606F\u5728\u53F3\uFF09\n * - \u663E\u793A\u6298\u6263\u6807\u7B7E\u3001\u4EF7\u683C\u3001\u8BC4\u5206\n * - \u652F\u6301 Add to Cart \u6309\u94AE\n * - \u72EC\u7ACB\u6210\u6BB5\u5C55\u793A\n * - \u652F\u6301\u81EA\u5B9A\u4E49\u6E32\u67D3\u51FD\u6570\n *\n * \u5E03\u5C40\uFF1A\n * ```\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n * \u2502 [\u56FE\u7247] \u6298\u6263\u6807\u7B7E \u2502\n * \u2502 \u5546\u54C1\u6807\u9898 \u2502\n * \u2502 $29.99 (\u539F\u4EF7) \u2502\n * \u2502 \u2B50 4.5 \u2502\n * \u2502 [Add to Cart] \u2502\n * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n * ```\n *\n * @example\n * ```tsx\n * const content: ProductCardContent = {\n * type: 'product_card',\n * data: {\n * product: { ... },\n * onAddToCart: (product) => { ... },\n * productCardRender: (product) => <CustomCard product={product} />\n * }\n * }\n * <ProductCard.render(content, false, false) />\n * ```\n */\nexport const ProductCard: MessageRenderer = {\n render: (content, isUser, isSystem) => {\n const productContent = content as ProductCardContent\n const { product, rawProduct, onAddToCart, productCardRender } = productContent.data\n\n if (!product) {\n return null\n }\n\n // \u5982\u679C\u63D0\u4F9B\u4E86\u81EA\u5B9A\u4E49\u6E32\u67D3\u51FD\u6570\uFF0C\u4F7F\u7528\u81EA\u5B9A\u4E49\u6E32\u67D3\uFF08\u4F20\u5165\u539F\u59CB\u540E\u7AEF\u6570\u636E\uFF09\n if (productCardRender) {\n return <>{productCardRender(rawProduct || product)}</>\n }\n\n // \u5426\u5219\u4F7F\u7528\u9ED8\u8BA4\u6E32\u67D3\n return <CompactProductCard product={product} onAddToCart={onAddToCart} />\n },\n}\n"],
5
- "mappings": "AA+FY,OAoHC,YAAAA,EApHD,OAAAC,EAkCE,QAAAC,MAlCF,oBAvFZ,OAAS,oBAAAC,EAAkB,uBAAAC,MAA2B,qBAKtD,SAASC,EAAYC,EAAiC,CACpD,KAAM,CAAE,OAAAC,EAAQ,SAAAC,CAAS,EAAIF,EAG7B,MAAO,GADQH,EAAiBK,CAAQ,GAAKA,CAC7B,GAAGD,EAAO,QAAQ,CAAC,CAAC,EACtC,CASA,SAASE,EACPC,EACAF,EACAG,EAAkBP,EAAoB,IAC9B,CACR,GAAI,CAACM,EAAS,eAAiBA,EAAS,iBAAmB,OACzD,MAAO,GAIT,MAAME,EACJ,OAAOF,EAAS,gBAAmB,SAAW,WAAWA,EAAS,cAAc,EAAIA,EAAS,eAE/F,OAAI,MAAME,CAAK,EACN,GAGLF,EAAS,gBAAkB,aACtB,GAAG,KAAK,MAAME,CAAK,CAAC,KAAKD,CAAO,GAGrCD,EAAS,gBAAkB,eAEtB,GADQP,EAAiBK,CAAQ,GAAKA,CAC7B,GAAG,KAAK,MAAMI,CAAK,CAAC,IAAID,CAAO,GAG1C,EACT,CAKA,MAAME,EAKD,CAAC,CAAE,QAAAC,EAAS,YAAAC,EAAa,cAAAC,EAAgBZ,EAAoB,UAAW,QAAAO,EAAUP,EAAoB,GAAI,IAAM,CACnH,KAAM,CAAE,MAAAa,EAAO,YAAAC,EAAa,MAAAZ,EAAO,SAAAa,EAAU,YAAAC,EAAa,cAAAC,EAAe,SAAAC,CAAS,EAAIR,EAEhFS,EAAeH,IAAgB,eAG/BI,EAAeF,IAAW,CAAC,EAC3BG,EAAcD,GAAc,UAAU,aACtCE,EAAgBD,EAAcD,GAAc,UAAU,eAAiB,KACvEd,EAAWc,GAAc,SAGzBG,EAAeD,EAAgB,CAAE,OAAQA,EAAe,SAAUpB,EAAM,QAAS,EAAIA,EAGrFsB,EAAgBlB,GAAYe,EAAchB,EAAoBC,EAAUJ,EAAM,SAAUK,CAAO,EAAI,GAEnGkB,EAAmBC,GAAwB,CAC/CA,EAAE,eAAe,EACjBA,EAAE,gBAAgB,EACdf,GACFA,EAAYD,CAAO,CAEvB,EAEA,OACEb,EAAC,OAAI,UAAU,oFACb,SAAAA,EAAC,OAAI,UAAU,QACb,SAAAC,EAAC,OAAI,UAAU,0BAEb,UAAAD,EAAC,OAAI,UAAU,0DAA0D,MAAO,CAAE,MAAO,KAAM,EAC7F,SAAAA,EAAC,OACC,IAAKkB,EACL,IAAKF,EACL,UAAW,8BAA8BM,EAAe,aAAe,EAAE,GACzE,QAAQ,OACV,EACF,EAGArB,EAAC,OAAI,UAAU,sCAEZ,UAAA0B,GACC3B,EAAC,OACC,UAAU,4FACV,MAAO,CAAE,gBAAiB,UAAW,WAAY,MAAO,cAAe,KAAM,EAE5E,SAAA2B,EACH,EAIF3B,EAAC,MAAG,UAAU,mFACX,SAAAgB,EACH,EAUAf,EAAC,OAAI,UAAU,+BACb,UAAAA,EAAC,OAAI,UAAU,0BAEb,UAAAD,EAAC,QAAK,UAAU,sEACb,SAAAI,EAAYsB,CAAY,EAC3B,EAECF,GACCxB,EAAC,QAAK,UAAU,mFACb,SAAAI,EAAYC,CAAK,EACpB,GAEJ,EAECe,IAAkB,QACjBnB,EAAC,OAAI,UAAU,kDACb,UAAAD,EAAC,QAAK,UAAU,kBAAkB,kBAAC,EACnCA,EAAC,QAAM,SAAAoB,EAAc,QAAQ,CAAC,EAAE,GAClC,GAEJ,EAGApB,EAAC,UACC,KAAK,SACL,QAAS4B,EACT,UAAU,wHACV,MAAO,CAAE,gBAAiB,SAAU,EAEnC,SAAAb,EACH,GACF,GACF,EACF,EACF,CAEJ,EAoCae,EAA+B,CAC1C,OAAQ,CAACC,EAASC,EAAQC,IAAa,CACrC,MAAMC,EAAiBH,EACjB,CAAE,QAAAlB,EAAS,WAAAsB,EAAY,YAAArB,EAAa,kBAAAsB,CAAkB,EAAIF,EAAe,KAE/E,OAAKrB,EAKDuB,EACKpC,EAAAD,EAAA,CAAG,SAAAqC,EAAkBD,GAActB,CAAO,EAAE,EAI9Cb,EAACY,EAAA,CAAmB,QAASC,EAAS,YAAaC,EAAa,EAT9D,IAUX,CACF",
6
- "names": ["Fragment", "jsx", "jsxs", "CURRENCY_SYMBOLS", "DEFAULT_COMMON_TEXT", "formatPrice", "price", "amount", "currency", "formatDiscountLabel", "discount", "offText", "value", "CompactProductCard", "product", "onAddToCart", "addToCartText", "title", "description", "imageUrl", "stockStatus", "averageRating", "variants", "isOutOfStock", "firstVariant", "hasDiscount", "discountPrice", "currentPrice", "discountLabel", "handleAddToCart", "e", "ProductCard", "content", "isUser", "isSystem", "productContent", "rawProduct", "productCardRender"]
4
+ "sourcesContent": ["/**\n * \u5546\u54C1\u5361\u7247\u6E32\u67D3\u5668 - \u7D27\u51D1\u578B\n * \u663E\u793A\u5355\u4E2A\u5546\u54C1\u7684\u8BE6\u7EC6\u4FE1\u606F\uFF08\u6A2A\u5411\u5E03\u5C40\uFF09\n * \u57FA\u4E8E specs/livechat-widget/data-model.md \u7684\u5546\u54C1\u6570\u636E\u6A21\u578B\n */\n\nimport React from 'react'\nimport type { MessageRenderer, ProductCardContent, Product, CommonText } from '../../types'\nimport { CURRENCY_SYMBOLS, DEFAULT_COMMON_TEXT } from '../../constants.js'\n\n/**\n * \u683C\u5F0F\u5316\u4EF7\u683C\n */\nfunction formatPrice(price: Product['price']): string {\n const { amount, currency } = price\n\n const symbol = CURRENCY_SYMBOLS[currency] || currency\n return `${symbol}${amount.toFixed(2)}`\n}\n\n/**\n * \u683C\u5F0F\u5316\u6298\u6263\u6807\u7B7E\u6587\u672C\n * @param discount \u6298\u6263\u5BF9\u8C61\n * @param currency \u8D27\u5E01\u4EE3\u7801\n * @param offText \"OFF\" \u6587\u6848\n * @returns \u683C\u5F0F\u5316\u540E\u7684\u6298\u6263\u6587\u672C\uFF08\u5982 \"$10 OFF\" \u6216 \"20% OFF\"\uFF09\n */\nfunction formatDiscountLabel(\n discount: { discount_type?: string; discount_value?: string | number },\n currency: string,\n offText: string = DEFAULT_COMMON_TEXT.off\n): string {\n if (!discount.discount_type || discount.discount_value === undefined) {\n return ''\n }\n\n // \u5C06 discount_value \u8F6C\u6362\u4E3A\u6570\u5B57\n const value =\n typeof discount.discount_value === 'string' ? parseFloat(discount.discount_value) : discount.discount_value\n\n if (isNaN(value)) {\n return ''\n }\n\n if (discount.discount_type === 'percentage') {\n return `${Math.round(value)}% ${offText}`\n }\n\n if (discount.discount_type === 'fixed_amount') {\n const symbol = CURRENCY_SYMBOLS[currency] || currency\n return `${symbol}${Math.round(value)} ${offText}`\n }\n\n return ''\n}\n\n/**\n * \u7D27\u51D1\u578B\u5546\u54C1\u5361\u7247\u7EC4\u4EF6\uFF08\u6A2A\u5411\u5E03\u5C40\uFF09\n */\nconst CompactProductCard: React.FC<{\n product: Product\n onAddToCart?: (product: Product) => void\n addToCartText?: string\n offText?: string\n}> = ({ product, onAddToCart, addToCartText = DEFAULT_COMMON_TEXT.addToCart, offText = DEFAULT_COMMON_TEXT.off }) => {\n const { title, description, price, imageUrl, stockStatus, averageRating, variants } = product\n\n const isOutOfStock = stockStatus === 'out_of_stock'\n\n // \u83B7\u53D6\u7B2C\u4E00\u4E2A\u53D8\u4F53\u7684\u6298\u6263\u4FE1\u606F\n const firstVariant = variants?.[0]\n const hasDiscount = firstVariant?.discount?.has_discount\n const discountPrice = hasDiscount ? firstVariant?.discount?.discount_price : null\n const discount = firstVariant?.discount\n\n // \u5F53\u524D\u663E\u793A\u4EF7\u683C\uFF1A\u6709\u6298\u6263\u65F6\u663E\u793A\u6298\u6263\u4EF7\uFF0C\u5426\u5219\u663E\u793A\u539F\u4EF7\n const currentPrice = discountPrice ? { amount: discountPrice, currency: price.currency } : price\n\n // \u683C\u5F0F\u5316\u6298\u6263\u6807\u7B7E\n const discountLabel = discount && hasDiscount ? formatDiscountLabel(discount, price.currency, offText) : ''\n\n const handleAddToCart = (e: React.MouseEvent) => {\n e.preventDefault()\n e.stopPropagation()\n if (onAddToCart) {\n onAddToCart(product)\n }\n }\n\n return (\n <div className=\"block w-full overflow-hidden rounded-2xl bg-[#F5F6F7] transition-shadow mb-[32px]\">\n <div className=\"block\">\n <div className=\"flex gap-2 p-4 bg-white\">\n {/* \u5546\u54C1\u56FE\u7247 */}\n <div className=\" flex shrink-0 items-center overflow-hidden rounded-md \" style={{ width: '40%' }}>\n <img\n src={imageUrl}\n alt={title}\n className={`h-auto w-full object-cover ${isOutOfStock ? 'opacity-50' : ''}`}\n loading=\"lazy\"\n />\n </div>\n\n {/* \u5546\u54C1\u4FE1\u606F */}\n <div className=\"flex flex-1 flex-col justify-center\">\n {/* \u6298\u6263\u6807\u7B7E */}\n {discountLabel && (\n <div\n className=\"mb-1 w-fit rounded-full px-2 text-sm font-bold leading-none tracking-[-0.04em] text-white\"\n style={{ backgroundColor: '#005D8E', paddingTop: '6px', paddingBottom: '4px' }}\n >\n {discountLabel}\n </div>\n )}\n\n {/* \u6807\u9898 */}\n <h4 className=\"line-clamp-2 text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#080A0F]\">\n {title}\n </h4>\n\n {/* \u63CF\u8FF0\uFF08\u53EF\u9009\uFF09 */}\n {/* {description && (\n <p className=\"line-clamp-2 text-sm font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\">\n {description}\n </p>\n )} */}\n\n {/* \u4EF7\u683C\u548C\u8BC4\u5206 */}\n <div className=\"mt-4 flex items-center gap-2\">\n <div className=\"flex items-center gap-1\">\n {/* \u5F53\u524D\u4EF7\u683C\uFF08\u6709\u6298\u6263\u65F6\u663E\u793A\u6298\u6263\u4EF7\uFF0C\u5426\u5219\u663E\u793A\u539F\u4EF7\uFF09 */}\n <span className=\"text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#1D1D1F]\">\n {formatPrice(currentPrice)}\n </span>\n {/* \u539F\u4EF7\uFF08\u5212\u7EBF\u4EF7\uFF09- \u4EC5\u5728\u6709\u6298\u6263\u65F6\u663E\u793A */}\n {hasDiscount && (\n <span className=\"text-base font-bold leading-[1.4] tracking-[-0.02em] text-[#6D6D6F] line-through\">\n {formatPrice(price)}\n </span>\n )}\n </div>\n {/* \u8BC4\u5206\uFF08\u53EF\u9009\uFF09 */}\n {averageRating !== undefined && (\n <div className=\"flex items-center gap-0.5 text-xs text-gray-600\">\n <span className=\"text-yellow-500\">\u2B50</span>\n <span>{averageRating.toFixed(1)}</span>\n </div>\n )}\n </div>\n\n {/* Add to Cart \u6309\u94AE - \u5728\u4EF7\u683C\u4E0B\u65B9 */}\n <button\n type=\"button\"\n onClick={handleAddToCart}\n className=\"mt-2 w-fit rounded-full px-[20px] py-[10px] text-center text-sm font-bold leading-[1.2] tracking-[-0.04em] text-white\"\n style={{ backgroundColor: '#1D1D1F' }}\n >\n {addToCartText}\n </button>\n </div>\n </div>\n </div>\n </div>\n )\n}\n\n/**\n * \u5546\u54C1\u5361\u7247\u6E32\u67D3\u5668\n *\n * \u529F\u80FD\uFF1A\n * - \u7D27\u51D1\u578B\u6A2A\u5411\u5E03\u5C40\uFF08\u56FE\u7247\u5728\u5DE6\uFF0C\u4FE1\u606F\u5728\u53F3\uFF09\n * - \u663E\u793A\u6298\u6263\u6807\u7B7E\u3001\u4EF7\u683C\u3001\u8BC4\u5206\n * - \u652F\u6301 Add to Cart \u6309\u94AE\n * - \u72EC\u7ACB\u6210\u6BB5\u5C55\u793A\n * - \u652F\u6301\u81EA\u5B9A\u4E49\u6E32\u67D3\u51FD\u6570\n *\n * \u5E03\u5C40\uFF1A\n * ```\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n * \u2502 [\u56FE\u7247] \u6298\u6263\u6807\u7B7E \u2502\n * \u2502 \u5546\u54C1\u6807\u9898 \u2502\n * \u2502 $29.99 (\u539F\u4EF7) \u2502\n * \u2502 \u2B50 4.5 \u2502\n * \u2502 [Add to Cart] \u2502\n * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n * ```\n *\n * @example\n * ```tsx\n * const content: ProductCardContent = {\n * type: 'product_card',\n * data: {\n * product: { ... },\n * onAddToCart: (product) => { ... },\n * productCardRender: (product, productHandle) => <CustomCard product={product} handle={productHandle} />\n * }\n * }\n * <ProductCard.render(content, false, false) />\n * ```\n */\nexport const ProductCard: MessageRenderer = {\n render: (content, isUser, isSystem) => {\n const productContent = content as ProductCardContent\n const { product, rawProduct, productHandle, onAddToCart, productCardRender } = productContent.data\n\n console.log('[ProductCard] \u6E32\u67D3\u4EA7\u54C1\u5361\u7247:', {\n productHandle,\n hasProduct: !!product,\n hasRawProduct: !!rawProduct,\n hasCustomRender: !!productCardRender,\n })\n\n // \u5982\u679C\u63D0\u4F9B\u4E86\u81EA\u5B9A\u4E49\u6E32\u67D3\u51FD\u6570\uFF0C\u4F7F\u7528\u81EA\u5B9A\u4E49\u6E32\u67D3\uFF08\u4F20\u5165\u539F\u59CB\u540E\u7AEF\u6570\u636E\u548C productHandle\uFF09\n // \u5373\u4F7F product \u4E3A\u7A7A\uFF0C\u4E5F\u8C03\u7528\u81EA\u5B9A\u4E49\u6E32\u67D3\u51FD\u6570\uFF0C\u8BA9\u5E94\u7528\u5C42\u53EF\u4EE5\u7528 productHandle \u67E5\u8BE2\u4EA7\u54C1\n if (productCardRender) {\n console.log('[ProductCard] \u4F7F\u7528\u81EA\u5B9A\u4E49\u6E32\u67D3, productHandle:', productHandle)\n return <>{productCardRender(rawProduct || product, productHandle)}</>\n }\n\n // \u9ED8\u8BA4\u6E32\u67D3\uFF1A\u5982\u679C\u6CA1\u6709\u4EA7\u54C1\u6570\u636E\u5219\u4E0D\u6E32\u67D3\n if (!product) {\n return null\n }\n\n return <CompactProductCard product={product} onAddToCart={onAddToCart} />\n },\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["Fragment", "jsx", "jsxs", "CURRENCY_SYMBOLS", "DEFAULT_COMMON_TEXT", "formatPrice", "price", "amount", "currency", "formatDiscountLabel", "discount", "offText", "value", "CompactProductCard", "product", "onAddToCart", "addToCartText", "title", "description", "imageUrl", "stockStatus", "averageRating", "variants", "isOutOfStock", "firstVariant", "hasDiscount", "discountPrice", "currentPrice", "discountLabel", "handleAddToCart", "e", "ProductCard", "content", "isUser", "isSystem", "productContent", "rawProduct", "productHandle", "productCardRender"]
7
7
  }
@@ -1,5 +1,5 @@
1
- import{jsx as t,jsxs as d}from"react/jsx-runtime";import{useRef as T,useEffect as c,useState as w,useCallback as f}from"react";import{ChatMessage as M}from"./ChatMessage";import{ScrollAnchor as C}from"./ScrollAnchor";const L=()=>t("div",{className:"flex h-full items-center justify-center text-gray-400",children:t("p",{className:"text-sm",children:"No messages yet"})}),k=()=>t("div",{className:"flex justify-center py-4",children:d("div",{className:"flex gap-1",children:[t("div",{className:"size-2 animate-bounce rounded-full bg-gray-400"}),t("div",{className:"size-2 animate-bounce rounded-full bg-gray-400",style:{animationDelay:"0.1s"}}),t("div",{className:"size-2 animate-bounce rounded-full bg-gray-400",style:{animationDelay:"0.2s"}})]})}),I=({messages:s,rendererRegistry:p,defaultRenderer:g,showTimestamp:v=!0,autoScroll:n=!0,isLoadingHistory:u=!1,emptyPlaceholder:h,className:m="",onAddToCart:y})=>{const r=T(null),[i,l]=w(!1),a=f((e=100)=>{const o=r.current;if(!o)return!0;const{scrollTop:x,scrollHeight:R,clientHeight:N}=o;return R-x-N<e},[]),b=f(()=>{const e=r.current;e&&e.scrollTo({top:e.scrollHeight,behavior:"smooth"})},[]);return c(()=>{const e=r.current;if(!e)return;const o=()=>{l(!a())};return o(),e.addEventListener("scroll",o,{passive:!0}),()=>e.removeEventListener("scroll",o)},[a]),c(()=>{if(n)return;const e=setTimeout(()=>{l(!a())},150);return()=>clearTimeout(e)},[s,n,a]),c(()=>{if(!n||!r.current)return;const e=setTimeout(()=>{r.current&&(r.current.scrollTop=r.current.scrollHeight,l(!1))},100);return()=>clearTimeout(e)},[s,n]),s.length===0&&!u?t("div",{className:`flex-1 overflow-hidden ${m}`,children:h||t(L,{})}):d("div",{className:"relative flex-1 overflow-hidden",children:[d("div",{ref:r,className:`
1
+ import{jsx as t,jsxs as d}from"react/jsx-runtime";import{useRef as M,useEffect as c,useState as x,useCallback as g}from"react";import{ChatMessage as w}from"./ChatMessage";import{ScrollAnchor as L}from"./ScrollAnchor";const C=()=>t("div",{className:"flex h-full items-center justify-center text-gray-400",children:t("p",{className:"text-sm",children:"No messages yet"})}),H=()=>t("div",{className:"flex justify-center py-4",children:d("div",{className:"flex gap-1",children:[t("div",{className:"size-2 animate-bounce rounded-full bg-gray-400"}),t("div",{className:"size-2 animate-bounce rounded-full bg-gray-400",style:{animationDelay:"0.1s"}}),t("div",{className:"size-2 animate-bounce rounded-full bg-gray-400",style:{animationDelay:"0.2s"}})]})}),z=({messages:s,rendererRegistry:v,defaultRenderer:h,showTimestamp:p=!0,autoScroll:l=!0,isLoadingHistory:u=!1,emptyPlaceholder:y,className:m="",onAddToCart:b})=>{const o=M(null),[f,a]=x(!1),n=g((e=100)=>{const r=o.current;if(!r)return!0;const{scrollTop:i,scrollHeight:N,clientHeight:T}=r;return N-i-T<e},[]),R=g(()=>{const e=o.current;e&&e.scrollTo({top:e.scrollHeight,behavior:"smooth"})},[]);return c(()=>{const e=o.current;if(console.log("[MessageList] useEffect - container:",e),!e)return;const r=()=>{const i=n();console.log("[MessageList] handleScroll:",{scrollTop:e.scrollTop,scrollHeight:e.scrollHeight,clientHeight:e.clientHeight,isNearBottom:i,willShowButton:!i}),a(!i)};return r(),e.addEventListener("scroll",r,{passive:!0}),()=>e.removeEventListener("scroll",r)},[n]),c(()=>{if(l)return;const e=setTimeout(()=>{a(!n())},150);return()=>clearTimeout(e)},[s,l,n]),c(()=>{if(!l||!o.current)return;const e=setTimeout(()=>{o.current&&(o.current.scrollTop=o.current.scrollHeight,a(!1))},100);return()=>clearTimeout(e)},[s,l]),s.length===0&&!u?t("div",{className:`flex-1 overflow-hidden ${m}`,children:y||t(C,{})}):d("div",{className:"relative flex-1 overflow-hidden",children:[d("div",{ref:o,className:`
2
2
  livechat-message-list absolute inset-0 overflow-y-auto p-4
3
3
  ${m}
4
- `,children:[u&&t(k,{}),t("div",{className:"flex flex-col gap-1",children:s.map(e=>t(M,{message:e,rendererRegistry:p,defaultRenderer:g,showTimestamp:v,onAddToCart:y},e.id))}),n&&t(C,{dependencies:[s]})]}),t("button",{onClick:b,className:`flex -translate-x-1/2 items-center justify-center ${i?"":"pointer-events-none"}`,style:{position:"absolute",bottom:"24px",left:"50%",zIndex:10,width:"40px",height:"40px",borderRadius:"50%",backgroundColor:"rgba(255, 255, 255, 0.95)",boxShadow:"0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",transition:"all 300ms ease-in-out",opacity:i?1:0,cursor:"pointer",border:"none"},"aria-label":"Scroll to bottom","aria-hidden":!i,children:t("svg",{width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"text-gray-700",children:t("polyline",{points:"6 9 12 15 18 9"})})})]})};export{I as MessageList};
4
+ `,children:[u&&t(H,{}),t("div",{className:"flex flex-col gap-1",children:s.map(e=>t(w,{message:e,rendererRegistry:v,defaultRenderer:h,showTimestamp:p,onAddToCart:b},e.id))}),l&&t(L,{dependencies:[s]})]}),t("button",{onClick:R,className:`livechat-scroll-to-bottom ${f?"visible":"hidden"}`,"aria-label":"Scroll to bottom","aria-hidden":!f,children:t("svg",{width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"text-gray-700",children:t("polyline",{points:"6 9 12 15 18 9"})})})]})};export{z as MessageList};
5
5
  //# sourceMappingURL=MessageList.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/components/LiveChatWidget/components/MessageList.tsx"],
4
- "sourcesContent": ["/**\n * \u6D88\u606F\u5217\u8868\u7EC4\u4EF6\n * \u663E\u793A\u6240\u6709\u804A\u5929\u6D88\u606F\uFF0C\u652F\u6301\u6EDA\u52A8\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u6D88\u606F\u5217\u8868\u8BBE\u8BA1\n */\n\nimport React, { useRef, useEffect, useState, useCallback } from 'react'\nimport type { Message, MessageRenderer } from '../types'\nimport { ChatMessage } from './ChatMessage'\nimport { ScrollAnchor } from './ScrollAnchor'\nimport { MessageRendererRegistry } from '../utils/messageRenderers'\n\nexport interface MessageListProps {\n /**\n * \u6D88\u606F\u5217\u8868\n */\n messages: Message[]\n\n /**\n * \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\u6CE8\u518C\u8868\n */\n rendererRegistry?: MessageRendererRegistry\n\n /**\n * \u9ED8\u8BA4\u6E32\u67D3\u5668\n */\n defaultRenderer?: MessageRenderer\n\n /**\n * \u662F\u5426\u663E\u793A\u65F6\u95F4\u6233\n * @default true\n */\n showTimestamp?: boolean\n\n /**\n * \u662F\u5426\u81EA\u52A8\u6EDA\u52A8\u5230\u5E95\u90E8\n * @default true\n */\n autoScroll?: boolean\n\n /**\n * \u662F\u5426\u6B63\u5728\u52A0\u8F7D\u5386\u53F2\u6D88\u606F\n * @default false\n */\n isLoadingHistory?: boolean\n\n /**\n * \u7A7A\u72B6\u6001\u5360\u4F4D\u5185\u5BB9\n */\n emptyPlaceholder?: React.ReactNode\n\n /**\n * \u81EA\u5B9A\u4E49\u6837\u5F0F\u7C7B\u540D\n */\n className?: string\n\n /**\n * \u5546\u54C1\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n */\n onAddToCart?: (product: any) => void\n}\n\n/**\n * \u9ED8\u8BA4\u7A7A\u72B6\u6001\u5360\u4F4D\n */\nconst DefaultEmptyPlaceholder: React.FC = () => (\n <div className=\"flex h-full items-center justify-center text-gray-400\">\n <p className=\"text-sm\">No messages yet</p>\n </div>\n)\n\n/**\n * \u52A0\u8F7D\u6307\u793A\u5668\n */\nconst LoadingIndicator: React.FC = () => (\n <div className=\"flex justify-center py-4\">\n <div className=\"flex gap-1\">\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" />\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" style={{ animationDelay: '0.1s' }} />\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" style={{ animationDelay: '0.2s' }} />\n </div>\n </div>\n)\n\n/**\n * \u6D88\u606F\u5217\u8868\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u663E\u793A\u6240\u6709\u6D88\u606F\uFF08\u7528\u6237\u3001\u52A9\u624B\u3001\u7CFB\u7EDF\uFF09\n * - \u81EA\u52A8\u6EDA\u52A8\u5230\u6700\u65B0\u6D88\u606F\n * - \u52A0\u8F7D\u5386\u53F2\u6D88\u606F\u65F6\u663E\u793A\u6307\u793A\u5668\n * - \u7A7A\u72B6\u6001\u5360\u4F4D\n *\n * \u6837\u5F0F\uFF1A\n * - \u4F7F\u7528\u81EA\u5B9A\u4E49\u6EDA\u52A8\u6761\u6837\u5F0F\uFF08livechat.css\uFF09\n * - \u5185\u5BB9\u95F4\u8DDD\u5408\u7406\n * - \u652F\u6301\u54CD\u5E94\u5F0F\u5E03\u5C40\n *\n * @example\n * ```tsx\n * <MessageList\n * messages={messages}\n * rendererRegistry={customRegistry}\n * autoScroll={true}\n * isLoadingHistory={isLoading}\n * />\n * ```\n */\nexport const MessageList: React.FC<MessageListProps> = ({\n messages,\n rendererRegistry,\n defaultRenderer,\n showTimestamp = true,\n autoScroll = true,\n isLoadingHistory = false,\n emptyPlaceholder,\n className = '',\n onAddToCart,\n}) => {\n const listRef = useRef<HTMLDivElement>(null)\n const [showScrollButton, setShowScrollButton] = useState(false)\n\n // \u68C0\u67E5\u662F\u5426\u63A5\u8FD1\u5E95\u90E8\n const isNearBottom = useCallback((threshold = 100) => {\n const container = listRef.current\n if (!container) return true\n\n const { scrollTop, scrollHeight, clientHeight } = container\n return scrollHeight - scrollTop - clientHeight < threshold\n }, [])\n\n // \u5E73\u6ED1\u6EDA\u52A8\u5230\u5E95\u90E8\n const scrollToBottom = useCallback(() => {\n const container = listRef.current\n if (!container) return\n\n container.scrollTo({\n top: container.scrollHeight,\n behavior: 'smooth',\n })\n }, [])\n\n // \u76D1\u542C\u6EDA\u52A8\u4E8B\u4EF6\uFF0C\u63A7\u5236\u6309\u94AE\u663E\u793A\n useEffect(() => {\n const container = listRef.current\n if (!container) return\n\n const handleScroll = () => {\n setShowScrollButton(!isNearBottom())\n }\n\n // \u521D\u59CB\u68C0\u67E5\n handleScroll()\n\n container.addEventListener('scroll', handleScroll, { passive: true })\n return () => container.removeEventListener('scroll', handleScroll)\n }, [isNearBottom])\n\n // \u5F53\u6D88\u606F\u5217\u8868\u53D8\u5316\u65F6\uFF0C\u91CD\u65B0\u68C0\u67E5\u6309\u94AE\u72B6\u6001\uFF08\u4F46\u4E0D\u5728\u81EA\u52A8\u6EDA\u52A8\u65F6\uFF09\n useEffect(() => {\n if (autoScroll) return // \u81EA\u52A8\u6EDA\u52A8\u4F1A\u5728\u53E6\u4E00\u4E2A effect \u4E2D\u5904\u7406\n\n const timeoutId = setTimeout(() => {\n setShowScrollButton(!isNearBottom())\n }, 150)\n\n return () => clearTimeout(timeoutId)\n }, [messages, autoScroll, isNearBottom])\n\n // \u76D1\u542C\u6D88\u606F\u5217\u8868\u53D8\u5316\uFF0C\u81EA\u52A8\u6EDA\u52A8\n useEffect(() => {\n if (!autoScroll || !listRef.current) return\n\n // \u5EF6\u8FDF\u6EDA\u52A8\u4EE5\u786E\u4FDD DOM \u5DF2\u66F4\u65B0\n const timeoutId = setTimeout(() => {\n if (listRef.current) {\n listRef.current.scrollTop = listRef.current.scrollHeight\n // \u81EA\u52A8\u6EDA\u52A8\u5230\u5E95\u90E8\u540E\uFF0C\u9690\u85CF\u6309\u94AE\n setShowScrollButton(false)\n }\n }, 100)\n\n return () => clearTimeout(timeoutId)\n }, [messages, autoScroll])\n\n // \u7A7A\u72B6\u6001\n if (messages.length === 0 && !isLoadingHistory) {\n return (\n <div className={`flex-1 overflow-hidden ${className}`}>{emptyPlaceholder || <DefaultEmptyPlaceholder />}</div>\n )\n }\n\n return (\n <div className=\"relative flex-1 overflow-hidden\">\n <div\n ref={listRef}\n className={`\n livechat-message-list absolute inset-0 overflow-y-auto p-4\n ${className}\n `}\n >\n {/* \u52A0\u8F7D\u5386\u53F2\u6D88\u606F\u6307\u793A\u5668 */}\n {isLoadingHistory && <LoadingIndicator />}\n\n {/* \u6D88\u606F\u5217\u8868 */}\n <div className=\"flex flex-col gap-1\">\n {messages.map(message => (\n <ChatMessage\n key={message.id}\n message={message}\n rendererRegistry={rendererRegistry}\n defaultRenderer={defaultRenderer}\n showTimestamp={showTimestamp}\n onAddToCart={onAddToCart}\n />\n ))}\n </div>\n\n {/* \u6EDA\u52A8\u951A\u70B9 */}\n {autoScroll && <ScrollAnchor dependencies={[messages]} />}\n </div>\n\n {/* \u56DE\u5230\u5E95\u90E8\u6309\u94AE */}\n <button\n onClick={scrollToBottom}\n className={`flex -translate-x-1/2 items-center justify-center ${showScrollButton ? '' : 'pointer-events-none'}`}\n style={{\n position: 'absolute',\n bottom: '24px',\n left: '50%',\n zIndex: 10,\n width: '40px',\n height: '40px',\n borderRadius: '50%',\n backgroundColor: 'rgba(255, 255, 255, 0.95)',\n boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',\n transition: 'all 300ms ease-in-out',\n opacity: showScrollButton ? 1 : 0,\n cursor: 'pointer',\n border: 'none',\n }}\n aria-label=\"Scroll to bottom\"\n aria-hidden={!showScrollButton}\n >\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"text-gray-700\"\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n </button>\n </div>\n )\n}\n"],
5
- "mappings": "AAmEI,cAAAA,EASA,QAAAC,MATA,oBA7DJ,OAAgB,UAAAC,EAAQ,aAAAC,EAAW,YAAAC,EAAU,eAAAC,MAAmB,QAEhE,OAAS,eAAAC,MAAmB,gBAC5B,OAAS,gBAAAC,MAAoB,iBAwD7B,MAAMC,EAAoC,IACxCR,EAAC,OAAI,UAAU,wDACb,SAAAA,EAAC,KAAE,UAAU,UAAU,2BAAe,EACxC,EAMIS,EAA6B,IACjCT,EAAC,OAAI,UAAU,2BACb,SAAAC,EAAC,OAAI,UAAU,aACb,UAAAD,EAAC,OAAI,UAAU,iDAAiD,EAChEA,EAAC,OAAI,UAAU,iDAAiD,MAAO,CAAE,eAAgB,MAAO,EAAG,EACnGA,EAAC,OAAI,UAAU,iDAAiD,MAAO,CAAE,eAAgB,MAAO,EAAG,GACrG,EACF,EA2BWU,EAA0C,CAAC,CACtD,SAAAC,EACA,iBAAAC,EACA,gBAAAC,EACA,cAAAC,EAAgB,GAChB,WAAAC,EAAa,GACb,iBAAAC,EAAmB,GACnB,iBAAAC,EACA,UAAAC,EAAY,GACZ,YAAAC,CACF,IAAM,CACJ,MAAMC,EAAUlB,EAAuB,IAAI,EACrC,CAACmB,EAAkBC,CAAmB,EAAIlB,EAAS,EAAK,EAGxDmB,EAAelB,EAAY,CAACmB,EAAY,MAAQ,CACpD,MAAMC,EAAYL,EAAQ,QAC1B,GAAI,CAACK,EAAW,MAAO,GAEvB,KAAM,CAAE,UAAAC,EAAW,aAAAC,EAAc,aAAAC,CAAa,EAAIH,EAClD,OAAOE,EAAeD,EAAYE,EAAeJ,CACnD,EAAG,CAAC,CAAC,EAGCK,EAAiBxB,EAAY,IAAM,CACvC,MAAMoB,EAAYL,EAAQ,QACrBK,GAELA,EAAU,SAAS,CACjB,IAAKA,EAAU,aACf,SAAU,QACZ,CAAC,CACH,EAAG,CAAC,CAAC,EA8CL,OA3CAtB,EAAU,IAAM,CACd,MAAMsB,EAAYL,EAAQ,QAC1B,GAAI,CAACK,EAAW,OAEhB,MAAMK,EAAe,IAAM,CACzBR,EAAoB,CAACC,EAAa,CAAC,CACrC,EAGA,OAAAO,EAAa,EAEbL,EAAU,iBAAiB,SAAUK,EAAc,CAAE,QAAS,EAAK,CAAC,EAC7D,IAAML,EAAU,oBAAoB,SAAUK,CAAY,CACnE,EAAG,CAACP,CAAY,CAAC,EAGjBpB,EAAU,IAAM,CACd,GAAIY,EAAY,OAEhB,MAAMgB,EAAY,WAAW,IAAM,CACjCT,EAAoB,CAACC,EAAa,CAAC,CACrC,EAAG,GAAG,EAEN,MAAO,IAAM,aAAaQ,CAAS,CACrC,EAAG,CAACpB,EAAUI,EAAYQ,CAAY,CAAC,EAGvCpB,EAAU,IAAM,CACd,GAAI,CAACY,GAAc,CAACK,EAAQ,QAAS,OAGrC,MAAMW,EAAY,WAAW,IAAM,CAC7BX,EAAQ,UACVA,EAAQ,QAAQ,UAAYA,EAAQ,QAAQ,aAE5CE,EAAoB,EAAK,EAE7B,EAAG,GAAG,EAEN,MAAO,IAAM,aAAaS,CAAS,CACrC,EAAG,CAACpB,EAAUI,CAAU,CAAC,EAGrBJ,EAAS,SAAW,GAAK,CAACK,EAE1BhB,EAAC,OAAI,UAAW,0BAA0BkB,CAAS,GAAK,SAAAD,GAAoBjB,EAACQ,EAAA,EAAwB,EAAG,EAK1GP,EAAC,OAAI,UAAU,kCACb,UAAAA,EAAC,OACC,IAAKmB,EACL,UAAW;AAAA;AAAA,YAEPF,CAAS;AAAA,UAIZ,UAAAF,GAAoBhB,EAACS,EAAA,EAAiB,EAGvCT,EAAC,OAAI,UAAU,sBACZ,SAAAW,EAAS,IAAIqB,GACZhC,EAACM,EAAA,CAEC,QAAS0B,EACT,iBAAkBpB,EAClB,gBAAiBC,EACjB,cAAeC,EACf,YAAaK,GALRa,EAAQ,EAMf,CACD,EACH,EAGCjB,GAAcf,EAACO,EAAA,CAAa,aAAc,CAACI,CAAQ,EAAG,GACzD,EAGAX,EAAC,UACC,QAAS6B,EACT,UAAW,qDAAqDR,EAAmB,GAAK,qBAAqB,GAC7G,MAAO,CACL,SAAU,WACV,OAAQ,OACR,KAAM,MACN,OAAQ,GACR,MAAO,OACP,OAAQ,OACR,aAAc,MACd,gBAAiB,4BACjB,UAAW,qEACX,WAAY,wBACZ,QAASA,EAAmB,EAAI,EAChC,OAAQ,UACR,OAAQ,MACV,EACA,aAAW,mBACX,cAAa,CAACA,EAEd,SAAArB,EAAC,OACC,MAAM,KACN,OAAO,KACP,QAAQ,YACR,KAAK,OACL,OAAO,eACP,YAAY,IACZ,cAAc,QACd,eAAe,QACf,UAAU,gBAEV,SAAAA,EAAC,YAAS,OAAO,iBAAiB,EACpC,EACF,GACF,CAEJ",
6
- "names": ["jsx", "jsxs", "useRef", "useEffect", "useState", "useCallback", "ChatMessage", "ScrollAnchor", "DefaultEmptyPlaceholder", "LoadingIndicator", "MessageList", "messages", "rendererRegistry", "defaultRenderer", "showTimestamp", "autoScroll", "isLoadingHistory", "emptyPlaceholder", "className", "onAddToCart", "listRef", "showScrollButton", "setShowScrollButton", "isNearBottom", "threshold", "container", "scrollTop", "scrollHeight", "clientHeight", "scrollToBottom", "handleScroll", "timeoutId", "message"]
4
+ "sourcesContent": ["/**\n * \u6D88\u606F\u5217\u8868\u7EC4\u4EF6\n * \u663E\u793A\u6240\u6709\u804A\u5929\u6D88\u606F\uFF0C\u652F\u6301\u6EDA\u52A8\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u6D88\u606F\u5217\u8868\u8BBE\u8BA1\n */\n\nimport React, { useRef, useEffect, useState, useCallback } from 'react'\nimport type { Message, MessageRenderer } from '../types'\nimport { ChatMessage } from './ChatMessage'\nimport { ScrollAnchor } from './ScrollAnchor'\nimport { MessageRendererRegistry } from '../utils/messageRenderers'\n\nexport interface MessageListProps {\n /**\n * \u6D88\u606F\u5217\u8868\n */\n messages: Message[]\n\n /**\n * \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\u6CE8\u518C\u8868\n */\n rendererRegistry?: MessageRendererRegistry\n\n /**\n * \u9ED8\u8BA4\u6E32\u67D3\u5668\n */\n defaultRenderer?: MessageRenderer\n\n /**\n * \u662F\u5426\u663E\u793A\u65F6\u95F4\u6233\n * @default true\n */\n showTimestamp?: boolean\n\n /**\n * \u662F\u5426\u81EA\u52A8\u6EDA\u52A8\u5230\u5E95\u90E8\n * @default true\n */\n autoScroll?: boolean\n\n /**\n * \u662F\u5426\u6B63\u5728\u52A0\u8F7D\u5386\u53F2\u6D88\u606F\n * @default false\n */\n isLoadingHistory?: boolean\n\n /**\n * \u7A7A\u72B6\u6001\u5360\u4F4D\u5185\u5BB9\n */\n emptyPlaceholder?: React.ReactNode\n\n /**\n * \u81EA\u5B9A\u4E49\u6837\u5F0F\u7C7B\u540D\n */\n className?: string\n\n /**\n * \u5546\u54C1\u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u56DE\u8C03\n */\n onAddToCart?: (product: any) => void\n}\n\n/**\n * \u9ED8\u8BA4\u7A7A\u72B6\u6001\u5360\u4F4D\n */\nconst DefaultEmptyPlaceholder: React.FC = () => (\n <div className=\"flex h-full items-center justify-center text-gray-400\">\n <p className=\"text-sm\">No messages yet</p>\n </div>\n)\n\n/**\n * \u52A0\u8F7D\u6307\u793A\u5668\n */\nconst LoadingIndicator: React.FC = () => (\n <div className=\"flex justify-center py-4\">\n <div className=\"flex gap-1\">\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" />\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" style={{ animationDelay: '0.1s' }} />\n <div className=\"size-2 animate-bounce rounded-full bg-gray-400\" style={{ animationDelay: '0.2s' }} />\n </div>\n </div>\n)\n\n/**\n * \u6D88\u606F\u5217\u8868\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u663E\u793A\u6240\u6709\u6D88\u606F\uFF08\u7528\u6237\u3001\u52A9\u624B\u3001\u7CFB\u7EDF\uFF09\n * - \u81EA\u52A8\u6EDA\u52A8\u5230\u6700\u65B0\u6D88\u606F\n * - \u52A0\u8F7D\u5386\u53F2\u6D88\u606F\u65F6\u663E\u793A\u6307\u793A\u5668\n * - \u7A7A\u72B6\u6001\u5360\u4F4D\n *\n * \u6837\u5F0F\uFF1A\n * - \u4F7F\u7528\u81EA\u5B9A\u4E49\u6EDA\u52A8\u6761\u6837\u5F0F\uFF08livechat.css\uFF09\n * - \u5185\u5BB9\u95F4\u8DDD\u5408\u7406\n * - \u652F\u6301\u54CD\u5E94\u5F0F\u5E03\u5C40\n *\n * @example\n * ```tsx\n * <MessageList\n * messages={messages}\n * rendererRegistry={customRegistry}\n * autoScroll={true}\n * isLoadingHistory={isLoading}\n * />\n * ```\n */\nexport const MessageList: React.FC<MessageListProps> = ({\n messages,\n rendererRegistry,\n defaultRenderer,\n showTimestamp = true,\n autoScroll = true,\n isLoadingHistory = false,\n emptyPlaceholder,\n className = '',\n onAddToCart,\n}) => {\n const listRef = useRef<HTMLDivElement>(null)\n const [showScrollButton, setShowScrollButton] = useState(false)\n\n // \u68C0\u67E5\u662F\u5426\u63A5\u8FD1\u5E95\u90E8\n const isNearBottom = useCallback((threshold = 100) => {\n const container = listRef.current\n if (!container) return true\n\n const { scrollTop, scrollHeight, clientHeight } = container\n return scrollHeight - scrollTop - clientHeight < threshold\n }, [])\n\n // \u5E73\u6ED1\u6EDA\u52A8\u5230\u5E95\u90E8\n const scrollToBottom = useCallback(() => {\n const container = listRef.current\n if (!container) return\n\n container.scrollTo({\n top: container.scrollHeight,\n behavior: 'smooth',\n })\n }, [])\n\n // \u76D1\u542C\u6EDA\u52A8\u4E8B\u4EF6\uFF0C\u63A7\u5236\u6309\u94AE\u663E\u793A\n useEffect(() => {\n const container = listRef.current\n console.log('[MessageList] useEffect - container:', container)\n if (!container) return\n\n const handleScroll = () => {\n const nearBottom = isNearBottom()\n console.log('[MessageList] handleScroll:', {\n scrollTop: container.scrollTop,\n scrollHeight: container.scrollHeight,\n clientHeight: container.clientHeight,\n isNearBottom: nearBottom,\n willShowButton: !nearBottom,\n })\n setShowScrollButton(!nearBottom)\n }\n\n // \u521D\u59CB\u68C0\u67E5\n handleScroll()\n\n container.addEventListener('scroll', handleScroll, { passive: true })\n return () => container.removeEventListener('scroll', handleScroll)\n }, [isNearBottom])\n\n // \u5F53\u6D88\u606F\u5217\u8868\u53D8\u5316\u65F6\uFF0C\u91CD\u65B0\u68C0\u67E5\u6309\u94AE\u72B6\u6001\uFF08\u4F46\u4E0D\u5728\u81EA\u52A8\u6EDA\u52A8\u65F6\uFF09\n useEffect(() => {\n if (autoScroll) return // \u81EA\u52A8\u6EDA\u52A8\u4F1A\u5728\u53E6\u4E00\u4E2A effect \u4E2D\u5904\u7406\n\n const timeoutId = setTimeout(() => {\n setShowScrollButton(!isNearBottom())\n }, 150)\n\n return () => clearTimeout(timeoutId)\n }, [messages, autoScroll, isNearBottom])\n\n // \u76D1\u542C\u6D88\u606F\u5217\u8868\u53D8\u5316\uFF0C\u81EA\u52A8\u6EDA\u52A8\n useEffect(() => {\n if (!autoScroll || !listRef.current) return\n\n // \u5EF6\u8FDF\u6EDA\u52A8\u4EE5\u786E\u4FDD DOM \u5DF2\u66F4\u65B0\n const timeoutId = setTimeout(() => {\n if (listRef.current) {\n listRef.current.scrollTop = listRef.current.scrollHeight\n // \u81EA\u52A8\u6EDA\u52A8\u5230\u5E95\u90E8\u540E\uFF0C\u9690\u85CF\u6309\u94AE\n setShowScrollButton(false)\n }\n }, 100)\n\n return () => clearTimeout(timeoutId)\n }, [messages, autoScroll])\n\n // \u7A7A\u72B6\u6001\n if (messages.length === 0 && !isLoadingHistory) {\n return (\n <div className={`flex-1 overflow-hidden ${className}`}>{emptyPlaceholder || <DefaultEmptyPlaceholder />}</div>\n )\n }\n\n return (\n <div className=\"relative flex-1 overflow-hidden\">\n <div\n ref={listRef}\n className={`\n livechat-message-list absolute inset-0 overflow-y-auto p-4\n ${className}\n `}\n >\n {/* \u52A0\u8F7D\u5386\u53F2\u6D88\u606F\u6307\u793A\u5668 */}\n {isLoadingHistory && <LoadingIndicator />}\n\n {/* \u6D88\u606F\u5217\u8868 */}\n <div className=\"flex flex-col gap-1\">\n {messages.map(message => (\n <ChatMessage\n key={message.id}\n message={message}\n rendererRegistry={rendererRegistry}\n defaultRenderer={defaultRenderer}\n showTimestamp={showTimestamp}\n onAddToCart={onAddToCart}\n />\n ))}\n </div>\n\n {/* \u6EDA\u52A8\u951A\u70B9 */}\n {autoScroll && <ScrollAnchor dependencies={[messages]} />}\n </div>\n\n {/* \u56DE\u5230\u5E95\u90E8\u6309\u94AE */}\n <button\n onClick={scrollToBottom}\n className={`livechat-scroll-to-bottom ${showScrollButton ? 'visible' : 'hidden'}`}\n aria-label=\"Scroll to bottom\"\n aria-hidden={!showScrollButton}\n >\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"text-gray-700\"\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n </button>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["jsx", "jsxs", "useRef", "useEffect", "useState", "useCallback", "ChatMessage", "ScrollAnchor", "DefaultEmptyPlaceholder", "LoadingIndicator", "MessageList", "messages", "rendererRegistry", "defaultRenderer", "showTimestamp", "autoScroll", "isLoadingHistory", "emptyPlaceholder", "className", "onAddToCart", "listRef", "showScrollButton", "setShowScrollButton", "isNearBottom", "threshold", "container", "scrollTop", "scrollHeight", "clientHeight", "scrollToBottom", "handleScroll", "nearBottom", "timeoutId", "message"]
7
7
  }
@@ -3,7 +3,7 @@
3
3
  * 管理消息列表、窗口状态、输入框状态、流式消息累积
4
4
  * 基于 specs/livechat-widget/plan.md 的状态管理策略
5
5
  */
6
- import type { Message, SSEEvent, Product } from '../types';
6
+ import type { Message, SSEEvent } from '../types';
7
7
  export interface UseChatStateOptions {
8
8
  /**
9
9
  * 初始欢迎消息
@@ -66,8 +66,10 @@ export interface UseChatStateOptions {
66
66
  onCart?: (cartId: string, checkoutUrl?: string) => void;
67
67
  /**
68
68
  * 自定义产品卡片渲染函数
69
+ * @param product 产品数据(如果在 product_list 中找到),否则为 undefined
70
+ * @param productHandle 文本占位符中的产品 ID,可用于应用层查询产品数据
69
71
  */
70
- productCardRender?: (product: Product) => React.ReactNode;
72
+ productCardRender?: (product: any, productHandle: string) => React.ReactNode;
71
73
  }
72
74
  export interface UseChatStateReturn {
73
75
  /**
@@ -1,2 +1,2 @@
1
- import{useState as A,useCallback as v,useRef as b,useEffect as rt}from"react";import{getUserId as at}from"../utils/userId";import{useSession as ct}from"./useSession";import{transformProducts as j}from"../utils/productTransformers";import{transformCartData as it}from"../utils/cartTransformers";function dt(s,f,y,S,h){const r=[],u=/\{\{(?:product:)?([^}]+)\}\}/g;let x=0,i,l=!1;for(;(i=u.exec(s))!==null;){l=!0;const a=s.slice(x,i.index);a&&r.push({type:"text",text:a});const d=i[1].trim(),m=f.get(d),T=y.get(d);m?(console.log("[useChatState] \u{1F3AF} \u5B9E\u65F6\u68C0\u6D4B\u5230\u4EA7\u54C1:",d,"\u2192",m.title),r.push({type:"product_card",data:{product:m,rawProduct:T,onAddToCart:S,productCardRender:h}})):console.warn("[useChatState] \u26A0\uFE0F \u5B9E\u65F6\u89E3\u6790\u672A\u627E\u5230\u4EA7\u54C1\uFF0C\u5DF2\u9690\u85CF:",d),x=u.lastIndex}if(l){const a=s.slice(x);return{contents:r,remainingBuffer:a}}else{const a=s.match(/\{\{[^}]*$/);if(a){const d=s.slice(0,a.index);return d&&r.push({type:"text",text:d}),{contents:r,remainingBuffer:a[0]}}else return s&&r.push({type:"text",text:s}),{contents:r,remainingBuffer:""}}}function ut(s,f,y,S,h){const r=[],u=/\{\{(?:product:)?([^}]+)\}\}/g;let x=0,i;for(;(i=u.exec(s))!==null;){const a=s.slice(x,i.index).trim(),d=i[1].trim();a&&r.push({type:"text",text:a});const m=f.get(d),T=y.get(d);m?(console.log(`[useChatState] \u2705 \u627E\u5230\u4EA7\u54C1\u5339\u914D: ${d} \u2192 ${m.title}`),r.push({type:"product_card",data:{product:m,rawProduct:T,onAddToCart:S,productCardRender:h}})):console.warn(`[useChatState] \u274C Product not found for ID: "${d}"\uFF0C\u5DF2\u9690\u85CF`),x=u.lastIndex}const l=s.slice(x).trim();return l&&r.push({type:"text",text:l}),r}function lt(s,f,y,S,h){const r=[];for(const u of s)if(u.type==="text"){const i=ut(u.text,f,y,S,h);r.push(...i)}else{if(u.type==="product_list")continue;r.push(u)}return r}function pt(s,f,y){if(!s.content.some(i=>i.type==="product_list")||!s.content.some(i=>i.type==="text"&&/\{\{(?:product:)?[^}]+\}\}/.test(i.text)))return s;console.log("[useChatState] \u68C0\u6D4B\u5230\u5386\u53F2\u6D88\u606F\u9700\u8981\u91CD\u7EC4, \u6D88\u606FID:",s.id);const r=new Map,u=new Map;s.structured_content&&s.structured_content.forEach(i=>{i.type==="product_list"&&Array.isArray(i.data)&&i.data.forEach(l=>{if(l&&l.shopify_product_id){const a=l.shopify_product_id;u.set(a,l);const d=a.match(/\d+$/)?.[0];d&&u.set(d,l),console.log("[useChatState] \u4ECE structured_content \u63D0\u53D6\u539F\u59CB\u4EA7\u54C1\u6570\u636E:",{fullId:a,numericId:d,title:l.title})}})}),s.content.forEach(i=>{i.type==="product_list"&&i.data.products.forEach(a=>{if(a&&a.shopifyId){r.set(a.shopifyId,a);const d=a.shopifyId.match(/\d+$/)?.[0];d&&r.set(d,a)}})});const x=lt(s.content,r,u,f,y);return{...s,content:x}}function yt(s={}){const{welcomeMessage:f,site:y,open:S,onOpenChange:h,onOpen:r,onClose:u,onMessageSend:x,onError:i,onTextMessage:l,onProductList:a,onPromotionList:d,onAddToCart:m,onCart:T,productCardRender:B}=s,{sessionId:U,saveSession:N,clearSession:q}=ct(),[G,J]=A("");rt(()=>{at().then(C=>J(C))},[]);const[K,D]=A(()=>f?[{id:`welcome-${Date.now()}`,role:"assistant",content:[{type:"text",text:f}],timestamp:Date.now()}]:[]),[Q,z]=A(!1),k=S!==void 0,V=k?S:Q,[X,Y]=A(""),[Z,O]=A(!1),t=b(null),F=b(!1),E=b(new Map),P=b(new Map),M=b(""),I=b([]),tt=v(()=>{k||z(!0),h?.(!0),r?.()},[k,h,r]),et=v(()=>{k||z(!1),h?.(!1),u?.()},[k,h,u]),nt=v(()=>{const C=!V;k||z(C),h?.(C),C?r?.():u?.()},[k,V,h,r,u]),L=v(C=>{if(!C){console.warn("[useChatState] Attempted to add null/undefined message");return}D(R=>[...R,C])},[]),st=v(C=>{const R=C.filter(n=>n!=null);R.length!==C.length&&console.warn("[useChatState] Filtered out null/undefined messages from batch set");const _=R.map(n=>pt(n,m,B));D(_)},[m,B]),H=v(()=>{D([])},[]),ot=v(C=>{const{event:R,data:_}=C;switch(R){case"message_start":{O(!0),F.current=!1,M.current="",I.current=[];const n=_;n.sessionId&&n.sessionId!==U&&N(n.sessionId),D(o=>{const e=o[o.length-1];if(e&&e.role==="assistant"&&e.content.length===1&&e.content[0].type==="thinking")return t.current=e,o;{const w=`msg-${Date.now()}`;return t.current={id:w,role:"assistant",content:[{type:"thinking",data:{status:"thinking"}}],timestamp:Date.now()},[...o,t.current]}});break}case"content_delta":{const n=_,o=n.delta||n.text||"";if(t.current&&o){F.current||(F.current=!0,l?.()),t.current.content.some(c=>c.type==="thinking")&&(t.current.content=t.current.content.filter(c=>c.type!=="thinking")),M.current+=o;const{contents:p,remainingBuffer:w}=dt(M.current,E.current,P.current,m,B);M.current=w,p.length>0&&(p.forEach(c=>{const g=t.current.content[t.current.content.length-1];c.type==="text"&&g&&g.type==="text"?g.text+=c.text:t.current.content.push(c)}),D(c=>{if(!t.current)return c;const g=[...c],$=g.findIndex(W=>W&&W.id===t.current.id);return $>=0?g[$]={...t.current}:g.push({...t.current}),g}))}break}case"content_block":{const n=_;if(t.current){const o=n.type||n.data?.type,e=n.data;if(!o||!e){console.warn("[useChatState] Invalid content_block:",n);break}let p;if(o==="product_list"&&Array.isArray(e)){a?.(),e.forEach(c=>{if(c&&c.shopify_product_id){const g=c.shopify_product_id;P.current.set(g,c);const $=g.match(/\d+$/)?.[0];$&&P.current.set($,c)}}),j(e,y).forEach(c=>{if(c&&c.shopifyId){E.current.set(c.shopifyId,c);const g=c.shopifyId.match(/\d+$/)?.[0];g&&E.current.set(g,c),console.log("[useChatState] \u5EFA\u7ACB\u4EA7\u54C1\u6620\u5C04:",{fullId:c.shopifyId,numericId:g,title:c.title})}}),console.log("[useChatState] \u2705 \u4EA7\u54C1\u5217\u8868\u5DF2\u7F13\u5B58\uFF0C\u4E0D\u6DFB\u52A0\u5230\u6D88\u606F\u4E2D\uFF08\u907F\u514D\u95EA\u70C1\uFF09");break}else o==="product_comparison"&&e.products?p={type:"product_comparison",data:{products:j(e.products,y),dimensions:e.dimensions||{}}}:o==="faq_list"&&e.found!==void 0?p={type:"faq_list",data:e}:o==="quick_replies"&&e.replies?p={type:"quick_replies",data:{replies:e.replies}}:o==="policy"&&e.title&&e.content?p={type:"policy",data:{title:e.title,content:e.content}}:o==="promotion_list"&&e.found!==void 0?(d?.(),p={type:"promotion_list",data:e}):o==="cart"&&e.id!==void 0?p={type:"cart",data:{...it(e),onCart:T}}:p={type:o,data:e};console.log("[useChatState] \u2705 \u5361\u7247\u5DF2\u7F13\u5B58\uFF0C\u7B49\u5F85\u6587\u672C\u5B8C\u6210\u540E\u663E\u793A:",o),I.current.push(p)}break}case"tool_start":case"tool_end":break;case"message_end":{if(O(!1),t.current&&M.current){console.log("[useChatState] \u5904\u7406\u5269\u4F59\u7F13\u51B2\u533A:",M.current);const n=t.current.content[t.current.content.length-1];n&&n.type==="text"?n.text+=M.current:t.current.content.push({type:"text",text:M.current})}t.current&&I.current.length>0&&(console.log("[useChatState] \u{1F4CB} \u6587\u672C\u5DF2\u5B8C\u6210\uFF0C\u73B0\u5728\u6DFB\u52A0",I.current.length,"\u4E2A\u7F13\u5B58\u7684\u5361\u7247"),t.current.content.push(...I.current)),t.current&&t.current.content.some(o=>o.type==="thinking")&&(console.log("[useChatState] \u26A0\uFE0F message_end \u65F6\u4ECD\u5B58\u5728 thinking block\uFF0C\u7ACB\u5373\u79FB\u9664\uFF08\u89C6\u4E3A\u8D85\u65F6\uFF09"),t.current.content=t.current.content.filter(o=>o.type!=="thinking"),t.current.content.length===0&&t.current.content.push({type:"text",text:"Response timed out, please try again."})),t.current&&D(n=>{if(!t.current)return n;const o=[...n],e=o.findIndex(p=>p&&p.id===t.current.id);return e>=0&&(o[e]={...t.current}),o}),M.current="",I.current=[],E.current.clear(),P.current.clear(),t.current=null;break}case"status":{_.type==="session_expired"&&(H(),q(),f&&L({id:`welcome-${Date.now()}`,role:"assistant",content:[{type:"text",text:f}],timestamp:Date.now()}));break}case"error":{const n=_;O(!1),M.current="",I.current=[],E.current.clear(),P.current.clear(),t.current=null,L({id:`error-${Date.now()}`,role:"system",content:[{type:"error",data:{message:n.message,code:n.code}}],timestamp:Date.now()}),i?.(new Error(n.message));break}case"done":{O(!1),M.current="",I.current=[],E.current.clear(),P.current.clear(),t.current=null;break}default:break}},[f,y,L,H,q,N,U,i,l,a,d,m,T]);return{messages:K,isOpen:V,userId:G,sessionId:U,inputValue:X,isStreaming:Z,openChat:tt,closeChat:et,toggleChat:nt,setInputValue:Y,addMessage:L,setMessages:st,clearMessages:H,handleSSEEvent:ot,saveSession:N,clearSession:q}}export{yt as useChatState};
1
+ import{useState as A,useCallback as E,useRef as P,useEffect as ot}from"react";import{getUserId as rt}from"../utils/userId";import{useSession as ct}from"./useSession";import{transformProducts as j}from"../utils/productTransformers";import{transformCartData as it}from"../utils/cartTransformers";function dt(a,f,x,y,l){const o=[],d=/\{\{(?:product:)?([^}]+)\}\}/g;let c=0,i,h=!1;for(;(i=d.exec(a))!==null;){h=!0;const p=a.slice(c,i.index);p&&o.push({type:"text",text:p});const u=i[1].trim(),m=f.get(u),I=x.get(u);m?console.log("[useChatState] \u{1F3AF} \u5B9E\u65F6\u68C0\u6D4B\u5230\u4EA7\u54C1:",u,"\u2192",m.title):console.log("[useChatState] \u{1F4E6} \u5B9E\u65F6\u68C0\u6D4B\u5230\u4EA7\u54C1\u5360\u4F4D\u7B26\uFF0C\u4EA7\u54C1\u6570\u636E\u5F85\u5E94\u7528\u5C42\u67E5\u8BE2:",u),o.push({type:"product_card",data:{product:m,rawProduct:I,productHandle:u,onAddToCart:y,productCardRender:l}}),c=d.lastIndex}if(h){const p=a.slice(c);return{contents:o,remainingBuffer:p}}else{const p=a.match(/\{\{[^}]*$/);if(p){const u=a.slice(0,p.index);return u&&o.push({type:"text",text:u}),{contents:o,remainingBuffer:p[0]}}else return a&&o.push({type:"text",text:a}),{contents:o,remainingBuffer:""}}}function ut(a,f,x,y,l){const o=[],d=/\{\{(?:product:)?([^}]+)\}\}/g;let c=0,i;for(;(i=d.exec(a))!==null;){const p=a.slice(c,i.index).trim(),u=i[1].trim();p&&o.push({type:"text",text:p});const m=f.get(u),I=x.get(u);console.log(m?`[useChatState] \u2705 \u627E\u5230\u4EA7\u54C1\u5339\u914D: ${u} \u2192 ${m.title}`:`[useChatState] \u{1F4E6} \u4EA7\u54C1\u5360\u4F4D\u7B26\uFF0C\u4EA7\u54C1\u6570\u636E\u5F85\u5E94\u7528\u5C42\u67E5\u8BE2: ${u}`),o.push({type:"product_card",data:{product:m,rawProduct:I,productHandle:u,onAddToCart:y,productCardRender:l}}),c=d.lastIndex}const h=a.slice(c).trim();return h&&o.push({type:"text",text:h}),o}function lt(a,f,x,y,l){const o=[];for(const d of a)if(d.type==="text"){const i=ut(d.text,f,x,y,l);o.push(...i)}else{if(d.type==="product_list")continue;o.push(d)}return o}function gt(a,f,x){if(!a.content.some(c=>c.type==="text"&&/\{\{(?:product:)?[^}]+\}\}/.test(c.text)))return a;console.log("[useChatState] \u68C0\u6D4B\u5230\u5386\u53F2\u6D88\u606F\u9700\u8981\u91CD\u7EC4, \u6D88\u606FID:",a.id);const l=new Map,o=new Map;a.structured_content&&a.structured_content.forEach(c=>{c.type==="product_list"&&Array.isArray(c.data)&&c.data.forEach(i=>{i&&i.handle&&(o.set(i.handle,i),console.log("[useChatState] \u4ECE structured_content \u63D0\u53D6\u539F\u59CB\u4EA7\u54C1\u6570\u636E, handle:",i.handle))})}),a.content.forEach(c=>{c.type==="product_list"&&c.data.products.forEach(h=>{h&&h.handle&&l.set(h.handle,h)})});const d=lt(a.content,l,o,f,x);return{...a,content:d}}function xt(a={}){const{welcomeMessage:f,site:x,open:y,onOpenChange:l,onOpen:o,onClose:d,onMessageSend:c,onError:i,onTextMessage:h,onProductList:p,onPromotionList:u,onAddToCart:m,onCart:I,productCardRender:H}=a,{sessionId:L,saveSession:$,clearSession:U}=ct(),[G,J]=A("");ot(()=>{rt().then(C=>J(C))},[]);const[K,D]=A(()=>f?[{id:`welcome-${Date.now()}`,role:"assistant",content:[{type:"text",text:f}],timestamp:Date.now()}]:[]),[Q,N]=A(!1),v=y!==void 0,q=v?y:Q,[X,Y]=A(""),[Z,O]=A(!1),t=P(null),z=P(!1),w=P(new Map),b=P(new Map),M=P(""),S=P([]),tt=E(()=>{v||N(!0),l?.(!0),o?.()},[v,l,o]),et=E(()=>{v||N(!1),l?.(!1),d?.()},[v,l,d]),nt=E(()=>{const C=!q;v||N(C),l?.(C),C?o?.():d?.()},[v,q,l,o,d]),B=E(C=>{if(!C){console.warn("[useChatState] Attempted to add null/undefined message");return}D(R=>[...R,C])},[]),st=E(C=>{const R=C.filter(n=>n!=null);R.length!==C.length&&console.warn("[useChatState] Filtered out null/undefined messages from batch set");const T=R.map(n=>gt(n,m,H));D(T)},[m,H]),V=E(()=>{D([])},[]),at=E(C=>{const{event:R,data:T}=C;switch(R){case"message_start":{O(!0),z.current=!1,M.current="",S.current=[];const n=T;n.sessionId&&n.sessionId!==L&&$(n.sessionId),D(s=>{const e=s[s.length-1];if(e&&e.role==="assistant"&&e.content.length===1&&e.content[0].type==="thinking")return t.current=e,s;{const _=`msg-${Date.now()}`;return t.current={id:_,role:"assistant",content:[{type:"thinking",data:{status:"thinking"}}],timestamp:Date.now()},[...s,t.current]}});break}case"content_delta":{const n=T,s=n.delta||n.text||"";if(t.current&&s){z.current||(z.current=!0,h?.()),t.current.content.some(r=>r.type==="thinking")&&(t.current.content=t.current.content.filter(r=>r.type!=="thinking")),M.current+=s;const{contents:g,remainingBuffer:_}=dt(M.current,w.current,b.current,m,H);M.current=_,g.length>0&&(g.forEach(r=>{const k=t.current.content[t.current.content.length-1];r.type==="text"&&k&&k.type==="text"?k.text+=r.text:t.current.content.push(r)}),D(r=>{if(!t.current)return r;const k=[...r],F=k.findIndex(W=>W&&W.id===t.current.id);return F>=0?k[F]={...t.current}:k.push({...t.current}),k}))}break}case"content_block":{const n=T;if(t.current){const s=n.type||n.data?.type,e=n.data;if(!s||!e){console.warn("[useChatState] Invalid content_block:",n);break}let g;if(s==="product_list"&&Array.isArray(e)){p?.(),e.forEach(r=>{r&&r.handle&&(b.current.set(r.handle,r),console.log("[useChatState] \u7F13\u5B58\u539F\u59CB\u4EA7\u54C1\u6570\u636E, handle:",r.handle))}),j(e,x).forEach(r=>{r&&r.handle&&(w.current.set(r.handle,r),console.log("[useChatState] \u5EFA\u7ACB\u4EA7\u54C1\u6620\u5C04:",{handle:r.handle,title:r.title}))}),console.log("[useChatState] \u2705 \u4EA7\u54C1\u5217\u8868\u5DF2\u7F13\u5B58\uFF0C\u4E0D\u6DFB\u52A0\u5230\u6D88\u606F\u4E2D\uFF08\u907F\u514D\u95EA\u70C1\uFF09");break}else s==="product_comparison"&&e.products?g={type:"product_comparison",data:{products:j(e.products,x),dimensions:e.dimensions||{}}}:s==="faq_list"&&e.found!==void 0?g={type:"faq_list",data:e}:s==="quick_replies"&&e.replies?g={type:"quick_replies",data:{replies:e.replies}}:s==="policy"&&e.title&&e.content?g={type:"policy",data:{title:e.title,content:e.content}}:s==="promotion_list"&&e.found!==void 0?(u?.(),g={type:"promotion_list",data:e}):s==="cart"&&e.id!==void 0?g={type:"cart",data:{...it(e),onCart:I}}:g={type:s,data:e};console.log("[useChatState] \u2705 \u5361\u7247\u5DF2\u7F13\u5B58\uFF0C\u7B49\u5F85\u6587\u672C\u5B8C\u6210\u540E\u663E\u793A:",s),S.current.push(g)}break}case"tool_start":case"tool_end":break;case"message_end":{if(O(!1),t.current&&M.current){console.log("[useChatState] \u5904\u7406\u5269\u4F59\u7F13\u51B2\u533A:",M.current);const n=t.current.content[t.current.content.length-1];n&&n.type==="text"?n.text+=M.current:t.current.content.push({type:"text",text:M.current})}t.current&&S.current.length>0&&(console.log("[useChatState] \u{1F4CB} \u6587\u672C\u5DF2\u5B8C\u6210\uFF0C\u73B0\u5728\u6DFB\u52A0",S.current.length,"\u4E2A\u7F13\u5B58\u7684\u5361\u7247"),t.current.content.push(...S.current)),t.current&&t.current.content.some(s=>s.type==="thinking")&&(console.log("[useChatState] \u26A0\uFE0F message_end \u65F6\u4ECD\u5B58\u5728 thinking block\uFF0C\u7ACB\u5373\u79FB\u9664\uFF08\u89C6\u4E3A\u8D85\u65F6\uFF09"),t.current.content=t.current.content.filter(s=>s.type!=="thinking"),t.current.content.length===0&&t.current.content.push({type:"text",text:"Response timed out, please try again."})),t.current&&D(n=>{if(!t.current)return n;const s=[...n],e=s.findIndex(g=>g&&g.id===t.current.id);return e>=0&&(s[e]={...t.current}),s}),M.current="",S.current=[],w.current.clear(),b.current.clear(),t.current=null;break}case"status":{T.type==="session_expired"&&(V(),U(),f&&B({id:`welcome-${Date.now()}`,role:"assistant",content:[{type:"text",text:f}],timestamp:Date.now()}));break}case"error":{const n=T;O(!1),M.current="",S.current=[],w.current.clear(),b.current.clear(),t.current=null,B({id:`error-${Date.now()}`,role:"system",content:[{type:"error",data:{message:n.message,code:n.code}}],timestamp:Date.now()}),i?.(new Error(n.message));break}case"done":{O(!1),M.current="",S.current=[],w.current.clear(),b.current.clear(),t.current=null;break}default:break}},[f,x,B,V,U,$,L,i,h,p,u,m,I]);return{messages:K,isOpen:q,userId:G,sessionId:L,inputValue:X,isStreaming:Z,openChat:tt,closeChat:et,toggleChat:nt,setInputValue:Y,addMessage:B,setMessages:st,clearMessages:V,handleSSEEvent:at,saveSession:$,clearSession:U}}export{xt as useChatState};
2
2
  //# sourceMappingURL=useChatState.js.map