@anker-in/campaign-ui 0.2.11-beta.36 → 0.2.11-beta.37

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.
@@ -43,7 +43,7 @@ export interface ChatInputProps {
43
43
  className?: string;
44
44
  /**
45
45
  * 底部提示文本
46
- * @default "The above content is provided by AI, for reference."
46
+ * 不传入则不显示
47
47
  */
48
48
  bottomTips?: string;
49
49
  }
@@ -1,2 +1,2 @@
1
- "use strict";var p=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var F=Object.prototype.hasOwnProperty;var R=(e,n)=>{for(var r in n)p(e,r,{get:n[r],enumerable:!0})},A=(e,n,r,i)=>{if(n&&typeof n=="object"||typeof n=="function")for(let o of E(n))!F.call(e,o)&&o!==r&&p(e,o,{get:()=>n[o],enumerable:!(i=v(n,o))||i.enumerable});return e};var N=e=>A(p({},"__esModule",{value:!0}),e);var H={};R(H,{ChatInput:()=>D});module.exports=N(H);var t=require("react/jsx-runtime"),s=require("react");const T=()=>(0,t.jsxs)("svg",{width:"20",height:"20",viewBox:"0 0 20 20",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:[(0,t.jsx)("path",{d:"M10.2069 15.6997V4.69971",stroke:"white","stroke-width":"2","stroke-linecap":"round"}),(0,t.jsx)("path",{d:"M15.3995 8.50341L10.9506 4.05446C10.534 3.6379 9.85866 3.63791 9.4421 4.05446L5.00005 8.49651",stroke:"white","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"})]}),D=({value:e,onChange:n,onSend:r,placeholder:i="",disabled:o=!1,autoFocus:u=!1,maxLength:l=5e3,bottomTips:f="The above content is provided by AI, for reference.",className:x=""})=>{const h=(0,s.useRef)(null),c=(0,s.useRef)(!1);(0,s.useEffect)(()=>{const a=h.current;if(!a)return;a.style.height="auto";const k=Math.min(a.scrollHeight,120);a.style.height=`${k}px`},[e]);const m=()=>{c.current=!0},b=()=>{c.current=!1},w=a=>{if(a.key==="Enter"&&!a.shiftKey){if(c.current)return;a.preventDefault(),e.trim()&&!o&&r()}},y=a=>{const g=a.target.value;g.length<=l&&n(g)},C=()=>{e.trim()&&!o&&r()},d=e.trim().length>0&&!o;return(0,t.jsxs)("div",{className:`flex flex-col gap-2 bg-white px-4 py-4 ${x}`,children:[(0,t.jsx)("div",{className:"flex items-center gap-2 rounded-2xl",style:{background:"linear-gradient(to right, #7687F3, #7687F3, #4DA8F5, #A3DFCE)",padding:"2px"},children:(0,t.jsxs)("div",{className:"flex flex-1 items-center gap-2 bg-white px-3 py-2",style:{borderRadius:"14px"},children:[(0,t.jsx)("textarea",{ref:h,value:e,onChange:y,onKeyDown:w,onCompositionStart:m,onCompositionEnd:b,placeholder:i,disabled:o,autoFocus:u,rows:1,className:"flex-1 resize-none bg-transparent font-bold text-sm text-gray-900 outline-none placeholder:text-gray-400 disabled:cursor-not-allowed disabled:opacity-50",style:{resize:"none"}}),e.length>l*.8&&(0,t.jsxs)("span",{className:"text-xs text-gray-400",children:[e.length,"/",l]}),(0,t.jsx)("button",{type:"button",onClick:C,disabled:!d,className:`shrink-0 rounded-full p-2 transition-all ${d?" active:scale-95":"cursor-not-allowed "}`,style:d?{background:"linear-gradient(to bottom, #A3DFCE, #4DA8F5, #7687F3)"}:{background:"#BEBEBE"},"aria-label":"Send message",children:(0,t.jsx)(T,{})})]})}),(0,t.jsx)("p",{className:"px-3 font-bold text-[12px] text-[#767880] tracking-tight leading-[1.4]",children:f})]})};
1
+ "use strict";var h=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var F=Object.prototype.hasOwnProperty;var R=(e,n)=>{for(var s in n)h(e,s,{get:n[s],enumerable:!0})},N=(e,n,s,i)=>{if(n&&typeof n=="object"||typeof n=="function")for(let o of v(n))!F.call(e,o)&&o!==s&&h(e,o,{get:()=>n[o],enumerable:!(i=E(n,o))||i.enumerable});return e};var A=e=>N(h({},"__esModule",{value:!0}),e);var M={};R(M,{ChatInput:()=>H});module.exports=A(M);var t=require("react/jsx-runtime"),r=require("react");const D=()=>(0,t.jsxs)("svg",{width:"20",height:"20",viewBox:"0 0 20 20",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:[(0,t.jsx)("path",{d:"M10.2069 15.6997V4.69971",stroke:"white","stroke-width":"2","stroke-linecap":"round"}),(0,t.jsx)("path",{d:"M15.3995 8.50341L10.9506 4.05446C10.534 3.6379 9.85866 3.63791 9.4421 4.05446L5.00005 8.49651",stroke:"white","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"})]}),H=({value:e,onChange:n,onSend:s,placeholder:i="",disabled:o=!1,autoFocus:x=!1,maxLength:l=5e3,bottomTips:p,className:f=""})=>{const u=(0,r.useRef)(null),c=(0,r.useRef)(!1);(0,r.useEffect)(()=>{const a=u.current;if(!a)return;a.style.height="auto";const k=Math.min(a.scrollHeight,120);a.style.height=`${k}px`},[e]);const m=()=>{c.current=!0},b=()=>{c.current=!1},w=a=>{if(a.key==="Enter"&&!a.shiftKey){if(c.current)return;a.preventDefault(),e.trim()&&!o&&s()}},C=a=>{const g=a.target.value;g.length<=l&&n(g)},y=()=>{e.trim()&&!o&&s()},d=e.trim().length>0&&!o;return(0,t.jsxs)("div",{className:`flex flex-col gap-2 bg-white px-4 py-4 ${f}`,children:[(0,t.jsx)("div",{className:"flex items-center gap-2 rounded-2xl",style:{background:"linear-gradient(to right, #7687F3, #7687F3, #4DA8F5, #A3DFCE)",padding:"2px"},children:(0,t.jsxs)("div",{className:"flex flex-1 items-center gap-2 bg-white px-3 py-2",style:{borderRadius:"14px"},children:[(0,t.jsx)("textarea",{ref:u,value:e,onChange:C,onKeyDown:w,onCompositionStart:m,onCompositionEnd:b,placeholder:i,disabled:o,autoFocus:x,rows:1,className:"flex-1 resize-none bg-transparent font-bold text-sm text-gray-900 outline-none placeholder:text-gray-400 disabled:cursor-not-allowed disabled:opacity-50",style:{resize:"none"}}),e.length>l*.8&&(0,t.jsxs)("span",{className:"text-xs text-gray-400",children:[e.length,"/",l]}),(0,t.jsx)("button",{type:"button",onClick:y,disabled:!d,className:`shrink-0 rounded-full p-2 transition-all ${d?" active:scale-95":"cursor-not-allowed "}`,style:d?{background:"linear-gradient(to bottom, #A3DFCE, #4DA8F5, #7687F3)"}:{background:"#BEBEBE"},"aria-label":"Send message",children:(0,t.jsx)(D,{})})]})}),p&&(0,t.jsx)("p",{className:"px-3 font-bold text-[12px] text-[#767880] tracking-tight leading-[1.4]",children:p})]})};
2
2
  //# sourceMappingURL=ChatInput.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/components/LiveChatWidget/components/ChatInput.tsx"],
4
- "sourcesContent": ["/**\n * \u804A\u5929\u8F93\u5165\u6846\u7EC4\u4EF6\n * \u63D0\u4F9B\u6587\u672C\u8F93\u5165\u548C\u53D1\u9001\u6309\u94AE\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u8F93\u5165\u6846\u8BBE\u8BA1\n */\n\nimport React, { useRef, useEffect } from 'react'\n\nexport interface ChatInputProps {\n /**\n * \u8F93\u5165\u6846\u5F53\u524D\u503C\n */\n value: string\n\n /**\n * \u503C\u53D8\u5316\u56DE\u8C03\n */\n onChange: (value: string) => void\n\n /**\n * \u53D1\u9001\u6D88\u606F\u56DE\u8C03\n */\n onSend: () => void\n\n /**\n * \u5360\u4F4D\u7B26\u6587\u672C\n * @default \"\u8F93\u5165\u6D88\u606F...\"\n */\n placeholder?: string\n\n /**\n * \u662F\u5426\u7981\u7528\u8F93\u5165\n * @default false\n */\n disabled?: boolean\n\n /**\n * \u662F\u5426\u81EA\u52A8\u805A\u7126\n * @default false\n */\n autoFocus?: boolean\n\n /**\n * \u6700\u5927\u5B57\u7B26\u6570\n * @default 5000\n */\n maxLength?: number\n\n /**\n * \u81EA\u5B9A\u4E49\u6837\u5F0F\u7C7B\u540D\n */\n className?: string\n\n /**\n * \u5E95\u90E8\u63D0\u793A\u6587\u672C\n * @default \"The above content is provided by AI, for reference.\"\n */\n bottomTips?: string\n}\n\n/**\n * \u53D1\u9001\u56FE\u6807 (\u5411\u4E0A\u7BAD\u5934)\n */\nconst SendIcon: React.FC = () => (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M10.2069 15.6997V4.69971\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" />\n <path\n d=\"M15.3995 8.50341L10.9506 4.05446C10.534 3.6379 9.85866 3.63791 9.4421 4.05446L5.00005 8.49651\"\n stroke=\"white\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n)\n\n/**\n * \u804A\u5929\u8F93\u5165\u6846\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u591A\u884C\u6587\u672C\u8F93\u5165\uFF08\u81EA\u52A8\u589E\u957F\uFF09\n * - Enter \u53D1\u9001\uFF0CShift+Enter \u6362\u884C\n * - \u5B57\u7B26\u6570\u9650\u5236\uFF085000\uFF09\n * - \u7981\u7528\u72B6\u6001\uFF08\u53D1\u9001\u4E2D\uFF09\n * - \u53D1\u9001\u6309\u94AE\uFF08\u7A7A\u5185\u5BB9\u65F6\u7981\u7528\uFF09\n *\n * \u6837\u5F0F\uFF1A\n * - \u56FA\u5B9A\u5E95\u90E8\n * - \u8FB9\u6846\u5206\u9694\u4E0A\u65B9\u5185\u5BB9\n * - \u81EA\u9002\u5E94\u9AD8\u5EA6\uFF081-5 \u884C\uFF09\n *\n * @example\n * ```tsx\n * <ChatInput\n * value={inputValue}\n * onChange={setInputValue}\n * onSend={handleSend}\n * disabled={isStreaming}\n * />\n * ```\n */\nexport const ChatInput: React.FC<ChatInputProps> = ({\n value,\n onChange,\n onSend,\n placeholder = '',\n disabled = false,\n autoFocus = false,\n maxLength = 5000,\n bottomTips = 'The above content is provided by AI, for reference.',\n className = '',\n}) => {\n const textareaRef = useRef<HTMLTextAreaElement>(null)\n // \u8DDF\u8E2A\u8F93\u5165\u6CD5\u72B6\u6001\uFF08\u662F\u5426\u6B63\u5728\u4F7F\u7528\u62FC\u97F3\u8F93\u5165\u7B49\uFF09\n const isComposingRef = useRef(false)\n\n /**\n * \u81EA\u52A8\u8C03\u6574 textarea \u9AD8\u5EA6\n */\n useEffect(() => {\n const textarea = textareaRef.current\n if (!textarea) return\n\n // \u91CD\u7F6E\u9AD8\u5EA6\u4EE5\u83B7\u53D6\u6B63\u786E\u7684 scrollHeight\n textarea.style.height = 'auto'\n\n // \u8BBE\u7F6E\u9AD8\u5EA6\uFF0C\u6700\u5927 5 \u884C\uFF08\u7EA6 120px\uFF09\n const maxHeight = 120\n const newHeight = Math.min(textarea.scrollHeight, maxHeight)\n textarea.style.height = `${newHeight}px`\n }, [value])\n\n /**\n * \u5904\u7406\u8F93\u5165\u6CD5\u5F00\u59CB\n */\n const handleCompositionStart = () => {\n isComposingRef.current = true\n }\n\n /**\n * \u5904\u7406\u8F93\u5165\u6CD5\u7ED3\u675F\n */\n const handleCompositionEnd = () => {\n isComposingRef.current = false\n }\n\n /**\n * \u5904\u7406\u952E\u76D8\u4E8B\u4EF6\n * Enter \u53D1\u9001\uFF0CShift+Enter \u6362\u884C\n * \u6CE8\u610F\uFF1A\u8F93\u5165\u6CD5\u6FC0\u6D3B\u65F6\uFF08\u5982\u62FC\u97F3\u8F93\u5165\u4E2D\uFF09\uFF0C\u56DE\u8F66\u952E\u4E0D\u5E94\u53D1\u9001\u6D88\u606F\n */\n const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key === 'Enter' && !event.shiftKey) {\n // \u5982\u679C\u6B63\u5728\u4F7F\u7528\u8F93\u5165\u6CD5\uFF08\u5982\u62FC\u97F3\u8F93\u5165\uFF09\uFF0C\u5219\u4E0D\u53D1\u9001\u6D88\u606F\n // \u8BA9\u8F93\u5165\u6CD5\u5148\u5B8C\u6210\u5B57\u7B26\u9009\u62E9\n if (isComposingRef.current) {\n return\n }\n\n event.preventDefault()\n if (value.trim() && !disabled) {\n onSend()\n }\n }\n }\n\n /**\n * \u5904\u7406\u8F93\u5165\u53D8\u5316\n */\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n const newValue = event.target.value\n\n // \u5B57\u7B26\u6570\u9650\u5236\n if (newValue.length <= maxLength) {\n onChange(newValue)\n }\n }\n\n /**\n * \u5904\u7406\u53D1\u9001\u6309\u94AE\u70B9\u51FB\n */\n const handleSendClick = () => {\n if (value.trim() && !disabled) {\n onSend()\n }\n }\n\n const canSend = value.trim().length > 0 && !disabled\n\n return (\n <div className={`flex flex-col gap-2 bg-white px-4 py-4 ${className}`}>\n {/* \u8F93\u5165\u6846\u5BB9\u5668 - \u5E26\u6E10\u53D8\u8FB9\u6846 */}\n <div\n className=\"flex items-center gap-2 rounded-2xl\"\n style={{\n background: 'linear-gradient(to right, #7687F3, #7687F3, #4DA8F5, #A3DFCE)',\n padding: '2px',\n }}\n >\n {/* \u5185\u90E8\u767D\u8272\u80CC\u666F */}\n <div className=\"flex flex-1 items-center gap-2 bg-white px-3 py-2\" style={{ borderRadius: '14px' }}>\n {/* \u6587\u672C\u8F93\u5165\u6846 */}\n <textarea\n ref={textareaRef}\n value={value}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n onCompositionStart={handleCompositionStart}\n onCompositionEnd={handleCompositionEnd}\n placeholder={placeholder}\n disabled={disabled}\n autoFocus={autoFocus}\n rows={1}\n className=\"flex-1 resize-none bg-transparent font-bold text-sm text-gray-900 outline-none placeholder:text-gray-400 disabled:cursor-not-allowed disabled:opacity-50\"\n style={{\n resize: 'none',\n }}\n />\n\n {/* \u5B57\u7B26\u6570\u63D0\u793A (\u63A5\u8FD1\u4E0A\u9650\u65F6\u663E\u793A) */}\n {value.length > maxLength * 0.8 && (\n <span className=\"text-xs text-gray-400\">\n {value.length}/{maxLength}\n </span>\n )}\n\n {/* \u53D1\u9001\u6309\u94AE */}\n <button\n type=\"button\"\n onClick={handleSendClick}\n disabled={!canSend}\n className={`shrink-0 rounded-full p-2 transition-all ${canSend ? ' active:scale-95' : 'cursor-not-allowed '}`}\n style={\n canSend\n ? {\n background: 'linear-gradient(to bottom, #A3DFCE, #4DA8F5, #7687F3)',\n }\n : { background: '#BEBEBE' }\n }\n aria-label=\"Send message\"\n >\n <SendIcon />\n </button>\n </div>\n </div>\n <p className=\"px-3 font-bold text-[12px] text-[#767880] tracking-tight leading-[1.4]\">\n {bottomTips}\n </p>\n </div>\n )\n}\n"],
5
- "mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,eAAAE,IAAA,eAAAC,EAAAH,GAgEE,IAAAI,EAAA,6BA1DFC,EAAyC,iBAyDzC,MAAMC,EAAqB,OACzB,QAAC,OAAI,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OAAO,MAAM,6BAChE,oBAAC,QAAK,EAAE,2BAA2B,OAAO,QAAQ,eAAa,IAAI,iBAAe,QAAQ,KAC1F,OAAC,QACC,EAAE,gGACF,OAAO,QACP,eAAa,IACb,iBAAe,QACf,kBAAgB,QAClB,GACF,EA4BWJ,EAAsC,CAAC,CAClD,MAAAK,EACA,SAAAC,EACA,OAAAC,EACA,YAAAC,EAAc,GACd,SAAAC,EAAW,GACX,UAAAC,EAAY,GACZ,UAAAC,EAAY,IACZ,WAAAC,EAAa,sDACb,UAAAC,EAAY,EACd,IAAM,CACJ,MAAMC,KAAc,UAA4B,IAAI,EAE9CC,KAAiB,UAAO,EAAK,KAKnC,aAAU,IAAM,CACd,MAAMC,EAAWF,EAAY,QAC7B,GAAI,CAACE,EAAU,OAGfA,EAAS,MAAM,OAAS,OAIxB,MAAMC,EAAY,KAAK,IAAID,EAAS,aADlB,GACyC,EAC3DA,EAAS,MAAM,OAAS,GAAGC,CAAS,IACtC,EAAG,CAACZ,CAAK,CAAC,EAKV,MAAMa,EAAyB,IAAM,CACnCH,EAAe,QAAU,EAC3B,EAKMI,EAAuB,IAAM,CACjCJ,EAAe,QAAU,EAC3B,EAOMK,EAAiBC,GAAoD,CACzE,GAAIA,EAAM,MAAQ,SAAW,CAACA,EAAM,SAAU,CAG5C,GAAIN,EAAe,QACjB,OAGFM,EAAM,eAAe,EACjBhB,EAAM,KAAK,GAAK,CAACI,GACnBF,EAAO,CAEX,CACF,EAKMe,EAAgBD,GAAkD,CACtE,MAAME,EAAWF,EAAM,OAAO,MAG1BE,EAAS,QAAUZ,GACrBL,EAASiB,CAAQ,CAErB,EAKMC,EAAkB,IAAM,CACxBnB,EAAM,KAAK,GAAK,CAACI,GACnBF,EAAO,CAEX,EAEMkB,EAAUpB,EAAM,KAAK,EAAE,OAAS,GAAK,CAACI,EAE5C,SACE,QAAC,OAAI,UAAW,0CAA0CI,CAAS,GAEjE,oBAAC,OACC,UAAU,sCACV,MAAO,CACL,WAAY,gEACZ,QAAS,KACX,EAGA,oBAAC,OAAI,UAAU,oDAAoD,MAAO,CAAE,aAAc,MAAO,EAE/F,oBAAC,YACC,IAAKC,EACL,MAAOT,EACP,SAAUiB,EACV,UAAWF,EACX,mBAAoBF,EACpB,iBAAkBC,EAClB,YAAaX,EACb,SAAUC,EACV,UAAWC,EACX,KAAM,EACN,UAAU,2JACV,MAAO,CACL,OAAQ,MACV,EACF,EAGCL,EAAM,OAASM,EAAY,OAC1B,QAAC,QAAK,UAAU,wBACb,UAAAN,EAAM,OAAO,IAAEM,GAClB,KAIF,OAAC,UACC,KAAK,SACL,QAASa,EACT,SAAU,CAACC,EACX,UAAW,4CAA4CA,EAAU,mBAAqB,qBAAqB,GAC3G,MACEA,EACI,CACE,WAAY,uDACd,EACA,CAAE,WAAY,SAAU,EAE9B,aAAW,eAEX,mBAACrB,EAAA,EAAS,EACZ,GACF,EACF,KACA,OAAC,KAAE,UAAU,yEACV,SAAAQ,EACH,GACF,CAEJ",
4
+ "sourcesContent": ["/**\n * \u804A\u5929\u8F93\u5165\u6846\u7EC4\u4EF6\n * \u63D0\u4F9B\u6587\u672C\u8F93\u5165\u548C\u53D1\u9001\u6309\u94AE\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u8F93\u5165\u6846\u8BBE\u8BA1\n */\n\nimport React, { useRef, useEffect } from 'react'\n\nexport interface ChatInputProps {\n /**\n * \u8F93\u5165\u6846\u5F53\u524D\u503C\n */\n value: string\n\n /**\n * \u503C\u53D8\u5316\u56DE\u8C03\n */\n onChange: (value: string) => void\n\n /**\n * \u53D1\u9001\u6D88\u606F\u56DE\u8C03\n */\n onSend: () => void\n\n /**\n * \u5360\u4F4D\u7B26\u6587\u672C\n * @default \"\u8F93\u5165\u6D88\u606F...\"\n */\n placeholder?: string\n\n /**\n * \u662F\u5426\u7981\u7528\u8F93\u5165\n * @default false\n */\n disabled?: boolean\n\n /**\n * \u662F\u5426\u81EA\u52A8\u805A\u7126\n * @default false\n */\n autoFocus?: boolean\n\n /**\n * \u6700\u5927\u5B57\u7B26\u6570\n * @default 5000\n */\n maxLength?: number\n\n /**\n * \u81EA\u5B9A\u4E49\u6837\u5F0F\u7C7B\u540D\n */\n className?: string\n\n /**\n * \u5E95\u90E8\u63D0\u793A\u6587\u672C\n * \u4E0D\u4F20\u5165\u5219\u4E0D\u663E\u793A\n */\n bottomTips?: string\n}\n\n/**\n * \u53D1\u9001\u56FE\u6807 (\u5411\u4E0A\u7BAD\u5934)\n */\nconst SendIcon: React.FC = () => (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M10.2069 15.6997V4.69971\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" />\n <path\n d=\"M15.3995 8.50341L10.9506 4.05446C10.534 3.6379 9.85866 3.63791 9.4421 4.05446L5.00005 8.49651\"\n stroke=\"white\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n)\n\n/**\n * \u804A\u5929\u8F93\u5165\u6846\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u591A\u884C\u6587\u672C\u8F93\u5165\uFF08\u81EA\u52A8\u589E\u957F\uFF09\n * - Enter \u53D1\u9001\uFF0CShift+Enter \u6362\u884C\n * - \u5B57\u7B26\u6570\u9650\u5236\uFF085000\uFF09\n * - \u7981\u7528\u72B6\u6001\uFF08\u53D1\u9001\u4E2D\uFF09\n * - \u53D1\u9001\u6309\u94AE\uFF08\u7A7A\u5185\u5BB9\u65F6\u7981\u7528\uFF09\n *\n * \u6837\u5F0F\uFF1A\n * - \u56FA\u5B9A\u5E95\u90E8\n * - \u8FB9\u6846\u5206\u9694\u4E0A\u65B9\u5185\u5BB9\n * - \u81EA\u9002\u5E94\u9AD8\u5EA6\uFF081-5 \u884C\uFF09\n *\n * @example\n * ```tsx\n * <ChatInput\n * value={inputValue}\n * onChange={setInputValue}\n * onSend={handleSend}\n * disabled={isStreaming}\n * />\n * ```\n */\nexport const ChatInput: React.FC<ChatInputProps> = ({\n value,\n onChange,\n onSend,\n placeholder = '',\n disabled = false,\n autoFocus = false,\n maxLength = 5000,\n bottomTips,\n className = '',\n}) => {\n const textareaRef = useRef<HTMLTextAreaElement>(null)\n // \u8DDF\u8E2A\u8F93\u5165\u6CD5\u72B6\u6001\uFF08\u662F\u5426\u6B63\u5728\u4F7F\u7528\u62FC\u97F3\u8F93\u5165\u7B49\uFF09\n const isComposingRef = useRef(false)\n\n /**\n * \u81EA\u52A8\u8C03\u6574 textarea \u9AD8\u5EA6\n */\n useEffect(() => {\n const textarea = textareaRef.current\n if (!textarea) return\n\n // \u91CD\u7F6E\u9AD8\u5EA6\u4EE5\u83B7\u53D6\u6B63\u786E\u7684 scrollHeight\n textarea.style.height = 'auto'\n\n // \u8BBE\u7F6E\u9AD8\u5EA6\uFF0C\u6700\u5927 5 \u884C\uFF08\u7EA6 120px\uFF09\n const maxHeight = 120\n const newHeight = Math.min(textarea.scrollHeight, maxHeight)\n textarea.style.height = `${newHeight}px`\n }, [value])\n\n /**\n * \u5904\u7406\u8F93\u5165\u6CD5\u5F00\u59CB\n */\n const handleCompositionStart = () => {\n isComposingRef.current = true\n }\n\n /**\n * \u5904\u7406\u8F93\u5165\u6CD5\u7ED3\u675F\n */\n const handleCompositionEnd = () => {\n isComposingRef.current = false\n }\n\n /**\n * \u5904\u7406\u952E\u76D8\u4E8B\u4EF6\n * Enter \u53D1\u9001\uFF0CShift+Enter \u6362\u884C\n * \u6CE8\u610F\uFF1A\u8F93\u5165\u6CD5\u6FC0\u6D3B\u65F6\uFF08\u5982\u62FC\u97F3\u8F93\u5165\u4E2D\uFF09\uFF0C\u56DE\u8F66\u952E\u4E0D\u5E94\u53D1\u9001\u6D88\u606F\n */\n const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key === 'Enter' && !event.shiftKey) {\n // \u5982\u679C\u6B63\u5728\u4F7F\u7528\u8F93\u5165\u6CD5\uFF08\u5982\u62FC\u97F3\u8F93\u5165\uFF09\uFF0C\u5219\u4E0D\u53D1\u9001\u6D88\u606F\n // \u8BA9\u8F93\u5165\u6CD5\u5148\u5B8C\u6210\u5B57\u7B26\u9009\u62E9\n if (isComposingRef.current) {\n return\n }\n\n event.preventDefault()\n if (value.trim() && !disabled) {\n onSend()\n }\n }\n }\n\n /**\n * \u5904\u7406\u8F93\u5165\u53D8\u5316\n */\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n const newValue = event.target.value\n\n // \u5B57\u7B26\u6570\u9650\u5236\n if (newValue.length <= maxLength) {\n onChange(newValue)\n }\n }\n\n /**\n * \u5904\u7406\u53D1\u9001\u6309\u94AE\u70B9\u51FB\n */\n const handleSendClick = () => {\n if (value.trim() && !disabled) {\n onSend()\n }\n }\n\n const canSend = value.trim().length > 0 && !disabled\n\n return (\n <div className={`flex flex-col gap-2 bg-white px-4 py-4 ${className}`}>\n {/* \u8F93\u5165\u6846\u5BB9\u5668 - \u5E26\u6E10\u53D8\u8FB9\u6846 */}\n <div\n className=\"flex items-center gap-2 rounded-2xl\"\n style={{\n background: 'linear-gradient(to right, #7687F3, #7687F3, #4DA8F5, #A3DFCE)',\n padding: '2px',\n }}\n >\n {/* \u5185\u90E8\u767D\u8272\u80CC\u666F */}\n <div className=\"flex flex-1 items-center gap-2 bg-white px-3 py-2\" style={{ borderRadius: '14px' }}>\n {/* \u6587\u672C\u8F93\u5165\u6846 */}\n <textarea\n ref={textareaRef}\n value={value}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n onCompositionStart={handleCompositionStart}\n onCompositionEnd={handleCompositionEnd}\n placeholder={placeholder}\n disabled={disabled}\n autoFocus={autoFocus}\n rows={1}\n className=\"flex-1 resize-none bg-transparent font-bold text-sm text-gray-900 outline-none placeholder:text-gray-400 disabled:cursor-not-allowed disabled:opacity-50\"\n style={{\n resize: 'none',\n }}\n />\n\n {/* \u5B57\u7B26\u6570\u63D0\u793A (\u63A5\u8FD1\u4E0A\u9650\u65F6\u663E\u793A) */}\n {value.length > maxLength * 0.8 && (\n <span className=\"text-xs text-gray-400\">\n {value.length}/{maxLength}\n </span>\n )}\n\n {/* \u53D1\u9001\u6309\u94AE */}\n <button\n type=\"button\"\n onClick={handleSendClick}\n disabled={!canSend}\n className={`shrink-0 rounded-full p-2 transition-all ${canSend ? ' active:scale-95' : 'cursor-not-allowed '}`}\n style={\n canSend\n ? {\n background: 'linear-gradient(to bottom, #A3DFCE, #4DA8F5, #7687F3)',\n }\n : { background: '#BEBEBE' }\n }\n aria-label=\"Send message\"\n >\n <SendIcon />\n </button>\n </div>\n </div>\n {bottomTips && (\n <p className=\"px-3 font-bold text-[12px] text-[#767880] tracking-tight leading-[1.4]\">\n {bottomTips}\n </p>\n )}\n </div>\n )\n}\n"],
5
+ "mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,eAAAE,IAAA,eAAAC,EAAAH,GAgEE,IAAAI,EAAA,6BA1DFC,EAAyC,iBAyDzC,MAAMC,EAAqB,OACzB,QAAC,OAAI,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OAAO,MAAM,6BAChE,oBAAC,QAAK,EAAE,2BAA2B,OAAO,QAAQ,eAAa,IAAI,iBAAe,QAAQ,KAC1F,OAAC,QACC,EAAE,gGACF,OAAO,QACP,eAAa,IACb,iBAAe,QACf,kBAAgB,QAClB,GACF,EA4BWJ,EAAsC,CAAC,CAClD,MAAAK,EACA,SAAAC,EACA,OAAAC,EACA,YAAAC,EAAc,GACd,SAAAC,EAAW,GACX,UAAAC,EAAY,GACZ,UAAAC,EAAY,IACZ,WAAAC,EACA,UAAAC,EAAY,EACd,IAAM,CACJ,MAAMC,KAAc,UAA4B,IAAI,EAE9CC,KAAiB,UAAO,EAAK,KAKnC,aAAU,IAAM,CACd,MAAMC,EAAWF,EAAY,QAC7B,GAAI,CAACE,EAAU,OAGfA,EAAS,MAAM,OAAS,OAIxB,MAAMC,EAAY,KAAK,IAAID,EAAS,aADlB,GACyC,EAC3DA,EAAS,MAAM,OAAS,GAAGC,CAAS,IACtC,EAAG,CAACZ,CAAK,CAAC,EAKV,MAAMa,EAAyB,IAAM,CACnCH,EAAe,QAAU,EAC3B,EAKMI,EAAuB,IAAM,CACjCJ,EAAe,QAAU,EAC3B,EAOMK,EAAiBC,GAAoD,CACzE,GAAIA,EAAM,MAAQ,SAAW,CAACA,EAAM,SAAU,CAG5C,GAAIN,EAAe,QACjB,OAGFM,EAAM,eAAe,EACjBhB,EAAM,KAAK,GAAK,CAACI,GACnBF,EAAO,CAEX,CACF,EAKMe,EAAgBD,GAAkD,CACtE,MAAME,EAAWF,EAAM,OAAO,MAG1BE,EAAS,QAAUZ,GACrBL,EAASiB,CAAQ,CAErB,EAKMC,EAAkB,IAAM,CACxBnB,EAAM,KAAK,GAAK,CAACI,GACnBF,EAAO,CAEX,EAEMkB,EAAUpB,EAAM,KAAK,EAAE,OAAS,GAAK,CAACI,EAE5C,SACE,QAAC,OAAI,UAAW,0CAA0CI,CAAS,GAEjE,oBAAC,OACC,UAAU,sCACV,MAAO,CACL,WAAY,gEACZ,QAAS,KACX,EAGA,oBAAC,OAAI,UAAU,oDAAoD,MAAO,CAAE,aAAc,MAAO,EAE/F,oBAAC,YACC,IAAKC,EACL,MAAOT,EACP,SAAUiB,EACV,UAAWF,EACX,mBAAoBF,EACpB,iBAAkBC,EAClB,YAAaX,EACb,SAAUC,EACV,UAAWC,EACX,KAAM,EACN,UAAU,2JACV,MAAO,CACL,OAAQ,MACV,EACF,EAGCL,EAAM,OAASM,EAAY,OAC1B,QAAC,QAAK,UAAU,wBACb,UAAAN,EAAM,OAAO,IAAEM,GAClB,KAIF,OAAC,UACC,KAAK,SACL,QAASa,EACT,SAAU,CAACC,EACX,UAAW,4CAA4CA,EAAU,mBAAqB,qBAAqB,GAC3G,MACEA,EACI,CACE,WAAY,uDACd,EACA,CAAE,WAAY,SAAU,EAE9B,aAAW,eAEX,mBAACrB,EAAA,EAAS,EACZ,GACF,EACF,EACCQ,MACC,OAAC,KAAE,UAAU,yEACV,SAAAA,EACH,GAEJ,CAEJ",
6
6
  "names": ["ChatInput_exports", "__export", "ChatInput", "__toCommonJS", "import_jsx_runtime", "import_react", "SendIcon", "value", "onChange", "onSend", "placeholder", "disabled", "autoFocus", "maxLength", "bottomTips", "className", "textareaRef", "isComposingRef", "textarea", "newHeight", "handleCompositionStart", "handleCompositionEnd", "handleKeyDown", "event", "handleChange", "newValue", "handleSendClick", "canSend"]
