@anker-in/campaign-ui 0.2.11-beta.25 → 0.2.11-beta.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/LiveChatWidget/LiveChatWidget.d.ts +23 -1
- package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js +1 -1
- package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/api/chat.d.ts +21 -2
- package/dist/cjs/components/LiveChatWidget/api/chat.js +2 -2
- package/dist/cjs/components/LiveChatWidget/api/chat.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/hooks/useChatAPI.d.ts +5 -0
- package/dist/cjs/components/LiveChatWidget/hooks/useChatAPI.js +1 -1
- package/dist/cjs/components/LiveChatWidget/hooks/useChatAPI.js.map +3 -3
- package/dist/cjs/components/LiveChatWidget/types.d.ts +17 -0
- package/dist/cjs/components/LiveChatWidget/types.js +1 -1
- package/dist/cjs/components/LiveChatWidget/types.js.map +1 -1
- package/dist/cjs/components/LiveChatWidget/utils/fetcher.d.ts +36 -0
- package/dist/cjs/components/LiveChatWidget/utils/fetcher.js +2 -0
- package/dist/cjs/components/LiveChatWidget/utils/fetcher.js.map +7 -0
- package/dist/cjs/stories/LiveChatWidget.stories.d.ts +8 -0
- package/dist/cjs/stories/LiveChatWidget.stories.js +35 -3
- package/dist/cjs/stories/LiveChatWidget.stories.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/LiveChatWidget.d.ts +23 -1
- package/dist/esm/components/LiveChatWidget/LiveChatWidget.js +1 -1
- package/dist/esm/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/api/chat.d.ts +21 -2
- package/dist/esm/components/LiveChatWidget/api/chat.js +2 -2
- package/dist/esm/components/LiveChatWidget/api/chat.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/hooks/useChatAPI.d.ts +5 -0
- package/dist/esm/components/LiveChatWidget/hooks/useChatAPI.js +1 -1
- package/dist/esm/components/LiveChatWidget/hooks/useChatAPI.js.map +3 -3
- package/dist/esm/components/LiveChatWidget/types.d.ts +17 -0
- package/dist/esm/components/LiveChatWidget/utils/fetcher.d.ts +36 -0
- package/dist/esm/components/LiveChatWidget/utils/fetcher.js +2 -0
- package/dist/esm/components/LiveChatWidget/utils/fetcher.js.map +7 -0
- package/dist/esm/stories/LiveChatWidget.stories.d.ts +8 -0
- package/dist/esm/stories/LiveChatWidget.stories.js +38 -6
- package/dist/esm/stories/LiveChatWidget.stories.js.map +3 -3
- package/package.json +3 -3
- package/src/components/LiveChatWidget/LiveChatWidget.tsx +31 -1
- package/src/components/LiveChatWidget/api/chat.ts +40 -11
- package/src/components/LiveChatWidget/hooks/useChatAPI.ts +11 -6
- package/src/components/LiveChatWidget/types.ts +20 -0
- package/src/components/LiveChatWidget/utils/fetcher.ts +126 -0
- package/src/stories/LiveChatWidget.stories.tsx +82 -4
|
@@ -15,6 +15,7 @@ import type { LiveChatWidgetProps } from './types';
|
|
|
15
15
|
* - 历史消息加载
|
|
16
16
|
* - 多种消息类型渲染
|
|
17
17
|
* - 自定义扩展机制
|
|
18
|
+
* - reCAPTCHA v3 安全防护
|
|
18
19
|
*
|
|
19
20
|
* 架构:
|
|
20
21
|
* - UI Layer: ChatBubble, ChatWindow, MessageList, etc.
|
|
@@ -23,7 +24,7 @@ import type { LiveChatWidgetProps } from './types';
|
|
|
23
24
|
*
|
|
24
25
|
* @example
|
|
25
26
|
* ```tsx
|
|
26
|
-
* //
|
|
27
|
+
* // 基础使用(使用默认位置)
|
|
27
28
|
* <LiveChatWidget
|
|
28
29
|
* apiBaseUrl="https://beta-api-livechat.anker.com"
|
|
29
30
|
* site="www.eufy.com"
|
|
@@ -38,6 +39,27 @@ import type { LiveChatWidgetProps } from './types';
|
|
|
38
39
|
* position={{ bottom: "20px", right: "30px" }}
|
|
39
40
|
* onMessageSend={(msg) => console.log('Sent:', msg)}
|
|
40
41
|
* />
|
|
42
|
+
*
|
|
43
|
+
* // 启用 reCAPTCHA v3 安全防护
|
|
44
|
+
* <LiveChatWidget
|
|
45
|
+
* apiBaseUrl="https://beta-api-livechat.anker.com"
|
|
46
|
+
* site="www.eufy.com"
|
|
47
|
+
* needRecaptcha={true}
|
|
48
|
+
* recaptchaSitekey="6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14"
|
|
49
|
+
* recaptchaAction="livechat"
|
|
50
|
+
* />
|
|
51
|
+
*
|
|
52
|
+
* // 使用自定义 headers 和 reCAPTCHA
|
|
53
|
+
* <LiveChatWidget
|
|
54
|
+
* apiBaseUrl="https://beta-api-livechat.anker.com"
|
|
55
|
+
* site="www.eufy.com"
|
|
56
|
+
* headers={{
|
|
57
|
+
* "Authorization": "Bearer your-token",
|
|
58
|
+
* "X-Custom-Header": "value"
|
|
59
|
+
* }}
|
|
60
|
+
* needRecaptcha={true}
|
|
61
|
+
* recaptchaSitekey="your-site-key"
|
|
62
|
+
* />
|
|
41
63
|
* ```
|
|
42
64
|
*/
|
|
43
65
|
export declare const LiveChatWidget: React.FC<LiveChatWidgetProps>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var Se=Object.create;var B=Object.defineProperty;var Me=Object.getOwnPropertyDescriptor;var De=Object.getOwnPropertyNames;var Re=Object.getPrototypeOf,qe=Object.prototype.hasOwnProperty;var Pe=(n,o)=>{for(var p in o)B(n,p,{get:o[p],enumerable:!0})},j=(n,o,p,R)=>{if(o&&typeof o=="object"||typeof o=="function")for(let g of De(o))!qe.call(n,g)&&g!==p&&B(n,g,{get:()=>o[g],enumerable:!(R=Me(o,g))||R.enumerable});return n};var k=(n,o,p)=>(p=n!=null?Se(Re(n)):{},j(o||!n||!n.__esModule?B(p,"default",{value:n,enumerable:!0}):p,n)),ve=n=>j(B({},"__esModule",{value:!0}),n);var Be={};Pe(Be,{LiveChatWidget:()=>Te});module.exports=ve(Be);var m=require("react/jsx-runtime"),d=k(require("react")),z=k(require("@radix-ui/react-dialog")),U=require("./constants"),b=require("./components/ChatBubble"),ee=require("./components/ChatWindow"),te=require("./hooks/useChatState"),se=require("./hooks/useChatAPI"),re=require("./utils/messageRenderers"),ae=require("./utils/validation"),N=require("./utils/productTransformers.js"),ne=require("./utils/cartTransformers.js"),r=require("./components/MessageContent/index.js");const Te=({apiBaseUrl:n,headers:o,needRecaptcha:p,recaptchaSitekey:R,recaptchaAction:g,site:u,loginUserId:y,cartId:$,accessToken:O,position:ie,welcomeMessage:x,quickReplies:f,customRenderers:L,logoUrl:oe,title:de,chatBubbleIcon:ce,onOpen:pe,onClose:ue,onMessageSend:F,onError:h,onTextMessage:le,onProductList:me,onPromotionList:fe,onAddToCart:q,onCart:A,showNewSessionButton:ge,commonText:V})=>{const w=d.default.useMemo(()=>({...U.DEFAULT_COMMON_TEXT,...V}),[V]),ye=(0,te.useChatState)({welcomeMessage:x,site:u,onOpen:pe,onClose:ue,onMessageSend:F,onError:h,onTextMessage:le,onProductList:me,onPromotionList:fe,onAddToCart:q,onCart:A}),{messages:he,isOpen:P,userId:l,sessionId:I,inputValue:W,isStreaming:_e,openChat:X,closeChat:G,setInputValue:E,addMessage:_,setMessages:H,clearMessages:v,handleSSEEvent:J,saveSession:T,clearSession:S}=ye,{sendMessageStream:K,createSession:C}=(0,se.useChatAPI)({apiBaseUrl:n,headers:o,recaptchaConfig:{needRecaptcha:p,recaptchaSitekey:R,recaptchaAction:g},onError:h}),Y=d.default.useRef(async e=>{}),Ce=d.default.useMemo(()=>{const e=new re.MessageRendererRegistry;e.register("text",r.TextBlock),e.register("product_card",r.ProductCard),e.register("product_list",r.ProductList),e.register("product_comparison",r.ProductComparisonRenderer),e.register("policy",r.PolicyBlock),e.register("thinking",r.ThinkingBlock),e.register("error",r.ErrorBlock),e.register("faq_list",r.FAQListRenderer),e.register("promotion_list",r.PromotionListRenderer),e.register("cart",r.CartCard);const s=(0,r.createQuickRepliesRenderer)(a=>{Y.current(a.value)});return e.register("quick_replies",s),L&&e.registerMany(L),e},[L]);(0,d.useEffect)(()=>{if(!P||!l)return;const e=I;e?xe(e):M()},[P,l]);const Z=(0,d.useCallback)(e=>{if(Array.isArray(e.content))return e;const s=[];typeof e.content=="string"&&e.content.trim()&&s.push({type:"text",text:e.content});const a=e.structuredContent||e.structured_content;return Array.isArray(a)&&a.forEach(t=>{if(t.type==="product_list"&&Array.isArray(t.data))s.push({type:"product_list",data:{products:(0,N.transformProducts)(t.data,u),title:void 0,commonText:w}});else if(t.type==="quick_replies"&&t.data?.replies)s.push({type:"quick_replies",data:{replies:t.data.replies}});else if(t.type==="policy"&&t.data?.title&&t.data?.content)s.push({type:"policy",data:{title:t.data.title,content:t.data.content}});else if(t.type==="product_comparison"&&t.data?.products&&t.data?.dimensions)s.push({type:"product_comparison",data:{products:(0,N.transformProducts)(t.data.products,u),dimensions:t.data.dimensions,onAddToCart:q,commonText:w}});else if(t.type==="faq_list"&&t.data?.found!==void 0)s.push({type:"faq_list",data:t.data});else if(t.type==="promotion_list"&&t.data?.found!==void 0)s.push({type:"promotion_list",data:{...t.data,commonText:w}});else if(t.type==="cart"&&t.data?.id!==void 0){const i=(0,ne.transformCartData)(t.data);s.push({type:"cart",data:{...i,onCart:A,commonText:w}})}else s.push(t)}),s.length===0&&s.push({type:"text",text:""}),{...e,content:s}},[u,A,q,w]),M=(0,d.useCallback)(async()=>{if(l)try{const e=await C({user_id:l,site:u,real_user_id:y});if(e.success){T(e.sessionId),v();const s=e.welcomeMessage||x;if(s){const a=[{type:"text",text:s}],t=e.quickQuestions;if(t&&t.length>0){const i=t.map((D,c)=>({id:`quick-${c}`,label:D,value:D}));a.push({type:"quick_replies",data:{replies:i}})}else f&&f.length>0&&a.push({type:"quick_replies",data:{replies:f}});_({id:`welcome-${Date.now()}`,role:"assistant",content:a,timestamp:Date.now()})}}}catch(e){console.error("[LiveChatWidget] Failed to create new session:",e),h?.(e)}},[l,u,y,C,T,v,x,f,_,h]),xe=(0,d.useCallback)(async e=>{try{const s=await C({user_id:l,session_id:e,site:u,real_user_id:y});if(s.success&&s.resumed)if(s.messages&&s.messages.length>0){const a=s.messages.filter(t=>t!=null).map(Z);H(a)}else{v();const a=s.welcomeMessage||x;if(a){const t=[{type:"text",text:a}],i=s.quickQuestions;if(i&&i.length>0){const D=i.map((c,we)=>({id:`quick-${we}`,label:c,value:c}));t.push({type:"quick_replies",data:{replies:D}})}else f&&f.length>0&&t.push({type:"quick_replies",data:{replies:f}});_({id:`welcome-${Date.now()}`,role:"assistant",content:t,timestamp:Date.now()})}}else s.resumed||(S(),M())}catch(s){console.error("[LiveChatWidget] Failed to resume session:",s),S(),M()}},[l,u,y,C,H,S,v,Z,M,x,f,_]),Q=(0,d.useCallback)(async e=>{const s=e||W.trim();if(!s)return;e||E("");const a=(0,ae.sanitizeInput)(s);if(!a){h?.(new Error("Invalid message"));return}const t={id:`user-${Date.now()}`,role:"user",content:[{type:"text",text:a}],timestamp:Date.now()};_(t),F?.(a);try{let i=I;if(!i){const c=await C({user_id:l,site:u,real_user_id:y});if(c.success)i=c.sessionId,T(i);else throw new Error("Failed to create session")}await K({message:a,user_id:l,session_id:i,context:{cartId:$,accessToken:O,real_user_id:y}},c=>{J(c),c.event==="status"&&c.data.type==="session_expired"&&S()})}catch(i){console.error("[LiveChatWidget] Failed to send message:",i),h?.(i)}},[W,l,I,u,y,$,O,E,_,C,K,J,T,S,F,h]);return d.default.useEffect(()=>{Y.current=Q},[Q]),(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(b.ChatBubble,{position:ie,onClick:X,visible:!P,iconImageUrl:ce}),(0,m.jsx)(z.Root,{open:P,onOpenChange:e=>e?X():G(),children:(0,m.jsx)(z.Portal,{children:(0,m.jsx)(z.Content,{className:"livechat-window-enter",style:{position:"fixed",zIndex:9998},children:(0,m.jsx)(ee.ChatWindow,{messages:he,inputValue:W,onInputChange:E,onSend:()=>Q(),onClose:G,onNewSession:M,title:de,logoUrl:oe,isSending:_e,rendererRegistry:Ce,inputPlaceholder:"",onAddToCart:q,showNewSessionButton:ge})})})})]})};
|
|
2
2
|
//# sourceMappingURL=LiveChatWidget.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/components/LiveChatWidget/LiveChatWidget.tsx"],
|
|
4
|
-
"sourcesContent": ["/**\n * LiveChat \u4E3B\u7EC4\u4EF6\n * \u96C6\u6210\u6240\u6709\u5B50\u7EC4\u4EF6\uFF0C\u63D0\u4F9B\u5B8C\u6574\u7684\u804A\u5929\u529F\u80FD\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u4E09\u5C42\u67B6\u6784\u8BBE\u8BA1\n */\n\nimport React, { useEffect, useCallback } from 'react'\nimport * as Dialog from '@radix-ui/react-dialog'\nimport type {\n LiveChatWidgetProps,\n QuickReply,\n Message,\n MessageContent,\n ChatStreamRequest,\n BackendCartData,\n CommonText,\n} from './types'\nimport { DEFAULT_COMMON_TEXT } from './constants'\nimport { ChatBubble } from './components/ChatBubble'\nimport { ChatWindow } from './components/ChatWindow'\nimport { useChatState } from './hooks/useChatState'\nimport { useChatAPI } from './hooks/useChatAPI'\nimport { MessageRendererRegistry } from './utils/messageRenderers'\nimport { sanitizeInput } from './utils/validation'\nimport { transformProducts } from './utils/productTransformers.js'\nimport { transformCartData } from './utils/cartTransformers.js'\nimport {\n TextBlock,\n ProductCard,\n ProductList,\n ProductComparisonRenderer,\n PolicyBlock,\n createQuickRepliesRenderer,\n ThinkingBlock,\n ErrorBlock,\n FAQListRenderer,\n PromotionListRenderer,\n CartCard,\n} from './components/MessageContent/index.js'\n\n/**\n * LiveChat \u804A\u5929\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u6C14\u6CE1\u5F39\u7A97\u804A\u5929\u754C\u9762\n * - SSE \u6D41\u5F0F\u6D88\u606F\u63A5\u6536\n * - \u4F1A\u8BDD\u7BA1\u7406\uFF08userId, sessionId\uFF09\n * - \u5386\u53F2\u6D88\u606F\u52A0\u8F7D\n * - \u591A\u79CD\u6D88\u606F\u7C7B\u578B\u6E32\u67D3\n * - \u81EA\u5B9A\u4E49\u6269\u5C55\u673A\u5236\n *\n * \u67B6\u6784\uFF1A\n * - UI Layer: ChatBubble, ChatWindow, MessageList, etc.\n * - Logic Layer: useChatState, useChatAPI, useSession\n * - Core Layer: MessageRendererRegistry, \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\n *\n * @example\n * ```tsx\n * // \u4F7F\u7528\u9ED8\u8BA4\u4F4D\u7F6E\uFF08\u53F3\u4E0B\u89D2\uFF09\n * <LiveChatWidget\n * apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n * site=\"www.eufy.com\"\n * welcomeMessage=\"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\"\n * onMessageSend={(msg) => console.log('Sent:', msg)}\n * />\n *\n * // \u4F7F\u7528\u81EA\u5B9A\u4E49\u4F4D\u7F6E\n * <LiveChatWidget\n * apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n * site=\"www.eufy.com\"\n * position={{ bottom: \"20px\", right: \"30px\" }}\n * onMessageSend={(msg) => console.log('Sent:', msg)}\n * />\n * ```\n */\nexport const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({\n apiBaseUrl,\n headers,\n site,\n loginUserId,\n cartId,\n accessToken,\n position,\n welcomeMessage,\n quickReplies,\n customRenderers,\n logoUrl,\n title,\n chatBubbleIcon,\n onOpen,\n onClose,\n onMessageSend,\n onError,\n onTextMessage,\n onProductList,\n onPromotionList,\n onAddToCart,\n onCart,\n showNewSessionButton,\n commonText,\n}) => {\n // \u5408\u5E76\u9ED8\u8BA4\u6587\u6848\u548C\u81EA\u5B9A\u4E49\u6587\u6848\n const mergedText: Required<CommonText> = React.useMemo(\n () => ({\n ...DEFAULT_COMMON_TEXT,\n ...commonText,\n }),\n [commonText]\n )\n\n // \u72B6\u6001\u7BA1\u7406\n const chatState = useChatState({\n welcomeMessage,\n site,\n onOpen,\n onClose,\n onMessageSend,\n onError,\n onTextMessage,\n onProductList,\n onPromotionList,\n onAddToCart,\n onCart,\n })\n\n const {\n messages,\n isOpen,\n userId,\n sessionId,\n inputValue,\n isStreaming,\n openChat,\n closeChat,\n setInputValue,\n addMessage,\n setMessages,\n clearMessages,\n handleSSEEvent,\n saveSession,\n clearSession,\n } = chatState\n\n // API \u8C03\u7528\n const { sendMessageStream, createSession } = useChatAPI({\n apiBaseUrl,\n headers,\n onError,\n })\n\n // \u4F7F\u7528 ref \u5B58\u50A8\u6700\u65B0\u7684 handleSendMessage\uFF0C\u907F\u514D\u5FAA\u73AF\u4F9D\u8D56\n const handleSendMessageRef = React.useRef<(_message?: string) => Promise<void>>(async (_message?: string) => {})\n\n // \u6D88\u606F\u6E32\u67D3\u5668\u6CE8\u518C\u8868\n const rendererRegistry = React.useMemo(() => {\n const registry = new MessageRendererRegistry()\n\n // \u6CE8\u518C\u9ED8\u8BA4\u6E32\u67D3\u5668\n registry.register('text', TextBlock)\n registry.register('product_card', ProductCard)\n registry.register('product_list', ProductList)\n registry.register('product_comparison', ProductComparisonRenderer)\n registry.register('policy', PolicyBlock)\n registry.register('thinking', ThinkingBlock)\n registry.register('error', ErrorBlock)\n registry.register('faq_list', FAQListRenderer)\n registry.register('promotion_list', PromotionListRenderer)\n registry.register('cart', CartCard)\n\n // \u6CE8\u518C\u5FEB\u6377\u56DE\u590D\u6E32\u67D3\u5668\uFF08\u5E26\u56DE\u8C03\uFF09\n const quickRepliesRenderer = createQuickRepliesRenderer((reply: QuickReply) => {\n // \u4F7F\u7528 ref \u8C03\u7528\u6700\u65B0\u7684 handleSendMessage\n handleSendMessageRef.current(reply.value)\n })\n registry.register('quick_replies', quickRepliesRenderer)\n\n // \u6CE8\u518C\u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\n if (customRenderers) {\n registry.registerMany(customRenderers)\n }\n\n return registry\n }, [customRenderers])\n\n /**\n * T043: \u6253\u5F00\u804A\u5929\u7A97\u53E3\u65F6\u521D\u59CB\u5316\u4F1A\u8BDD\n * \u4F7F\u7528 API v2.0.0 \u7684\u7EDF\u4E00\u63A5\u53E3\uFF1A\n * - \u5982\u679C\u6CA1\u6709 sessionId\uFF0C\u521B\u5EFA\u65B0\u4F1A\u8BDD\n * - \u5982\u679C\u6709 sessionId\uFF0C\u6062\u590D\u4F1A\u8BDD\u5E76\u52A0\u8F7D\u5386\u53F2\u6D88\u606F\n */\n useEffect(() => {\n if (!isOpen || !userId) return\n\n const currentSessionId = sessionId\n\n if (!currentSessionId) {\n // \u6CA1\u6709\u4F1A\u8BDD\uFF0C\u521B\u5EFA\u65B0\u4F1A\u8BDD\n handleCreateNewSession()\n } else {\n // \u6709\u4F1A\u8BDD\uFF0C\u5C1D\u8BD5\u6062\u590D\u4F1A\u8BDD\u548C\u5386\u53F2\u6D88\u606F\n handleResumeSession(currentSessionId)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isOpen, userId])\n\n /**\n * \u89C4\u8303\u5316\u6D88\u606F\u683C\u5F0F\uFF08\u786E\u4FDD content \u662F\u6570\u7EC4\uFF09\n * \u540E\u7AEF\u8FD4\u56DE\u683C\u5F0F\uFF1A\n * - content: \u5B57\u7B26\u4E32\uFF08\u6587\u672C\u5185\u5BB9\uFF09\n * - structuredContent: \u6570\u7EC4\uFF08\u7ED3\u6784\u5316\u5185\u5BB9\uFF0C\u5982\u4EA7\u54C1\u5217\u8868\u3001\u653F\u7B56\u7B49\uFF09\n * \u9700\u8981\u5408\u5E76\u4E3A\u7EDF\u4E00\u7684 content \u6570\u7EC4\u683C\u5F0F\n */\n const normalizeMessage = useCallback(\n (message: any): Message => {\n // \u5982\u679C content \u5DF2\u7ECF\u662F\u6570\u7EC4\uFF0C\u76F4\u63A5\u8FD4\u56DE\n if (Array.isArray(message.content)) {\n return message as Message\n }\n\n const contentBlocks: MessageContent[] = []\n\n // \u5904\u7406\u6587\u672C\u5185\u5BB9\n if (typeof message.content === 'string' && message.content.trim()) {\n contentBlocks.push({\n type: 'text',\n text: message.content,\n })\n }\n\n // \u5904\u7406\u7ED3\u6784\u5316\u5185\u5BB9\n // \u5386\u53F2\u6D88\u606F\u683C\u5F0F: structured_content: [{type, data}]\n const structuredData = message.structuredContent || message.structured_content\n\n if (Array.isArray(structuredData)) {\n structuredData.forEach((block: any) => {\n if (block.type === 'product_list' && Array.isArray(block.data)) {\n // \u8F6C\u6362\u4EA7\u54C1\u5217\u8868\u6570\u636E\u7ED3\u6784 - data \u76F4\u63A5\u662F\u4EA7\u54C1\u6570\u7EC4\n contentBlocks.push({\n type: 'product_list',\n data: {\n products: transformProducts(block.data, site),\n title: undefined, // \u5386\u53F2\u6D88\u606F\u4E0D\u5305\u542B title\n commonText: mergedText,\n },\n })\n } else if (block.type === 'quick_replies' && block.data?.replies) {\n contentBlocks.push({\n type: 'quick_replies',\n data: {\n replies: block.data.replies,\n },\n })\n } else if (block.type === 'policy' && block.data?.title && block.data?.content) {\n contentBlocks.push({\n type: 'policy',\n data: {\n title: block.data.title,\n content: block.data.content,\n },\n })\n } else if (block.type === 'product_comparison' && block.data?.products && block.data?.dimensions) {\n // \u8F6C\u6362\u4EA7\u54C1\u5BF9\u6BD4\u6570\u636E\u7ED3\u6784\u5E76\u6CE8\u5165 onAddToCart \u56DE\u8C03\n contentBlocks.push({\n type: 'product_comparison',\n data: {\n products: transformProducts(block.data.products, site),\n dimensions: block.data.dimensions,\n onAddToCart: onAddToCart,\n commonText: mergedText,\n },\n })\n } else if (block.type === 'faq_list' && block.data?.found !== undefined) {\n // FAQ \u5217\u8868\u5361\u7247 - \u76F4\u63A5\u4F7F\u7528\u540E\u7AEF\u6570\u636E\n contentBlocks.push({\n type: 'faq_list',\n data: block.data,\n })\n } else if (block.type === 'promotion_list' && block.data?.found !== undefined) {\n // \u4FC3\u9500\u6D3B\u52A8\u5217\u8868\u5361\u7247 - \u76F4\u63A5\u4F7F\u7528\u540E\u7AEF\u6570\u636E\n contentBlocks.push({\n type: 'promotion_list',\n data: {\n ...block.data,\n commonText: mergedText,\n },\n })\n } else if (block.type === 'cart' && block.data?.id !== undefined) {\n // \u8D2D\u7269\u8F66\u5361\u7247 - \u8F6C\u6362\u540E\u7AEF\u6570\u636E\u683C\u5F0F\u5E76\u6CE8\u5165 onCart \u56DE\u8C03\n const transformedData = transformCartData(block.data as BackendCartData)\n contentBlocks.push({\n type: 'cart',\n data: {\n ...transformedData,\n onCart: onCart,\n commonText: mergedText,\n },\n })\n } else {\n // \u5176\u4ED6\u7C7B\u578B\u76F4\u63A5\u6DFB\u52A0\n contentBlocks.push(block)\n }\n })\n }\n\n // \u5982\u679C\u6CA1\u6709\u4EFB\u4F55\u5185\u5BB9\u5757\uFF0C\u8FD4\u56DE\u7A7A\u6587\u672C\u5757\n if (contentBlocks.length === 0) {\n contentBlocks.push({\n type: 'text',\n text: '',\n })\n }\n\n return {\n ...message,\n content: contentBlocks,\n } as Message\n },\n [site, onCart, onAddToCart, mergedText]\n )\n\n /**\n * \u521B\u5EFA\u65B0\u4F1A\u8BDD\n */\n const handleCreateNewSession = useCallback(async () => {\n if (!userId) return\n\n try {\n const response = await createSession({\n user_id: userId,\n site: site,\n real_user_id: loginUserId,\n })\n\n if (response.success) {\n // \u4FDD\u5B58\u65B0\u4F1A\u8BDD ID\n saveSession(response.sessionId)\n\n // \u6E05\u7A7A\u6D88\u606F\u5217\u8868\n clearMessages()\n\n // \u4F7F\u7528\u540E\u7AEF\u8FD4\u56DE\u7684\u6B22\u8FCE\u6D88\u606F\uFF0C\u5982\u679C\u6CA1\u6709\u5219\u4F7F\u7528 props \u4E2D\u7684\u9ED8\u8BA4\u503C\n const messageText = response.welcomeMessage || welcomeMessage\n\n if (messageText) {\n const welcomeContent: MessageContent[] = [{ type: 'text', text: messageText }]\n\n // \u4F7F\u7528\u540E\u7AEF\u8FD4\u56DE\u7684\u5FEB\u6377\u95EE\u9898\uFF0C\u5982\u679C\u6CA1\u6709\u5219\u4F7F\u7528 props \u4E2D\u7684\u5FEB\u6377\u56DE\u590D\n const questions = response.quickQuestions\n if (questions && questions.length > 0) {\n // \u5C06\u540E\u7AEF\u7684 quickQuestions (\u5B57\u7B26\u4E32\u6570\u7EC4) \u8F6C\u6362\u4E3A QuickReply \u683C\u5F0F\n const quickRepliesFromBackend = questions.map((question, index) => ({\n id: `quick-${index}`,\n label: question,\n value: question,\n }))\n\n welcomeContent.push({\n type: 'quick_replies',\n data: {\n replies: quickRepliesFromBackend,\n },\n })\n } else if (quickReplies && quickReplies.length > 0) {\n // \u5982\u679C\u540E\u7AEF\u6CA1\u6709\u8FD4\u56DE\uFF0C\u4F7F\u7528 props \u4E2D\u7684\u5FEB\u6377\u56DE\u590D\n welcomeContent.push({\n type: 'quick_replies',\n data: {\n replies: quickReplies,\n },\n })\n }\n\n addMessage({\n id: `welcome-${Date.now()}`,\n role: 'assistant',\n content: welcomeContent,\n timestamp: Date.now(),\n })\n }\n }\n } catch (error) {\n console.error('[LiveChatWidget] Failed to create new session:', error)\n onError?.(error as Error)\n }\n }, [\n userId,\n site,\n loginUserId,\n createSession,\n saveSession,\n clearMessages,\n welcomeMessage,\n quickReplies,\n addMessage,\n onError,\n ])\n\n /**\n * \u6062\u590D\u4F1A\u8BDD\u548C\u5386\u53F2\u6D88\u606F\uFF08\u4F7F\u7528\u65B0\u7684 API v2.0.0\uFF09\n */\n const handleResumeSession = useCallback(\n async (existingSessionId: string) => {\n try {\n const response = await createSession({\n user_id: userId,\n session_id: existingSessionId,\n site: site,\n real_user_id: loginUserId,\n })\n\n if (response.success && response.resumed) {\n // \u4F1A\u8BDD\u6062\u590D\u6210\u529F\n if (response.messages && response.messages.length > 0) {\n // \u6709\u5386\u53F2\u6D88\u606F\uFF0C\u89C4\u8303\u5316\u5E76\u52A0\u8F7D\uFF08\u8FC7\u6EE4\u6389 null/undefined \u503C\uFF09\n const normalizedMessages = response.messages.filter((msg: any) => msg != null).map(normalizeMessage)\n setMessages(normalizedMessages)\n } else {\n // \u6CA1\u6709\u5386\u53F2\u6D88\u606F\uFF0C\u663E\u793A\u6B22\u8FCE\u6D88\u606F\n clearMessages()\n\n // \u4F7F\u7528\u540E\u7AEF\u8FD4\u56DE\u7684\u6B22\u8FCE\u6D88\u606F\uFF0C\u5982\u679C\u6CA1\u6709\u5219\u4F7F\u7528 props \u4E2D\u7684\u9ED8\u8BA4\u503C\n const messageText = response.welcomeMessage || welcomeMessage\n\n if (messageText) {\n const welcomeContent: MessageContent[] = [{ type: 'text', text: messageText }]\n\n // \u4F7F\u7528\u540E\u7AEF\u8FD4\u56DE\u7684\u5FEB\u6377\u95EE\u9898\n const questions = response.quickQuestions\n if (questions && questions.length > 0) {\n const quickRepliesFromBackend = questions.map((question, index) => ({\n id: `quick-${index}`,\n label: question,\n value: question,\n }))\n\n welcomeContent.push({\n type: 'quick_replies',\n data: {\n replies: quickRepliesFromBackend,\n },\n })\n } else if (quickReplies && quickReplies.length > 0) {\n welcomeContent.push({\n type: 'quick_replies',\n data: {\n replies: quickReplies,\n },\n })\n }\n\n addMessage({\n id: `welcome-${Date.now()}`,\n role: 'assistant',\n content: welcomeContent,\n timestamp: Date.now(),\n })\n }\n }\n } else if (!response.resumed) {\n // \u4F1A\u8BDD\u65E0\u6548\u6216\u8FC7\u671F\uFF0C\u521B\u5EFA\u65B0\u4F1A\u8BDD\n clearSession()\n handleCreateNewSession()\n }\n } catch (error) {\n console.error('[LiveChatWidget] Failed to resume session:', error)\n // \u6062\u590D\u5931\u8D25\uFF0C\u6E05\u7A7A\u4F1A\u8BDD\u5E76\u521B\u5EFA\u65B0\u7684\n clearSession()\n handleCreateNewSession()\n }\n },\n [\n userId,\n site,\n loginUserId,\n createSession,\n setMessages,\n clearSession,\n clearMessages,\n normalizeMessage,\n handleCreateNewSession,\n welcomeMessage,\n quickReplies,\n addMessage,\n ]\n )\n\n /**\n * \u53D1\u9001\u6D88\u606F\n */\n const handleSendMessage = useCallback(\n async (message?: string) => {\n const textToSend = message || inputValue.trim()\n\n if (!textToSend) return\n\n // \u6E05\u7A7A\u8F93\u5165\u6846\n if (!message) {\n setInputValue('')\n }\n\n // \u8F93\u5165\u9A8C\u8BC1\n const sanitized = sanitizeInput(textToSend)\n if (!sanitized) {\n onError?.(new Error('Invalid message'))\n return\n }\n\n // \u6DFB\u52A0\u7528\u6237\u6D88\u606F\u5230\u754C\u9762\n const userMessage = {\n id: `user-${Date.now()}`,\n role: 'user' as const,\n content: [{ type: 'text' as const, text: sanitized }],\n timestamp: Date.now(),\n }\n addMessage(userMessage)\n\n // \u89E6\u53D1\u6D88\u606F\u53D1\u9001\u56DE\u8C03\n onMessageSend?.(sanitized)\n\n try {\n // \u786E\u4FDD\u6709 sessionId\uFF0C\u5982\u679C\u6CA1\u6709\u5219\u5148\u521B\u5EFA\u4F1A\u8BDD\n let currentSessionId = sessionId\n if (!currentSessionId) {\n // \u6CA1\u6709\u4F1A\u8BDD\uFF0C\u521B\u5EFA\u65B0\u4F1A\u8BDD\n const response = await createSession({\n user_id: userId,\n site: site,\n real_user_id: loginUserId,\n })\n if (response.success) {\n currentSessionId = response.sessionId\n saveSession(currentSessionId)\n } else {\n throw new Error('Failed to create session')\n }\n }\n\n // \u6784\u5EFA\u8BF7\u6C42\u53C2\u6570\uFF08session_id \u73B0\u5728\u662F\u5FC5\u586B\u7684\uFF09\n const requestPayload: ChatStreamRequest = {\n message: sanitized,\n user_id: userId,\n session_id: currentSessionId,\n context: {\n cartId: cartId,\n accessToken: accessToken,\n real_user_id: loginUserId,\n },\n }\n\n // \u53D1\u9001\u6D88\u606F\u5230\u540E\u7AEF\n await sendMessageStream(requestPayload, event => {\n // \u5904\u7406 SSE \u4E8B\u4EF6\n handleSSEEvent(event)\n\n // \u7279\u6B8A\u5904\u7406\uFF1A\u4F1A\u8BDD\u8FC7\u671F\n if (event.event === 'status' && event.data.type === 'session_expired') {\n clearSession()\n }\n })\n } catch (error) {\n console.error('[LiveChatWidget] Failed to send message:', error)\n onError?.(error as Error)\n }\n },\n [\n inputValue,\n userId,\n sessionId,\n site,\n loginUserId,\n cartId,\n accessToken,\n setInputValue,\n addMessage,\n createSession,\n sendMessageStream,\n handleSSEEvent,\n saveSession,\n clearSession,\n onMessageSend,\n onError,\n ]\n )\n\n // \u66F4\u65B0 ref \u4EE5\u4FDD\u6301\u6700\u65B0\u7684 handleSendMessage\n React.useEffect(() => {\n handleSendMessageRef.current = handleSendMessage\n }, [handleSendMessage])\n\n return (\n <>\n {/* \u6C14\u6CE1\u6309\u94AE */}\n <ChatBubble position={position} onClick={openChat} visible={!isOpen} iconImageUrl={chatBubbleIcon} />\n\n {/* \u804A\u5929\u7A97\u53E3\uFF08\u4F7F\u7528 Radix UI Dialog\uFF09 */}\n <Dialog.Root open={isOpen} onOpenChange={open => (open ? openChat() : closeChat())}>\n <Dialog.Portal>\n <Dialog.Content\n className=\"livechat-window-enter\"\n style={{\n position: 'fixed',\n zIndex: 9998,\n }}\n >\n <ChatWindow\n messages={messages}\n inputValue={inputValue}\n onInputChange={setInputValue}\n onSend={() => handleSendMessage()}\n onClose={closeChat}\n onNewSession={handleCreateNewSession}\n title={title}\n logoUrl={logoUrl}\n isSending={isStreaming}\n rendererRegistry={rendererRegistry}\n inputPlaceholder=\"\"\n onAddToCart={onAddToCart}\n showNewSessionButton={showNewSessionButton}\n />\n </Dialog.Content>\n </Dialog.Portal>\n </Dialog.Root>\n </>\n )\n}\n"],
|
|
5
|
-
"mappings": "skBAAA,IAAAA,GAAA,GAAAC,GAAAD,GAAA,oBAAAE,KAAA,eAAAC,GAAAH,
|
|
6
|
-
"names": ["LiveChatWidget_exports", "__export", "LiveChatWidget", "__toCommonJS", "import_jsx_runtime", "import_react", "Dialog", "import_constants", "import_ChatBubble", "import_ChatWindow", "import_useChatState", "import_useChatAPI", "import_messageRenderers", "import_validation", "import_productTransformers", "import_cartTransformers", "import_MessageContent", "apiBaseUrl", "headers", "site", "loginUserId", "cartId", "accessToken", "position", "welcomeMessage", "quickReplies", "customRenderers", "logoUrl", "title", "chatBubbleIcon", "onOpen", "onClose", "onMessageSend", "onError", "onTextMessage", "onProductList", "onPromotionList", "onAddToCart", "onCart", "showNewSessionButton", "commonText", "mergedText", "React", "chatState", "messages", "isOpen", "userId", "sessionId", "inputValue", "isStreaming", "openChat", "closeChat", "setInputValue", "addMessage", "setMessages", "clearMessages", "handleSSEEvent", "saveSession", "clearSession", "sendMessageStream", "createSession", "handleSendMessageRef", "_message", "rendererRegistry", "registry", "quickRepliesRenderer", "reply", "currentSessionId", "handleResumeSession", "handleCreateNewSession", "normalizeMessage", "message", "contentBlocks", "structuredData", "block", "transformedData", "response", "messageText", "welcomeContent", "questions", "quickRepliesFromBackend", "question", "index", "error", "existingSessionId", "normalizedMessages", "msg", "handleSendMessage", "textToSend", "sanitized", "userMessage", "event", "open"]
|
|
4
|
+
"sourcesContent": ["/**\n * LiveChat \u4E3B\u7EC4\u4EF6\n * \u96C6\u6210\u6240\u6709\u5B50\u7EC4\u4EF6\uFF0C\u63D0\u4F9B\u5B8C\u6574\u7684\u804A\u5929\u529F\u80FD\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684\u4E09\u5C42\u67B6\u6784\u8BBE\u8BA1\n */\n\nimport React, { useEffect, useCallback } from 'react'\nimport * as Dialog from '@radix-ui/react-dialog'\nimport type {\n LiveChatWidgetProps,\n QuickReply,\n Message,\n MessageContent,\n ChatStreamRequest,\n BackendCartData,\n CommonText,\n} from './types'\nimport { DEFAULT_COMMON_TEXT } from './constants'\nimport { ChatBubble } from './components/ChatBubble'\nimport { ChatWindow } from './components/ChatWindow'\nimport { useChatState } from './hooks/useChatState'\nimport { useChatAPI } from './hooks/useChatAPI'\nimport { MessageRendererRegistry } from './utils/messageRenderers'\nimport { sanitizeInput } from './utils/validation'\nimport { transformProducts } from './utils/productTransformers.js'\nimport { transformCartData } from './utils/cartTransformers.js'\nimport {\n TextBlock,\n ProductCard,\n ProductList,\n ProductComparisonRenderer,\n PolicyBlock,\n createQuickRepliesRenderer,\n ThinkingBlock,\n ErrorBlock,\n FAQListRenderer,\n PromotionListRenderer,\n CartCard,\n} from './components/MessageContent/index.js'\n\n/**\n * LiveChat \u804A\u5929\u7EC4\u4EF6\n *\n * \u529F\u80FD\uFF1A\n * - \u6C14\u6CE1\u5F39\u7A97\u804A\u5929\u754C\u9762\n * - SSE \u6D41\u5F0F\u6D88\u606F\u63A5\u6536\n * - \u4F1A\u8BDD\u7BA1\u7406\uFF08userId, sessionId\uFF09\n * - \u5386\u53F2\u6D88\u606F\u52A0\u8F7D\n * - \u591A\u79CD\u6D88\u606F\u7C7B\u578B\u6E32\u67D3\n * - \u81EA\u5B9A\u4E49\u6269\u5C55\u673A\u5236\n * - reCAPTCHA v3 \u5B89\u5168\u9632\u62A4\n *\n * \u67B6\u6784\uFF1A\n * - UI Layer: ChatBubble, ChatWindow, MessageList, etc.\n * - Logic Layer: useChatState, useChatAPI, useSession\n * - Core Layer: MessageRendererRegistry, \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\n *\n * @example\n * ```tsx\n * // \u57FA\u7840\u4F7F\u7528\uFF08\u4F7F\u7528\u9ED8\u8BA4\u4F4D\u7F6E\uFF09\n * <LiveChatWidget\n * apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n * site=\"www.eufy.com\"\n * welcomeMessage=\"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\"\n * onMessageSend={(msg) => console.log('Sent:', msg)}\n * />\n *\n * // \u4F7F\u7528\u81EA\u5B9A\u4E49\u4F4D\u7F6E\n * <LiveChatWidget\n * apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n * site=\"www.eufy.com\"\n * position={{ bottom: \"20px\", right: \"30px\" }}\n * onMessageSend={(msg) => console.log('Sent:', msg)}\n * />\n *\n * // \u542F\u7528 reCAPTCHA v3 \u5B89\u5168\u9632\u62A4\n * <LiveChatWidget\n * apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n * site=\"www.eufy.com\"\n * needRecaptcha={true}\n * recaptchaSitekey=\"6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14\"\n * recaptchaAction=\"livechat\"\n * />\n *\n * // \u4F7F\u7528\u81EA\u5B9A\u4E49 headers \u548C reCAPTCHA\n * <LiveChatWidget\n * apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n * site=\"www.eufy.com\"\n * headers={{\n * \"Authorization\": \"Bearer your-token\",\n * \"X-Custom-Header\": \"value\"\n * }}\n * needRecaptcha={true}\n * recaptchaSitekey=\"your-site-key\"\n * />\n * ```\n */\nexport const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({\n apiBaseUrl,\n headers,\n needRecaptcha,\n recaptchaSitekey,\n recaptchaAction,\n site,\n loginUserId,\n cartId,\n accessToken,\n position,\n welcomeMessage,\n quickReplies,\n customRenderers,\n logoUrl,\n title,\n chatBubbleIcon,\n onOpen,\n onClose,\n onMessageSend,\n onError,\n onTextMessage,\n onProductList,\n onPromotionList,\n onAddToCart,\n onCart,\n showNewSessionButton,\n commonText,\n}) => {\n // \u5408\u5E76\u9ED8\u8BA4\u6587\u6848\u548C\u81EA\u5B9A\u4E49\u6587\u6848\n const mergedText: Required<CommonText> = React.useMemo(\n () => ({\n ...DEFAULT_COMMON_TEXT,\n ...commonText,\n }),\n [commonText]\n )\n\n // \u72B6\u6001\u7BA1\u7406\n const chatState = useChatState({\n welcomeMessage,\n site,\n onOpen,\n onClose,\n onMessageSend,\n onError,\n onTextMessage,\n onProductList,\n onPromotionList,\n onAddToCart,\n onCart,\n })\n\n const {\n messages,\n isOpen,\n userId,\n sessionId,\n inputValue,\n isStreaming,\n openChat,\n closeChat,\n setInputValue,\n addMessage,\n setMessages,\n clearMessages,\n handleSSEEvent,\n saveSession,\n clearSession,\n } = chatState\n\n // API \u8C03\u7528\n const { sendMessageStream, createSession } = useChatAPI({\n apiBaseUrl,\n headers,\n recaptchaConfig: {\n needRecaptcha,\n recaptchaSitekey,\n recaptchaAction,\n },\n onError,\n })\n\n // \u4F7F\u7528 ref \u5B58\u50A8\u6700\u65B0\u7684 handleSendMessage\uFF0C\u907F\u514D\u5FAA\u73AF\u4F9D\u8D56\n const handleSendMessageRef = React.useRef<(_message?: string) => Promise<void>>(async (_message?: string) => {})\n\n // \u6D88\u606F\u6E32\u67D3\u5668\u6CE8\u518C\u8868\n const rendererRegistry = React.useMemo(() => {\n const registry = new MessageRendererRegistry()\n\n // \u6CE8\u518C\u9ED8\u8BA4\u6E32\u67D3\u5668\n registry.register('text', TextBlock)\n registry.register('product_card', ProductCard)\n registry.register('product_list', ProductList)\n registry.register('product_comparison', ProductComparisonRenderer)\n registry.register('policy', PolicyBlock)\n registry.register('thinking', ThinkingBlock)\n registry.register('error', ErrorBlock)\n registry.register('faq_list', FAQListRenderer)\n registry.register('promotion_list', PromotionListRenderer)\n registry.register('cart', CartCard)\n\n // \u6CE8\u518C\u5FEB\u6377\u56DE\u590D\u6E32\u67D3\u5668\uFF08\u5E26\u56DE\u8C03\uFF09\n const quickRepliesRenderer = createQuickRepliesRenderer((reply: QuickReply) => {\n // \u4F7F\u7528 ref \u8C03\u7528\u6700\u65B0\u7684 handleSendMessage\n handleSendMessageRef.current(reply.value)\n })\n registry.register('quick_replies', quickRepliesRenderer)\n\n // \u6CE8\u518C\u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\n if (customRenderers) {\n registry.registerMany(customRenderers)\n }\n\n return registry\n }, [customRenderers])\n\n /**\n * T043: \u6253\u5F00\u804A\u5929\u7A97\u53E3\u65F6\u521D\u59CB\u5316\u4F1A\u8BDD\n * \u4F7F\u7528 API v2.0.0 \u7684\u7EDF\u4E00\u63A5\u53E3\uFF1A\n * - \u5982\u679C\u6CA1\u6709 sessionId\uFF0C\u521B\u5EFA\u65B0\u4F1A\u8BDD\n * - \u5982\u679C\u6709 sessionId\uFF0C\u6062\u590D\u4F1A\u8BDD\u5E76\u52A0\u8F7D\u5386\u53F2\u6D88\u606F\n */\n useEffect(() => {\n if (!isOpen || !userId) return\n\n const currentSessionId = sessionId\n\n if (!currentSessionId) {\n // \u6CA1\u6709\u4F1A\u8BDD\uFF0C\u521B\u5EFA\u65B0\u4F1A\u8BDD\n handleCreateNewSession()\n } else {\n // \u6709\u4F1A\u8BDD\uFF0C\u5C1D\u8BD5\u6062\u590D\u4F1A\u8BDD\u548C\u5386\u53F2\u6D88\u606F\n handleResumeSession(currentSessionId)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isOpen, userId])\n\n /**\n * \u89C4\u8303\u5316\u6D88\u606F\u683C\u5F0F\uFF08\u786E\u4FDD content \u662F\u6570\u7EC4\uFF09\n * \u540E\u7AEF\u8FD4\u56DE\u683C\u5F0F\uFF1A\n * - content: \u5B57\u7B26\u4E32\uFF08\u6587\u672C\u5185\u5BB9\uFF09\n * - structuredContent: \u6570\u7EC4\uFF08\u7ED3\u6784\u5316\u5185\u5BB9\uFF0C\u5982\u4EA7\u54C1\u5217\u8868\u3001\u653F\u7B56\u7B49\uFF09\n * \u9700\u8981\u5408\u5E76\u4E3A\u7EDF\u4E00\u7684 content \u6570\u7EC4\u683C\u5F0F\n */\n const normalizeMessage = useCallback(\n (message: any): Message => {\n // \u5982\u679C content \u5DF2\u7ECF\u662F\u6570\u7EC4\uFF0C\u76F4\u63A5\u8FD4\u56DE\n if (Array.isArray(message.content)) {\n return message as Message\n }\n\n const contentBlocks: MessageContent[] = []\n\n // \u5904\u7406\u6587\u672C\u5185\u5BB9\n if (typeof message.content === 'string' && message.content.trim()) {\n contentBlocks.push({\n type: 'text',\n text: message.content,\n })\n }\n\n // \u5904\u7406\u7ED3\u6784\u5316\u5185\u5BB9\n // \u5386\u53F2\u6D88\u606F\u683C\u5F0F: structured_content: [{type, data}]\n const structuredData = message.structuredContent || message.structured_content\n\n if (Array.isArray(structuredData)) {\n structuredData.forEach((block: any) => {\n if (block.type === 'product_list' && Array.isArray(block.data)) {\n // \u8F6C\u6362\u4EA7\u54C1\u5217\u8868\u6570\u636E\u7ED3\u6784 - data \u76F4\u63A5\u662F\u4EA7\u54C1\u6570\u7EC4\n contentBlocks.push({\n type: 'product_list',\n data: {\n products: transformProducts(block.data, site),\n title: undefined, // \u5386\u53F2\u6D88\u606F\u4E0D\u5305\u542B title\n commonText: mergedText,\n },\n })\n } else if (block.type === 'quick_replies' && block.data?.replies) {\n contentBlocks.push({\n type: 'quick_replies',\n data: {\n replies: block.data.replies,\n },\n })\n } else if (block.type === 'policy' && block.data?.title && block.data?.content) {\n contentBlocks.push({\n type: 'policy',\n data: {\n title: block.data.title,\n content: block.data.content,\n },\n })\n } else if (block.type === 'product_comparison' && block.data?.products && block.data?.dimensions) {\n // \u8F6C\u6362\u4EA7\u54C1\u5BF9\u6BD4\u6570\u636E\u7ED3\u6784\u5E76\u6CE8\u5165 onAddToCart \u56DE\u8C03\n contentBlocks.push({\n type: 'product_comparison',\n data: {\n products: transformProducts(block.data.products, site),\n dimensions: block.data.dimensions,\n onAddToCart: onAddToCart,\n commonText: mergedText,\n },\n })\n } else if (block.type === 'faq_list' && block.data?.found !== undefined) {\n // FAQ \u5217\u8868\u5361\u7247 - \u76F4\u63A5\u4F7F\u7528\u540E\u7AEF\u6570\u636E\n contentBlocks.push({\n type: 'faq_list',\n data: block.data,\n })\n } else if (block.type === 'promotion_list' && block.data?.found !== undefined) {\n // \u4FC3\u9500\u6D3B\u52A8\u5217\u8868\u5361\u7247 - \u76F4\u63A5\u4F7F\u7528\u540E\u7AEF\u6570\u636E\n contentBlocks.push({\n type: 'promotion_list',\n data: {\n ...block.data,\n commonText: mergedText,\n },\n })\n } else if (block.type === 'cart' && block.data?.id !== undefined) {\n // \u8D2D\u7269\u8F66\u5361\u7247 - \u8F6C\u6362\u540E\u7AEF\u6570\u636E\u683C\u5F0F\u5E76\u6CE8\u5165 onCart \u56DE\u8C03\n const transformedData = transformCartData(block.data as BackendCartData)\n contentBlocks.push({\n type: 'cart',\n data: {\n ...transformedData,\n onCart: onCart,\n commonText: mergedText,\n },\n })\n } else {\n // \u5176\u4ED6\u7C7B\u578B\u76F4\u63A5\u6DFB\u52A0\n contentBlocks.push(block)\n }\n })\n }\n\n // \u5982\u679C\u6CA1\u6709\u4EFB\u4F55\u5185\u5BB9\u5757\uFF0C\u8FD4\u56DE\u7A7A\u6587\u672C\u5757\n if (contentBlocks.length === 0) {\n contentBlocks.push({\n type: 'text',\n text: '',\n })\n }\n\n return {\n ...message,\n content: contentBlocks,\n } as Message\n },\n [site, onCart, onAddToCart, mergedText]\n )\n\n /**\n * \u521B\u5EFA\u65B0\u4F1A\u8BDD\n */\n const handleCreateNewSession = useCallback(async () => {\n if (!userId) return\n\n try {\n const response = await createSession({\n user_id: userId,\n site: site,\n real_user_id: loginUserId,\n })\n\n if (response.success) {\n // \u4FDD\u5B58\u65B0\u4F1A\u8BDD ID\n saveSession(response.sessionId)\n\n // \u6E05\u7A7A\u6D88\u606F\u5217\u8868\n clearMessages()\n\n // \u4F7F\u7528\u540E\u7AEF\u8FD4\u56DE\u7684\u6B22\u8FCE\u6D88\u606F\uFF0C\u5982\u679C\u6CA1\u6709\u5219\u4F7F\u7528 props \u4E2D\u7684\u9ED8\u8BA4\u503C\n const messageText = response.welcomeMessage || welcomeMessage\n\n if (messageText) {\n const welcomeContent: MessageContent[] = [{ type: 'text', text: messageText }]\n\n // \u4F7F\u7528\u540E\u7AEF\u8FD4\u56DE\u7684\u5FEB\u6377\u95EE\u9898\uFF0C\u5982\u679C\u6CA1\u6709\u5219\u4F7F\u7528 props \u4E2D\u7684\u5FEB\u6377\u56DE\u590D\n const questions = response.quickQuestions\n if (questions && questions.length > 0) {\n // \u5C06\u540E\u7AEF\u7684 quickQuestions (\u5B57\u7B26\u4E32\u6570\u7EC4) \u8F6C\u6362\u4E3A QuickReply \u683C\u5F0F\n const quickRepliesFromBackend = questions.map((question, index) => ({\n id: `quick-${index}`,\n label: question,\n value: question,\n }))\n\n welcomeContent.push({\n type: 'quick_replies',\n data: {\n replies: quickRepliesFromBackend,\n },\n })\n } else if (quickReplies && quickReplies.length > 0) {\n // \u5982\u679C\u540E\u7AEF\u6CA1\u6709\u8FD4\u56DE\uFF0C\u4F7F\u7528 props \u4E2D\u7684\u5FEB\u6377\u56DE\u590D\n welcomeContent.push({\n type: 'quick_replies',\n data: {\n replies: quickReplies,\n },\n })\n }\n\n addMessage({\n id: `welcome-${Date.now()}`,\n role: 'assistant',\n content: welcomeContent,\n timestamp: Date.now(),\n })\n }\n }\n } catch (error) {\n console.error('[LiveChatWidget] Failed to create new session:', error)\n onError?.(error as Error)\n }\n }, [\n userId,\n site,\n loginUserId,\n createSession,\n saveSession,\n clearMessages,\n welcomeMessage,\n quickReplies,\n addMessage,\n onError,\n ])\n\n /**\n * \u6062\u590D\u4F1A\u8BDD\u548C\u5386\u53F2\u6D88\u606F\uFF08\u4F7F\u7528\u65B0\u7684 API v2.0.0\uFF09\n */\n const handleResumeSession = useCallback(\n async (existingSessionId: string) => {\n try {\n const response = await createSession({\n user_id: userId,\n session_id: existingSessionId,\n site: site,\n real_user_id: loginUserId,\n })\n\n if (response.success && response.resumed) {\n // \u4F1A\u8BDD\u6062\u590D\u6210\u529F\n if (response.messages && response.messages.length > 0) {\n // \u6709\u5386\u53F2\u6D88\u606F\uFF0C\u89C4\u8303\u5316\u5E76\u52A0\u8F7D\uFF08\u8FC7\u6EE4\u6389 null/undefined \u503C\uFF09\n const normalizedMessages = response.messages.filter((msg: any) => msg != null).map(normalizeMessage)\n setMessages(normalizedMessages)\n } else {\n // \u6CA1\u6709\u5386\u53F2\u6D88\u606F\uFF0C\u663E\u793A\u6B22\u8FCE\u6D88\u606F\n clearMessages()\n\n // \u4F7F\u7528\u540E\u7AEF\u8FD4\u56DE\u7684\u6B22\u8FCE\u6D88\u606F\uFF0C\u5982\u679C\u6CA1\u6709\u5219\u4F7F\u7528 props \u4E2D\u7684\u9ED8\u8BA4\u503C\n const messageText = response.welcomeMessage || welcomeMessage\n\n if (messageText) {\n const welcomeContent: MessageContent[] = [{ type: 'text', text: messageText }]\n\n // \u4F7F\u7528\u540E\u7AEF\u8FD4\u56DE\u7684\u5FEB\u6377\u95EE\u9898\n const questions = response.quickQuestions\n if (questions && questions.length > 0) {\n const quickRepliesFromBackend = questions.map((question, index) => ({\n id: `quick-${index}`,\n label: question,\n value: question,\n }))\n\n welcomeContent.push({\n type: 'quick_replies',\n data: {\n replies: quickRepliesFromBackend,\n },\n })\n } else if (quickReplies && quickReplies.length > 0) {\n welcomeContent.push({\n type: 'quick_replies',\n data: {\n replies: quickReplies,\n },\n })\n }\n\n addMessage({\n id: `welcome-${Date.now()}`,\n role: 'assistant',\n content: welcomeContent,\n timestamp: Date.now(),\n })\n }\n }\n } else if (!response.resumed) {\n // \u4F1A\u8BDD\u65E0\u6548\u6216\u8FC7\u671F\uFF0C\u521B\u5EFA\u65B0\u4F1A\u8BDD\n clearSession()\n handleCreateNewSession()\n }\n } catch (error) {\n console.error('[LiveChatWidget] Failed to resume session:', error)\n // \u6062\u590D\u5931\u8D25\uFF0C\u6E05\u7A7A\u4F1A\u8BDD\u5E76\u521B\u5EFA\u65B0\u7684\n clearSession()\n handleCreateNewSession()\n }\n },\n [\n userId,\n site,\n loginUserId,\n createSession,\n setMessages,\n clearSession,\n clearMessages,\n normalizeMessage,\n handleCreateNewSession,\n welcomeMessage,\n quickReplies,\n addMessage,\n ]\n )\n\n /**\n * \u53D1\u9001\u6D88\u606F\n */\n const handleSendMessage = useCallback(\n async (message?: string) => {\n const textToSend = message || inputValue.trim()\n\n if (!textToSend) return\n\n // \u6E05\u7A7A\u8F93\u5165\u6846\n if (!message) {\n setInputValue('')\n }\n\n // \u8F93\u5165\u9A8C\u8BC1\n const sanitized = sanitizeInput(textToSend)\n if (!sanitized) {\n onError?.(new Error('Invalid message'))\n return\n }\n\n // \u6DFB\u52A0\u7528\u6237\u6D88\u606F\u5230\u754C\u9762\n const userMessage = {\n id: `user-${Date.now()}`,\n role: 'user' as const,\n content: [{ type: 'text' as const, text: sanitized }],\n timestamp: Date.now(),\n }\n addMessage(userMessage)\n\n // \u89E6\u53D1\u6D88\u606F\u53D1\u9001\u56DE\u8C03\n onMessageSend?.(sanitized)\n\n try {\n // \u786E\u4FDD\u6709 sessionId\uFF0C\u5982\u679C\u6CA1\u6709\u5219\u5148\u521B\u5EFA\u4F1A\u8BDD\n let currentSessionId = sessionId\n if (!currentSessionId) {\n // \u6CA1\u6709\u4F1A\u8BDD\uFF0C\u521B\u5EFA\u65B0\u4F1A\u8BDD\n const response = await createSession({\n user_id: userId,\n site: site,\n real_user_id: loginUserId,\n })\n if (response.success) {\n currentSessionId = response.sessionId\n saveSession(currentSessionId)\n } else {\n throw new Error('Failed to create session')\n }\n }\n\n // \u6784\u5EFA\u8BF7\u6C42\u53C2\u6570\uFF08session_id \u73B0\u5728\u662F\u5FC5\u586B\u7684\uFF09\n const requestPayload: ChatStreamRequest = {\n message: sanitized,\n user_id: userId,\n session_id: currentSessionId,\n context: {\n cartId: cartId,\n accessToken: accessToken,\n real_user_id: loginUserId,\n },\n }\n\n // \u53D1\u9001\u6D88\u606F\u5230\u540E\u7AEF\n await sendMessageStream(requestPayload, event => {\n // \u5904\u7406 SSE \u4E8B\u4EF6\n handleSSEEvent(event)\n\n // \u7279\u6B8A\u5904\u7406\uFF1A\u4F1A\u8BDD\u8FC7\u671F\n if (event.event === 'status' && event.data.type === 'session_expired') {\n clearSession()\n }\n })\n } catch (error) {\n console.error('[LiveChatWidget] Failed to send message:', error)\n onError?.(error as Error)\n }\n },\n [\n inputValue,\n userId,\n sessionId,\n site,\n loginUserId,\n cartId,\n accessToken,\n setInputValue,\n addMessage,\n createSession,\n sendMessageStream,\n handleSSEEvent,\n saveSession,\n clearSession,\n onMessageSend,\n onError,\n ]\n )\n\n // \u66F4\u65B0 ref \u4EE5\u4FDD\u6301\u6700\u65B0\u7684 handleSendMessage\n React.useEffect(() => {\n handleSendMessageRef.current = handleSendMessage\n }, [handleSendMessage])\n\n return (\n <>\n {/* \u6C14\u6CE1\u6309\u94AE */}\n <ChatBubble position={position} onClick={openChat} visible={!isOpen} iconImageUrl={chatBubbleIcon} />\n\n {/* \u804A\u5929\u7A97\u53E3\uFF08\u4F7F\u7528 Radix UI Dialog\uFF09 */}\n <Dialog.Root open={isOpen} onOpenChange={open => (open ? openChat() : closeChat())}>\n <Dialog.Portal>\n <Dialog.Content\n className=\"livechat-window-enter\"\n style={{\n position: 'fixed',\n zIndex: 9998,\n }}\n >\n <ChatWindow\n messages={messages}\n inputValue={inputValue}\n onInputChange={setInputValue}\n onSend={() => handleSendMessage()}\n onClose={closeChat}\n onNewSession={handleCreateNewSession}\n title={title}\n logoUrl={logoUrl}\n isSending={isStreaming}\n rendererRegistry={rendererRegistry}\n inputPlaceholder=\"\"\n onAddToCart={onAddToCart}\n showNewSessionButton={showNewSessionButton}\n />\n </Dialog.Content>\n </Dialog.Portal>\n </Dialog.Root>\n </>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["LiveChatWidget_exports", "__export", "LiveChatWidget", "__toCommonJS", "import_jsx_runtime", "import_react", "Dialog", "import_constants", "import_ChatBubble", "import_ChatWindow", "import_useChatState", "import_useChatAPI", "import_messageRenderers", "import_validation", "import_productTransformers", "import_cartTransformers", "import_MessageContent", "apiBaseUrl", "headers", "needRecaptcha", "recaptchaSitekey", "recaptchaAction", "site", "loginUserId", "cartId", "accessToken", "position", "welcomeMessage", "quickReplies", "customRenderers", "logoUrl", "title", "chatBubbleIcon", "onOpen", "onClose", "onMessageSend", "onError", "onTextMessage", "onProductList", "onPromotionList", "onAddToCart", "onCart", "showNewSessionButton", "commonText", "mergedText", "React", "chatState", "messages", "isOpen", "userId", "sessionId", "inputValue", "isStreaming", "openChat", "closeChat", "setInputValue", "addMessage", "setMessages", "clearMessages", "handleSSEEvent", "saveSession", "clearSession", "sendMessageStream", "createSession", "handleSendMessageRef", "_message", "rendererRegistry", "registry", "quickRepliesRenderer", "reply", "currentSessionId", "handleResumeSession", "handleCreateNewSession", "normalizeMessage", "message", "contentBlocks", "structuredData", "block", "transformedData", "response", "messageText", "welcomeContent", "questions", "quickRepliesFromBackend", "question", "index", "error", "existingSessionId", "normalizedMessages", "msg", "handleSendMessage", "textToSend", "sanitized", "userMessage", "event", "open"]
|
|
7
7
|
}
|
|
@@ -4,14 +4,32 @@
|
|
|
4
4
|
* 基于 specs/livechat-widget/contracts/*.yaml
|
|
5
5
|
*/
|
|
6
6
|
import type { ChatStreamRequest, SSEEvent, NewSessionRequest, NewSessionResponse } from '../types';
|
|
7
|
+
/**
|
|
8
|
+
* Recaptcha 配置
|
|
9
|
+
*/
|
|
10
|
+
export interface RecaptchaConfig {
|
|
11
|
+
/**
|
|
12
|
+
* 是否启用 reCAPTCHA
|
|
13
|
+
*/
|
|
14
|
+
needRecaptcha?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* reCAPTCHA site key
|
|
17
|
+
*/
|
|
18
|
+
recaptchaSitekey?: string;
|
|
19
|
+
/**
|
|
20
|
+
* reCAPTCHA action 名称
|
|
21
|
+
*/
|
|
22
|
+
recaptchaAction?: string;
|
|
23
|
+
}
|
|
7
24
|
/**
|
|
8
25
|
* 发送消息并接收 SSE 流式响应
|
|
9
26
|
* @param apiBaseUrl API 基础 URL
|
|
10
27
|
* @param request 请求参数
|
|
11
28
|
* @param onEvent SSE 事件回调
|
|
12
29
|
* @param customHeaders 自定义请求头
|
|
30
|
+
* @param recaptchaConfig reCAPTCHA 配置
|
|
13
31
|
*/
|
|
14
|
-
export declare function sendMessage(apiBaseUrl: string, request: ChatStreamRequest, onEvent: (event: SSEEvent) => void, customHeaders?: Record<string, string
|
|
32
|
+
export declare function sendMessage(apiBaseUrl: string, request: ChatStreamRequest, onEvent: (event: SSEEvent) => void, customHeaders?: Record<string, string>, recaptchaConfig?: RecaptchaConfig): Promise<void>;
|
|
15
33
|
/**
|
|
16
34
|
* 创建新会话或恢复现有会话
|
|
17
35
|
*
|
|
@@ -22,6 +40,7 @@ export declare function sendMessage(apiBaseUrl: string, request: ChatStreamReque
|
|
|
22
40
|
* @param apiBaseUrl API 基础 URL
|
|
23
41
|
* @param request 请求参数
|
|
24
42
|
* @param customHeaders 自定义请求头
|
|
43
|
+
* @param recaptchaConfig reCAPTCHA 配置
|
|
25
44
|
* @returns 会话信息,可能包含历史消息
|
|
26
45
|
*/
|
|
27
|
-
export declare function createNewSession(apiBaseUrl: string, request: NewSessionRequest, customHeaders?: Record<string, string
|
|
46
|
+
export declare function createNewSession(apiBaseUrl: string, request: NewSessionRequest, customHeaders?: Record<string, string>, recaptchaConfig?: RecaptchaConfig): Promise<NewSessionResponse>;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var l=Object.defineProperty;var
|
|
2
|
-
`);
|
|
1
|
+
"use strict";var l=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var g=Object.prototype.hasOwnProperty;var v=(r,t)=>{for(var s in t)l(r,s,{get:t[s],enumerable:!0})},T=(r,t,s,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let e of E(t))!g.call(r,e)&&e!==s&&l(r,e,{get:()=>t[e],enumerable:!(a=y(t,e))||a.enumerable});return r};var b=r=>T(l({},"__esModule",{value:!0}),r);var A={};v(A,{createNewSession:()=>k,sendMessage:()=>P});module.exports=b(A);var R=require("../utils/fetcher");async function P(r,t,s,a,e){const o=await(0,R.fetcher)({url:`${r}/api/chat/stream`,method:"POST",headers:{Accept:"text/event-stream",...a},body:t,needRecaptcha:e?.needRecaptcha,recaptchaSitekey:e?.recaptchaSitekey,recaptchaAction:e?.recaptchaAction||"chat_stream"});if(!o.ok)throw new Error(`HTTP ${o.status}: ${o.statusText}`);const i=o.body?.getReader();if(!i)throw new Error("Response body is not readable");const m=new TextDecoder;let d="",p=null;try{for(;;){const{done:c,value:w}=await i.read();if(c)break;d+=m.decode(w,{stream:!0});const S=d.split(`
|
|
2
|
+
`);d=S.pop()||"";for(const f of S){const n=f.trim();if(n===""){p=null;continue}if(n.startsWith("event:"))p=n.substring(6).trim();else if(n.startsWith("data:")){const u=n.substring(5).trim();try{const h=JSON.parse(u);s({event:p||"message",data:h})}catch(h){console.error("[LiveChat API] Failed to parse SSE data:",u,h),s({event:"error",data:{message:"Failed to parse SSE data",code:"PARSE_ERROR"}})}}}}}catch(c){throw console.error("[LiveChat API] SSE stream error:",c),c}finally{i.releaseLock()}}async function k(r,t,s,a){const e=await(0,R.fetcher)({url:`${r}/api/chat/new-session`,method:"POST",headers:s,body:t,needRecaptcha:a?.needRecaptcha,recaptchaSitekey:a?.recaptchaSitekey,recaptchaAction:a?.recaptchaAction||"new_session"});if(!e.ok){const o=await e.json().catch(()=>({success:!1,error:`HTTP ${e.status}: ${e.statusText}`,code:"HTTP_ERROR"}));throw new Error(o.error||"Failed to create session")}return e.json()}
|
|
3
3
|
//# sourceMappingURL=chat.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/components/LiveChatWidget/api/chat.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * LiveChat API \u5C42\n * \u5904\u7406 SSE \u6D41\u5F0F\u901A\u4FE1\u548C HTTP \u8BF7\u6C42\n * \u57FA\u4E8E specs/livechat-widget/contracts/*.yaml\n */\n\nimport type { ChatStreamRequest, SSEEvent, NewSessionRequest, NewSessionResponse, ErrorResponse } from '../types'\n\n// ============================================================================\n// SSE \u6D41\u5F0F\u6D88\u606F\u5904\u7406 (T006)\n// ============================================================================\n\n/**\n * \u53D1\u9001\u6D88\u606F\u5E76\u63A5\u6536 SSE \u6D41\u5F0F\u54CD\u5E94\n * @param apiBaseUrl API \u57FA\u7840 URL\n * @param request \u8BF7\u6C42\u53C2\u6570\n * @param onEvent SSE \u4E8B\u4EF6\u56DE\u8C03\n * @param customHeaders \u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\n */\nexport async function sendMessage(\n apiBaseUrl: string,\n request: ChatStreamRequest,\n onEvent: (event: SSEEvent) => void,\n customHeaders?: Record<string, string
|
|
5
|
-
"mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,sBAAAE,EAAA,gBAAAC,IAAA,eAAAC,EAAAJ,
|
|
6
|
-
"names": ["chat_exports", "__export", "createNewSession", "sendMessage", "__toCommonJS", "apiBaseUrl", "request", "onEvent", "customHeaders", "response", "reader", "decoder", "buffer", "currentEvent", "done", "value", "lines", "line", "trimmed", "dataStr", "data", "err", "error", "errorData"]
|
|
4
|
+
"sourcesContent": ["/**\n * LiveChat API \u5C42\n * \u5904\u7406 SSE \u6D41\u5F0F\u901A\u4FE1\u548C HTTP \u8BF7\u6C42\n * \u57FA\u4E8E specs/livechat-widget/contracts/*.yaml\n */\n\nimport type { ChatStreamRequest, SSEEvent, NewSessionRequest, NewSessionResponse, ErrorResponse } from '../types'\nimport { fetcher } from '../utils/fetcher'\n\n/**\n * Recaptcha \u914D\u7F6E\n */\nexport interface RecaptchaConfig {\n /**\n * \u662F\u5426\u542F\u7528 reCAPTCHA\n */\n needRecaptcha?: boolean\n /**\n * reCAPTCHA site key\n */\n recaptchaSitekey?: string\n /**\n * reCAPTCHA action \u540D\u79F0\n */\n recaptchaAction?: string\n}\n\n// ============================================================================\n// SSE \u6D41\u5F0F\u6D88\u606F\u5904\u7406 (T006)\n// ============================================================================\n\n/**\n * \u53D1\u9001\u6D88\u606F\u5E76\u63A5\u6536 SSE \u6D41\u5F0F\u54CD\u5E94\n * @param apiBaseUrl API \u57FA\u7840 URL\n * @param request \u8BF7\u6C42\u53C2\u6570\n * @param onEvent SSE \u4E8B\u4EF6\u56DE\u8C03\n * @param customHeaders \u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\n * @param recaptchaConfig reCAPTCHA \u914D\u7F6E\n */\nexport async function sendMessage(\n apiBaseUrl: string,\n request: ChatStreamRequest,\n onEvent: (event: SSEEvent) => void,\n customHeaders?: Record<string, string>,\n recaptchaConfig?: RecaptchaConfig\n): Promise<void> {\n // \u4F7F\u7528 fetcher \u5904\u7406 reCAPTCHA \u548C\u81EA\u5B9A\u4E49 headers\n const response = await fetcher({\n url: `${apiBaseUrl}/api/chat/stream`,\n method: 'POST',\n headers: {\n Accept: 'text/event-stream',\n ...customHeaders,\n },\n body: request,\n needRecaptcha: recaptchaConfig?.needRecaptcha,\n recaptchaSitekey: recaptchaConfig?.recaptchaSitekey,\n recaptchaAction: recaptchaConfig?.recaptchaAction || 'chat_stream',\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n\n const reader = response.body?.getReader()\n if (!reader) {\n throw new Error('Response body is not readable')\n }\n\n const decoder = new TextDecoder()\n let buffer = ''\n let currentEvent: string | null = null\n\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n // \u89E3\u7801\u5E76\u8FFD\u52A0\u5230\u7F13\u51B2\u533A\n buffer += decoder.decode(value, { stream: true })\n\n // \u6309\u884C\u5206\u5272\n const lines = buffer.split('\\n')\n\n // \u4FDD\u7559\u6700\u540E\u4E00\u4E2A\u4E0D\u5B8C\u6574\u7684\u884C\n buffer = lines.pop() || ''\n\n for (const line of lines) {\n const trimmed = line.trim()\n\n if (trimmed === '') {\n // \u7A7A\u884C\u8868\u793A\u4E8B\u4EF6\u8FB9\u754C\uFF0C\u91CD\u7F6E\u5F53\u524D\u4E8B\u4EF6\u7C7B\u578B\n currentEvent = null\n continue\n }\n\n if (trimmed.startsWith('event:')) {\n // \u63D0\u53D6\u4E8B\u4EF6\u7C7B\u578B\n currentEvent = trimmed.substring(6).trim()\n } else if (trimmed.startsWith('data:')) {\n // \u63D0\u53D6\u6570\u636E\u5E76\u89E3\u6790 JSON\n const dataStr = trimmed.substring(5).trim()\n try {\n const data = JSON.parse(dataStr)\n onEvent({\n event: (currentEvent as any) || 'message',\n data,\n })\n } catch (err) {\n console.error('[LiveChat API] Failed to parse SSE data:', dataStr, err)\n onEvent({\n event: 'error',\n data: {\n message: 'Failed to parse SSE data',\n code: 'PARSE_ERROR',\n },\n })\n }\n }\n }\n }\n } catch (error) {\n console.error('[LiveChat API] SSE stream error:', error)\n throw error\n } finally {\n reader.releaseLock()\n }\n}\n\n// ============================================================================\n// \u521B\u5EFA/\u6062\u590D\u4F1A\u8BDD (T009)\n// ============================================================================\n\n/**\n * \u521B\u5EFA\u65B0\u4F1A\u8BDD\u6216\u6062\u590D\u73B0\u6709\u4F1A\u8BDD\n *\n * \u4F7F\u7528\u573A\u666F\uFF1A\n * 1. \u521B\u5EFA\u65B0\u4F1A\u8BDD\uFF1A\u4E0D\u4F20 session_id\uFF0C\u8FD4\u56DE\u65B0\u7684 sessionId\n * 2. \u6062\u590D\u4F1A\u8BDD\uFF1A\u4F20\u5165 session_id\uFF0C\u9A8C\u8BC1\u4F1A\u8BDD\u6709\u6548\u6027\u5E76\u8FD4\u56DE\u5386\u53F2\u6D88\u606F\uFF08resumed: true, messages: [...]\uFF09\n *\n * @param apiBaseUrl API \u57FA\u7840 URL\n * @param request \u8BF7\u6C42\u53C2\u6570\n * @param customHeaders \u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\n * @param recaptchaConfig reCAPTCHA \u914D\u7F6E\n * @returns \u4F1A\u8BDD\u4FE1\u606F\uFF0C\u53EF\u80FD\u5305\u542B\u5386\u53F2\u6D88\u606F\n */\nexport async function createNewSession(\n apiBaseUrl: string,\n request: NewSessionRequest,\n customHeaders?: Record<string, string>,\n recaptchaConfig?: RecaptchaConfig\n): Promise<NewSessionResponse> {\n // \u4F7F\u7528 fetcher \u5904\u7406 reCAPTCHA \u548C\u81EA\u5B9A\u4E49 headers\n const response = await fetcher({\n url: `${apiBaseUrl}/api/chat/new-session`,\n method: 'POST',\n headers: customHeaders,\n body: request,\n needRecaptcha: recaptchaConfig?.needRecaptcha,\n recaptchaSitekey: recaptchaConfig?.recaptchaSitekey,\n recaptchaAction: recaptchaConfig?.recaptchaAction || 'new_session',\n })\n\n if (!response.ok) {\n const errorData: ErrorResponse = await response.json().catch(() => ({\n success: false,\n error: `HTTP ${response.status}: ${response.statusText}`,\n code: 'HTTP_ERROR',\n }))\n throw new Error(errorData.error || 'Failed to create session')\n }\n\n return response.json()\n}\n"],
|
|
5
|
+
"mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,sBAAAE,EAAA,gBAAAC,IAAA,eAAAC,EAAAJ,GAOA,IAAAK,EAAwB,4BAgCxB,eAAsBF,EACpBG,EACAC,EACAC,EACAC,EACAC,EACe,CAEf,MAAMC,EAAW,QAAM,WAAQ,CAC7B,IAAK,GAAGL,CAAU,mBAClB,OAAQ,OACR,QAAS,CACP,OAAQ,oBACR,GAAGG,CACL,EACA,KAAMF,EACN,cAAeG,GAAiB,cAChC,iBAAkBA,GAAiB,iBACnC,gBAAiBA,GAAiB,iBAAmB,aACvD,CAAC,EAED,GAAI,CAACC,EAAS,GACZ,MAAM,IAAI,MAAM,QAAQA,EAAS,MAAM,KAAKA,EAAS,UAAU,EAAE,EAGnE,MAAMC,EAASD,EAAS,MAAM,UAAU,EACxC,GAAI,CAACC,EACH,MAAM,IAAI,MAAM,+BAA+B,EAGjD,MAAMC,EAAU,IAAI,YACpB,IAAIC,EAAS,GACTC,EAA8B,KAElC,GAAI,CACF,OAAa,CACX,KAAM,CAAE,KAAAC,EAAM,MAAAC,CAAM,EAAI,MAAML,EAAO,KAAK,EAC1C,GAAII,EAAM,MAGVF,GAAUD,EAAQ,OAAOI,EAAO,CAAE,OAAQ,EAAK,CAAC,EAGhD,MAAMC,EAAQJ,EAAO,MAAM;AAAA,CAAI,EAG/BA,EAASI,EAAM,IAAI,GAAK,GAExB,UAAWC,KAAQD,EAAO,CACxB,MAAME,EAAUD,EAAK,KAAK,EAE1B,GAAIC,IAAY,GAAI,CAElBL,EAAe,KACf,QACF,CAEA,GAAIK,EAAQ,WAAW,QAAQ,EAE7BL,EAAeK,EAAQ,UAAU,CAAC,EAAE,KAAK,UAChCA,EAAQ,WAAW,OAAO,EAAG,CAEtC,MAAMC,EAAUD,EAAQ,UAAU,CAAC,EAAE,KAAK,EAC1C,GAAI,CACF,MAAME,EAAO,KAAK,MAAMD,CAAO,EAC/Bb,EAAQ,CACN,MAAQO,GAAwB,UAChC,KAAAO,CACF,CAAC,CACH,OAASC,EAAK,CACZ,QAAQ,MAAM,2CAA4CF,EAASE,CAAG,EACtEf,EAAQ,CACN,MAAO,QACP,KAAM,CACJ,QAAS,2BACT,KAAM,aACR,CACF,CAAC,CACH,CACF,CACF,CACF,CACF,OAASgB,EAAO,CACd,cAAQ,MAAM,mCAAoCA,CAAK,EACjDA,CACR,QAAE,CACAZ,EAAO,YAAY,CACrB,CACF,CAmBA,eAAsBV,EACpBI,EACAC,EACAE,EACAC,EAC6B,CAE7B,MAAMC,EAAW,QAAM,WAAQ,CAC7B,IAAK,GAAGL,CAAU,wBAClB,OAAQ,OACR,QAASG,EACT,KAAMF,EACN,cAAeG,GAAiB,cAChC,iBAAkBA,GAAiB,iBACnC,gBAAiBA,GAAiB,iBAAmB,aACvD,CAAC,EAED,GAAI,CAACC,EAAS,GAAI,CAChB,MAAMc,EAA2B,MAAMd,EAAS,KAAK,EAAE,MAAM,KAAO,CAClE,QAAS,GACT,MAAO,QAAQA,EAAS,MAAM,KAAKA,EAAS,UAAU,GACtD,KAAM,YACR,EAAE,EACF,MAAM,IAAI,MAAMc,EAAU,OAAS,0BAA0B,CAC/D,CAEA,OAAOd,EAAS,KAAK,CACvB",
|
|
6
|
+
"names": ["chat_exports", "__export", "createNewSession", "sendMessage", "__toCommonJS", "import_fetcher", "apiBaseUrl", "request", "onEvent", "customHeaders", "recaptchaConfig", "response", "reader", "decoder", "buffer", "currentEvent", "done", "value", "lines", "line", "trimmed", "dataStr", "data", "err", "error", "errorData"]
|
|
7
7
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* 基于 specs/livechat-widget/plan.md 的 API 调用策略
|
|
5
5
|
*/
|
|
6
6
|
import type { ChatStreamRequest, SSEEvent, NewSessionRequest, NewSessionResponse } from '../types';
|
|
7
|
+
import { type RecaptchaConfig } from '../api/chat';
|
|
7
8
|
export interface UseChatAPIOptions {
|
|
8
9
|
/**
|
|
9
10
|
* API 基础 URL
|
|
@@ -13,6 +14,10 @@ export interface UseChatAPIOptions {
|
|
|
13
14
|
* 自定义请求头
|
|
14
15
|
*/
|
|
15
16
|
headers?: Record<string, string>;
|
|
17
|
+
/**
|
|
18
|
+
* reCAPTCHA 配置
|
|
19
|
+
*/
|
|
20
|
+
recaptchaConfig?: RecaptchaConfig;
|
|
16
21
|
/**
|
|
17
22
|
* 错误处理回调
|
|
18
23
|
*/
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var f=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var R=Object.getOwnPropertyNames;var d=Object.prototype.hasOwnProperty;var E=(t,e)=>{for(var s in e)f(t,s,{get:e[s],enumerable:!0})},g=(t,e,s,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of R(e))!d.call(t,r)&&r!==s&&f(t,r,{get:()=>e[r],enumerable:!(o=m(e,r))||o.enumerable});return t};var w=t=>g(f({},"__esModule",{value:!0}),t);var b={};E(b,{useChatAPI:()=>v});module.exports=w(b);var a=require("react"),l=require("../api/chat");function v(t){const{apiBaseUrl:e,headers:s,recaptchaConfig:o,onError:r}=t,n=(0,a.useRef)(null),u=(0,a.useRef)(!1),h=(0,a.useCallback)(async(S,i)=>{n.current&&n.current.abort(),n.current=new AbortController,u.current=!0;try{await(0,l.sendMessage)(e,S,i,s,o)}catch(c){if(c instanceof Error&&c.name==="AbortError")return;throw console.error("[useChatAPI] Stream error:",c),r?.(c),c}finally{u.current=!1,n.current=null}},[e,s,o,r]),p=(0,a.useCallback)(async S=>{try{return await(0,l.createNewSession)(e,S,s,o)}catch(i){throw console.error("[useChatAPI] Create session error:",i),r?.(i),i}},[e,s,o,r]),C=(0,a.useCallback)(()=>{n.current&&(n.current.abort(),n.current=null,u.current=!1)},[]);return{sendMessageStream:h,createSession:p,abortStream:C,isSending:u.current}}
|
|
2
2
|
//# sourceMappingURL=useChatAPI.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/components/LiveChatWidget/hooks/useChatAPI.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * \u804A\u5929 API \u8C03\u7528 Hook\n * \u5C01\u88C5 SSE \u6D41\u5F0F\u6D88\u606F\u63A5\u6536\u3001\u5386\u53F2\u6D88\u606F\u52A0\u8F7D\u3001\u65B0\u4F1A\u8BDD\u521B\u5EFA\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684 API \u8C03\u7528\u7B56\u7565\n */\n\nimport { useCallback, useRef } from 'react'\nimport type { ChatStreamRequest, SSEEvent, NewSessionRequest, NewSessionResponse } from '../types'\nimport { sendMessage, createNewSession } from '../api/chat'\n\nexport interface UseChatAPIOptions {\n /**\n * API \u57FA\u7840 URL\n */\n apiBaseUrl: string\n\n /**\n * \u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\n */\n headers?: Record<string, string>\n\n /**\n * \u9519\u8BEF\u5904\u7406\u56DE\u8C03\n */\n onError?: (error: Error) => void\n}\n\nexport interface UseChatAPIReturn {\n /**\n * \u53D1\u9001\u6D88\u606F\u5E76\u63A5\u6536 SSE \u6D41\u5F0F\u54CD\u5E94\n * @param request \u8BF7\u6C42\u53C2\u6570\n * @param onEvent SSE \u4E8B\u4EF6\u56DE\u8C03\n * @returns Promise<void>\n */\n sendMessageStream: (request: ChatStreamRequest, onEvent: (event: SSEEvent) => void) => Promise<void>\n\n /**\n * \u521B\u5EFA\u65B0\u4F1A\u8BDD\u6216\u6062\u590D\u73B0\u6709\u4F1A\u8BDD\n * @param request \u8BF7\u6C42\u53C2\u6570\n * @returns \u65B0\u4F1A\u8BDD\u4FE1\u606F\uFF0C\u53EF\u80FD\u5305\u542B\u5386\u53F2\u6D88\u606F\n */\n createSession: (request: NewSessionRequest) => Promise<NewSessionResponse>\n\n /**\n * \u4E2D\u65AD\u5F53\u524D\u7684 SSE \u6D41\n */\n abortStream: () => void\n\n /**\n * \u662F\u5426\u6B63\u5728\u53D1\u9001\u6D88\u606F\n */\n isSending: boolean\n}\n\n/**\n * \u804A\u5929 API \u8C03\u7528 Hook\n *\n * \u529F\u80FD\uFF1A\n * 1. sendMessageStream: \u53D1\u9001\u6D88\u606F\u5E76\u63A5\u6536 SSE \u6D41\u5F0F\u54CD\u5E94\n * 2. createSession: \u521B\u5EFA\u65B0\u4F1A\u8BDD\u6216\u6062\u590D\u73B0\u6709\u4F1A\u8BDD\n * 3. abortStream: \u4E2D\u65AD\u5F53\u524D\u6D41\n *\n * @param options Hook \u914D\u7F6E\u9009\u9879\n * @returns API \u8C03\u7528\u5DE5\u5177\u5BF9\u8C61\n */\nexport function useChatAPI(options: UseChatAPIOptions): UseChatAPIReturn {\n const { apiBaseUrl, headers, onError } = options\n\n // \u7528\u4E8E\u4E2D\u65AD\u5F53\u524D\u8BF7\u6C42\u7684 AbortController\n const abortControllerRef = useRef<AbortController | null>(null)\n\n // \u53D1\u9001\u72B6\u6001\u6807\u8BB0\n const isSendingRef = useRef(false)\n\n /**\n * \u53D1\u9001\u6D88\u606F\u5E76\u63A5\u6536 SSE \u6D41\u5F0F\u54CD\u5E94\n */\n const sendMessageStream = useCallback(\n async (request: ChatStreamRequest, onEvent: (event: SSEEvent) => void) => {\n // \u5982\u679C\u6B63\u5728\u53D1\u9001\uFF0C\u5148\u4E2D\u65AD\u4E4B\u524D\u7684\u8BF7\u6C42\n if (abortControllerRef.current) {\n abortControllerRef.current.abort()\n }\n\n // \u521B\u5EFA\u65B0\u7684 AbortController\n abortControllerRef.current = new AbortController()\n isSendingRef.current = true\n\n try {\n await sendMessage(apiBaseUrl, request, onEvent, headers)\n } catch (error) {\n // \u5FFD\u7565\u624B\u52A8\u4E2D\u65AD\u7684\u9519\u8BEF\n if (error instanceof Error && error.name === 'AbortError') {\n return\n }\n\n console.error('[useChatAPI] Stream error:', error)\n onError?.(error as Error)\n throw error\n } finally {\n isSendingRef.current = false\n abortControllerRef.current = null\n }\n },\n [apiBaseUrl, headers, onError]\n )\n\n /**\n * \u521B\u5EFA\u65B0\u4F1A\u8BDD\u6216\u6062\u590D\u73B0\u6709\u4F1A\u8BDD\n */\n const createSession = useCallback(\n async (request: NewSessionRequest): Promise<NewSessionResponse> => {\n try {\n return await createNewSession(apiBaseUrl, request, headers)\n } catch (error) {\n console.error('[useChatAPI] Create session error:', error)\n onError?.(error as Error)\n throw error\n }\n },\n [apiBaseUrl, headers, onError]\n )\n\n /**\n * \u4E2D\u65AD\u5F53\u524D\u7684 SSE \u6D41\n */\n const abortStream = useCallback(() => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort()\n abortControllerRef.current = null\n isSendingRef.current = false\n }\n }, [])\n\n return {\n sendMessageStream,\n createSession,\n abortStream,\n isSending: isSendingRef.current,\n }\n}\n"],
|
|
5
|
-
"mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,gBAAAE,IAAA,eAAAC,EAAAH,GAMA,IAAAI,EAAoC,iBAEpCC,
|
|
6
|
-
"names": ["useChatAPI_exports", "__export", "useChatAPI", "__toCommonJS", "import_react", "import_chat", "options", "apiBaseUrl", "headers", "onError", "abortControllerRef", "isSendingRef", "sendMessageStream", "request", "onEvent", "error", "createSession", "abortStream"]
|
|
4
|
+
"sourcesContent": ["/**\n * \u804A\u5929 API \u8C03\u7528 Hook\n * \u5C01\u88C5 SSE \u6D41\u5F0F\u6D88\u606F\u63A5\u6536\u3001\u5386\u53F2\u6D88\u606F\u52A0\u8F7D\u3001\u65B0\u4F1A\u8BDD\u521B\u5EFA\n * \u57FA\u4E8E specs/livechat-widget/plan.md \u7684 API \u8C03\u7528\u7B56\u7565\n */\n\nimport { useCallback, useRef } from 'react'\nimport type { ChatStreamRequest, SSEEvent, NewSessionRequest, NewSessionResponse } from '../types'\nimport { sendMessage, createNewSession, type RecaptchaConfig } from '../api/chat'\n\nexport interface UseChatAPIOptions {\n /**\n * API \u57FA\u7840 URL\n */\n apiBaseUrl: string\n\n /**\n * \u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\n */\n headers?: Record<string, string>\n\n /**\n * reCAPTCHA \u914D\u7F6E\n */\n recaptchaConfig?: RecaptchaConfig\n\n /**\n * \u9519\u8BEF\u5904\u7406\u56DE\u8C03\n */\n onError?: (error: Error) => void\n}\n\nexport interface UseChatAPIReturn {\n /**\n * \u53D1\u9001\u6D88\u606F\u5E76\u63A5\u6536 SSE \u6D41\u5F0F\u54CD\u5E94\n * @param request \u8BF7\u6C42\u53C2\u6570\n * @param onEvent SSE \u4E8B\u4EF6\u56DE\u8C03\n * @returns Promise<void>\n */\n sendMessageStream: (request: ChatStreamRequest, onEvent: (event: SSEEvent) => void) => Promise<void>\n\n /**\n * \u521B\u5EFA\u65B0\u4F1A\u8BDD\u6216\u6062\u590D\u73B0\u6709\u4F1A\u8BDD\n * @param request \u8BF7\u6C42\u53C2\u6570\n * @returns \u65B0\u4F1A\u8BDD\u4FE1\u606F\uFF0C\u53EF\u80FD\u5305\u542B\u5386\u53F2\u6D88\u606F\n */\n createSession: (request: NewSessionRequest) => Promise<NewSessionResponse>\n\n /**\n * \u4E2D\u65AD\u5F53\u524D\u7684 SSE \u6D41\n */\n abortStream: () => void\n\n /**\n * \u662F\u5426\u6B63\u5728\u53D1\u9001\u6D88\u606F\n */\n isSending: boolean\n}\n\n/**\n * \u804A\u5929 API \u8C03\u7528 Hook\n *\n * \u529F\u80FD\uFF1A\n * 1. sendMessageStream: \u53D1\u9001\u6D88\u606F\u5E76\u63A5\u6536 SSE \u6D41\u5F0F\u54CD\u5E94\n * 2. createSession: \u521B\u5EFA\u65B0\u4F1A\u8BDD\u6216\u6062\u590D\u73B0\u6709\u4F1A\u8BDD\n * 3. abortStream: \u4E2D\u65AD\u5F53\u524D\u6D41\n *\n * @param options Hook \u914D\u7F6E\u9009\u9879\n * @returns API \u8C03\u7528\u5DE5\u5177\u5BF9\u8C61\n */\nexport function useChatAPI(options: UseChatAPIOptions): UseChatAPIReturn {\n const { apiBaseUrl, headers, recaptchaConfig, onError } = options\n\n // \u7528\u4E8E\u4E2D\u65AD\u5F53\u524D\u8BF7\u6C42\u7684 AbortController\n const abortControllerRef = useRef<AbortController | null>(null)\n\n // \u53D1\u9001\u72B6\u6001\u6807\u8BB0\n const isSendingRef = useRef(false)\n\n /**\n * \u53D1\u9001\u6D88\u606F\u5E76\u63A5\u6536 SSE \u6D41\u5F0F\u54CD\u5E94\n */\n const sendMessageStream = useCallback(\n async (request: ChatStreamRequest, onEvent: (event: SSEEvent) => void) => {\n // \u5982\u679C\u6B63\u5728\u53D1\u9001\uFF0C\u5148\u4E2D\u65AD\u4E4B\u524D\u7684\u8BF7\u6C42\n if (abortControllerRef.current) {\n abortControllerRef.current.abort()\n }\n\n // \u521B\u5EFA\u65B0\u7684 AbortController\n abortControllerRef.current = new AbortController()\n isSendingRef.current = true\n\n try {\n await sendMessage(apiBaseUrl, request, onEvent, headers, recaptchaConfig)\n } catch (error) {\n // \u5FFD\u7565\u624B\u52A8\u4E2D\u65AD\u7684\u9519\u8BEF\n if (error instanceof Error && error.name === 'AbortError') {\n return\n }\n\n console.error('[useChatAPI] Stream error:', error)\n onError?.(error as Error)\n throw error\n } finally {\n isSendingRef.current = false\n abortControllerRef.current = null\n }\n },\n [apiBaseUrl, headers, recaptchaConfig, onError]\n )\n\n /**\n * \u521B\u5EFA\u65B0\u4F1A\u8BDD\u6216\u6062\u590D\u73B0\u6709\u4F1A\u8BDD\n */\n const createSession = useCallback(\n async (request: NewSessionRequest): Promise<NewSessionResponse> => {\n try {\n return await createNewSession(apiBaseUrl, request, headers, recaptchaConfig)\n } catch (error) {\n console.error('[useChatAPI] Create session error:', error)\n onError?.(error as Error)\n throw error\n }\n },\n [apiBaseUrl, headers, recaptchaConfig, onError]\n )\n\n /**\n * \u4E2D\u65AD\u5F53\u524D\u7684 SSE \u6D41\n */\n const abortStream = useCallback(() => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort()\n abortControllerRef.current = null\n isSendingRef.current = false\n }\n }, [])\n\n return {\n sendMessageStream,\n createSession,\n abortStream,\n isSending: isSendingRef.current,\n }\n}\n"],
|
|
5
|
+
"mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,gBAAAE,IAAA,eAAAC,EAAAH,GAMA,IAAAI,EAAoC,iBAEpCC,EAAoE,uBA8D7D,SAASH,EAAWI,EAA8C,CACvE,KAAM,CAAE,WAAAC,EAAY,QAAAC,EAAS,gBAAAC,EAAiB,QAAAC,CAAQ,EAAIJ,EAGpDK,KAAqB,UAA+B,IAAI,EAGxDC,KAAe,UAAO,EAAK,EAK3BC,KAAoB,eACxB,MAAOC,EAA4BC,IAAuC,CAEpEJ,EAAmB,SACrBA,EAAmB,QAAQ,MAAM,EAInCA,EAAmB,QAAU,IAAI,gBACjCC,EAAa,QAAU,GAEvB,GAAI,CACF,QAAM,eAAYL,EAAYO,EAASC,EAASP,EAASC,CAAe,CAC1E,OAASO,EAAO,CAEd,GAAIA,aAAiB,OAASA,EAAM,OAAS,aAC3C,OAGF,cAAQ,MAAM,6BAA8BA,CAAK,EACjDN,IAAUM,CAAc,EAClBA,CACR,QAAE,CACAJ,EAAa,QAAU,GACvBD,EAAmB,QAAU,IAC/B,CACF,EACA,CAACJ,EAAYC,EAASC,EAAiBC,CAAO,CAChD,EAKMO,KAAgB,eACpB,MAAOH,GAA4D,CACjE,GAAI,CACF,OAAO,QAAM,oBAAiBP,EAAYO,EAASN,EAASC,CAAe,CAC7E,OAASO,EAAO,CACd,cAAQ,MAAM,qCAAsCA,CAAK,EACzDN,IAAUM,CAAc,EAClBA,CACR,CACF,EACA,CAACT,EAAYC,EAASC,EAAiBC,CAAO,CAChD,EAKMQ,KAAc,eAAY,IAAM,CAChCP,EAAmB,UACrBA,EAAmB,QAAQ,MAAM,EACjCA,EAAmB,QAAU,KAC7BC,EAAa,QAAU,GAE3B,EAAG,CAAC,CAAC,EAEL,MAAO,CACL,kBAAAC,EACA,cAAAI,EACA,YAAAC,EACA,UAAWN,EAAa,OAC1B,CACF",
|
|
6
|
+
"names": ["useChatAPI_exports", "__export", "useChatAPI", "__toCommonJS", "import_react", "import_chat", "options", "apiBaseUrl", "headers", "recaptchaConfig", "onError", "abortControllerRef", "isSendingRef", "sendMessageStream", "request", "onEvent", "error", "createSession", "abortStream"]
|
|
7
7
|
}
|
|
@@ -557,6 +557,23 @@ export interface LiveChatWidgetProps {
|
|
|
557
557
|
* @example { "Authorization": "Bearer token", "X-Custom-Header": "value" }
|
|
558
558
|
*/
|
|
559
559
|
headers?: Record<string, string>;
|
|
560
|
+
/**
|
|
561
|
+
* 是否启用 reCAPTCHA 验证
|
|
562
|
+
* @default false
|
|
563
|
+
*/
|
|
564
|
+
needRecaptcha?: boolean;
|
|
565
|
+
/**
|
|
566
|
+
* reCAPTCHA site key
|
|
567
|
+
* 当 needRecaptcha=true 时必填
|
|
568
|
+
* @example "6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14"
|
|
569
|
+
*/
|
|
570
|
+
recaptchaSitekey?: string;
|
|
571
|
+
/**
|
|
572
|
+
* reCAPTCHA action 前缀
|
|
573
|
+
* 实际使用时会根据不同接口添加后缀(如 chat_stream, new_session)
|
|
574
|
+
* @default "livechat"
|
|
575
|
+
*/
|
|
576
|
+
recaptchaAction?: string;
|
|
560
577
|
/**
|
|
561
578
|
* Shopify 店铺域名
|
|
562
579
|
* @example "www.eufy.com"
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var o=Object.defineProperty;var
|
|
1
|
+
"use strict";var o=Object.defineProperty;var a=Object.getOwnPropertyDescriptor;var s=Object.getOwnPropertyNames;var c=Object.prototype.hasOwnProperty;var u=(e,t,i,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of s(t))!c.call(e,r)&&r!==i&&o(e,r,{get:()=>t[r],enumerable:!(n=a(t,r))||n.enumerable});return e};var g=e=>u(o({},"__esModule",{value:!0}),e);var d={};module.exports=g(d);
|
|
2
2
|
//# sourceMappingURL=types.js.map
|
|
@@ -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}\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 }\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}\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 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// ============================================================================\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 * Shopify \u5E97\u94FA\u57DF\u540D\n * @example \"www.eufy.com\"\n */\n site?: 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 * \u4E8B\u4EF6\u56DE\u8C03\n */\n onOpen?: () => void\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\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}\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 }\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}\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 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// ============================================================================\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 * \u662F\u5426\u542F\u7528 reCAPTCHA \u9A8C\u8BC1\n * @default false\n */\n needRecaptcha?: boolean\n\n /**\n * reCAPTCHA site key\n * \u5F53 needRecaptcha=true \u65F6\u5FC5\u586B\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 * \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 * \u4E8B\u4EF6\u56DE\u8C03\n */\n onOpen?: () => void\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\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
|
}
|