@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.
- package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js +1 -1
- package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/api/chat.js +2 -2
- package/dist/cjs/components/LiveChatWidget/api/chat.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js +6 -6
- package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js.map +2 -2
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.d.ts +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.js +1 -1
- package/dist/cjs/components/LiveChatWidget/components/MessageContent/ProductCard.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/components/MessageList.js +3 -3
- package/dist/cjs/components/LiveChatWidget/components/MessageList.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/hooks/useChatState.d.ts +4 -2
- package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js +1 -1
- package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/types.d.ts +18 -11
- package/dist/cjs/components/LiveChatWidget/types.js.map +1 -1
- package/dist/cjs/components/index.d.ts +1 -1
- package/dist/cjs/components/index.js +1 -1
- package/dist/cjs/components/index.js.map +2 -2
- package/dist/cjs/stories/LiveChatWidget.stories.d.ts +1 -109
- package/dist/cjs/stories/LiveChatWidget.stories.js +7 -166
- package/dist/cjs/stories/LiveChatWidget.stories.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/LiveChatWidget.js +1 -1
- package/dist/esm/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/api/chat.js +2 -2
- package/dist/esm/components/LiveChatWidget/api/chat.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js +7 -7
- package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js.map +2 -2
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.d.ts +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.js +1 -1
- package/dist/esm/components/LiveChatWidget/components/MessageContent/ProductCard.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/components/MessageList.js +2 -2
- package/dist/esm/components/LiveChatWidget/components/MessageList.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/hooks/useChatState.d.ts +4 -2
- package/dist/esm/components/LiveChatWidget/hooks/useChatState.js +1 -1
- package/dist/esm/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/types.d.ts +18 -11
- package/dist/esm/components/index.d.ts +1 -1
- package/dist/esm/components/index.js +1 -1
- package/dist/esm/components/index.js.map +2 -2
- package/dist/esm/stories/LiveChatWidget.stories.d.ts +1 -109
- package/dist/esm/stories/LiveChatWidget.stories.js +7 -166
- package/dist/esm/stories/LiveChatWidget.stories.js.map +3 -3
- package/package.json +1 -1
- package/src/components/LiveChatWidget/LiveChatWidget.tsx +65 -23
- package/src/components/LiveChatWidget/api/chat.ts +8 -7
- package/src/components/LiveChatWidget/components/ComplianceDialog.tsx +1 -5
- package/src/components/LiveChatWidget/components/MessageContent/ProductCard.tsx +17 -8
- package/src/components/LiveChatWidget/components/MessageList.tsx +11 -17
- package/src/components/LiveChatWidget/hooks/useChatState.ts +64 -98
- package/src/components/LiveChatWidget/types.ts +18 -11
- package/src/components/index.ts +2 -1
- package/src/stories/LiveChatWidget.stories.tsx +95 -660
- 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(
|
|
5
|
-
"mappings": "
|
|
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
|
|
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
|
|
5
|
-
"mappings": "AA+FY,
|
|
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
|
|
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(
|
|
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
|
|
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,
|
|
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
|
|
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:
|
|
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
|
|
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
|