7
7
  }
@@ -86,7 +86,7 @@ export interface ChatWindowProps {
86
86
  showNewSessionButton?: boolean;
87
87
  /**
88
88
  * 输入框底部提示文本
89
- * @default "The above content is provided by AI, for reference."
89
+ * 不传入则不显示
90
90
  */
91
91
  bottomTips?: string;
92
92
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/components/LiveChatWidget/components/ChatWindow.tsx"],
4
- "sourcesContent": ["/**\n * \u804A\u5929\u7A97\u53E3\u5BB9\u5668\u7EC4\u4EF6\n * \u5305\u542B\u5934\u90E8\u3001\u6D88\u606F\u5217\u8868\u3001\u8F93\u5165\u6846\u7684\u5B8C\u6574\u804A\u5929\u754C\u9762\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u7A97\u53E3\u8BBE\u8BA1\n */\n\nimport React, { useState, useRef, useEffect, useCallback } from 'react'\nimport type { Message, MessageRenderer } from '../types'\nimport { ChatHeader } from './ChatHeader'\nimport { MessageList } from './MessageList'\nimport { ChatInput } from './ChatInput'\nimport { MessageRendererRegistry } from '../utils/messageRenderers'\n\nexport interface ChatWindowProps {\n /**\n * \u6D88\u606F\u5217\u8868\n */\n messages: Message[]\n\n /**\n * \u8F93\u5165\u6846\u5F53\u524D\u503C\n */\n inputValue: string\n\n /**\n * \u8F93\u5165\u6846\u503C\u53D8\u5316\u56DE\u8C03\n */\n onInputChange: (value: string) => void\n\n /**\n * \u53D1\u9001\u6D88\u606F\u56DE\u8C03\n */\n onSend: () => void\n\n /**\n * \u5173\u95ED\u7A97\u53E3\u56DE\u8C03\n */\n onClose?: () => void\n\n /**\n * \u65B0\u4F1A\u8BDD\u56DE\u8C03\n */\n onNewSession?: () => void\n\n /**\n * \u5934\u90E8\u6807\u9898\n */\n title?: string\n\n /**\n * Logo URL\n */\n logoUrl?: string\n\n /**\n * \u662F\u5426\u6B63\u5728\u53D1\u9001\u6D88\u606F\n * @default false\n */\n isSending?: boolean\n\n /**\n * \u662F\u5426\u6B63\u5728\u52A0\u8F7D\u5386\u53F2\u6D88\u606F\n * @default false\n */\n isLoadingHistory?: boolean\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\n * @default true\n */\n autoScroll?: boolean\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 * \u8F93\u5165\u6846\u5360\u4F4D\u7B26\n */\n inputPlaceholder?: string\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 * \u662F\u5426\u663E\u793A\u65B0\u4F1A\u8BDD\u6309\u94AE\n * @default true\n */\n showNewSessionButton?: boolean\n\n /**\n * \u8F93\u5165\u6846\u5E95\u90E8\u63D0\u793A\u6587\u672C\n * @default \"The above content is provided by AI, for reference.\"\n */\n bottomTips?: string\n}\n\n/**\n * \u804A\u5929\u7A97\u53E3\u5BB9\u5668\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u7EC4\u5408\u5934\u90E8\u3001\u6D88\u606F\u5217\u8868\u3001\u8F93\u5165\u6846\n * - \u54CD\u5E94\u5F0F\u5E03\u5C40\uFF08\u79FB\u52A8\u7AEF\u5168\u5C4F\uFF0C\u684C\u9762\u7AEF\u56FA\u5B9A\u5C3A\u5BF8\uFF09\n * - \u52A8\u753B\u8FDB\u5165/\u9000\u51FA\u6548\u679C\n *\n * \u5E03\u5C40\u7ED3\u6784\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\u2510\n * \u2502 ChatHeader \u2502 (\u56FA\u5B9A\u9876\u90E8)\n * \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n * \u2502 MessageList \u2502 (\u53EF\u6EDA\u52A8\u533A\u57DF)\n * \u2502 (flex-1) \u2502\n * \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n * \u2502 ChatInput \u2502 (\u56FA\u5B9A\u5E95\u90E8)\n * \u2514\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 * \u54CD\u5E94\u5F0F\u8BBE\u8BA1\uFF1A\n * - \u79FB\u52A8\u7AEF (< 768px): \u5168\u5C4F\u663E\u793A\n * - \u5E73\u677F\u53CA\u4EE5\u4E0A (>= 768px): \u56FA\u5B9A\u5C3A\u5BF8\u5F39\u7A97\n *\n * @example\n * ```tsx\n * <ChatWindow\n * messages={messages}\n * inputValue={inputValue}\n * onInputChange={setInputValue}\n * onSend={handleSend}\n * onClose={() => setIsOpen(false)}\n * onNewSession={handleNewSession}\n * isSending={isStreaming}\n * title=\"AI \u52A9\u624B\"\n * logoUrl=\"/logo.png\"\n * />\n * ```\n */\nexport const ChatWindow: React.FC<ChatWindowProps> = ({\n messages,\n inputValue,\n onInputChange,\n onSend,\n onClose,\n onNewSession,\n title,\n logoUrl,\n isSending = false,\n isLoadingHistory = false,\n showTimestamp = true,\n autoScroll = true,\n rendererRegistry,\n defaultRenderer,\n inputPlaceholder,\n className = '',\n onAddToCart,\n showNewSessionButton = true,\n bottomTips,\n}) => {\n // \u5E38\u91CF\uFF1A\u9AD8\u5EA6\u9650\u5236\u6BD4\u4F8B\n const MIN_HEIGHT_RATIO = 0.4 // \u6700\u5C0F40%\n const MAX_HEIGHT_RATIO = 0.8 // \u6700\u592780%\n const DEFAULT_HEIGHT = 680 // \u9ED8\u8BA4680px\n\n // \u83B7\u53D6\u5F53\u524D\u5141\u8BB8\u7684\u9AD8\u5EA6\u8303\u56F4\n const getHeightConstraints = useCallback(() => {\n if (typeof window === 'undefined') {\n return { minHeight: DEFAULT_HEIGHT, maxHeight: DEFAULT_HEIGHT }\n }\n return {\n minHeight: window.innerHeight * MIN_HEIGHT_RATIO,\n maxHeight: window.innerHeight * MAX_HEIGHT_RATIO,\n }\n }, [MIN_HEIGHT_RATIO, MAX_HEIGHT_RATIO, DEFAULT_HEIGHT])\n\n // \u5C06\u9AD8\u5EA6\u9650\u5236\u5728\u5141\u8BB8\u8303\u56F4\u5185\n const clampHeight = useCallback(\n (height: number) => {\n const { minHeight, maxHeight } = getHeightConstraints()\n return Math.max(minHeight, Math.min(maxHeight, height))\n },\n [getHeightConstraints]\n )\n\n // \u8BA1\u7B97\u521D\u59CB\u9AD8\u5EA6\uFF1A\u9ED8\u8BA4680px\uFF0C\u4F46\u9700\u8981\u572830%-80%\u8303\u56F4\u5185\n const getInitialHeight = () => {\n if (typeof window === 'undefined') return DEFAULT_HEIGHT\n return clampHeight(DEFAULT_HEIGHT)\n }\n\n // \u79FB\u52A8\u7AEF\u9AD8\u5EA6\u8C03\u8282\u72B6\u6001\uFF08\u9ED8\u8BA4680px\u6216\u5C4F\u5E5580%\uFF0C\u53D6\u8F83\u5C0F\u503C\uFF09\n const [mobileHeight, setMobileHeight] = useState(getInitialHeight)\n const [isDragging, setIsDragging] = useState(false)\n const dragStartY = useRef(0)\n const dragStartHeight = useRef(getInitialHeight())\n const windowRef = useRef<HTMLDivElement>(null)\n\n // \u66F4\u65B0\u9AD8\u5EA6\u5E76\u540C\u6B65CSS\u53D8\u91CF\n const updateHeight = useCallback(\n (newHeight: number) => {\n const clampedHeight = clampHeight(newHeight)\n setMobileHeight(clampedHeight)\n // \u540C\u6B65\u66F4\u65B0CSS\u53D8\u91CF\uFF0C\u4F9B livechat.css \u4F7F\u7528\n if (windowRef.current) {\n windowRef.current.style.setProperty('--livechat-mobile-height', `${clampedHeight}px`)\n }\n return clampedHeight\n },\n [clampHeight]\n )\n\n // \u68C0\u6D4B\u662F\u5426\u4E3A\u79FB\u52A8\u7AEF\uFF08\u521D\u59CB\u5316\u65F6\u7ACB\u5373\u68C0\u6D4B\uFF09\n const [isMobile, setIsMobile] = useState(() => {\n if (typeof window !== 'undefined') {\n return window.innerWidth < 768\n }\n return false\n })\n\n useEffect(() => {\n const handleResize = () => {\n const newIsMobile = window.innerWidth < 768\n setIsMobile(newIsMobile)\n\n // \u5982\u679C\u662F\u79FB\u52A8\u7AEF\uFF0C\u68C0\u67E5\u5F53\u524D\u9AD8\u5EA6\u662F\u5426\u5728\u5141\u8BB8\u8303\u56F4\u5185\n if (newIsMobile) {\n const clampedHeight = clampHeight(mobileHeight)\n if (clampedHeight !== mobileHeight) {\n updateHeight(clampedHeight)\n }\n }\n }\n\n window.addEventListener('resize', handleResize)\n return () => window.removeEventListener('resize', handleResize)\n }, [mobileHeight, clampHeight, updateHeight])\n\n // \u62D6\u62FD\u5F00\u59CB\n const handleDragStart = (e: React.MouseEvent | React.TouchEvent) => {\n if (!isMobile) return\n\n setIsDragging(true)\n const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY\n dragStartY.current = clientY\n dragStartHeight.current = mobileHeight\n\n // \u963B\u6B62\u9ED8\u8BA4\u884C\u4E3A\n e.preventDefault()\n }\n\n // \u62D6\u62FD\u4E2D\n useEffect(() => {\n if (!isDragging) return\n\n const handleDragMove = (e: MouseEvent | TouchEvent) => {\n const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY\n const deltaY = dragStartY.current - clientY // \u5411\u4E0A\u62D6\u52A8\u4E3A\u6B63\u503C\n const newHeight = dragStartHeight.current + deltaY\n\n updateHeight(newHeight)\n }\n\n const handleDragEnd = () => {\n setIsDragging(false)\n }\n\n document.addEventListener('mousemove', handleDragMove)\n document.addEventListener('mouseup', handleDragEnd)\n document.addEventListener('touchmove', handleDragMove, { passive: false })\n document.addEventListener('touchend', handleDragEnd)\n\n return () => {\n document.removeEventListener('mousemove', handleDragMove)\n document.removeEventListener('mouseup', handleDragEnd)\n document.removeEventListener('touchmove', handleDragMove)\n document.removeEventListener('touchend', handleDragEnd)\n }\n }, [isDragging, updateHeight])\n\n return (\n <div\n ref={windowRef}\n className={`livechat-window flex flex-col overflow-hidden bg-white ${className}`}\n style={\n {\n borderRadius: '16px',\n borderBottomLeftRadius: isMobile && '0px',\n borderBottomRightRadius: isMobile && '0px',\n '--livechat-mobile-height': `${mobileHeight}px`,\n } as React.CSSProperties\n }\n >\n {/* \u79FB\u52A8\u7AEF\u62D6\u62FD\u624B\u67C4 */}\n {isMobile && (\n <div\n className=\"flex cursor-ns-resize items-center justify-center py-2\"\n style={{\n touchAction: 'none',\n backgroundColor: '#ffffff',\n }}\n onMouseDown={handleDragStart}\n onTouchStart={handleDragStart}\n >\n <div\n style={{\n width: '48px',\n height: '6px',\n borderRadius: '999px',\n backgroundColor: '#DADCE0',\n }}\n />\n </div>\n )}\n\n {/* \u5934\u90E8 */}\n <ChatHeader\n title={title}\n logoUrl={logoUrl}\n onClose={onClose}\n onNewSession={onNewSession}\n showNewSessionButton={showNewSessionButton}\n />\n\n {/* \u6D88\u606F\u5217\u8868\uFF08\u53EF\u6EDA\u52A8\u533A\u57DF\uFF09 */}\n <MessageList\n messages={messages}\n rendererRegistry={rendererRegistry}\n defaultRenderer={defaultRenderer}\n showTimestamp={showTimestamp}\n autoScroll={autoScroll}\n isLoadingHistory={isLoadingHistory}\n onAddToCart={onAddToCart}\n />\n\n {/* \u8F93\u5165\u6846 */}\n <ChatInput\n value={inputValue}\n onChange={onInputChange}\n onSend={onSend}\n placeholder={inputPlaceholder}\n disabled={isSending}\n bottomTips={bottomTips}\n />\n </div>\n )\n}\n"],
4
+ "sourcesContent": ["/**\n * \u804A\u5929\u7A97\u53E3\u5BB9\u5668\u7EC4\u4EF6\n * \u5305\u542B\u5934\u90E8\u3001\u6D88\u606F\u5217\u8868\u3001\u8F93\u5165\u6846\u7684\u5B8C\u6574\u804A\u5929\u754C\u9762\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u7A97\u53E3\u8BBE\u8BA1\n */\n\nimport React, { useState, useRef, useEffect, useCallback } from 'react'\nimport type { Message, MessageRenderer } from '../types'\nimport { ChatHeader } from './ChatHeader'\nimport { MessageList } from './MessageList'\nimport { ChatInput } from './ChatInput'\nimport { MessageRendererRegistry } from '../utils/messageRenderers'\n\nexport interface ChatWindowProps {\n /**\n * \u6D88\u606F\u5217\u8868\n */\n messages: Message[]\n\n /**\n * \u8F93\u5165\u6846\u5F53\u524D\u503C\n */\n inputValue: string\n\n /**\n * \u8F93\u5165\u6846\u503C\u53D8\u5316\u56DE\u8C03\n */\n onInputChange: (value: string) => void\n\n /**\n * \u53D1\u9001\u6D88\u606F\u56DE\u8C03\n */\n onSend: () => void\n\n /**\n * \u5173\u95ED\u7A97\u53E3\u56DE\u8C03\n */\n onClose?: () => void\n\n /**\n * \u65B0\u4F1A\u8BDD\u56DE\u8C03\n */\n onNewSession?: () => void\n\n /**\n * \u5934\u90E8\u6807\u9898\n */\n title?: string\n\n /**\n * Logo URL\n */\n logoUrl?: string\n\n /**\n * \u662F\u5426\u6B63\u5728\u53D1\u9001\u6D88\u606F\n * @default false\n */\n isSending?: boolean\n\n /**\n * \u662F\u5426\u6B63\u5728\u52A0\u8F7D\u5386\u53F2\u6D88\u606F\n * @default false\n */\n isLoadingHistory?: boolean\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\n * @default true\n */\n autoScroll?: boolean\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 * \u8F93\u5165\u6846\u5360\u4F4D\u7B26\n */\n inputPlaceholder?: string\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 * \u662F\u5426\u663E\u793A\u65B0\u4F1A\u8BDD\u6309\u94AE\n * @default true\n */\n showNewSessionButton?: boolean\n\n /**\n * \u8F93\u5165\u6846\u5E95\u90E8\u63D0\u793A\u6587\u672C\n * \u4E0D\u4F20\u5165\u5219\u4E0D\u663E\u793A\n */\n bottomTips?: string\n}\n\n/**\n * \u804A\u5929\u7A97\u53E3\u5BB9\u5668\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u7EC4\u5408\u5934\u90E8\u3001\u6D88\u606F\u5217\u8868\u3001\u8F93\u5165\u6846\n * - \u54CD\u5E94\u5F0F\u5E03\u5C40\uFF08\u79FB\u52A8\u7AEF\u5168\u5C4F\uFF0C\u684C\u9762\u7AEF\u56FA\u5B9A\u5C3A\u5BF8\uFF09\n * - \u52A8\u753B\u8FDB\u5165/\u9000\u51FA\u6548\u679C\n *\n * \u5E03\u5C40\u7ED3\u6784\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\u2510\n * \u2502 ChatHeader \u2502 (\u56FA\u5B9A\u9876\u90E8)\n * \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n * \u2502 MessageList \u2502 (\u53EF\u6EDA\u52A8\u533A\u57DF)\n * \u2502 (flex-1) \u2502\n * \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n * \u2502 ChatInput \u2502 (\u56FA\u5B9A\u5E95\u90E8)\n * \u2514\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 * \u54CD\u5E94\u5F0F\u8BBE\u8BA1\uFF1A\n * - \u79FB\u52A8\u7AEF (< 768px): \u5168\u5C4F\u663E\u793A\n * - \u5E73\u677F\u53CA\u4EE5\u4E0A (>= 768px): \u56FA\u5B9A\u5C3A\u5BF8\u5F39\u7A97\n *\n * @example\n * ```tsx\n * <ChatWindow\n * messages={messages}\n * inputValue={inputValue}\n * onInputChange={setInputValue}\n * onSend={handleSend}\n * onClose={() => setIsOpen(false)}\n * onNewSession={handleNewSession}\n * isSending={isStreaming}\n * title=\"AI \u52A9\u624B\"\n * logoUrl=\"/logo.png\"\n * />\n * ```\n */\nexport const ChatWindow: React.FC<ChatWindowProps> = ({\n messages,\n inputValue,\n onInputChange,\n onSend,\n onClose,\n onNewSession,\n title,\n logoUrl,\n isSending = false,\n isLoadingHistory = false,\n showTimestamp = true,\n autoScroll = true,\n rendererRegistry,\n defaultRenderer,\n inputPlaceholder,\n className = '',\n onAddToCart,\n showNewSessionButton = true,\n bottomTips,\n}) => {\n // \u5E38\u91CF\uFF1A\u9AD8\u5EA6\u9650\u5236\u6BD4\u4F8B\n const MIN_HEIGHT_RATIO = 0.4 // \u6700\u5C0F40%\n const MAX_HEIGHT_RATIO = 0.8 // \u6700\u592780%\n const DEFAULT_HEIGHT = 680 // \u9ED8\u8BA4680px\n\n // \u83B7\u53D6\u5F53\u524D\u5141\u8BB8\u7684\u9AD8\u5EA6\u8303\u56F4\n const getHeightConstraints = useCallback(() => {\n if (typeof window === 'undefined') {\n return { minHeight: DEFAULT_HEIGHT, maxHeight: DEFAULT_HEIGHT }\n }\n return {\n minHeight: window.innerHeight * MIN_HEIGHT_RATIO,\n maxHeight: window.innerHeight * MAX_HEIGHT_RATIO,\n }\n }, [MIN_HEIGHT_RATIO, MAX_HEIGHT_RATIO, DEFAULT_HEIGHT])\n\n // \u5C06\u9AD8\u5EA6\u9650\u5236\u5728\u5141\u8BB8\u8303\u56F4\u5185\n const clampHeight = useCallback(\n (height: number) => {\n const { minHeight, maxHeight } = getHeightConstraints()\n return Math.max(minHeight, Math.min(maxHeight, height))\n },\n [getHeightConstraints]\n )\n\n // \u8BA1\u7B97\u521D\u59CB\u9AD8\u5EA6\uFF1A\u9ED8\u8BA4680px\uFF0C\u4F46\u9700\u8981\u572830%-80%\u8303\u56F4\u5185\n const getInitialHeight = () => {\n if (typeof window === 'undefined') return DEFAULT_HEIGHT\n return clampHeight(DEFAULT_HEIGHT)\n }\n\n // \u79FB\u52A8\u7AEF\u9AD8\u5EA6\u8C03\u8282\u72B6\u6001\uFF08\u9ED8\u8BA4680px\u6216\u5C4F\u5E5580%\uFF0C\u53D6\u8F83\u5C0F\u503C\uFF09\n const [mobileHeight, setMobileHeight] = useState(getInitialHeight)\n const [isDragging, setIsDragging] = useState(false)\n const dragStartY = useRef(0)\n const dragStartHeight = useRef(getInitialHeight())\n const windowRef = useRef<HTMLDivElement>(null)\n\n // \u66F4\u65B0\u9AD8\u5EA6\u5E76\u540C\u6B65CSS\u53D8\u91CF\n const updateHeight = useCallback(\n (newHeight: number) => {\n const clampedHeight = clampHeight(newHeight)\n setMobileHeight(clampedHeight)\n // \u540C\u6B65\u66F4\u65B0CSS\u53D8\u91CF\uFF0C\u4F9B livechat.css \u4F7F\u7528\n if (windowRef.current) {\n windowRef.current.style.setProperty('--livechat-mobile-height', `${clampedHeight}px`)\n }\n return clampedHeight\n },\n [clampHeight]\n )\n\n // \u68C0\u6D4B\u662F\u5426\u4E3A\u79FB\u52A8\u7AEF\uFF08\u521D\u59CB\u5316\u65F6\u7ACB\u5373\u68C0\u6D4B\uFF09\n const [isMobile, setIsMobile] = useState(() => {\n if (typeof window !== 'undefined') {\n return window.innerWidth < 768\n }\n return false\n })\n\n useEffect(() => {\n const handleResize = () => {\n const newIsMobile = window.innerWidth < 768\n setIsMobile(newIsMobile)\n\n // \u5982\u679C\u662F\u79FB\u52A8\u7AEF\uFF0C\u68C0\u67E5\u5F53\u524D\u9AD8\u5EA6\u662F\u5426\u5728\u5141\u8BB8\u8303\u56F4\u5185\n if (newIsMobile) {\n const clampedHeight = clampHeight(mobileHeight)\n if (clampedHeight !== mobileHeight) {\n updateHeight(clampedHeight)\n }\n }\n }\n\n window.addEventListener('resize', handleResize)\n return () => window.removeEventListener('resize', handleResize)\n }, [mobileHeight, clampHeight, updateHeight])\n\n // \u62D6\u62FD\u5F00\u59CB\n const handleDragStart = (e: React.MouseEvent | React.TouchEvent) => {\n if (!isMobile) return\n\n setIsDragging(true)\n const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY\n dragStartY.current = clientY\n dragStartHeight.current = mobileHeight\n\n // \u963B\u6B62\u9ED8\u8BA4\u884C\u4E3A\n e.preventDefault()\n }\n\n // \u62D6\u62FD\u4E2D\n useEffect(() => {\n if (!isDragging) return\n\n const handleDragMove = (e: MouseEvent | TouchEvent) => {\n const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY\n const deltaY = dragStartY.current - clientY // \u5411\u4E0A\u62D6\u52A8\u4E3A\u6B63\u503C\n const newHeight = dragStartHeight.current + deltaY\n\n updateHeight(newHeight)\n }\n\n const handleDragEnd = () => {\n setIsDragging(false)\n }\n\n document.addEventListener('mousemove', handleDragMove)\n document.addEventListener('mouseup', handleDragEnd)\n document.addEventListener('touchmove', handleDragMove, { passive: false })\n document.addEventListener('touchend', handleDragEnd)\n\n return () => {\n document.removeEventListener('mousemove', handleDragMove)\n document.removeEventListener('mouseup', handleDragEnd)\n document.removeEventListener('touchmove', handleDragMove)\n document.removeEventListener('touchend', handleDragEnd)\n }\n }, [isDragging, updateHeight])\n\n return (\n <div\n ref={windowRef}\n className={`livechat-window flex flex-col overflow-hidden bg-white ${className}`}\n style={\n {\n borderRadius: '16px',\n borderBottomLeftRadius: isMobile && '0px',\n borderBottomRightRadius: isMobile && '0px',\n '--livechat-mobile-height': `${mobileHeight}px`,\n } as React.CSSProperties\n }\n >\n {/* \u79FB\u52A8\u7AEF\u62D6\u62FD\u624B\u67C4 */}\n {isMobile && (\n <div\n className=\"flex cursor-ns-resize items-center justify-center py-2\"\n style={{\n touchAction: 'none',\n backgroundColor: '#ffffff',\n }}\n onMouseDown={handleDragStart}\n onTouchStart={handleDragStart}\n >\n <div\n style={{\n width: '48px',\n height: '6px',\n borderRadius: '999px',\n backgroundColor: '#DADCE0',\n }}\n />\n </div>\n )}\n\n {/* \u5934\u90E8 */}\n <ChatHeader\n title={title}\n logoUrl={logoUrl}\n onClose={onClose}\n onNewSession={onNewSession}\n showNewSessionButton={showNewSessionButton}\n />\n\n {/* \u6D88\u606F\u5217\u8868\uFF08\u53EF\u6EDA\u52A8\u533A\u57DF\uFF09 */}\n <MessageList\n messages={messages}\n rendererRegistry={rendererRegistry}\n defaultRenderer={defaultRenderer}\n showTimestamp={showTimestamp}\n autoScroll={autoScroll}\n isLoadingHistory={isLoadingHistory}\n onAddToCart={onAddToCart}\n />\n\n {/* \u8F93\u5165\u6846 */}\n <ChatInput\n value={inputValue}\n onChange={onInputChange}\n onSend={onSend}\n placeholder={inputPlaceholder}\n disabled={isSending}\n bottomTips={bottomTips}\n />\n </div>\n )\n}\n"],
5
5
  "mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,gBAAAE,IAAA,eAAAC,EAAAH,GAySI,IAAAI,EAAA,6BAnSJC,EAAgE,iBAEhEC,EAA2B,wBAC3BC,EAA4B,yBAC5BC,EAA0B,uBAiJnB,MAAMN,EAAwC,CAAC,CACpD,SAAAO,EACA,WAAAC,EACA,cAAAC,EACA,OAAAC,EACA,QAAAC,EACA,aAAAC,EACA,MAAAC,EACA,QAAAC,EACA,UAAAC,EAAY,GACZ,iBAAAC,EAAmB,GACnB,cAAAC,EAAgB,GAChB,WAAAC,EAAa,GACb,iBAAAC,EACA,gBAAAC,EACA,iBAAAC,EACA,UAAAC,EAAY,GACZ,YAAAC,EACA,qBAAAC,EAAuB,GACvB,WAAAC,CACF,IAAM,CAOJ,MAAMC,KAAuB,eAAY,IACnC,OAAO,OAAW,IACb,CAAE,UAAW,IAAgB,UAAW,GAAe,EAEzD,CACL,UAAW,OAAO,YAAc,GAChC,UAAW,OAAO,YAAc,EAClC,EACC,CAAC,GAAkB,GAAkB,GAAc,CAAC,EAGjDC,KAAc,eACjBC,GAAmB,CAClB,KAAM,CAAE,UAAAC,EAAW,UAAAC,CAAU,EAAIJ,EAAqB,EACtD,OAAO,KAAK,IAAIG,EAAW,KAAK,IAAIC,EAAWF,CAAM,CAAC,CACxD,EACA,CAACF,CAAoB,CACvB,EAGMK,EAAmB,IACnB,OAAO,OAAW,IAAoB,IACnCJ,EAAY,GAAc,EAI7B,CAACK,EAAcC,CAAe,KAAI,YAASF,CAAgB,EAC3D,CAACG,EAAYC,CAAa,KAAI,YAAS,EAAK,EAC5CC,KAAa,UAAO,CAAC,EACrBC,KAAkB,UAAON,EAAiB,CAAC,EAC3CO,KAAY,UAAuB,IAAI,EAGvCC,KAAe,eAClBC,GAAsB,CACrB,MAAMC,EAAgBd,EAAYa,CAAS,EAC3C,OAAAP,EAAgBQ,CAAa,EAEzBH,EAAU,SACZA,EAAU,QAAQ,MAAM,YAAY,2BAA4B,GAAGG,CAAa,IAAI,EAE/EA,CACT,EACA,CAACd,CAAW,CACd,EAGM,CAACe,EAAUC,CAAW,KAAI,YAAS,IACnC,OAAO,OAAW,IACb,OAAO,WAAa,IAEtB,EACR,KAED,aAAU,IAAM,CACd,MAAMC,EAAe,IAAM,CACzB,MAAMC,EAAc,OAAO,WAAa,IAIxC,GAHAF,EAAYE,CAAW,EAGnBA,EAAa,CACf,MAAMJ,EAAgBd,EAAYK,CAAY,EAC1CS,IAAkBT,GACpBO,EAAaE,CAAa,CAE9B,CACF,EAEA,cAAO,iBAAiB,SAAUG,CAAY,EACvC,IAAM,OAAO,oBAAoB,SAAUA,CAAY,CAChE,EAAG,CAACZ,EAAcL,EAAaY,CAAY,CAAC,EAG5C,MAAMO,EAAmB,GAA2C,CAClE,GAAI,CAACJ,EAAU,OAEfP,EAAc,EAAI,EAClB,MAAMY,EAAU,YAAa,EAAI,EAAE,QAAQ,CAAC,EAAE,QAAU,EAAE,QAC1DX,EAAW,QAAUW,EACrBV,EAAgB,QAAUL,EAG1B,EAAE,eAAe,CACnB,EAGA,sBAAU,IAAM,CACd,GAAI,CAACE,EAAY,OAEjB,MAAMc,EAAkBC,GAA+B,CACrD,MAAMF,EAAU,YAAaE,EAAIA,EAAE,QAAQ,CAAC,EAAE,QAAUA,EAAE,QACpDC,EAASd,EAAW,QAAUW,EAC9BP,EAAYH,EAAgB,QAAUa,EAE5CX,EAAaC,CAAS,CACxB,EAEMW,EAAgB,IAAM,CAC1BhB,EAAc,EAAK,CACrB,EAEA,gBAAS,iBAAiB,YAAaa,CAAc,EACrD,SAAS,iBAAiB,UAAWG,CAAa,EAClD,SAAS,iBAAiB,YAAaH,EAAgB,CAAE,QAAS,EAAM,CAAC,EACzE,SAAS,iBAAiB,WAAYG,CAAa,EAE5C,IAAM,CACX,SAAS,oBAAoB,YAAaH,CAAc,EACxD,SAAS,oBAAoB,UAAWG,CAAa,EACrD,SAAS,oBAAoB,YAAaH,CAAc,EACxD,SAAS,oBAAoB,WAAYG,CAAa,CACxD,CACF,EAAG,CAACjB,EAAYK,CAAY,CAAC,KAG3B,QAAC,OACC,IAAKD,EACL,UAAW,0DAA0DhB,CAAS,GAC9E,MACE,CACE,aAAc,OACd,uBAAwBoB,GAAY,MACpC,wBAAyBA,GAAY,MACrC,2BAA4B,GAAGV,CAAY,IAC7C,EAID,UAAAU,MACC,OAAC,OACC,UAAU,yDACV,MAAO,CACL,YAAa,OACb,gBAAiB,SACnB,EACA,YAAaI,EACb,aAAcA,EAEd,mBAAC,OACC,MAAO,CACL,MAAO,OACP,OAAQ,MACR,aAAc,QACd,gBAAiB,SACnB,EACF,EACF,KAIF,OAAC,cACC,MAAOjC,EACP,QAASC,EACT,QAASH,EACT,aAAcC,EACd,qBAAsBY,EACxB,KAGA,OAAC,eACC,SAAUjB,EACV,iBAAkBY,EAClB,gBAAiBC,EACjB,cAAeH,EACf,WAAYC,EACZ,iBAAkBF,EAClB,YAAaO,EACf,KAGA,OAAC,aACC,MAAOf,EACP,SAAUC,EACV,OAAQC,EACR,YAAaW,EACb,SAAUN,EACV,WAAYU,EACd,GACF,CAEJ",
6
6
  "names": ["ChatWindow_exports", "__export", "ChatWindow", "__toCommonJS", "import_jsx_runtime", "import_react", "import_ChatHeader", "import_MessageList", "import_ChatInput", "messages", "inputValue", "onInputChange", "onSend", "onClose", "onNewSession", "title", "logoUrl", "isSending", "isLoadingHistory", "showTimestamp", "autoScroll", "rendererRegistry", "defaultRenderer", "inputPlaceholder", "className", "onAddToCart", "showNewSessionButton", "bottomTips", "getHeightConstraints", "clampHeight", "height", "minHeight", "maxHeight", "getInitialHeight", "mobileHeight", "setMobileHeight", "isDragging", "setIsDragging", "dragStartY", "dragStartHeight", "windowRef", "updateHeight", "newHeight", "clampedHeight", "isMobile", "setIsMobile", "handleResize", "newIsMobile", "handleDragStart", "clientY", "handleDragMove", "e", "deltaY", "handleDragEnd"]
7
7
  }
@@ -786,7 +786,7 @@ export interface LiveChatWidgetProps {
786
786
  productCardRender?: (product: any) => React.ReactNode;
787
787
  /**
788
788
  * 输入框底部提示文本
789
- * @default "The above content is provided by AI, for reference."
789
+ * 不传入则不显示
790
790
  */
791
791
  bottomTips?: string;
792
792
  /**
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/components/LiveChatWidget/types.ts"],
4
- "sourcesContent": ["/**\n * LiveChat \u7EC4\u4EF6\u6838\u5FC3\u7C7B\u578B\u5B9A\u4E49\n * \u57FA\u4E8E specs/livechat-widget/data-model.md\n */\n\n// ============================================================================\n// Session Types (\u4F1A\u8BDD)\n// ============================================================================\n\nexport type SessionStatus = 'active' | 'expired'\n\nexport interface Session {\n sessionId: string\n userId: string\n site: string\n status: SessionStatus\n createdAt?: number\n lastActivityAt?: number\n}\n\n// ============================================================================\n// Message Types (\u6D88\u606F)\n// ============================================================================\n\nexport type MessageRole = 'user' | 'assistant' | 'system' | 'tool'\n\nexport interface MessageMetadata {\n tokenUsage?: {\n inputTokens: number\n outputTokens: number\n }\n toolCalls?: Array<{\n id: string\n type: string\n name: string\n }>\n}\n\nexport interface Message {\n id: string\n sessionId?: string\n role: MessageRole\n content: MessageContent[]\n timestamp: number\n metadata?: MessageMetadata\n structured_content?: Array<{\n type: string\n data: any\n }>\n}\n\n// ============================================================================\n// Message Content Types (\u6D88\u606F\u5185\u5BB9)\n// ============================================================================\n\nexport type MessageContentType =\n | 'text'\n | 'product_card'\n | 'product_list'\n | 'product_comparison'\n | 'policy'\n | 'quick_replies'\n | 'thinking'\n | 'error'\n | 'faq_list'\n | 'cart'\n\nexport type MessageContent =\n | TextContent\n | ProductCardContent\n | ProductListContent\n | ProductComparisonContent\n | PolicyContent\n | QuickRepliesContent\n | ThinkingContent\n | ErrorContent\n | FAQListContent\n | PromotionListContent\n | CartContent\n\nexport interface TextContent {\n type: 'text'\n text: string\n}\n\nexport interface ProductCardContent {\n type: 'product_card'\n data: {\n product: Product\n rawProduct?: any // Raw backend product data (for custom render)\n onAddToCart?: (product: Product) => void\n productCardRender?: (product: any) => React.ReactNode\n }\n}\n\nexport interface ProductListContent {\n type: 'product_list'\n data: {\n products: Product[]\n title?: string\n onAddToCart?: (product: Product) => void\n commonText?: CommonText\n }\n}\n\nexport interface ProductComparisonContent {\n type: 'product_comparison'\n data: {\n products: Product[]\n dimensions: {\n price?: {\n label: string\n values: Array<{\n product_id: string\n min: number\n max: number\n currency: string\n has_discount: boolean\n }>\n }\n variants?: {\n label: string\n values: Array<{\n product_id: string\n count: number\n }>\n }\n member_price?: {\n label: string\n values: Array<{\n product_id: string\n available: boolean\n min: number\n max: number\n currency: string\n }>\n }\n discount?: {\n label: string\n values: Array<{\n product_id: string\n has_discount: boolean\n }>\n }\n [key: string]: any\n }\n onAddToCart?: (product: Product) => void\n commonText?: CommonText\n }\n}\n\nexport interface PolicyContent {\n type: 'policy'\n data: {\n title: string\n content: string\n }\n}\n\nexport interface QuickRepliesContent {\n type: 'quick_replies'\n data: {\n replies: QuickReply[]\n }\n}\n\nexport interface ThinkingContent {\n type: 'thinking'\n data: {\n status: string\n }\n}\n\nexport interface ErrorContent {\n type: 'error'\n data: {\n message: string\n code?: string\n }\n}\n\nexport interface FAQListContent {\n type: 'faq_list'\n data: {\n found: boolean\n count: number\n total?: number\n results: FAQItem[]\n }\n}\n\nexport interface PromotionListContent {\n type: 'promotion_list'\n data: {\n found: boolean\n count: number\n total?: number\n results: PromotionItem[]\n commonText?: CommonText\n }\n}\n\n// ============================================================================\n// FAQ Types (\u5E38\u89C1\u95EE\u9898)\n// ============================================================================\n\nexport type FAQCategory = 'shipping' | 'return' | 'product' | 'payment' | 'general'\n\nexport interface FAQItem {\n id: string\n question: string\n answer: string\n category: FAQCategory\n keywords?: string[]\n relatedQuestions?: string[]\n metadata?: {\n language?: string\n priority?: number\n lastUpdated?: string\n }\n}\n\n// ============================================================================\n// Promotion Types (\u4FC3\u9500\u6D3B\u52A8)\n// ============================================================================\n\nexport interface PromotionItem {\n id: string\n title: string\n subtitle?: string\n description?: string\n banner_url?: string\n url?: string\n time_range: {\n start: string\n end?: string | null\n is_active: boolean\n }\n priority?: number\n product_count?: number\n metadata?: {\n display_order?: number\n target_audience?: string\n }\n}\n\n// ============================================================================\n// Product Types (\u5546\u54C1)\n// ============================================================================\n\nexport type StockStatus = 'in_stock' | 'low_stock' | 'out_of_stock'\n\nexport interface Price {\n amount: number\n currency: string\n}\n\nexport interface PriceRange {\n min: number\n max: number\n currency: string\n}\n\nexport interface VariantDiscount {\n has_discount: boolean\n discount_price?: number\n discount_code?: string\n discount_percentage?: number\n /** \u6298\u6263\u7C7B\u578B\uFF1Afixed_amount \u56FA\u5B9A\u91D1\u989D\u6298\u6263\uFF0Cpercentage \u767E\u5206\u6BD4\u6298\u6263 */\n discount_type?: 'fixed_amount' | 'percentage'\n /** \u6298\u6263\u6570\u503C\uFF08\u53EF\u80FD\u662F\u5B57\u7B26\u4E32\u6216\u6570\u5B57\uFF09 */\n discount_value?: string | number\n}\n\nexport interface VariantMemberPrice {\n has_member_price: boolean\n price?: number\n}\n\nexport interface ProductFeatures {\n is_new?: boolean\n has_rental?: boolean\n has_presale?: boolean\n has_member_price?: boolean\n has_discount?: boolean\n}\n\nexport interface Variant {\n id: string\n title: string\n sku?: string\n price?: Price\n availableForSale: boolean\n color?: string\n discount?: VariantDiscount\n memberPrice?: VariantMemberPrice\n inventoryQuantity?: number\n option1?: string\n option2?: string\n option3?: string\n}\n\nexport interface Product {\n shopifyId: string\n sku?: string\n handle: string\n title: string\n description?: string\n vendor?: string\n price: Price\n priceRange?: PriceRange\n memberPriceRange?: PriceRange\n imageUrl: string\n productUrl: string\n stockStatus: StockStatus\n hotScore?: number\n averageRating?: number\n reviewCount?: number\n variants?: Variant[]\n variantCount?: number\n availableCount?: number\n features?: ProductFeatures\n tags?: string[]\n}\n\n// ============================================================================\n// Quick Reply Types (\u5FEB\u6377\u56DE\u590D)\n// ============================================================================\n\nexport interface QuickReply {\n id: string\n label: string\n value: string\n icon?: string\n}\n\n// ============================================================================\n// Policy Types (\u653F\u7B56)\n// ============================================================================\n\nexport interface Policy {\n title: string\n content: string\n}\n\n// ============================================================================\n// Cart Types (\u8D2D\u7269\u8F66)\n// ============================================================================\n\n/**\n * \u8D2D\u7269\u8F66\u91D1\u989D\u4FE1\u606F\n */\nexport interface CartAmount {\n /** \u91D1\u989D\u5B57\u7B26\u4E32\uFF08\u5982 \"99.99\"\uFF09 */\n amount: string\n /** \u8D27\u5E01\u4EE3\u7801\uFF08\u5982 \"USD\"\uFF09 */\n currencyCode: string\n}\n\n/**\n * \u8D2D\u7269\u8F66\u4EF7\u683C\u6C47\u603B\n */\nexport interface CartCost {\n /** \u5E94\u4ED8\u603B\u4EF7\uFF08\u5DF2\u5E94\u7528\u6298\u6263\uFF09 */\n totalAmount: CartAmount\n /** \u539F\u4EF7\u5C0F\u8BA1\uFF08\u672A\u5E94\u7528\u6298\u6263\uFF09 */\n subtotalAmount: CartAmount\n}\n\n/**\n * \u8D2D\u7269\u8F66\u5546\u54C1\u53D8\u4F53\u4FE1\u606F\n */\nexport interface CartMerchandise {\n /** \u53D8\u4F53 ID (Shopify ProductVariant GID) */\n id: string\n /** \u53D8\u4F53\u6807\u9898\uFF08\u5982 \"Black\", \"Large\" \u7B49\uFF09 */\n title: string\n /** \u5355\u4EF7 */\n price: CartAmount\n /** \u5546\u54C1\u56FE\u7247 URL */\n image?: {\n url: string\n altText?: string\n }\n /** \u5173\u8054\u7684\u5546\u54C1\u4FE1\u606F */\n product: {\n /** \u5546\u54C1 ID */\n id: string\n /** \u5546\u54C1\u6807\u9898 */\n title: string\n /** \u5546\u54C1 handle */\n handle: string\n }\n}\n\n/**\n * \u8D2D\u7269\u8F66\u5546\u54C1\u884C\n */\nexport interface CartLine {\n /** \u8D2D\u7269\u8F66\u884C ID (\u7528\u4E8E\u66F4\u65B0/\u5220\u9664\u64CD\u4F5C) */\n id: string\n /** \u5546\u54C1\u6570\u91CF */\n quantity: number\n /** \u4EF7\u683C\u4FE1\u606F */\n cost: {\n /** \u884C\u603B\u4EF7\uFF08\u5355\u4EF7 \u00D7 \u6570\u91CF\uFF0C\u5DF2\u5E94\u7528\u6298\u6263\uFF09 */\n totalAmount: CartAmount\n /** \u5355\u4EF7 */\n amountPerQuantity: CartAmount\n /** \u884C\u5C0F\u8BA1\uFF08\u5355\u4EF7 \u00D7 \u6570\u91CF\uFF0C\u672A\u5E94\u7528\u6298\u6263\uFF09 */\n subtotalAmount: CartAmount\n }\n /** \u5546\u54C1\u53D8\u4F53\u4FE1\u606F */\n merchandise: CartMerchandise\n /** \u81EA\u5B9A\u4E49\u5C5E\u6027\uFF08\u53EF\u9009\uFF09 */\n attributes?: Array<{\n key: string\n value: string\n }>\n}\n\n/**\n * \u8D2D\u7269\u8F66\u6298\u6263\u7801\n */\nexport interface CartDiscountCode {\n /** \u6298\u6263\u7801 */\n code: string\n /** \u662F\u5426\u6709\u6548/\u9002\u7528 */\n applicable: boolean\n}\n\n/**\n * \u8D2D\u7269\u8F66\u6570\u636E\n */\nexport interface CartData {\n /** \u8D2D\u7269\u8F66\u662F\u5426\u4E3A\u7A7A */\n isEmpty: boolean\n /** \u8D2D\u7269\u8F66 ID (Shopify Cart GID) */\n cartId: string\n /** \u5546\u54C1\u603B\u6570\u91CF */\n totalQuantity: number\n /** \u5546\u54C1\u5217\u8868 */\n lines: CartLine[]\n /** \u4EF7\u683C\u6C47\u603B */\n cost: CartCost\n /** \u6298\u6263\u7801\u5217\u8868 */\n discountCodes?: CartDiscountCode[]\n /** \u7ED3\u8D26\u9875\u9762 URL */\n checkoutUrl?: string\n /** \u8D2D\u7269\u8F66\u6309\u94AE\u56DE\u8C03\u51FD\u6570 */\n onCart?: (cartId: string, checkoutUrl?: string) => void\n /** \u901A\u7528\u6587\u6848\u914D\u7F6E */\n commonText?: CommonText\n}\n\n/**\n * \u8D2D\u7269\u8F66\u5185\u5BB9\u5757\n */\nexport interface CartContent {\n type: 'cart'\n data: CartData\n}\n\n// ============================================================================\n// Backend Cart Types (\u540E\u7AEF\u8D2D\u7269\u8F66\u6570\u636E\u683C\u5F0F - Shopify GraphQL)\n// ============================================================================\n\n/**\n * \u540E\u7AEF\u8FD4\u56DE\u7684\u8D2D\u7269\u8F66\u5546\u54C1\u884C\u6570\u636E (Shopify GraphQL \u683C\u5F0F)\n */\nexport interface BackendCartLineNode {\n id: string\n quantity: number\n cost: {\n totalAmount: CartAmount\n amountPerQuantity: CartAmount\n compareAtAmountPerQuantity?: CartAmount | null\n subtotalAmount: CartAmount\n }\n discountAllocations: any[]\n merchandise: {\n id: string\n title: string\n availableForSale: boolean\n quantityAvailable?: number\n price: CartAmount\n compareAtPrice?: CartAmount | null\n image?: {\n url: string\n altText?: string | null\n width?: number\n height?: number\n } | null\n product: {\n id: string\n title: string\n handle: string\n vendor?: string\n featuredImage?: {\n url: string\n altText?: string | null\n width?: number\n height?: number\n } | null\n tags?: string[]\n }\n }\n attributes?: Array<{\n key: string\n value: string\n }>\n}\n\n/**\n * \u540E\u7AEF\u8FD4\u56DE\u7684\u8D2D\u7269\u8F66\u6570\u636E (Shopify GraphQL \u683C\u5F0F)\n */\nexport interface BackendCartData {\n id: string\n checkoutUrl?: string\n totalQuantity: number\n lines: {\n edges: Array<{\n node: BackendCartLineNode\n }>\n }\n cost: {\n totalAmount: CartAmount\n subtotalAmount: CartAmount\n checkoutChargeAmount?: CartAmount\n totalAmountEstimated?: boolean\n subtotalAmountEstimated?: boolean\n totalTaxAmount?: CartAmount | null\n totalDutyAmount?: CartAmount | null\n }\n discountAllocations?: any[]\n buyerIdentity?: any\n attributes?: Array<{\n key: string\n value: string\n }>\n discountCodes?: any[]\n createdAt?: string\n updatedAt?: string\n}\n\n// ============================================================================\n// SSE Event Types (SSE \u4E8B\u4EF6)\n// ============================================================================\n\nexport type SSEEventType =\n | 'message_start'\n | 'content_delta'\n | 'content_block'\n | 'message_end'\n | 'tool_start'\n | 'tool_end'\n | 'status'\n | 'error'\n | 'done'\n\nexport interface SSEEvent<T = any> {\n event: SSEEventType | null\n data: T\n}\n\n// \u5177\u4F53\u4E8B\u4EF6\u6570\u636E\u7C7B\u578B\nexport interface MessageStartData {\n sessionId: string\n}\n\nexport interface ContentDeltaData {\n text: string\n}\n\nexport interface ContentBlockData {\n type: string\n data: any\n}\n\nexport interface MessageEndData {\n usage: {\n inputTokens: number\n outputTokens: number\n }\n}\n\nexport interface ToolStartData {\n id: string\n type: string\n name: string\n}\n\nexport interface ToolEndData {\n id: string\n}\n\nexport interface StatusData {\n type: string\n message?: string\n}\n\nexport interface ErrorData {\n message: string\n code?: string\n type?: string\n}\n\n// ============================================================================\n// API Request/Response Types (API \u8BF7\u6C42\u54CD\u5E94)\n// ============================================================================\n\n/**\n * \u6D41\u5F0F\u5BF9\u8BDD\u8BF7\u6C42\u53C2\u6570\n */\nexport interface ChatStreamRequest {\n /** \u7528\u6237\u7684\u6D88\u606F\u6587\u672C */\n message: string\n /** \u7528\u6237\u6807\u8BC6\u7B26 */\n user_id: string\n /** \u6765\u81EA new-session \u7AEF\u70B9\u7684\u4F1A\u8BDD ID */\n session_id: string\n /** \u53EF\u9009\u7684\u4E0A\u4E0B\u6587\u4FE1\u606F */\n context?: {\n /** \u7528\u4E8E\u8D2D\u7269\u8F66\u64CD\u4F5C\u7684 Shopify \u8D2D\u7269\u8F66 ID */\n cartId?: string\n /** Storefront API \u8BBF\u95EE\u4EE4\u724C */\n accessToken?: string\n /** \u5DF2\u767B\u5F55\u7528\u6237\u7684 ID */\n real_user_id?: string\n }\n}\n\nexport interface NewSessionRequest {\n user_id: string\n session_id?: string\n site?: string\n channel_code?: string\n real_user_id?: string\n}\n\nexport interface NewSessionResponse {\n success: boolean\n sessionId: string\n message: string\n resumed?: boolean\n messages?: Message[]\n welcomeMessage?: string\n quickQuestions?: string[]\n brand?: string\n}\n\nexport interface ErrorResponse {\n success: boolean\n error: string\n code?: string\n details?: any\n}\n\n// ============================================================================\n// Common Text Types (\u901A\u7528\u6587\u6848)\n// ============================================================================\n\n/**\n * \u901A\u7528\u6587\u6848\u914D\u7F6E\n * \u7528\u4E8E\u81EA\u5B9A\u4E49\u7EC4\u4EF6\u4E2D\u7684\u6309\u94AE\u6587\u6848\n */\nexport interface CommonText {\n /**\n * \u4EA7\u54C1\u5217\u8868\u5C55\u5F00\u6309\u94AE\u6587\u6848\n * @default \"Learn More\"\n */\n learnMore?: string\n\n /**\n * \u4EA7\u54C1\u5217\u8868\u6536\u8D77\u6309\u94AE\u6587\u6848\n * @default \"Show Less\"\n */\n showLess?: string\n\n /**\n * \u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u6309\u94AE\u6587\u6848\n * @default \"Add to Cart\"\n */\n addToCart?: string\n\n /**\n * \u67E5\u770B\u8D2D\u7269\u8F66/\u66F4\u591A\u6309\u94AE\u6587\u6848\n * @default \"View More\"\n */\n viewMore?: string\n\n /**\n * \u6298\u6263\u6807\u7B7E\u540E\u7F00\u6587\u6848\uFF08\u5982 \"20% OFF\" \u4E2D\u7684 \"OFF\"\uFF09\n * @default \"OFF\"\n */\n off?: string\n\n /**\n * \u8D2D\u7269\u8F66\u603B\u8BA1\u6587\u6848\n * @default \"Total\"\n */\n total?: string\n}\n\n// ============================================================================\n// Compliance Dialog Types (\u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97)\n// ============================================================================\n\n/**\n * \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u914D\u7F6E\n */\nexport interface ComplianceDialogConfig {\n /**\n * \u5F39\u7A97\u6807\u9898\n * @example \"Hi! I'm your eufy AI assistant.\"\n */\n title: string\n\n /**\n * \u5F39\u7A97\u5185\u5BB9\u6587\u672C\uFF08\u652F\u6301 HTML\uFF09\n * @example \"AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data\"\n */\n content: string\n\n /**\n * \u52FE\u9009\u6846\u6587\u672C\uFF08\u652F\u6301\u5B8C\u6574 HTML\uFF0C\u5305\u62EC\u94FE\u63A5\uFF09\n * \u53EF\u4EE5\u76F4\u63A5\u5305\u542B <a> \u6807\u7B7E\u7B49 HTML \u5143\u7D20\n * @example \"By starting to use \\\"Live Chat\\\", you agree to Anker's <a href=\\\"https://www.anker.com/privacy\\\" target=\\\"_blank\\\">LIVE CHAT PRIVACY NOTICE</a>.\"\n */\n checkboxText: string\n\n /**\n * \u540C\u610F\u6309\u94AE\u6587\u672C\n * @default \"Agree\"\n */\n agreeButtonText?: string\n\n /**\n * Cookie \u540D\u79F0\uFF0C\u7528\u4E8E\u8BB0\u5F55\u7528\u6237\u540C\u610F\u72B6\u6001\n * Cookie \u6709\u6548\u671F\u4E3A 365 \u5929\n * @default \"livechat_compliance_agreed\"\n */\n cookieName?: string\n}\n\n// ============================================================================\n// Component Props Types (\u7EC4\u4EF6 Props)\n// ============================================================================\n\nexport interface LiveChatWidgetProps {\n /**\n * API \u57FA\u7840 URL\n * @example \"https://beta-api-livechat.anker.com\"\n */\n apiBaseUrl: string\n\n /**\n * \u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\n * \u5C06\u5728\u6240\u6709 API \u8BF7\u6C42\u4E2D\u6DFB\u52A0\u8FD9\u4E9B\u8BF7\u6C42\u5934\n * @example { \"Authorization\": \"Bearer token\", \"X-Custom-Header\": \"value\" }\n */\n headers?: Record<string, string>\n\n /**\n * reCAPTCHA site key\n * \u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u81EA\u52A8\u542F\u7528 reCAPTCHA v3 \u9A8C\u8BC1\n * @example \"6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14\"\n */\n recaptchaSitekey?: string\n\n /**\n * reCAPTCHA action \u524D\u7F00\n * \u5B9E\u9645\u4F7F\u7528\u65F6\u4F1A\u6839\u636E\u4E0D\u540C\u63A5\u53E3\u6DFB\u52A0\u540E\u7F00\uFF08\u5982 chat_stream, new_session\uFF09\n * @default \"livechat\"\n */\n recaptchaAction?: string\n\n /**\n * Shopify \u5E97\u94FA\u57DF\u540D\n * @example \"www.eufy.com\"\n */\n site?: string\n\n /**\n * \u6E20\u9053\u7F16\u7801\n * \u7528\u4E8E\u6807\u8BC6\u6765\u6E90\u6E20\u9053\n * @example \"web_homepage\"\n */\n channelCode?: string\n\n /**\n * \u5DF2\u767B\u5F55\u7528\u6237\u7684 ID\uFF08\u53EF\u9009\uFF09\n * \u5982\u679C\u63D0\u4F9B\uFF0C\u5C06\u5728 API \u8BF7\u6C42\u4E2D\u4F20\u9012\n */\n loginUserId?: string\n\n /**\n * Shopify \u8D2D\u7269\u8F66 ID\uFF08\u53EF\u9009\uFF09\n * \u7528\u4E8E\u8D2D\u7269\u8F66\u64CD\u4F5C\uFF0C\u5C06\u5728 stream \u63A5\u53E3\u7684 context \u4E2D\u4F20\u9012\n * @example \"gid://shopify/Cart/Z2NwLXVzLWVhc3QxOjAxSkZH...\"\n */\n cartId?: string\n\n /**\n * Storefront API \u8BBF\u95EE\u4EE4\u724C\uFF08\u53EF\u9009\uFF09\n * \u7528\u4E8E\u8D2D\u7269\u8F66\u64CD\u4F5C\uFF0C\u5C06\u5728 stream \u63A5\u53E3\u7684 context \u4E2D\u4F20\u9012\n */\n accessToken?: string\n\n /**\n * \u6C14\u6CE1\u6309\u94AE\u4F4D\u7F6E\n * \u81EA\u5B9A\u4E49\u4F4D\u7F6E\u5BF9\u8C61\uFF1A{ top?: string, bottom?: string, left?: string, right?: string }\n * @default { bottom: \"1.5rem\", right: \"1.5rem\" }\n * @example\n * // \u81EA\u5B9A\u4E49\u4F4D\u7F6E\n * position={{ bottom: \"20px\", right: \"30px\" }}\n * position={{ top: \"100px\", left: \"50px\" }}\n */\n position?: BubblePosition\n\n /**\n * \u6B22\u8FCE\u6D88\u606F\n * @default \"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F\"\n */\n welcomeMessage?: string\n\n /**\n * \u521D\u59CB\u5FEB\u6377\u56DE\u590D\u6309\u94AE\n */\n quickReplies?: QuickReply[]\n\n /**\n * \u81EA\u5B9A\u4E49\u6D88\u606F\u6E32\u67D3\u5668\n */\n customRenderers?: Record<string, MessageRenderer>\n\n /**\n * Logo URL\n */\n logoUrl?: string\n\n /**\n * \u804A\u5929\u7A97\u53E3\u6807\u9898\n * @default \"AI \u52A9\u624B\"\n */\n title?: string\n\n /**\n * \u804A\u5929\u6C14\u6CE1\u6309\u94AE\u56FE\u6807\uFF08\u56FE\u7247 URL\uFF09\n * \u5982\u679C\u63D0\u4F9B\uFF0C\u5C06\u4F7F\u7528\u56FE\u7247\u66FF\u4EE3\u9ED8\u8BA4\u7684 SVG \u56FE\u6807\n */\n chatBubbleIcon?: string\n\n /**\n * \u53D7\u63A7\u6A21\u5F0F\uFF1A\u662F\u5426\u6253\u5F00\u804A\u5929\u7A97\u53E3\n * \u63D0\u4F9B\u6B64\u53C2\u6570\u65F6\uFF0C\u7EC4\u4EF6\u5C06\u5904\u4E8E\u53D7\u63A7\u6A21\u5F0F\n * @example\n * ```tsx\n * const [isOpen, setIsOpen] = useState(false)\n * <LiveChatWidget open={isOpen} onOpenChange={setIsOpen} />\n * ```\n */\n open?: boolean\n\n /**\n * \u53D7\u63A7\u6A21\u5F0F\uFF1A\u6253\u5F00/\u5173\u95ED\u72B6\u6001\u53D8\u5316\u56DE\u8C03\n * \u3010\u5FC5\u9700\u3011\u914D\u5408 `open` \u4F7F\u7528\uFF0C\u7528\u4E8E\u540C\u6B65\u72B6\u6001\u5230\u7236\u7EC4\u4EF6\n * \u5F53\u7528\u6237\u70B9\u51FB\u6253\u5F00\u6216\u5173\u95ED\u6309\u94AE\u65F6\u89E6\u53D1\n * @example\n * ```tsx\n * <LiveChatWidget\n * open={isOpen}\n * onOpenChange={setIsOpen} // \u5FC5\u9700\uFF1A\u540C\u6B65\u72B6\u6001\n * />\n * ```\n */\n onOpenChange?: (open: boolean) => void\n\n /**\n * \u3010\u53EF\u9009\u3011\u7A97\u53E3\u6253\u5F00\u4E8B\u4EF6\u76D1\u542C\n * \u7528\u4E8E\u57CB\u70B9\u3001\u65E5\u5FD7\u7B49\u526F\u4F5C\u7528\uFF0C\u4E0D\u5F71\u54CD\u72B6\u6001\u63A7\u5236\n * \u5728 onOpenChange \u4E4B\u540E\u89E6\u53D1\n * @example\n * ```tsx\n * <LiveChatWidget\n * open={isOpen}\n * onOpenChange={setIsOpen}\n * onOpen={() => trackEvent('chat_opened')} // \u53EF\u9009\uFF1A\u57CB\u70B9\n * />\n * ```\n */\n onOpen?: () => void\n\n /**\n * \u3010\u53EF\u9009\u3011\u7A97\u53E3\u5173\u95ED\u4E8B\u4EF6\u76D1\u542C\n * \u7528\u4E8E\u57CB\u70B9\u3001\u65E5\u5FD7\u7B49\u526F\u4F5C\u7528\uFF0C\u4E0D\u5F71\u54CD\u72B6\u6001\u63A7\u5236\n * \u5728 onOpenChange \u4E4B\u540E\u89E6\u53D1\n * @example\n * ```tsx\n * <LiveChatWidget\n * open={isOpen}\n * onOpenChange={setIsOpen}\n * onClose={() => trackEvent('chat_closed')} // \u53EF\u9009\uFF1A\u57CB\u70B9\n * />\n * ```\n */\n onClose?: () => void\n onMessageSend?: (message: string) => void\n onError?: (error: Error) => void\n\n /**\n * AI \u6D88\u606F\u56DE\u8C03\n */\n /**\n * AI \u56DE\u590D\u6587\u672C\u6D88\u606F\u65F6\u89E6\u53D1\n */\n onTextMessage?: () => void\n\n /**\n * AI \u56DE\u590D\u5546\u54C1\u5217\u8868\u5361\u7247\u65F6\u89E6\u53D1\n */\n onProductList?: () => void\n\n /**\n * AI \u56DE\u590D\u4FC3\u9500\u5361\u7247\u65F6\u89E6\u53D1\n */\n onPromotionList?: () => void\n\n /**\n * \u5546\u54C1\u64CD\u4F5C\u56DE\u8C03\n */\n onAddToCart?: (product: Product) => void\n\n /**\n * \u8D2D\u7269\u8F66\u6309\u94AE\u70B9\u51FB\u56DE\u8C03\n */\n onCart?: (cartId: string, checkoutUrl?: string) => void\n\n /**\n * \u662F\u5426\u663E\u793A\u65B0\u4F1A\u8BDD\u6309\u94AE\n * @default true\n */\n showNewSessionButton?: boolean\n\n /**\n * \u901A\u7528\u6587\u6848\u914D\u7F6E\n * \u7528\u4E8E\u81EA\u5B9A\u4E49\u6309\u94AE\u6587\u6848\n */\n commonText?: CommonText\n\n /**\n * \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\u51FD\u6570\n * \u7528\u4E8E\u81EA\u5B9A\u4E49\u6E32\u67D3 product_card \u7C7B\u578B\u7684\u4EA7\u54C1\u5361\u7247\n * \u5F53\u63D0\u4F9B\u6B64\u51FD\u6570\u65F6\uFF0C\u5C06\u66FF\u4EE3\u9ED8\u8BA4\u7684\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\u903B\u8F91\n * @param product \u539F\u59CB\u540E\u7AEF\u4EA7\u54C1\u6570\u636E\uFF08\u672A\u7ECF\u8F6C\u6362\u7684\u6570\u636E\uFF09\n * @returns React \u53EF\u6E32\u67D3\u7684\u5185\u5BB9\n * @example\n * ```tsx\n * <LiveChatWidget\n * productCardRender={(product) => (\n * <div>\n * <h3>{product.title}</h3>\n * <p>{product.price_range?.min}</p>\n * </div>\n * )}\n * />\n * ```\n */\n productCardRender?: (product: any) => React.ReactNode\n\n /**\n * \u8F93\u5165\u6846\u5E95\u90E8\u63D0\u793A\u6587\u672C\n * @default \"The above content is provided by AI, for reference.\"\n */\n bottomTips?: string\n\n /**\n * \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u914D\u7F6E\n * \u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u5728\u7528\u6237\u9996\u6B21\u70B9\u51FB\u804A\u5929\u6C14\u6CE1\u65F6\u663E\u793A\u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\n * \u7528\u6237\u540C\u610F\u540E\u624D\u4F1A\u6253\u5F00\u804A\u5929\u7A97\u53E3\n */\n complianceConfig?: ComplianceDialogConfig\n}\n\nexport interface MessageRenderer {\n render: (content: MessageContent, isUser: boolean, isSystem: boolean) => React.ReactNode\n}\n\nexport interface CustomRendererMap {\n [type: string]: MessageRenderer\n}\n\n// ============================================================================\n// Utility Types (\u5DE5\u5177\u7C7B\u578B)\n// ============================================================================\n\nexport interface PositionStyles {\n bottom?: string\n top?: string\n left?: string\n right?: string\n}\n\nexport type BubblePosition = PositionStyles\n"],
4
+ "sourcesContent": ["/**\n * LiveChat \u7EC4\u4EF6\u6838\u5FC3\u7C7B\u578B\u5B9A\u4E49\n * \u57FA\u4E8E specs/livechat-widget/data-model.md\n */\n\n// ============================================================================\n// Session Types (\u4F1A\u8BDD)\n// ============================================================================\n\nexport type SessionStatus = 'active' | 'expired'\n\nexport interface Session {\n sessionId: string\n userId: string\n site: string\n status: SessionStatus\n createdAt?: number\n lastActivityAt?: number\n}\n\n// ============================================================================\n// Message Types (\u6D88\u606F)\n// ============================================================================\n\nexport type MessageRole = 'user' | 'assistant' | 'system' | 'tool'\n\nexport interface MessageMetadata {\n tokenUsage?: {\n inputTokens: number\n outputTokens: number\n }\n toolCalls?: Array<{\n id: string\n type: string\n name: string\n }>\n}\n\nexport interface Message {\n id: string\n sessionId?: string\n role: MessageRole\n content: MessageContent[]\n timestamp: number\n metadata?: MessageMetadata\n structured_content?: Array<{\n type: string\n data: any\n }>\n}\n\n// ============================================================================\n// Message Content Types (\u6D88\u606F\u5185\u5BB9)\n// ============================================================================\n\nexport type MessageContentType =\n | 'text'\n | 'product_card'\n | 'product_list'\n | 'product_comparison'\n | 'policy'\n | 'quick_replies'\n | 'thinking'\n | 'error'\n | 'faq_list'\n | 'cart'\n\nexport type MessageContent =\n | TextContent\n | ProductCardContent\n | ProductListContent\n | ProductComparisonContent\n | PolicyContent\n | QuickRepliesContent\n | ThinkingContent\n | ErrorContent\n | FAQListContent\n | PromotionListContent\n | CartContent\n\nexport interface TextContent {\n type: 'text'\n text: string\n}\n\nexport interface ProductCardContent {\n type: 'product_card'\n data: {\n product: Product\n rawProduct?: any // Raw backend product data (for custom render)\n onAddToCart?: (product: Product) => void\n productCardRender?: (product: any) => React.ReactNode\n }\n}\n\nexport interface ProductListContent {\n type: 'product_list'\n data: {\n products: Product[]\n title?: string\n onAddToCart?: (product: Product) => void\n commonText?: CommonText\n }\n}\n\nexport interface ProductComparisonContent {\n type: 'product_comparison'\n data: {\n products: Product[]\n dimensions: {\n price?: {\n label: string\n values: Array<{\n product_id: string\n min: number\n max: number\n currency: string\n has_discount: boolean\n }>\n }\n variants?: {\n label: string\n values: Array<{\n product_id: string\n count: number\n }>\n }\n member_price?: {\n label: string\n values: Array<{\n product_id: string\n available: boolean\n min: number\n max: number\n currency: string\n }>\n }\n discount?: {\n label: string\n values: Array<{\n product_id: string\n has_discount: boolean\n }>\n }\n [key: string]: any\n }\n onAddToCart?: (product: Product) => void\n commonText?: CommonText\n }\n}\n\nexport interface PolicyContent {\n type: 'policy'\n data: {\n title: string\n content: string\n }\n}\n\nexport interface QuickRepliesContent {\n type: 'quick_replies'\n data: {\n replies: QuickReply[]\n }\n}\n\nexport interface ThinkingContent {\n type: 'thinking'\n data: {\n status: string\n }\n}\n\nexport interface ErrorContent {\n type: 'error'\n data: {\n message: string\n code?: string\n }\n}\n\nexport interface FAQListContent {\n type: 'faq_list'\n data: {\n found: boolean\n count: number\n total?: number\n results: FAQItem[]\n }\n}\n\nexport interface PromotionListContent {\n type: 'promotion_list'\n data: {\n found: boolean\n count: number\n total?: number\n results: PromotionItem[]\n commonText?: CommonText\n }\n}\n\n// ============================================================================\n// FAQ Types (\u5E38\u89C1\u95EE\u9898)\n// ============================================================================\n\nexport type FAQCategory = 'shipping' | 'return' | 'product' | 'payment' | 'general'\n\nexport interface FAQItem {\n id: string\n question: string\n answer: string\n category: FAQCategory\n keywords?: string[]\n relatedQuestions?: string[]\n metadata?: {\n language?: string\n priority?: number\n lastUpdated?: string\n }\n}\n\n// ============================================================================\n// Promotion Types (\u4FC3\u9500\u6D3B\u52A8)\n// ============================================================================\n\nexport interface PromotionItem {\n id: string\n title: string\n subtitle?: string\n description?: string\n banner_url?: string\n url?: string\n time_range: {\n start: string\n end?: string | null\n is_active: boolean\n }\n priority?: number\n product_count?: number\n metadata?: {\n display_order?: number\n target_audience?: string\n }\n}\n\n// ============================================================================\n// Product Types (\u5546\u54C1)\n// ============================================================================\n\nexport type StockStatus = 'in_stock' | 'low_stock' | 'out_of_stock'\n\nexport interface Price {\n amount: number\n currency: string\n}\n\nexport interface PriceRange {\n min: number\n max: number\n currency: string\n}\n\nexport interface VariantDiscount {\n has_discount: boolean\n discount_price?: number\n discount_code?: string\n discount_percentage?: number\n /** \u6298\u6263\u7C7B\u578B\uFF1Afixed_amount \u56FA\u5B9A\u91D1\u989D\u6298\u6263\uFF0Cpercentage \u767E\u5206\u6BD4\u6298\u6263 */\n discount_type?: 'fixed_amount' | 'percentage'\n /** \u6298\u6263\u6570\u503C\uFF08\u53EF\u80FD\u662F\u5B57\u7B26\u4E32\u6216\u6570\u5B57\uFF09 */\n discount_value?: string | number\n}\n\nexport interface VariantMemberPrice {\n has_member_price: boolean\n price?: number\n}\n\nexport interface ProductFeatures {\n is_new?: boolean\n has_rental?: boolean\n has_presale?: boolean\n has_member_price?: boolean\n has_discount?: boolean\n}\n\nexport interface Variant {\n id: string\n title: string\n sku?: string\n price?: Price\n availableForSale: boolean\n color?: string\n discount?: VariantDiscount\n memberPrice?: VariantMemberPrice\n inventoryQuantity?: number\n option1?: string\n option2?: string\n option3?: string\n}\n\nexport interface Product {\n shopifyId: string\n sku?: string\n handle: string\n title: string\n description?: string\n vendor?: string\n price: Price\n priceRange?: PriceRange\n memberPriceRange?: PriceRange\n imageUrl: string\n productUrl: string\n stockStatus: StockStatus\n hotScore?: number\n averageRating?: number\n reviewCount?: number\n variants?: Variant[]\n variantCount?: number\n availableCount?: number\n features?: ProductFeatures\n tags?: string[]\n}\n\n// ============================================================================\n// Quick Reply Types (\u5FEB\u6377\u56DE\u590D)\n// ============================================================================\n\nexport interface QuickReply {\n id: string\n label: string\n value: string\n icon?: string\n}\n\n// ============================================================================\n// Policy Types (\u653F\u7B56)\n// ============================================================================\n\nexport interface Policy {\n title: string\n content: string\n}\n\n// ============================================================================\n// Cart Types (\u8D2D\u7269\u8F66)\n// ============================================================================\n\n/**\n * \u8D2D\u7269\u8F66\u91D1\u989D\u4FE1\u606F\n */\nexport interface CartAmount {\n /** \u91D1\u989D\u5B57\u7B26\u4E32\uFF08\u5982 \"99.99\"\uFF09 */\n amount: string\n /** \u8D27\u5E01\u4EE3\u7801\uFF08\u5982 \"USD\"\uFF09 */\n currencyCode: string\n}\n\n/**\n * \u8D2D\u7269\u8F66\u4EF7\u683C\u6C47\u603B\n */\nexport interface CartCost {\n /** \u5E94\u4ED8\u603B\u4EF7\uFF08\u5DF2\u5E94\u7528\u6298\u6263\uFF09 */\n totalAmount: CartAmount\n /** \u539F\u4EF7\u5C0F\u8BA1\uFF08\u672A\u5E94\u7528\u6298\u6263\uFF09 */\n subtotalAmount: CartAmount\n}\n\n/**\n * \u8D2D\u7269\u8F66\u5546\u54C1\u53D8\u4F53\u4FE1\u606F\n */\nexport interface CartMerchandise {\n /** \u53D8\u4F53 ID (Shopify ProductVariant GID) */\n id: string\n /** \u53D8\u4F53\u6807\u9898\uFF08\u5982 \"Black\", \"Large\" \u7B49\uFF09 */\n title: string\n /** \u5355\u4EF7 */\n price: CartAmount\n /** \u5546\u54C1\u56FE\u7247 URL */\n image?: {\n url: string\n altText?: string\n }\n /** \u5173\u8054\u7684\u5546\u54C1\u4FE1\u606F */\n product: {\n /** \u5546\u54C1 ID */\n id: string\n /** \u5546\u54C1\u6807\u9898 */\n title: string\n /** \u5546\u54C1 handle */\n handle: string\n }\n}\n\n/**\n * \u8D2D\u7269\u8F66\u5546\u54C1\u884C\n */\nexport interface CartLine {\n /** \u8D2D\u7269\u8F66\u884C ID (\u7528\u4E8E\u66F4\u65B0/\u5220\u9664\u64CD\u4F5C) */\n id: string\n /** \u5546\u54C1\u6570\u91CF */\n quantity: number\n /** \u4EF7\u683C\u4FE1\u606F */\n cost: {\n /** \u884C\u603B\u4EF7\uFF08\u5355\u4EF7 \u00D7 \u6570\u91CF\uFF0C\u5DF2\u5E94\u7528\u6298\u6263\uFF09 */\n totalAmount: CartAmount\n /** \u5355\u4EF7 */\n amountPerQuantity: CartAmount\n /** \u884C\u5C0F\u8BA1\uFF08\u5355\u4EF7 \u00D7 \u6570\u91CF\uFF0C\u672A\u5E94\u7528\u6298\u6263\uFF09 */\n subtotalAmount: CartAmount\n }\n /** \u5546\u54C1\u53D8\u4F53\u4FE1\u606F */\n merchandise: CartMerchandise\n /** \u81EA\u5B9A\u4E49\u5C5E\u6027\uFF08\u53EF\u9009\uFF09 */\n attributes?: Array<{\n key: string\n value: string\n }>\n}\n\n/**\n * \u8D2D\u7269\u8F66\u6298\u6263\u7801\n */\nexport interface CartDiscountCode {\n /** \u6298\u6263\u7801 */\n code: string\n /** \u662F\u5426\u6709\u6548/\u9002\u7528 */\n applicable: boolean\n}\n\n/**\n * \u8D2D\u7269\u8F66\u6570\u636E\n */\nexport interface CartData {\n /** \u8D2D\u7269\u8F66\u662F\u5426\u4E3A\u7A7A */\n isEmpty: boolean\n /** \u8D2D\u7269\u8F66 ID (Shopify Cart GID) */\n cartId: string\n /** \u5546\u54C1\u603B\u6570\u91CF */\n totalQuantity: number\n /** \u5546\u54C1\u5217\u8868 */\n lines: CartLine[]\n /** \u4EF7\u683C\u6C47\u603B */\n cost: CartCost\n /** \u6298\u6263\u7801\u5217\u8868 */\n discountCodes?: CartDiscountCode[]\n /** \u7ED3\u8D26\u9875\u9762 URL */\n checkoutUrl?: string\n /** \u8D2D\u7269\u8F66\u6309\u94AE\u56DE\u8C03\u51FD\u6570 */\n onCart?: (cartId: string, checkoutUrl?: string) => void\n /** \u901A\u7528\u6587\u6848\u914D\u7F6E */\n commonText?: CommonText\n}\n\n/**\n * \u8D2D\u7269\u8F66\u5185\u5BB9\u5757\n */\nexport interface CartContent {\n type: 'cart'\n data: CartData\n}\n\n// ============================================================================\n// Backend Cart Types (\u540E\u7AEF\u8D2D\u7269\u8F66\u6570\u636E\u683C\u5F0F - Shopify GraphQL)\n// ============================================================================\n\n/**\n * \u540E\u7AEF\u8FD4\u56DE\u7684\u8D2D\u7269\u8F66\u5546\u54C1\u884C\u6570\u636E (Shopify GraphQL \u683C\u5F0F)\n */\nexport interface BackendCartLineNode {\n id: string\n quantity: number\n cost: {\n totalAmount: CartAmount\n amountPerQuantity: CartAmount\n compareAtAmountPerQuantity?: CartAmount | null\n subtotalAmount: CartAmount\n }\n discountAllocations: any[]\n merchandise: {\n id: string\n title: string\n availableForSale: boolean\n quantityAvailable?: number\n price: CartAmount\n compareAtPrice?: CartAmount | null\n image?: {\n url: string\n altText?: string | null\n width?: number\n height?: number\n } | null\n product: {\n id: string\n title: string\n handle: string\n vendor?: string\n featuredImage?: {\n url: string\n altText?: string | null\n width?: number\n height?: number\n } | null\n tags?: string[]\n }\n }\n attributes?: Array<{\n key: string\n value: string\n }>\n}\n\n/**\n * \u540E\u7AEF\u8FD4\u56DE\u7684\u8D2D\u7269\u8F66\u6570\u636E (Shopify GraphQL \u683C\u5F0F)\n */\nexport interface BackendCartData {\n id: string\n checkoutUrl?: string\n totalQuantity: number\n lines: {\n edges: Array<{\n node: BackendCartLineNode\n }>\n }\n cost: {\n totalAmount: CartAmount\n subtotalAmount: CartAmount\n checkoutChargeAmount?: CartAmount\n totalAmountEstimated?: boolean\n subtotalAmountEstimated?: boolean\n totalTaxAmount?: CartAmount | null\n totalDutyAmount?: CartAmount | null\n }\n discountAllocations?: any[]\n buyerIdentity?: any\n attributes?: Array<{\n key: string\n value: string\n }>\n discountCodes?: any[]\n createdAt?: string\n updatedAt?: string\n}\n\n// ============================================================================\n// SSE Event Types (SSE \u4E8B\u4EF6)\n// ============================================================================\n\nexport type SSEEventType =\n | 'message_start'\n | 'content_delta'\n | 'content_block'\n | 'message_end'\n | 'tool_start'\n | 'tool_end'\n | 'status'\n | 'error'\n | 'done'\n\nexport interface SSEEvent<T = any> {\n event: SSEEventType | null\n data: T\n}\n\n// \u5177\u4F53\u4E8B\u4EF6\u6570\u636E\u7C7B\u578B\nexport interface MessageStartData {\n sessionId: string\n}\n\nexport interface ContentDeltaData {\n text: string\n}\n\nexport interface ContentBlockData {\n type: string\n data: any\n}\n\nexport interface MessageEndData {\n usage: {\n inputTokens: number\n outputTokens: number\n }\n}\n\nexport interface ToolStartData {\n id: string\n type: string\n name: string\n}\n\nexport interface ToolEndData {\n id: string\n}\n\nexport interface StatusData {\n type: string\n message?: string\n}\n\nexport interface ErrorData {\n message: string\n code?: string\n type?: string\n}\n\n// ============================================================================\n// API Request/Response Types (API \u8BF7\u6C42\u54CD\u5E94)\n// ============================================================================\n\n/**\n * \u6D41\u5F0F\u5BF9\u8BDD\u8BF7\u6C42\u53C2\u6570\n */\nexport interface ChatStreamRequest {\n /** \u7528\u6237\u7684\u6D88\u606F\u6587\u672C */\n message: string\n /** \u7528\u6237\u6807\u8BC6\u7B26 */\n user_id: string\n /** \u6765\u81EA new-session \u7AEF\u70B9\u7684\u4F1A\u8BDD ID */\n session_id: string\n /** \u53EF\u9009\u7684\u4E0A\u4E0B\u6587\u4FE1\u606F */\n context?: {\n /** \u7528\u4E8E\u8D2D\u7269\u8F66\u64CD\u4F5C\u7684 Shopify \u8D2D\u7269\u8F66 ID */\n cartId?: string\n /** Storefront API \u8BBF\u95EE\u4EE4\u724C */\n accessToken?: string\n /** \u5DF2\u767B\u5F55\u7528\u6237\u7684 ID */\n real_user_id?: string\n }\n}\n\nexport interface NewSessionRequest {\n user_id: string\n session_id?: string\n site?: string\n channel_code?: string\n real_user_id?: string\n}\n\nexport interface NewSessionResponse {\n success: boolean\n sessionId: string\n message: string\n resumed?: boolean\n messages?: Message[]\n welcomeMessage?: string\n quickQuestions?: string[]\n brand?: string\n}\n\nexport interface ErrorResponse {\n success: boolean\n error: string\n code?: string\n details?: any\n}\n\n// ============================================================================\n// Common Text Types (\u901A\u7528\u6587\u6848)\n// ============================================================================\n\n/**\n * \u901A\u7528\u6587\u6848\u914D\u7F6E\n * \u7528\u4E8E\u81EA\u5B9A\u4E49\u7EC4\u4EF6\u4E2D\u7684\u6309\u94AE\u6587\u6848\n */\nexport interface CommonText {\n /**\n * \u4EA7\u54C1\u5217\u8868\u5C55\u5F00\u6309\u94AE\u6587\u6848\n * @default \"Learn More\"\n */\n learnMore?: string\n\n /**\n * \u4EA7\u54C1\u5217\u8868\u6536\u8D77\u6309\u94AE\u6587\u6848\n * @default \"Show Less\"\n */\n showLess?: string\n\n /**\n * \u6DFB\u52A0\u5230\u8D2D\u7269\u8F66\u6309\u94AE\u6587\u6848\n * @default \"Add to Cart\"\n */\n addToCart?: string\n\n /**\n * \u67E5\u770B\u8D2D\u7269\u8F66/\u66F4\u591A\u6309\u94AE\u6587\u6848\n * @default \"View More\"\n */\n viewMore?: string\n\n /**\n * \u6298\u6263\u6807\u7B7E\u540E\u7F00\u6587\u6848\uFF08\u5982 \"20% OFF\" \u4E2D\u7684 \"OFF\"\uFF09\n * @default \"OFF\"\n */\n off?: string\n\n /**\n * \u8D2D\u7269\u8F66\u603B\u8BA1\u6587\u6848\n * @default \"Total\"\n */\n total?: string\n}\n\n// ============================================================================\n// Compliance Dialog Types (\u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97)\n// ============================================================================\n\n/**\n * \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u914D\u7F6E\n */\nexport interface ComplianceDialogConfig {\n /**\n * \u5F39\u7A97\u6807\u9898\n * @example \"Hi! I'm your eufy AI assistant.\"\n */\n title: string\n\n /**\n * \u5F39\u7A97\u5185\u5BB9\u6587\u672C\uFF08\u652F\u6301 HTML\uFF09\n * @example \"AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data\"\n */\n content: string\n\n /**\n * \u52FE\u9009\u6846\u6587\u672C\uFF08\u652F\u6301\u5B8C\u6574 HTML\uFF0C\u5305\u62EC\u94FE\u63A5\uFF09\n * \u53EF\u4EE5\u76F4\u63A5\u5305\u542B <a> \u6807\u7B7E\u7B49 HTML \u5143\u7D20\n * @example \"By starting to use \\\"Live Chat\\\", you agree to Anker's <a href=\\\"https://www.anker.com/privacy\\\" target=\\\"_blank\\\">LIVE CHAT PRIVACY NOTICE</a>.\"\n */\n checkboxText: string\n\n /**\n * \u540C\u610F\u6309\u94AE\u6587\u672C\n * @default \"Agree\"\n */\n agreeButtonText?: string\n\n /**\n * Cookie \u540D\u79F0\uFF0C\u7528\u4E8E\u8BB0\u5F55\u7528\u6237\u540C\u610F\u72B6\u6001\n * Cookie \u6709\u6548\u671F\u4E3A 365 \u5929\n * @default \"livechat_compliance_agreed\"\n */\n cookieName?: string\n}\n\n// ============================================================================\n// Component Props Types (\u7EC4\u4EF6 Props)\n// ============================================================================\n\nexport interface LiveChatWidgetProps {\n /**\n * API \u57FA\u7840 URL\n * @example \"https://beta-api-livechat.anker.com\"\n */\n apiBaseUrl: string\n\n /**\n * \u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\n * \u5C06\u5728\u6240\u6709 API \u8BF7\u6C42\u4E2D\u6DFB\u52A0\u8FD9\u4E9B\u8BF7\u6C42\u5934\n * @example { \"Authorization\": \"Bearer token\", \"X-Custom-Header\": \"value\" }\n */\n headers?: Record<string, string>\n\n /**\n * reCAPTCHA site key\n * \u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u81EA\u52A8\u542F\u7528 reCAPTCHA v3 \u9A8C\u8BC1\n * @example \"6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14\"\n */\n recaptchaSitekey?: string\n\n /**\n * reCAPTCHA action \u524D\u7F00\n * \u5B9E\u9645\u4F7F\u7528\u65F6\u4F1A\u6839\u636E\u4E0D\u540C\u63A5\u53E3\u6DFB\u52A0\u540E\u7F00\uFF08\u5982 chat_stream, new_session\uFF09\n * @default \"livechat\"\n */\n recaptchaAction?: string\n\n /**\n * Shopify \u5E97\u94FA\u57DF\u540D\n * @example \"www.eufy.com\"\n */\n site?: string\n\n /**\n * \u6E20\u9053\u7F16\u7801\n * \u7528\u4E8E\u6807\u8BC6\u6765\u6E90\u6E20\u9053\n * @example \"web_homepage\"\n */\n channelCode?: string\n\n /**\n * \u5DF2\u767B\u5F55\u7528\u6237\u7684 ID\uFF08\u53EF\u9009\uFF09\n * \u5982\u679C\u63D0\u4F9B\uFF0C\u5C06\u5728 API \u8BF7\u6C42\u4E2D\u4F20\u9012\n */\n loginUserId?: string\n\n /**\n * Shopify \u8D2D\u7269\u8F66 ID\uFF08\u53EF\u9009\uFF09\n * \u7528\u4E8E\u8D2D\u7269\u8F66\u64CD\u4F5C\uFF0C\u5C06\u5728 stream \u63A5\u53E3\u7684 context \u4E2D\u4F20\u9012\n * @example \"gid://shopify/Cart/Z2NwLXVzLWVhc3QxOjAxSkZH...\"\n */\n cartId?: string\n\n /**\n * Storefront API \u8BBF\u95EE\u4EE4\u724C\uFF08\u53EF\u9009\uFF09\n * \u7528\u4E8E\u8D2D\u7269\u8F66\u64CD\u4F5C\uFF0C\u5C06\u5728 stream \u63A5\u53E3\u7684 context \u4E2D\u4F20\u9012\n */\n accessToken?: string\n\n /**\n * \u6C14\u6CE1\u6309\u94AE\u4F4D\u7F6E\n * \u81EA\u5B9A\u4E49\u4F4D\u7F6E\u5BF9\u8C61\uFF1A{ top?: string, bottom?: string, left?: string, right?: string }\n * @default { bottom: \"1.5rem\", right: \"1.5rem\" }\n * @example\n * // \u81EA\u5B9A\u4E49\u4F4D\u7F6E\n * position={{ bottom: \"20px\", right: \"30px\" }}\n * position={{ top: \"100px\", left: \"50px\" }}\n */\n position?: BubblePosition\n\n /**\n * \u6B22\u8FCE\u6D88\u606F\n * @default \"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F\"\n */\n welcomeMessage?: string\n\n /**\n * \u521D\u59CB\u5FEB\u6377\u56DE\u590D\u6309\u94AE\n */\n quickReplies?: QuickReply[]\n\n /**\n * \u81EA\u5B9A\u4E49\u6D88\u606F\u6E32\u67D3\u5668\n */\n customRenderers?: Record<string, MessageRenderer>\n\n /**\n * Logo URL\n */\n logoUrl?: string\n\n /**\n * \u804A\u5929\u7A97\u53E3\u6807\u9898\n * @default \"AI \u52A9\u624B\"\n */\n title?: string\n\n /**\n * \u804A\u5929\u6C14\u6CE1\u6309\u94AE\u56FE\u6807\uFF08\u56FE\u7247 URL\uFF09\n * \u5982\u679C\u63D0\u4F9B\uFF0C\u5C06\u4F7F\u7528\u56FE\u7247\u66FF\u4EE3\u9ED8\u8BA4\u7684 SVG \u56FE\u6807\n */\n chatBubbleIcon?: string\n\n /**\n * \u53D7\u63A7\u6A21\u5F0F\uFF1A\u662F\u5426\u6253\u5F00\u804A\u5929\u7A97\u53E3\n * \u63D0\u4F9B\u6B64\u53C2\u6570\u65F6\uFF0C\u7EC4\u4EF6\u5C06\u5904\u4E8E\u53D7\u63A7\u6A21\u5F0F\n * @example\n * ```tsx\n * const [isOpen, setIsOpen] = useState(false)\n * <LiveChatWidget open={isOpen} onOpenChange={setIsOpen} />\n * ```\n */\n open?: boolean\n\n /**\n * \u53D7\u63A7\u6A21\u5F0F\uFF1A\u6253\u5F00/\u5173\u95ED\u72B6\u6001\u53D8\u5316\u56DE\u8C03\n * \u3010\u5FC5\u9700\u3011\u914D\u5408 `open` \u4F7F\u7528\uFF0C\u7528\u4E8E\u540C\u6B65\u72B6\u6001\u5230\u7236\u7EC4\u4EF6\n * \u5F53\u7528\u6237\u70B9\u51FB\u6253\u5F00\u6216\u5173\u95ED\u6309\u94AE\u65F6\u89E6\u53D1\n * @example\n * ```tsx\n * <LiveChatWidget\n * open={isOpen}\n * onOpenChange={setIsOpen} // \u5FC5\u9700\uFF1A\u540C\u6B65\u72B6\u6001\n * />\n * ```\n */\n onOpenChange?: (open: boolean) => void\n\n /**\n * \u3010\u53EF\u9009\u3011\u7A97\u53E3\u6253\u5F00\u4E8B\u4EF6\u76D1\u542C\n * \u7528\u4E8E\u57CB\u70B9\u3001\u65E5\u5FD7\u7B49\u526F\u4F5C\u7528\uFF0C\u4E0D\u5F71\u54CD\u72B6\u6001\u63A7\u5236\n * \u5728 onOpenChange \u4E4B\u540E\u89E6\u53D1\n * @example\n * ```tsx\n * <LiveChatWidget\n * open={isOpen}\n * onOpenChange={setIsOpen}\n * onOpen={() => trackEvent('chat_opened')} // \u53EF\u9009\uFF1A\u57CB\u70B9\n * />\n * ```\n */\n onOpen?: () => void\n\n /**\n * \u3010\u53EF\u9009\u3011\u7A97\u53E3\u5173\u95ED\u4E8B\u4EF6\u76D1\u542C\n * \u7528\u4E8E\u57CB\u70B9\u3001\u65E5\u5FD7\u7B49\u526F\u4F5C\u7528\uFF0C\u4E0D\u5F71\u54CD\u72B6\u6001\u63A7\u5236\n * \u5728 onOpenChange \u4E4B\u540E\u89E6\u53D1\n * @example\n * ```tsx\n * <LiveChatWidget\n * open={isOpen}\n * onOpenChange={setIsOpen}\n * onClose={() => trackEvent('chat_closed')} // \u53EF\u9009\uFF1A\u57CB\u70B9\n * />\n * ```\n */\n onClose?: () => void\n onMessageSend?: (message: string) => void\n onError?: (error: Error) => void\n\n /**\n * AI \u6D88\u606F\u56DE\u8C03\n */\n /**\n * AI \u56DE\u590D\u6587\u672C\u6D88\u606F\u65F6\u89E6\u53D1\n */\n onTextMessage?: () => void\n\n /**\n * AI \u56DE\u590D\u5546\u54C1\u5217\u8868\u5361\u7247\u65F6\u89E6\u53D1\n */\n onProductList?: () => void\n\n /**\n * AI \u56DE\u590D\u4FC3\u9500\u5361\u7247\u65F6\u89E6\u53D1\n */\n onPromotionList?: () => void\n\n /**\n * \u5546\u54C1\u64CD\u4F5C\u56DE\u8C03\n */\n onAddToCart?: (product: Product) => void\n\n /**\n * \u8D2D\u7269\u8F66\u6309\u94AE\u70B9\u51FB\u56DE\u8C03\n */\n onCart?: (cartId: string, checkoutUrl?: string) => void\n\n /**\n * \u662F\u5426\u663E\u793A\u65B0\u4F1A\u8BDD\u6309\u94AE\n * @default true\n */\n showNewSessionButton?: boolean\n\n /**\n * \u901A\u7528\u6587\u6848\u914D\u7F6E\n * \u7528\u4E8E\u81EA\u5B9A\u4E49\u6309\u94AE\u6587\u6848\n */\n commonText?: CommonText\n\n /**\n * \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\u51FD\u6570\n * \u7528\u4E8E\u81EA\u5B9A\u4E49\u6E32\u67D3 product_card \u7C7B\u578B\u7684\u4EA7\u54C1\u5361\u7247\n * \u5F53\u63D0\u4F9B\u6B64\u51FD\u6570\u65F6\uFF0C\u5C06\u66FF\u4EE3\u9ED8\u8BA4\u7684\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\u903B\u8F91\n * @param product \u539F\u59CB\u540E\u7AEF\u4EA7\u54C1\u6570\u636E\uFF08\u672A\u7ECF\u8F6C\u6362\u7684\u6570\u636E\uFF09\n * @returns React \u53EF\u6E32\u67D3\u7684\u5185\u5BB9\n * @example\n * ```tsx\n * <LiveChatWidget\n * productCardRender={(product) => (\n * <div>\n * <h3>{product.title}</h3>\n * <p>{product.price_range?.min}</p>\n * </div>\n * )}\n * />\n * ```\n */\n productCardRender?: (product: any) => React.ReactNode\n\n /**\n * \u8F93\u5165\u6846\u5E95\u90E8\u63D0\u793A\u6587\u672C\n * \u4E0D\u4F20\u5165\u5219\u4E0D\u663E\u793A\n */\n bottomTips?: string\n\n /**\n * \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\u914D\u7F6E\n * \u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u5728\u7528\u6237\u9996\u6B21\u70B9\u51FB\u804A\u5929\u6C14\u6CE1\u65F6\u663E\u793A\u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\n * \u7528\u6237\u540C\u610F\u540E\u624D\u4F1A\u6253\u5F00\u804A\u5929\u7A97\u53E3\n */\n complianceConfig?: ComplianceDialogConfig\n}\n\nexport interface MessageRenderer {\n render: (content: MessageContent, isUser: boolean, isSystem: boolean) => React.ReactNode\n}\n\nexport interface CustomRendererMap {\n [type: string]: MessageRenderer\n}\n\n// ============================================================================\n// Utility Types (\u5DE5\u5177\u7C7B\u578B)\n// ============================================================================\n\nexport interface PositionStyles {\n bottom?: string\n top?: string\n left?: string\n right?: string\n}\n\nexport type BubblePosition = PositionStyles\n"],
5
5
  "mappings": "+WAAA,IAAAA,EAAA,kBAAAC,EAAAD",
6
6
  "names": ["types_exports", "__toCommonJS"]
7
7
  }
@@ -43,7 +43,7 @@ export interface ChatInputProps {
43
43
  className?: string;
44
44
  /**
45
45
  * 底部提示文本
46
- * @default "The above content is provided by AI, for reference."
46
+ * 不传入则不显示
47
47
  */
48
48
  bottomTips?: string;
49
49
  }
@@ -1,2 +1,2 @@
1
- import{jsx as n,jsxs as a}from"react/jsx-runtime";import{useRef as g,useEffect as v}from"react";const E=()=>a("svg",{width:"20",height:"20",viewBox:"0 0 20 20",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:[n("path",{d:"M10.2069 15.6997V4.69971",stroke:"white","stroke-width":"2","stroke-linecap":"round"}),n("path",{d:"M15.3995 8.50341L10.9506 4.05446C10.534 3.6379 9.85866 3.63791 9.4421 4.05446L5.00005 8.49651",stroke:"white","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"})]}),A=({value:t,onChange:p,onSend:c,placeholder:h="",disabled:o=!1,autoFocus:u=!1,maxLength:r=5e3,bottomTips:f="The above content is provided by AI, for reference.",className:x=""})=>{const d=g(null),s=g(!1);v(()=>{const e=d.current;if(!e)return;e.style.height="auto";const k=Math.min(e.scrollHeight,120);e.style.height=`${k}px`},[t]);const m=()=>{s.current=!0},b=()=>{s.current=!1},w=e=>{if(e.key==="Enter"&&!e.shiftKey){if(s.current)return;e.preventDefault(),t.trim()&&!o&&c()}},y=e=>{const l=e.target.value;l.length<=r&&p(l)},C=()=>{t.trim()&&!o&&c()},i=t.trim().length>0&&!o;return a("div",{className:`flex flex-col gap-2 bg-white px-4 py-4 ${x}`,children:[n("div",{className:"flex items-center gap-2 rounded-2xl",style:{background:"linear-gradient(to right, #7687F3, #7687F3, #4DA8F5, #A3DFCE)",padding:"2px"},children:a("div",{className:"flex flex-1 items-center gap-2 bg-white px-3 py-2",style:{borderRadius:"14px"},children:[n("textarea",{ref:d,value:t,onChange:y,onKeyDown:w,onCompositionStart:m,onCompositionEnd:b,placeholder:h,disabled:o,autoFocus:u,rows:1,className:"flex-1 resize-none bg-transparent font-bold text-sm text-gray-900 outline-none placeholder:text-gray-400 disabled:cursor-not-allowed disabled:opacity-50",style:{resize:"none"}}),t.length>r*.8&&a("span",{className:"text-xs text-gray-400",children:[t.length,"/",r]}),n("button",{type:"button",onClick:C,disabled:!i,className:`shrink-0 rounded-full p-2 transition-all ${i?" active:scale-95":"cursor-not-allowed "}`,style:i?{background:"linear-gradient(to bottom, #A3DFCE, #4DA8F5, #7687F3)"}:{background:"#BEBEBE"},"aria-label":"Send message",children:n(E,{})})]})}),n("p",{className:"px-3 font-bold text-[12px] text-[#767880] tracking-tight leading-[1.4]",children:f})]})};export{A as ChatInput};
1
+ import{jsx as n,jsxs as a}from"react/jsx-runtime";import{useRef as h,useEffect as E}from"react";const v=()=>a("svg",{width:"20",height:"20",viewBox:"0 0 20 20",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:[n("path",{d:"M10.2069 15.6997V4.69971",stroke:"white","stroke-width":"2","stroke-linecap":"round"}),n("path",{d:"M15.3995 8.50341L10.9506 4.05446C10.534 3.6379 9.85866 3.63791 9.4421 4.05446L5.00005 8.49651",stroke:"white","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"})]}),N=({value:t,onChange:p,onSend:c,placeholder:u="",disabled:o=!1,autoFocus:x=!1,maxLength:s=5e3,bottomTips:d,className:f=""})=>{const g=h(null),r=h(!1);E(()=>{const e=g.current;if(!e)return;e.style.height="auto";const k=Math.min(e.scrollHeight,120);e.style.height=`${k}px`},[t]);const m=()=>{r.current=!0},b=()=>{r.current=!1},w=e=>{if(e.key==="Enter"&&!e.shiftKey){if(r.current)return;e.preventDefault(),t.trim()&&!o&&c()}},C=e=>{const l=e.target.value;l.length<=s&&p(l)},y=()=>{t.trim()&&!o&&c()},i=t.trim().length>0&&!o;return a("div",{className:`flex flex-col gap-2 bg-white px-4 py-4 ${f}`,children:[n("div",{className:"flex items-center gap-2 rounded-2xl",style:{background:"linear-gradient(to right, #7687F3, #7687F3, #4DA8F5, #A3DFCE)",padding:"2px"},children:a("div",{className:"flex flex-1 items-center gap-2 bg-white px-3 py-2",style:{borderRadius:"14px"},children:[n("textarea",{ref:g,value:t,onChange:C,onKeyDown:w,onCompositionStart:m,onCompositionEnd:b,placeholder:u,disabled:o,autoFocus:x,rows:1,className:"flex-1 resize-none bg-transparent font-bold text-sm text-gray-900 outline-none placeholder:text-gray-400 disabled:cursor-not-allowed disabled:opacity-50",style:{resize:"none"}}),t.length>s*.8&&a("span",{className:"text-xs text-gray-400",children:[t.length,"/",s]}),n("button",{type:"button",onClick:y,disabled:!i,className:`shrink-0 rounded-full p-2 transition-all ${i?" active:scale-95":"cursor-not-allowed "}`,style:i?{background:"linear-gradient(to bottom, #A3DFCE, #4DA8F5, #7687F3)"}:{background:"#BEBEBE"},"aria-label":"Send message",children:n(v,{})})]})}),d&&n("p",{className:"px-3 font-bold text-[12px] text-[#767880] tracking-tight leading-[1.4]",children:d})]})};export{N as ChatInput};
2
2
  //# sourceMappingURL=ChatInput.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/components/LiveChatWidget/components/ChatInput.tsx"],
4
- "sourcesContent": ["/**\n * \u804A\u5929\u8F93\u5165\u6846\u7EC4\u4EF6\n * \u63D0\u4F9B\u6587\u672C\u8F93\u5165\u548C\u53D1\u9001\u6309\u94AE\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u8F93\u5165\u6846\u8BBE\u8BA1\n */\n\nimport React, { useRef, useEffect } from 'react'\n\nexport interface ChatInputProps {\n /**\n * \u8F93\u5165\u6846\u5F53\u524D\u503C\n */\n value: string\n\n /**\n * \u503C\u53D8\u5316\u56DE\u8C03\n */\n onChange: (value: string) => void\n\n /**\n * \u53D1\u9001\u6D88\u606F\u56DE\u8C03\n */\n onSend: () => void\n\n /**\n * \u5360\u4F4D\u7B26\u6587\u672C\n * @default \"\u8F93\u5165\u6D88\u606F...\"\n */\n placeholder?: string\n\n /**\n * \u662F\u5426\u7981\u7528\u8F93\u5165\n * @default false\n */\n disabled?: boolean\n\n /**\n * \u662F\u5426\u81EA\u52A8\u805A\u7126\n * @default false\n */\n autoFocus?: boolean\n\n /**\n * \u6700\u5927\u5B57\u7B26\u6570\n * @default 5000\n */\n maxLength?: number\n\n /**\n * \u81EA\u5B9A\u4E49\u6837\u5F0F\u7C7B\u540D\n */\n className?: string\n\n /**\n * \u5E95\u90E8\u63D0\u793A\u6587\u672C\n * @default \"The above content is provided by AI, for reference.\"\n */\n bottomTips?: string\n}\n\n/**\n * \u53D1\u9001\u56FE\u6807 (\u5411\u4E0A\u7BAD\u5934)\n */\nconst SendIcon: React.FC = () => (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M10.2069 15.6997V4.69971\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" />\n <path\n d=\"M15.3995 8.50341L10.9506 4.05446C10.534 3.6379 9.85866 3.63791 9.4421 4.05446L5.00005 8.49651\"\n stroke=\"white\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n)\n\n/**\n * \u804A\u5929\u8F93\u5165\u6846\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u591A\u884C\u6587\u672C\u8F93\u5165\uFF08\u81EA\u52A8\u589E\u957F\uFF09\n * - Enter \u53D1\u9001\uFF0CShift+Enter \u6362\u884C\n * - \u5B57\u7B26\u6570\u9650\u5236\uFF085000\uFF09\n * - \u7981\u7528\u72B6\u6001\uFF08\u53D1\u9001\u4E2D\uFF09\n * - \u53D1\u9001\u6309\u94AE\uFF08\u7A7A\u5185\u5BB9\u65F6\u7981\u7528\uFF09\n *\n * \u6837\u5F0F\uFF1A\n * - \u56FA\u5B9A\u5E95\u90E8\n * - \u8FB9\u6846\u5206\u9694\u4E0A\u65B9\u5185\u5BB9\n * - \u81EA\u9002\u5E94\u9AD8\u5EA6\uFF081-5 \u884C\uFF09\n *\n * @example\n * ```tsx\n * <ChatInput\n * value={inputValue}\n * onChange={setInputValue}\n * onSend={handleSend}\n * disabled={isStreaming}\n * />\n * ```\n */\nexport const ChatInput: React.FC<ChatInputProps> = ({\n value,\n onChange,\n onSend,\n placeholder = '',\n disabled = false,\n autoFocus = false,\n maxLength = 5000,\n bottomTips = 'The above content is provided by AI, for reference.',\n className = '',\n}) => {\n const textareaRef = useRef<HTMLTextAreaElement>(null)\n // \u8DDF\u8E2A\u8F93\u5165\u6CD5\u72B6\u6001\uFF08\u662F\u5426\u6B63\u5728\u4F7F\u7528\u62FC\u97F3\u8F93\u5165\u7B49\uFF09\n const isComposingRef = useRef(false)\n\n /**\n * \u81EA\u52A8\u8C03\u6574 textarea \u9AD8\u5EA6\n */\n useEffect(() => {\n const textarea = textareaRef.current\n if (!textarea) return\n\n // \u91CD\u7F6E\u9AD8\u5EA6\u4EE5\u83B7\u53D6\u6B63\u786E\u7684 scrollHeight\n textarea.style.height = 'auto'\n\n // \u8BBE\u7F6E\u9AD8\u5EA6\uFF0C\u6700\u5927 5 \u884C\uFF08\u7EA6 120px\uFF09\n const maxHeight = 120\n const newHeight = Math.min(textarea.scrollHeight, maxHeight)\n textarea.style.height = `${newHeight}px`\n }, [value])\n\n /**\n * \u5904\u7406\u8F93\u5165\u6CD5\u5F00\u59CB\n */\n const handleCompositionStart = () => {\n isComposingRef.current = true\n }\n\n /**\n * \u5904\u7406\u8F93\u5165\u6CD5\u7ED3\u675F\n */\n const handleCompositionEnd = () => {\n isComposingRef.current = false\n }\n\n /**\n * \u5904\u7406\u952E\u76D8\u4E8B\u4EF6\n * Enter \u53D1\u9001\uFF0CShift+Enter \u6362\u884C\n * \u6CE8\u610F\uFF1A\u8F93\u5165\u6CD5\u6FC0\u6D3B\u65F6\uFF08\u5982\u62FC\u97F3\u8F93\u5165\u4E2D\uFF09\uFF0C\u56DE\u8F66\u952E\u4E0D\u5E94\u53D1\u9001\u6D88\u606F\n */\n const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key === 'Enter' && !event.shiftKey) {\n // \u5982\u679C\u6B63\u5728\u4F7F\u7528\u8F93\u5165\u6CD5\uFF08\u5982\u62FC\u97F3\u8F93\u5165\uFF09\uFF0C\u5219\u4E0D\u53D1\u9001\u6D88\u606F\n // \u8BA9\u8F93\u5165\u6CD5\u5148\u5B8C\u6210\u5B57\u7B26\u9009\u62E9\n if (isComposingRef.current) {\n return\n }\n\n event.preventDefault()\n if (value.trim() && !disabled) {\n onSend()\n }\n }\n }\n\n /**\n * \u5904\u7406\u8F93\u5165\u53D8\u5316\n */\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n const newValue = event.target.value\n\n // \u5B57\u7B26\u6570\u9650\u5236\n if (newValue.length <= maxLength) {\n onChange(newValue)\n }\n }\n\n /**\n * \u5904\u7406\u53D1\u9001\u6309\u94AE\u70B9\u51FB\n */\n const handleSendClick = () => {\n if (value.trim() && !disabled) {\n onSend()\n }\n }\n\n const canSend = value.trim().length > 0 && !disabled\n\n return (\n <div className={`flex flex-col gap-2 bg-white px-4 py-4 ${className}`}>\n {/* \u8F93\u5165\u6846\u5BB9\u5668 - \u5E26\u6E10\u53D8\u8FB9\u6846 */}\n <div\n className=\"flex items-center gap-2 rounded-2xl\"\n style={{\n background: 'linear-gradient(to right, #7687F3, #7687F3, #4DA8F5, #A3DFCE)',\n padding: '2px',\n }}\n >\n {/* \u5185\u90E8\u767D\u8272\u80CC\u666F */}\n <div className=\"flex flex-1 items-center gap-2 bg-white px-3 py-2\" style={{ borderRadius: '14px' }}>\n {/* \u6587\u672C\u8F93\u5165\u6846 */}\n <textarea\n ref={textareaRef}\n value={value}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n onCompositionStart={handleCompositionStart}\n onCompositionEnd={handleCompositionEnd}\n placeholder={placeholder}\n disabled={disabled}\n autoFocus={autoFocus}\n rows={1}\n className=\"flex-1 resize-none bg-transparent font-bold text-sm text-gray-900 outline-none placeholder:text-gray-400 disabled:cursor-not-allowed disabled:opacity-50\"\n style={{\n resize: 'none',\n }}\n />\n\n {/* \u5B57\u7B26\u6570\u63D0\u793A (\u63A5\u8FD1\u4E0A\u9650\u65F6\u663E\u793A) */}\n {value.length > maxLength * 0.8 && (\n <span className=\"text-xs text-gray-400\">\n {value.length}/{maxLength}\n </span>\n )}\n\n {/* \u53D1\u9001\u6309\u94AE */}\n <button\n type=\"button\"\n onClick={handleSendClick}\n disabled={!canSend}\n className={`shrink-0 rounded-full p-2 transition-all ${canSend ? ' active:scale-95' : 'cursor-not-allowed '}`}\n style={\n canSend\n ? {\n background: 'linear-gradient(to bottom, #A3DFCE, #4DA8F5, #7687F3)',\n }\n : { background: '#BEBEBE' }\n }\n aria-label=\"Send message\"\n >\n <SendIcon />\n </button>\n </div>\n </div>\n <p className=\"px-3 font-bold text-[12px] text-[#767880] tracking-tight leading-[1.4]\">\n {bottomTips}\n </p>\n </div>\n )\n}\n"],
5
- "mappings": "AAgEE,OACE,OAAAA,EADF,QAAAC,MAAA,oBA1DF,OAAgB,UAAAC,EAAQ,aAAAC,MAAiB,QAyDzC,MAAMC,EAAqB,IACzBH,EAAC,OAAI,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OAAO,MAAM,6BAChE,UAAAD,EAAC,QAAK,EAAE,2BAA2B,OAAO,QAAQ,eAAa,IAAI,iBAAe,QAAQ,EAC1FA,EAAC,QACC,EAAE,gGACF,OAAO,QACP,eAAa,IACb,iBAAe,QACf,kBAAgB,QAClB,GACF,EA4BWK,EAAsC,CAAC,CAClD,MAAAC,EACA,SAAAC,EACA,OAAAC,EACA,YAAAC,EAAc,GACd,SAAAC,EAAW,GACX,UAAAC,EAAY,GACZ,UAAAC,EAAY,IACZ,WAAAC,EAAa,sDACb,UAAAC,EAAY,EACd,IAAM,CACJ,MAAMC,EAAcb,EAA4B,IAAI,EAE9Cc,EAAiBd,EAAO,EAAK,EAKnCC,EAAU,IAAM,CACd,MAAMc,EAAWF,EAAY,QAC7B,GAAI,CAACE,EAAU,OAGfA,EAAS,MAAM,OAAS,OAIxB,MAAMC,EAAY,KAAK,IAAID,EAAS,aADlB,GACyC,EAC3DA,EAAS,MAAM,OAAS,GAAGC,CAAS,IACtC,EAAG,CAACZ,CAAK,CAAC,EAKV,MAAMa,EAAyB,IAAM,CACnCH,EAAe,QAAU,EAC3B,EAKMI,EAAuB,IAAM,CACjCJ,EAAe,QAAU,EAC3B,EAOMK,EAAiBC,GAAoD,CACzE,GAAIA,EAAM,MAAQ,SAAW,CAACA,EAAM,SAAU,CAG5C,GAAIN,EAAe,QACjB,OAGFM,EAAM,eAAe,EACjBhB,EAAM,KAAK,GAAK,CAACI,GACnBF,EAAO,CAEX,CACF,EAKMe,EAAgBD,GAAkD,CACtE,MAAME,EAAWF,EAAM,OAAO,MAG1BE,EAAS,QAAUZ,GACrBL,EAASiB,CAAQ,CAErB,EAKMC,EAAkB,IAAM,CACxBnB,EAAM,KAAK,GAAK,CAACI,GACnBF,EAAO,CAEX,EAEMkB,EAAUpB,EAAM,KAAK,EAAE,OAAS,GAAK,CAACI,EAE5C,OACET,EAAC,OAAI,UAAW,0CAA0Ca,CAAS,GAEjE,UAAAd,EAAC,OACC,UAAU,sCACV,MAAO,CACL,WAAY,gEACZ,QAAS,KACX,EAGA,SAAAC,EAAC,OAAI,UAAU,oDAAoD,MAAO,CAAE,aAAc,MAAO,EAE/F,UAAAD,EAAC,YACC,IAAKe,EACL,MAAOT,EACP,SAAUiB,EACV,UAAWF,EACX,mBAAoBF,EACpB,iBAAkBC,EAClB,YAAaX,EACb,SAAUC,EACV,UAAWC,EACX,KAAM,EACN,UAAU,2JACV,MAAO,CACL,OAAQ,MACV,EACF,EAGCL,EAAM,OAASM,EAAY,IAC1BX,EAAC,QAAK,UAAU,wBACb,UAAAK,EAAM,OAAO,IAAEM,GAClB,EAIFZ,EAAC,UACC,KAAK,SACL,QAASyB,EACT,SAAU,CAACC,EACX,UAAW,4CAA4CA,EAAU,mBAAqB,qBAAqB,GAC3G,MACEA,EACI,CACE,WAAY,uDACd,EACA,CAAE,WAAY,SAAU,EAE9B,aAAW,eAEX,SAAA1B,EAACI,EAAA,EAAS,EACZ,GACF,EACF,EACAJ,EAAC,KAAE,UAAU,yEACV,SAAAa,EACH,GACF,CAEJ",
4
+ "sourcesContent": ["/**\n * \u804A\u5929\u8F93\u5165\u6846\u7EC4\u4EF6\n * \u63D0\u4F9B\u6587\u672C\u8F93\u5165\u548C\u53D1\u9001\u6309\u94AE\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u8F93\u5165\u6846\u8BBE\u8BA1\n */\n\nimport React, { useRef, useEffect } from 'react'\n\nexport interface ChatInputProps {\n /**\n * \u8F93\u5165\u6846\u5F53\u524D\u503C\n */\n value: string\n\n /**\n * \u503C\u53D8\u5316\u56DE\u8C03\n */\n onChange: (value: string) => void\n\n /**\n * \u53D1\u9001\u6D88\u606F\u56DE\u8C03\n */\n onSend: () => void\n\n /**\n * \u5360\u4F4D\u7B26\u6587\u672C\n * @default \"\u8F93\u5165\u6D88\u606F...\"\n */\n placeholder?: string\n\n /**\n * \u662F\u5426\u7981\u7528\u8F93\u5165\n * @default false\n */\n disabled?: boolean\n\n /**\n * \u662F\u5426\u81EA\u52A8\u805A\u7126\n * @default false\n */\n autoFocus?: boolean\n\n /**\n * \u6700\u5927\u5B57\u7B26\u6570\n * @default 5000\n */\n maxLength?: number\n\n /**\n * \u81EA\u5B9A\u4E49\u6837\u5F0F\u7C7B\u540D\n */\n className?: string\n\n /**\n * \u5E95\u90E8\u63D0\u793A\u6587\u672C\n * \u4E0D\u4F20\u5165\u5219\u4E0D\u663E\u793A\n */\n bottomTips?: string\n}\n\n/**\n * \u53D1\u9001\u56FE\u6807 (\u5411\u4E0A\u7BAD\u5934)\n */\nconst SendIcon: React.FC = () => (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M10.2069 15.6997V4.69971\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" />\n <path\n d=\"M15.3995 8.50341L10.9506 4.05446C10.534 3.6379 9.85866 3.63791 9.4421 4.05446L5.00005 8.49651\"\n stroke=\"white\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n)\n\n/**\n * \u804A\u5929\u8F93\u5165\u6846\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u591A\u884C\u6587\u672C\u8F93\u5165\uFF08\u81EA\u52A8\u589E\u957F\uFF09\n * - Enter \u53D1\u9001\uFF0CShift+Enter \u6362\u884C\n * - \u5B57\u7B26\u6570\u9650\u5236\uFF085000\uFF09\n * - \u7981\u7528\u72B6\u6001\uFF08\u53D1\u9001\u4E2D\uFF09\n * - \u53D1\u9001\u6309\u94AE\uFF08\u7A7A\u5185\u5BB9\u65F6\u7981\u7528\uFF09\n *\n * \u6837\u5F0F\uFF1A\n * - \u56FA\u5B9A\u5E95\u90E8\n * - \u8FB9\u6846\u5206\u9694\u4E0A\u65B9\u5185\u5BB9\n * - \u81EA\u9002\u5E94\u9AD8\u5EA6\uFF081-5 \u884C\uFF09\n *\n * @example\n * ```tsx\n * <ChatInput\n * value={inputValue}\n * onChange={setInputValue}\n * onSend={handleSend}\n * disabled={isStreaming}\n * />\n * ```\n */\nexport const ChatInput: React.FC<ChatInputProps> = ({\n value,\n onChange,\n onSend,\n placeholder = '',\n disabled = false,\n autoFocus = false,\n maxLength = 5000,\n bottomTips,\n className = '',\n}) => {\n const textareaRef = useRef<HTMLTextAreaElement>(null)\n // \u8DDF\u8E2A\u8F93\u5165\u6CD5\u72B6\u6001\uFF08\u662F\u5426\u6B63\u5728\u4F7F\u7528\u62FC\u97F3\u8F93\u5165\u7B49\uFF09\n const isComposingRef = useRef(false)\n\n /**\n * \u81EA\u52A8\u8C03\u6574 textarea \u9AD8\u5EA6\n */\n useEffect(() => {\n const textarea = textareaRef.current\n if (!textarea) return\n\n // \u91CD\u7F6E\u9AD8\u5EA6\u4EE5\u83B7\u53D6\u6B63\u786E\u7684 scrollHeight\n textarea.style.height = 'auto'\n\n // \u8BBE\u7F6E\u9AD8\u5EA6\uFF0C\u6700\u5927 5 \u884C\uFF08\u7EA6 120px\uFF09\n const maxHeight = 120\n const newHeight = Math.min(textarea.scrollHeight, maxHeight)\n textarea.style.height = `${newHeight}px`\n }, [value])\n\n /**\n * \u5904\u7406\u8F93\u5165\u6CD5\u5F00\u59CB\n */\n const handleCompositionStart = () => {\n isComposingRef.current = true\n }\n\n /**\n * \u5904\u7406\u8F93\u5165\u6CD5\u7ED3\u675F\n */\n const handleCompositionEnd = () => {\n isComposingRef.current = false\n }\n\n /**\n * \u5904\u7406\u952E\u76D8\u4E8B\u4EF6\n * Enter \u53D1\u9001\uFF0CShift+Enter \u6362\u884C\n * \u6CE8\u610F\uFF1A\u8F93\u5165\u6CD5\u6FC0\u6D3B\u65F6\uFF08\u5982\u62FC\u97F3\u8F93\u5165\u4E2D\uFF09\uFF0C\u56DE\u8F66\u952E\u4E0D\u5E94\u53D1\u9001\u6D88\u606F\n */\n const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key === 'Enter' && !event.shiftKey) {\n // \u5982\u679C\u6B63\u5728\u4F7F\u7528\u8F93\u5165\u6CD5\uFF08\u5982\u62FC\u97F3\u8F93\u5165\uFF09\uFF0C\u5219\u4E0D\u53D1\u9001\u6D88\u606F\n // \u8BA9\u8F93\u5165\u6CD5\u5148\u5B8C\u6210\u5B57\u7B26\u9009\u62E9\n if (isComposingRef.current) {\n return\n }\n\n event.preventDefault()\n if (value.trim() && !disabled) {\n onSend()\n }\n }\n }\n\n /**\n * \u5904\u7406\u8F93\u5165\u53D8\u5316\n */\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n const newValue = event.target.value\n\n // \u5B57\u7B26\u6570\u9650\u5236\n if (newValue.length <= maxLength) {\n onChange(newValue)\n }\n }\n\n /**\n * \u5904\u7406\u53D1\u9001\u6309\u94AE\u70B9\u51FB\n */\n const handleSendClick = () => {\n if (value.trim() && !disabled) {\n onSend()\n }\n }\n\n const canSend = value.trim().length > 0 && !disabled\n\n return (\n <div className={`flex flex-col gap-2 bg-white px-4 py-4 ${className}`}>\n {/* \u8F93\u5165\u6846\u5BB9\u5668 - \u5E26\u6E10\u53D8\u8FB9\u6846 */}\n <div\n className=\"flex items-center gap-2 rounded-2xl\"\n style={{\n background: 'linear-gradient(to right, #7687F3, #7687F3, #4DA8F5, #A3DFCE)',\n padding: '2px',\n }}\n >\n {/* \u5185\u90E8\u767D\u8272\u80CC\u666F */}\n <div className=\"flex flex-1 items-center gap-2 bg-white px-3 py-2\" style={{ borderRadius: '14px' }}>\n {/* \u6587\u672C\u8F93\u5165\u6846 */}\n <textarea\n ref={textareaRef}\n value={value}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n onCompositionStart={handleCompositionStart}\n onCompositionEnd={handleCompositionEnd}\n placeholder={placeholder}\n disabled={disabled}\n autoFocus={autoFocus}\n rows={1}\n className=\"flex-1 resize-none bg-transparent font-bold text-sm text-gray-900 outline-none placeholder:text-gray-400 disabled:cursor-not-allowed disabled:opacity-50\"\n style={{\n resize: 'none',\n }}\n />\n\n {/* \u5B57\u7B26\u6570\u63D0\u793A (\u63A5\u8FD1\u4E0A\u9650\u65F6\u663E\u793A) */}\n {value.length > maxLength * 0.8 && (\n <span className=\"text-xs text-gray-400\">\n {value.length}/{maxLength}\n </span>\n )}\n\n {/* \u53D1\u9001\u6309\u94AE */}\n <button\n type=\"button\"\n onClick={handleSendClick}\n disabled={!canSend}\n className={`shrink-0 rounded-full p-2 transition-all ${canSend ? ' active:scale-95' : 'cursor-not-allowed '}`}\n style={\n canSend\n ? {\n background: 'linear-gradient(to bottom, #A3DFCE, #4DA8F5, #7687F3)',\n }\n : { background: '#BEBEBE' }\n }\n aria-label=\"Send message\"\n >\n <SendIcon />\n </button>\n </div>\n </div>\n {bottomTips && (\n <p className=\"px-3 font-bold text-[12px] text-[#767880] tracking-tight leading-[1.4]\">\n {bottomTips}\n </p>\n )}\n </div>\n )\n}\n"],
5
+ "mappings": "AAgEE,OACE,OAAAA,EADF,QAAAC,MAAA,oBA1DF,OAAgB,UAAAC,EAAQ,aAAAC,MAAiB,QAyDzC,MAAMC,EAAqB,IACzBH,EAAC,OAAI,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OAAO,MAAM,6BAChE,UAAAD,EAAC,QAAK,EAAE,2BAA2B,OAAO,QAAQ,eAAa,IAAI,iBAAe,QAAQ,EAC1FA,EAAC,QACC,EAAE,gGACF,OAAO,QACP,eAAa,IACb,iBAAe,QACf,kBAAgB,QAClB,GACF,EA4BWK,EAAsC,CAAC,CAClD,MAAAC,EACA,SAAAC,EACA,OAAAC,EACA,YAAAC,EAAc,GACd,SAAAC,EAAW,GACX,UAAAC,EAAY,GACZ,UAAAC,EAAY,IACZ,WAAAC,EACA,UAAAC,EAAY,EACd,IAAM,CACJ,MAAMC,EAAcb,EAA4B,IAAI,EAE9Cc,EAAiBd,EAAO,EAAK,EAKnCC,EAAU,IAAM,CACd,MAAMc,EAAWF,EAAY,QAC7B,GAAI,CAACE,EAAU,OAGfA,EAAS,MAAM,OAAS,OAIxB,MAAMC,EAAY,KAAK,IAAID,EAAS,aADlB,GACyC,EAC3DA,EAAS,MAAM,OAAS,GAAGC,CAAS,IACtC,EAAG,CAACZ,CAAK,CAAC,EAKV,MAAMa,EAAyB,IAAM,CACnCH,EAAe,QAAU,EAC3B,EAKMI,EAAuB,IAAM,CACjCJ,EAAe,QAAU,EAC3B,EAOMK,EAAiBC,GAAoD,CACzE,GAAIA,EAAM,MAAQ,SAAW,CAACA,EAAM,SAAU,CAG5C,GAAIN,EAAe,QACjB,OAGFM,EAAM,eAAe,EACjBhB,EAAM,KAAK,GAAK,CAACI,GACnBF,EAAO,CAEX,CACF,EAKMe,EAAgBD,GAAkD,CACtE,MAAME,EAAWF,EAAM,OAAO,MAG1BE,EAAS,QAAUZ,GACrBL,EAASiB,CAAQ,CAErB,EAKMC,EAAkB,IAAM,CACxBnB,EAAM,KAAK,GAAK,CAACI,GACnBF,EAAO,CAEX,EAEMkB,EAAUpB,EAAM,KAAK,EAAE,OAAS,GAAK,CAACI,EAE5C,OACET,EAAC,OAAI,UAAW,0CAA0Ca,CAAS,GAEjE,UAAAd,EAAC,OACC,UAAU,sCACV,MAAO,CACL,WAAY,gEACZ,QAAS,KACX,EAGA,SAAAC,EAAC,OAAI,UAAU,oDAAoD,MAAO,CAAE,aAAc,MAAO,EAE/F,UAAAD,EAAC,YACC,IAAKe,EACL,MAAOT,EACP,SAAUiB,EACV,UAAWF,EACX,mBAAoBF,EACpB,iBAAkBC,EAClB,YAAaX,EACb,SAAUC,EACV,UAAWC,EACX,KAAM,EACN,UAAU,2JACV,MAAO,CACL,OAAQ,MACV,EACF,EAGCL,EAAM,OAASM,EAAY,IAC1BX,EAAC,QAAK,UAAU,wBACb,UAAAK,EAAM,OAAO,IAAEM,GAClB,EAIFZ,EAAC,UACC,KAAK,SACL,QAASyB,EACT,SAAU,CAACC,EACX,UAAW,4CAA4CA,EAAU,mBAAqB,qBAAqB,GAC3G,MACEA,EACI,CACE,WAAY,uDACd,EACA,CAAE,WAAY,SAAU,EAE9B,aAAW,eAEX,SAAA1B,EAACI,EAAA,EAAS,EACZ,GACF,EACF,EACCS,GACCb,EAAC,KAAE,UAAU,yEACV,SAAAa,EACH,GAEJ,CAEJ",
6
6
  "names": ["jsx", "jsxs", "useRef", "useEffect", "SendIcon", "ChatInput", "value", "onChange", "onSend", "placeholder", "disabled", "autoFocus", "maxLength", "bottomTips", "className", "textareaRef", "isComposingRef", "textarea", "newHeight", "handleCompositionStart", "handleCompositionEnd", "handleKeyDown", "event", "handleChange", "newValue", "handleSendClick", "canSend"]
7
7
  }
@@ -86,7 +86,7 @@ export interface ChatWindowProps {
86
86
  showNewSessionButton?: boolean;
87
87
  /**
88
88
  * 输入框底部提示文本
89
- * @default "The above content is provided by AI, for reference."
89
+ * 不传入则不显示
90
90
  */
91
91
  bottomTips?: string;
92
92
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/components/LiveChatWidget/components/ChatWindow.tsx"],
4
- "sourcesContent": ["/**\n * \u804A\u5929\u7A97\u53E3\u5BB9\u5668\u7EC4\u4EF6\n * \u5305\u542B\u5934\u90E8\u3001\u6D88\u606F\u5217\u8868\u3001\u8F93\u5165\u6846\u7684\u5B8C\u6574\u804A\u5929\u754C\u9762\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u7A97\u53E3\u8BBE\u8BA1\n */\n\nimport React, { useState, useRef, useEffect, useCallback } from 'react'\nimport type { Message, MessageRenderer } from '../types'\nimport { ChatHeader } from './ChatHeader'\nimport { MessageList } from './MessageList'\nimport { ChatInput } from './ChatInput'\nimport { MessageRendererRegistry } from '../utils/messageRenderers'\n\nexport interface ChatWindowProps {\n /**\n * \u6D88\u606F\u5217\u8868\n */\n messages: Message[]\n\n /**\n * \u8F93\u5165\u6846\u5F53\u524D\u503C\n */\n inputValue: string\n\n /**\n * \u8F93\u5165\u6846\u503C\u53D8\u5316\u56DE\u8C03\n */\n onInputChange: (value: string) => void\n\n /**\n * \u53D1\u9001\u6D88\u606F\u56DE\u8C03\n */\n onSend: () => void\n\n /**\n * \u5173\u95ED\u7A97\u53E3\u56DE\u8C03\n */\n onClose?: () => void\n\n /**\n * \u65B0\u4F1A\u8BDD\u56DE\u8C03\n */\n onNewSession?: () => void\n\n /**\n * \u5934\u90E8\u6807\u9898\n */\n title?: string\n\n /**\n * Logo URL\n */\n logoUrl?: string\n\n /**\n * \u662F\u5426\u6B63\u5728\u53D1\u9001\u6D88\u606F\n * @default false\n */\n isSending?: boolean\n\n /**\n * \u662F\u5426\u6B63\u5728\u52A0\u8F7D\u5386\u53F2\u6D88\u606F\n * @default false\n */\n isLoadingHistory?: boolean\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\n * @default true\n */\n autoScroll?: boolean\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 * \u8F93\u5165\u6846\u5360\u4F4D\u7B26\n */\n inputPlaceholder?: string\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 * \u662F\u5426\u663E\u793A\u65B0\u4F1A\u8BDD\u6309\u94AE\n * @default true\n */\n showNewSessionButton?: boolean\n\n /**\n * \u8F93\u5165\u6846\u5E95\u90E8\u63D0\u793A\u6587\u672C\n * @default \"The above content is provided by AI, for reference.\"\n */\n bottomTips?: string\n}\n\n/**\n * \u804A\u5929\u7A97\u53E3\u5BB9\u5668\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u7EC4\u5408\u5934\u90E8\u3001\u6D88\u606F\u5217\u8868\u3001\u8F93\u5165\u6846\n * - \u54CD\u5E94\u5F0F\u5E03\u5C40\uFF08\u79FB\u52A8\u7AEF\u5168\u5C4F\uFF0C\u684C\u9762\u7AEF\u56FA\u5B9A\u5C3A\u5BF8\uFF09\n * - \u52A8\u753B\u8FDB\u5165/\u9000\u51FA\u6548\u679C\n *\n * \u5E03\u5C40\u7ED3\u6784\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\u2510\n * \u2502 ChatHeader \u2502 (\u56FA\u5B9A\u9876\u90E8)\n * \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n * \u2502 MessageList \u2502 (\u53EF\u6EDA\u52A8\u533A\u57DF)\n * \u2502 (flex-1) \u2502\n * \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n * \u2502 ChatInput \u2502 (\u56FA\u5B9A\u5E95\u90E8)\n * \u2514\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 * \u54CD\u5E94\u5F0F\u8BBE\u8BA1\uFF1A\n * - \u79FB\u52A8\u7AEF (< 768px): \u5168\u5C4F\u663E\u793A\n * - \u5E73\u677F\u53CA\u4EE5\u4E0A (>= 768px): \u56FA\u5B9A\u5C3A\u5BF8\u5F39\u7A97\n *\n * @example\n * ```tsx\n * <ChatWindow\n * messages={messages}\n * inputValue={inputValue}\n * onInputChange={setInputValue}\n * onSend={handleSend}\n * onClose={() => setIsOpen(false)}\n * onNewSession={handleNewSession}\n * isSending={isStreaming}\n * title=\"AI \u52A9\u624B\"\n * logoUrl=\"/logo.png\"\n * />\n * ```\n */\nexport const ChatWindow: React.FC<ChatWindowProps> = ({\n messages,\n inputValue,\n onInputChange,\n onSend,\n onClose,\n onNewSession,\n title,\n logoUrl,\n isSending = false,\n isLoadingHistory = false,\n showTimestamp = true,\n autoScroll = true,\n rendererRegistry,\n defaultRenderer,\n inputPlaceholder,\n className = '',\n onAddToCart,\n showNewSessionButton = true,\n bottomTips,\n}) => {\n // \u5E38\u91CF\uFF1A\u9AD8\u5EA6\u9650\u5236\u6BD4\u4F8B\n const MIN_HEIGHT_RATIO = 0.4 // \u6700\u5C0F40%\n const MAX_HEIGHT_RATIO = 0.8 // \u6700\u592780%\n const DEFAULT_HEIGHT = 680 // \u9ED8\u8BA4680px\n\n // \u83B7\u53D6\u5F53\u524D\u5141\u8BB8\u7684\u9AD8\u5EA6\u8303\u56F4\n const getHeightConstraints = useCallback(() => {\n if (typeof window === 'undefined') {\n return { minHeight: DEFAULT_HEIGHT, maxHeight: DEFAULT_HEIGHT }\n }\n return {\n minHeight: window.innerHeight * MIN_HEIGHT_RATIO,\n maxHeight: window.innerHeight * MAX_HEIGHT_RATIO,\n }\n }, [MIN_HEIGHT_RATIO, MAX_HEIGHT_RATIO, DEFAULT_HEIGHT])\n\n // \u5C06\u9AD8\u5EA6\u9650\u5236\u5728\u5141\u8BB8\u8303\u56F4\u5185\n const clampHeight = useCallback(\n (height: number) => {\n const { minHeight, maxHeight } = getHeightConstraints()\n return Math.max(minHeight, Math.min(maxHeight, height))\n },\n [getHeightConstraints]\n )\n\n // \u8BA1\u7B97\u521D\u59CB\u9AD8\u5EA6\uFF1A\u9ED8\u8BA4680px\uFF0C\u4F46\u9700\u8981\u572830%-80%\u8303\u56F4\u5185\n const getInitialHeight = () => {\n if (typeof window === 'undefined') return DEFAULT_HEIGHT\n return clampHeight(DEFAULT_HEIGHT)\n }\n\n // \u79FB\u52A8\u7AEF\u9AD8\u5EA6\u8C03\u8282\u72B6\u6001\uFF08\u9ED8\u8BA4680px\u6216\u5C4F\u5E5580%\uFF0C\u53D6\u8F83\u5C0F\u503C\uFF09\n const [mobileHeight, setMobileHeight] = useState(getInitialHeight)\n const [isDragging, setIsDragging] = useState(false)\n const dragStartY = useRef(0)\n const dragStartHeight = useRef(getInitialHeight())\n const windowRef = useRef<HTMLDivElement>(null)\n\n // \u66F4\u65B0\u9AD8\u5EA6\u5E76\u540C\u6B65CSS\u53D8\u91CF\n const updateHeight = useCallback(\n (newHeight: number) => {\n const clampedHeight = clampHeight(newHeight)\n setMobileHeight(clampedHeight)\n // \u540C\u6B65\u66F4\u65B0CSS\u53D8\u91CF\uFF0C\u4F9B livechat.css \u4F7F\u7528\n if (windowRef.current) {\n windowRef.current.style.setProperty('--livechat-mobile-height', `${clampedHeight}px`)\n }\n return clampedHeight\n },\n [clampHeight]\n )\n\n // \u68C0\u6D4B\u662F\u5426\u4E3A\u79FB\u52A8\u7AEF\uFF08\u521D\u59CB\u5316\u65F6\u7ACB\u5373\u68C0\u6D4B\uFF09\n const [isMobile, setIsMobile] = useState(() => {\n if (typeof window !== 'undefined') {\n return window.innerWidth < 768\n }\n return false\n })\n\n useEffect(() => {\n const handleResize = () => {\n const newIsMobile = window.innerWidth < 768\n setIsMobile(newIsMobile)\n\n // \u5982\u679C\u662F\u79FB\u52A8\u7AEF\uFF0C\u68C0\u67E5\u5F53\u524D\u9AD8\u5EA6\u662F\u5426\u5728\u5141\u8BB8\u8303\u56F4\u5185\n if (newIsMobile) {\n const clampedHeight = clampHeight(mobileHeight)\n if (clampedHeight !== mobileHeight) {\n updateHeight(clampedHeight)\n }\n }\n }\n\n window.addEventListener('resize', handleResize)\n return () => window.removeEventListener('resize', handleResize)\n }, [mobileHeight, clampHeight, updateHeight])\n\n // \u62D6\u62FD\u5F00\u59CB\n const handleDragStart = (e: React.MouseEvent | React.TouchEvent) => {\n if (!isMobile) return\n\n setIsDragging(true)\n const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY\n dragStartY.current = clientY\n dragStartHeight.current = mobileHeight\n\n // \u963B\u6B62\u9ED8\u8BA4\u884C\u4E3A\n e.preventDefault()\n }\n\n // \u62D6\u62FD\u4E2D\n useEffect(() => {\n if (!isDragging) return\n\n const handleDragMove = (e: MouseEvent | TouchEvent) => {\n const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY\n const deltaY = dragStartY.current - clientY // \u5411\u4E0A\u62D6\u52A8\u4E3A\u6B63\u503C\n const newHeight = dragStartHeight.current + deltaY\n\n updateHeight(newHeight)\n }\n\n const handleDragEnd = () => {\n setIsDragging(false)\n }\n\n document.addEventListener('mousemove', handleDragMove)\n document.addEventListener('mouseup', handleDragEnd)\n document.addEventListener('touchmove', handleDragMove, { passive: false })\n document.addEventListener('touchend', handleDragEnd)\n\n return () => {\n document.removeEventListener('mousemove', handleDragMove)\n document.removeEventListener('mouseup', handleDragEnd)\n document.removeEventListener('touchmove', handleDragMove)\n document.removeEventListener('touchend', handleDragEnd)\n }\n }, [isDragging, updateHeight])\n\n return (\n <div\n ref={windowRef}\n className={`livechat-window flex flex-col overflow-hidden bg-white ${className}`}\n style={\n {\n borderRadius: '16px',\n borderBottomLeftRadius: isMobile && '0px',\n borderBottomRightRadius: isMobile && '0px',\n '--livechat-mobile-height': `${mobileHeight}px`,\n } as React.CSSProperties\n }\n >\n {/* \u79FB\u52A8\u7AEF\u62D6\u62FD\u624B\u67C4 */}\n {isMobile && (\n <div\n className=\"flex cursor-ns-resize items-center justify-center py-2\"\n style={{\n touchAction: 'none',\n backgroundColor: '#ffffff',\n }}\n onMouseDown={handleDragStart}\n onTouchStart={handleDragStart}\n >\n <div\n style={{\n width: '48px',\n height: '6px',\n borderRadius: '999px',\n backgroundColor: '#DADCE0',\n }}\n />\n </div>\n )}\n\n {/* \u5934\u90E8 */}\n <ChatHeader\n title={title}\n logoUrl={logoUrl}\n onClose={onClose}\n onNewSession={onNewSession}\n showNewSessionButton={showNewSessionButton}\n />\n\n {/* \u6D88\u606F\u5217\u8868\uFF08\u53EF\u6EDA\u52A8\u533A\u57DF\uFF09 */}\n <MessageList\n messages={messages}\n rendererRegistry={rendererRegistry}\n defaultRenderer={defaultRenderer}\n showTimestamp={showTimestamp}\n autoScroll={autoScroll}\n isLoadingHistory={isLoadingHistory}\n onAddToCart={onAddToCart}\n />\n\n {/* \u8F93\u5165\u6846 */}\n <ChatInput\n value={inputValue}\n onChange={onInputChange}\n onSend={onSend}\n placeholder={inputPlaceholder}\n disabled={isSending}\n bottomTips={bottomTips}\n />\n </div>\n )\n}\n"],
4
+ "sourcesContent": ["/**\n * \u804A\u5929\u7A97\u53E3\u5BB9\u5668\u7EC4\u4EF6\n * \u5305\u542B\u5934\u90E8\u3001\u6D88\u606F\u5217\u8868\u3001\u8F93\u5165\u6846\u7684\u5B8C\u6574\u804A\u5929\u754C\u9762\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u7A97\u53E3\u8BBE\u8BA1\n */\n\nimport React, { useState, useRef, useEffect, useCallback } from 'react'\nimport type { Message, MessageRenderer } from '../types'\nimport { ChatHeader } from './ChatHeader'\nimport { MessageList } from './MessageList'\nimport { ChatInput } from './ChatInput'\nimport { MessageRendererRegistry } from '../utils/messageRenderers'\n\nexport interface ChatWindowProps {\n /**\n * \u6D88\u606F\u5217\u8868\n */\n messages: Message[]\n\n /**\n * \u8F93\u5165\u6846\u5F53\u524D\u503C\n */\n inputValue: string\n\n /**\n * \u8F93\u5165\u6846\u503C\u53D8\u5316\u56DE\u8C03\n */\n onInputChange: (value: string) => void\n\n /**\n * \u53D1\u9001\u6D88\u606F\u56DE\u8C03\n */\n onSend: () => void\n\n /**\n * \u5173\u95ED\u7A97\u53E3\u56DE\u8C03\n */\n onClose?: () => void\n\n /**\n * \u65B0\u4F1A\u8BDD\u56DE\u8C03\n */\n onNewSession?: () => void\n\n /**\n * \u5934\u90E8\u6807\u9898\n */\n title?: string\n\n /**\n * Logo URL\n */\n logoUrl?: string\n\n /**\n * \u662F\u5426\u6B63\u5728\u53D1\u9001\u6D88\u606F\n * @default false\n */\n isSending?: boolean\n\n /**\n * \u662F\u5426\u6B63\u5728\u52A0\u8F7D\u5386\u53F2\u6D88\u606F\n * @default false\n */\n isLoadingHistory?: boolean\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\n * @default true\n */\n autoScroll?: boolean\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 * \u8F93\u5165\u6846\u5360\u4F4D\u7B26\n */\n inputPlaceholder?: string\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 * \u662F\u5426\u663E\u793A\u65B0\u4F1A\u8BDD\u6309\u94AE\n * @default true\n */\n showNewSessionButton?: boolean\n\n /**\n * \u8F93\u5165\u6846\u5E95\u90E8\u63D0\u793A\u6587\u672C\n * \u4E0D\u4F20\u5165\u5219\u4E0D\u663E\u793A\n */\n bottomTips?: string\n}\n\n/**\n * \u804A\u5929\u7A97\u53E3\u5BB9\u5668\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u7EC4\u5408\u5934\u90E8\u3001\u6D88\u606F\u5217\u8868\u3001\u8F93\u5165\u6846\n * - \u54CD\u5E94\u5F0F\u5E03\u5C40\uFF08\u79FB\u52A8\u7AEF\u5168\u5C4F\uFF0C\u684C\u9762\u7AEF\u56FA\u5B9A\u5C3A\u5BF8\uFF09\n * - \u52A8\u753B\u8FDB\u5165/\u9000\u51FA\u6548\u679C\n *\n * \u5E03\u5C40\u7ED3\u6784\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\u2510\n * \u2502 ChatHeader \u2502 (\u56FA\u5B9A\u9876\u90E8)\n * \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n * \u2502 MessageList \u2502 (\u53EF\u6EDA\u52A8\u533A\u57DF)\n * \u2502 (flex-1) \u2502\n * \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n * \u2502 ChatInput \u2502 (\u56FA\u5B9A\u5E95\u90E8)\n * \u2514\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 * \u54CD\u5E94\u5F0F\u8BBE\u8BA1\uFF1A\n * - \u79FB\u52A8\u7AEF (< 768px): \u5168\u5C4F\u663E\u793A\n * - \u5E73\u677F\u53CA\u4EE5\u4E0A (>= 768px): \u56FA\u5B9A\u5C3A\u5BF8\u5F39\u7A97\n *\n * @example\n * ```tsx\n * <ChatWindow\n * messages={messages}\n * inputValue={inputValue}\n * onInputChange={setInputValue}\n * onSend={handleSend}\n * onClose={() => setIsOpen(false)}\n * onNewSession={handleNewSession}\n * isSending={isStreaming}\n * title=\"AI \u52A9\u624B\"\n * logoUrl=\"/logo.png\"\n * />\n * ```\n */\nexport const ChatWindow: React.FC<ChatWindowProps> = ({\n messages,\n inputValue,\n onInputChange,\n onSend,\n onClose,\n onNewSession,\n title,\n logoUrl,\n isSending = false,\n isLoadingHistory = false,\n showTimestamp = true,\n autoScroll = true,\n rendererRegistry,\n defaultRenderer,\n inputPlaceholder,\n className = '',\n onAddToCart,\n showNewSessionButton = true,\n bottomTips,\n}) => {\n // \u5E38\u91CF\uFF1A\u9AD8\u5EA6\u9650\u5236\u6BD4\u4F8B\n const MIN_HEIGHT_RATIO = 0.4 // \u6700\u5C0F40%\n const MAX_HEIGHT_RATIO = 0.8 // \u6700\u592780%\n const DEFAULT_HEIGHT = 680 // \u9ED8\u8BA4680px\n\n // \u83B7\u53D6\u5F53\u524D\u5141\u8BB8\u7684\u9AD8\u5EA6\u8303\u56F4\n const getHeightConstraints = useCallback(() => {\n if (typeof window === 'undefined') {\n return { minHeight: DEFAULT_HEIGHT, maxHeight: DEFAULT_HEIGHT }\n }\n return {\n minHeight: window.innerHeight * MIN_HEIGHT_RATIO,\n maxHeight: window.innerHeight * MAX_HEIGHT_RATIO,\n }\n }, [MIN_HEIGHT_RATIO, MAX_HEIGHT_RATIO, DEFAULT_HEIGHT])\n\n // \u5C06\u9AD8\u5EA6\u9650\u5236\u5728\u5141\u8BB8\u8303\u56F4\u5185\n const clampHeight = useCallback(\n (height: number) => {\n const { minHeight, maxHeight } = getHeightConstraints()\n return Math.max(minHeight, Math.min(maxHeight, height))\n },\n [getHeightConstraints]\n )\n\n // \u8BA1\u7B97\u521D\u59CB\u9AD8\u5EA6\uFF1A\u9ED8\u8BA4680px\uFF0C\u4F46\u9700\u8981\u572830%-80%\u8303\u56F4\u5185\n const getInitialHeight = () => {\n if (typeof window === 'undefined') return DEFAULT_HEIGHT\n return clampHeight(DEFAULT_HEIGHT)\n }\n\n // \u79FB\u52A8\u7AEF\u9AD8\u5EA6\u8C03\u8282\u72B6\u6001\uFF08\u9ED8\u8BA4680px\u6216\u5C4F\u5E5580%\uFF0C\u53D6\u8F83\u5C0F\u503C\uFF09\n const [mobileHeight, setMobileHeight] = useState(getInitialHeight)\n const [isDragging, setIsDragging] = useState(false)\n const dragStartY = useRef(0)\n const dragStartHeight = useRef(getInitialHeight())\n const windowRef = useRef<HTMLDivElement>(null)\n\n // \u66F4\u65B0\u9AD8\u5EA6\u5E76\u540C\u6B65CSS\u53D8\u91CF\n const updateHeight = useCallback(\n (newHeight: number) => {\n const clampedHeight = clampHeight(newHeight)\n setMobileHeight(clampedHeight)\n // \u540C\u6B65\u66F4\u65B0CSS\u53D8\u91CF\uFF0C\u4F9B livechat.css \u4F7F\u7528\n if (windowRef.current) {\n windowRef.current.style.setProperty('--livechat-mobile-height', `${clampedHeight}px`)\n }\n return clampedHeight\n },\n [clampHeight]\n )\n\n // \u68C0\u6D4B\u662F\u5426\u4E3A\u79FB\u52A8\u7AEF\uFF08\u521D\u59CB\u5316\u65F6\u7ACB\u5373\u68C0\u6D4B\uFF09\n const [isMobile, setIsMobile] = useState(() => {\n if (typeof window !== 'undefined') {\n return window.innerWidth < 768\n }\n return false\n })\n\n useEffect(() => {\n const handleResize = () => {\n const newIsMobile = window.innerWidth < 768\n setIsMobile(newIsMobile)\n\n // \u5982\u679C\u662F\u79FB\u52A8\u7AEF\uFF0C\u68C0\u67E5\u5F53\u524D\u9AD8\u5EA6\u662F\u5426\u5728\u5141\u8BB8\u8303\u56F4\u5185\n if (newIsMobile) {\n const clampedHeight = clampHeight(mobileHeight)\n if (clampedHeight !== mobileHeight) {\n updateHeight(clampedHeight)\n }\n }\n }\n\n window.addEventListener('resize', handleResize)\n return () => window.removeEventListener('resize', handleResize)\n }, [mobileHeight, clampHeight, updateHeight])\n\n // \u62D6\u62FD\u5F00\u59CB\n const handleDragStart = (e: React.MouseEvent | React.TouchEvent) => {\n if (!isMobile) return\n\n setIsDragging(true)\n const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY\n dragStartY.current = clientY\n dragStartHeight.current = mobileHeight\n\n // \u963B\u6B62\u9ED8\u8BA4\u884C\u4E3A\n e.preventDefault()\n }\n\n // \u62D6\u62FD\u4E2D\n useEffect(() => {\n if (!isDragging) return\n\n const handleDragMove = (e: MouseEvent | TouchEvent) => {\n const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY\n const deltaY = dragStartY.current - clientY // \u5411\u4E0A\u62D6\u52A8\u4E3A\u6B63\u503C\n const newHeight = dragStartHeight.current + deltaY\n\n updateHeight(newHeight)\n }\n\n const handleDragEnd = () => {\n setIsDragging(false)\n }\n\n document.addEventListener('mousemove', handleDragMove)\n document.addEventListener('mouseup', handleDragEnd)\n document.addEventListener('touchmove', handleDragMove, { passive: false })\n document.addEventListener('touchend', handleDragEnd)\n\n return () => {\n document.removeEventListener('mousemove', handleDragMove)\n document.removeEventListener('mouseup', handleDragEnd)\n document.removeEventListener('touchmove', handleDragMove)\n document.removeEventListener('touchend', handleDragEnd)\n }\n }, [isDragging, updateHeight])\n\n return (\n <div\n ref={windowRef}\n className={`livechat-window flex flex-col overflow-hidden bg-white ${className}`}\n style={\n {\n borderRadius: '16px',\n borderBottomLeftRadius: isMobile && '0px',\n borderBottomRightRadius: isMobile && '0px',\n '--livechat-mobile-height': `${mobileHeight}px`,\n } as React.CSSProperties\n }\n >\n {/* \u79FB\u52A8\u7AEF\u62D6\u62FD\u624B\u67C4 */}\n {isMobile && (\n <div\n className=\"flex cursor-ns-resize items-center justify-center py-2\"\n style={{\n touchAction: 'none',\n backgroundColor: '#ffffff',\n }}\n onMouseDown={handleDragStart}\n onTouchStart={handleDragStart}\n >\n <div\n style={{\n width: '48px',\n height: '6px',\n borderRadius: '999px',\n backgroundColor: '#DADCE0',\n }}\n />\n </div>\n )}\n\n {/* \u5934\u90E8 */}\n <ChatHeader\n title={title}\n logoUrl={logoUrl}\n onClose={onClose}\n onNewSession={onNewSession}\n showNewSessionButton={showNewSessionButton}\n />\n\n {/* \u6D88\u606F\u5217\u8868\uFF08\u53EF\u6EDA\u52A8\u533A\u57DF\uFF09 */}\n <MessageList\n messages={messages}\n rendererRegistry={rendererRegistry}\n defaultRenderer={defaultRenderer}\n showTimestamp={showTimestamp}\n autoScroll={autoScroll}\n isLoadingHistory={isLoadingHistory}\n onAddToCart={onAddToCart}\n />\n\n {/* \u8F93\u5165\u6846 */}\n <ChatInput\n value={inputValue}\n onChange={onInputChange}\n onSend={onSend}\n placeholder={inputPlaceholder}\n disabled={isSending}\n bottomTips={bottomTips}\n />\n </div>\n )\n}\n"],
5
5
  "mappings": "AAySI,OAuBM,OAAAA,EAvBN,QAAAC,MAAA,oBAnSJ,OAAgB,YAAAC,EAAU,UAAAC,EAAQ,aAAAC,EAAW,eAAAC,MAAmB,QAEhE,OAAS,cAAAC,MAAkB,eAC3B,OAAS,eAAAC,MAAmB,gBAC5B,OAAS,aAAAC,MAAiB,cAiJnB,MAAMC,GAAwC,CAAC,CACpD,SAAAC,EACA,WAAAC,EACA,cAAAC,EACA,OAAAC,EACA,QAAAC,EACA,aAAAC,EACA,MAAAC,EACA,QAAAC,EACA,UAAAC,EAAY,GACZ,iBAAAC,EAAmB,GACnB,cAAAC,EAAgB,GAChB,WAAAC,EAAa,GACb,iBAAAC,EACA,gBAAAC,EACA,iBAAAC,EACA,UAAAC,EAAY,GACZ,YAAAC,EACA,qBAAAC,EAAuB,GACvB,WAAAC,CACF,IAAM,CAOJ,MAAMC,EAAuBxB,EAAY,IACnC,OAAO,OAAW,IACb,CAAE,UAAW,IAAgB,UAAW,GAAe,EAEzD,CACL,UAAW,OAAO,YAAc,GAChC,UAAW,OAAO,YAAc,EAClC,EACC,CAAC,GAAkB,GAAkB,GAAc,CAAC,EAGjDyB,EAAczB,EACjB0B,GAAmB,CAClB,KAAM,CAAE,UAAAC,EAAW,UAAAC,CAAU,EAAIJ,EAAqB,EACtD,OAAO,KAAK,IAAIG,EAAW,KAAK,IAAIC,EAAWF,CAAM,CAAC,CACxD,EACA,CAACF,CAAoB,CACvB,EAGMK,EAAmB,IACnB,OAAO,OAAW,IAAoB,IACnCJ,EAAY,GAAc,EAI7B,CAACK,EAAcC,CAAe,EAAIlC,EAASgC,CAAgB,EAC3D,CAACG,EAAYC,CAAa,EAAIpC,EAAS,EAAK,EAC5CqC,EAAapC,EAAO,CAAC,EACrBqC,EAAkBrC,EAAO+B,EAAiB,CAAC,EAC3CO,EAAYtC,EAAuB,IAAI,EAGvCuC,EAAerC,EAClBsC,GAAsB,CACrB,MAAMC,EAAgBd,EAAYa,CAAS,EAC3C,OAAAP,EAAgBQ,CAAa,EAEzBH,EAAU,SACZA,EAAU,QAAQ,MAAM,YAAY,2BAA4B,GAAGG,CAAa,IAAI,EAE/EA,CACT,EACA,CAACd,CAAW,CACd,EAGM,CAACe,EAAUC,CAAW,EAAI5C,EAAS,IACnC,OAAO,OAAW,IACb,OAAO,WAAa,IAEtB,EACR,EAEDE,EAAU,IAAM,CACd,MAAM2C,EAAe,IAAM,CACzB,MAAMC,EAAc,OAAO,WAAa,IAIxC,GAHAF,EAAYE,CAAW,EAGnBA,EAAa,CACf,MAAMJ,EAAgBd,EAAYK,CAAY,EAC1CS,IAAkBT,GACpBO,EAAaE,CAAa,CAE9B,CACF,EAEA,cAAO,iBAAiB,SAAUG,CAAY,EACvC,IAAM,OAAO,oBAAoB,SAAUA,CAAY,CAChE,EAAG,CAACZ,EAAcL,EAAaY,CAAY,CAAC,EAG5C,MAAMO,EAAmB,GAA2C,CAClE,GAAI,CAACJ,EAAU,OAEfP,EAAc,EAAI,EAClB,MAAMY,EAAU,YAAa,EAAI,EAAE,QAAQ,CAAC,EAAE,QAAU,EAAE,QAC1DX,EAAW,QAAUW,EACrBV,EAAgB,QAAUL,EAG1B,EAAE,eAAe,CACnB,EAGA,OAAA/B,EAAU,IAAM,CACd,GAAI,CAACiC,EAAY,OAEjB,MAAMc,EAAkBC,GAA+B,CACrD,MAAMF,EAAU,YAAaE,EAAIA,EAAE,QAAQ,CAAC,EAAE,QAAUA,EAAE,QACpDC,EAASd,EAAW,QAAUW,EAC9BP,EAAYH,EAAgB,QAAUa,EAE5CX,EAAaC,CAAS,CACxB,EAEMW,EAAgB,IAAM,CAC1BhB,EAAc,EAAK,CACrB,EAEA,gBAAS,iBAAiB,YAAaa,CAAc,EACrD,SAAS,iBAAiB,UAAWG,CAAa,EAClD,SAAS,iBAAiB,YAAaH,EAAgB,CAAE,QAAS,EAAM,CAAC,EACzE,SAAS,iBAAiB,WAAYG,CAAa,EAE5C,IAAM,CACX,SAAS,oBAAoB,YAAaH,CAAc,EACxD,SAAS,oBAAoB,UAAWG,CAAa,EACrD,SAAS,oBAAoB,YAAaH,CAAc,EACxD,SAAS,oBAAoB,WAAYG,CAAa,CACxD,CACF,EAAG,CAACjB,EAAYK,CAAY,CAAC,EAG3BzC,EAAC,OACC,IAAKwC,EACL,UAAW,0DAA0DhB,CAAS,GAC9E,MACE,CACE,aAAc,OACd,uBAAwBoB,GAAY,MACpC,wBAAyBA,GAAY,MACrC,2BAA4B,GAAGV,CAAY,IAC7C,EAID,UAAAU,GACC7C,EAAC,OACC,UAAU,yDACV,MAAO,CACL,YAAa,OACb,gBAAiB,SACnB,EACA,YAAaiD,EACb,aAAcA,EAEd,SAAAjD,EAAC,OACC,MAAO,CACL,MAAO,OACP,OAAQ,MACR,aAAc,QACd,gBAAiB,SACnB,EACF,EACF,EAIFA,EAACM,EAAA,CACC,MAAOU,EACP,QAASC,EACT,QAASH,EACT,aAAcC,EACd,qBAAsBY,EACxB,EAGA3B,EAACO,EAAA,CACC,SAAUG,EACV,iBAAkBY,EAClB,gBAAiBC,EACjB,cAAeH,EACf,WAAYC,EACZ,iBAAkBF,EAClB,YAAaO,EACf,EAGA1B,EAACQ,EAAA,CACC,MAAOG,EACP,SAAUC,EACV,OAAQC,EACR,YAAaW,EACb,SAAUN,EACV,WAAYU,EACd,GACF,CAEJ",
6
6
  "names": ["jsx", "jsxs", "useState", "useRef", "useEffect", "useCallback", "ChatHeader", "MessageList", "ChatInput", "ChatWindow", "messages", "inputValue", "onInputChange", "onSend", "onClose", "onNewSession", "title", "logoUrl", "isSending", "isLoadingHistory", "showTimestamp", "autoScroll", "rendererRegistry", "defaultRenderer", "inputPlaceholder", "className", "onAddToCart", "showNewSessionButton", "bottomTips", "getHeightConstraints", "clampHeight", "height", "minHeight", "maxHeight", "getInitialHeight", "mobileHeight", "setMobileHeight", "isDragging", "setIsDragging", "dragStartY", "dragStartHeight", "windowRef", "updateHeight", "newHeight", "clampedHeight", "isMobile", "setIsMobile", "handleResize", "newIsMobile", "handleDragStart", "clientY", "handleDragMove", "e", "deltaY", "handleDragEnd"]
7
7
  }
@@ -786,7 +786,7 @@ export interface LiveChatWidgetProps {
786
786
  productCardRender?: (product: any) => React.ReactNode;
787
787
  /**
788
788
  * 输入框底部提示文本
789
- * @default "The above content is provided by AI, for reference."
789
+ * 不传入则不显示
790
790
  */
791
791
  bottomTips?: string;
792
792
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anker-in/campaign-ui",
3
- "version": "0.2.11-beta.36",
3
+ "version": "0.2.11-beta.37",
4
4
  "description": "Campaign UI components and utilities for Anker projects",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -53,7 +53,7 @@ export interface ChatInputProps {
53
53
 
54
54
  /**
55
55
  * 底部提示文本
56
- * @default "The above content is provided by AI, for reference."
56
+ * 不传入则不显示
57
57
  */
58
58
  bottomTips?: string
59
59
  }
@@ -107,7 +107,7 @@ export const ChatInput: React.FC<ChatInputProps> = ({
107
107
  disabled = false,
108
108
  autoFocus = false,
109
109
  maxLength = 5000,
110
- bottomTips = 'The above content is provided by AI, for reference.',
110
+ bottomTips,
111
111
  className = '',
112
112
  }) => {
113
113
  const textareaRef = useRef<HTMLTextAreaElement>(null)
@@ -243,9 +243,11 @@ export const ChatInput: React.FC<ChatInputProps> = ({
243
243
  </button>
244
244
  </div>
245
245
  </div>
246
- <p className="px-3 font-bold text-[12px] text-[#767880] tracking-tight leading-[1.4]">
247
- {bottomTips}
248
- </p>
246
+ {bottomTips && (
247
+ <p className="px-3 font-bold text-[12px] text-[#767880] tracking-tight leading-[1.4]">
248
+ {bottomTips}
249
+ </p>
250
+ )}
249
251
  </div>
250
252
  )
251
253
  }
@@ -109,7 +109,7 @@ export interface ChatWindowProps {
109
109
 
110
110
  /**
111
111
  * 输入框底部提示文本
112
- * @default "The above content is provided by AI, for reference."
112
+ * 不传入则不显示
113
113
  */
114
114
  bottomTips?: string
115
115
  }
@@ -970,7 +970,7 @@ export interface LiveChatWidgetProps {
970
970
 
971
971
  /**
972
972
  * 输入框底部提示文本
973
- * @default "The above content is provided by AI, for reference."
973
+ * 不传入则不显示
974
974
  */
975
975
  bottomTips?: string
976
976