diff --git a/web/public/sdk/cs-ai-agent-sdk.min.js b/web/public/sdk/cs-ai-agent-sdk.min.js index c084a84..2cf693f 100644 --- a/web/public/sdk/cs-ai-agent-sdk.min.js +++ b/web/public/sdk/cs-ai-agent-sdk.min.js @@ -1 +1,363 @@ -(function(){var DEFAULT_CONFIG={position:"right",themeColor:"#0f6cbd",width:"380px",externalSource:"web_chat",};var state=window.__CS_AGENT_WIDGET_STATE__;if(!state){state={button:null,frame:null,frameLoaded:false,frameReady:false,initSent:false,isOpen:false,isMaximized:false,frameHideTimer:null,frameDestroyTimer:null,config:null,frameUrl:null,animationDuration:260,};window.__CS_AGENT_WIDGET_STATE__=state;}function normalizeConfig(config){var merged={};var key;for(key in DEFAULT_CONFIG){if(Object.prototype.hasOwnProperty.call(DEFAULT_CONFIG,key)){merged[key]=DEFAULT_CONFIG[key];}}config=config||{};for(key in config){if(Object.prototype.hasOwnProperty.call(config,key)){merged[key]=config[key];}}merged.baseUrl=String(merged.baseUrl||window.location.origin).replace(/\/$/,"");merged.apiBaseUrl=String(merged.apiBaseUrl||merged.baseUrl).replace(/\/$/,"");merged.channelId=String(merged.channelId||"");merged.externalSource=String(merged.externalSource||"web_chat");return merged;}function resolveWidgetBaseUrl(config){var currentScript=document.currentScript;if(currentScript&¤tScript.src){return currentScript.src.replace(/\/sdk\/cs-ai-agent-sdk\.min\.js(?:\?.*)?$/,"");}return String(config.widgetBaseUrl||config.baseUrl||window.location.origin).replace(/\/$/,"");}function createFrameUrl(config){var widgetBaseUrl=resolveWidgetBaseUrl(config);var frameUrl=new URL(widgetBaseUrl+"/kefu/chat/");frameUrl.searchParams.set("channelId",config.channelId);frameUrl.searchParams.set("baseUrl",config.baseUrl);if(config.apiBaseUrl)frameUrl.searchParams.set("apiBaseUrl",config.apiBaseUrl);if(config.externalSource)frameUrl.searchParams.set("externalSource",config.externalSource);if(config.title)frameUrl.searchParams.set("title",config.title);if(config.subtitle)frameUrl.searchParams.set("subtitle",config.subtitle);if(config.position)frameUrl.searchParams.set("position",config.position);if(config.themeColor)frameUrl.searchParams.set("themeColor",config.themeColor);if(config.width)frameUrl.searchParams.set("width",config.width);if(config.subject)frameUrl.searchParams.set("subject",config.subject);return frameUrl;}function clearFrameTimers(){if(state.frameHideTimer){window.clearTimeout(state.frameHideTimer);state.frameHideTimer=null;}if(state.frameDestroyTimer){window.clearTimeout(state.frameDestroyTimer);state.frameDestroyTimer=null;}}function applyFrameLayout(){var frame=state.frame;var config=state.config;if(!frame||!config){return;}frame.style.position="fixed";frame.style.border="0";frame.style.overflow="hidden";frame.style.background="#fff";frame.style.zIndex="2147483000";frame.style.boxShadow="0 28px 80px rgba(15,35,65,0.28)";frame.style.willChange="top,right,bottom,left,width,height,opacity,transform,border-radius";frame.style.transition="top 260ms cubic-bezier(0.22,1,0.36,1),right 260ms cubic-bezier(0.22,1,0.36,1),bottom 260ms cubic-bezier(0.22,1,0.36,1),left 260ms cubic-bezier(0.22,1,0.36,1),width 260ms cubic-bezier(0.22,1,0.36,1),height 260ms cubic-bezier(0.22,1,0.36,1),opacity 220ms ease,transform 260ms cubic-bezier(0.22,1,0.36,1),border-radius 260ms cubic-bezier(0.22,1,0.36,1),box-shadow 260ms ease";frame.style.transformOrigin=config.position==="left"?"left bottom":"right bottom";if(state.isMaximized){frame.style.top="20px";frame.style.right="20px";frame.style.bottom="20px";frame.style.left="20px";frame.style.width="calc(100vw-40px)";frame.style.maxWidth="none";frame.style.height="calc(100vh-40px)";frame.style.borderRadius="24px";return;}frame.style.top="";frame.style.bottom="88px";frame.style.right=config.position==="left"?"":"24px";frame.style.left=config.position==="left"?"24px":"";frame.style.width=config.width||"380px";frame.style.maxWidth="calc(100vw-24px)";frame.style.height="min(760px,calc(100vh-112px))";frame.style.borderRadius="28px";}function postToFrame(message){if(!state.frame||!state.frame.contentWindow||!state.frameUrl){return;}try{state.frame.contentWindow.postMessage(message,state.frameUrl.origin);}catch(error){console.error("[cs-agent-widget]postMessage failed",error);}}function flushFrameState(){if(!state.frame||!state.frameLoaded||!state.frameReady){return;}if(!state.initSent){state.initSent=true;postToFrame({type:"cs-agent:init",payload:state.config,});}postToFrame({type:state.isOpen?"cs-agent:open":"cs-agent:minimize"});postToFrame({type:"cs-agent:maximized",payload:{isMaximized:state.isMaximized},});}function syncFrameVisibility(){var frame=state.frame;if(!frame){return;}clearFrameTimers();applyFrameLayout();frame.style.display="block";if(state.isOpen){frame.style.visibility="visible";frame.style.pointerEvents="auto";state.frameHideTimer=window.setTimeout(function(){if(!state.frame){return;}state.frame.style.opacity="1";state.frame.style.transform="translate3d(0,0,0)scale(1)";},16);flushFrameState();return;}frame.style.pointerEvents="none";frame.style.opacity="0";frame.style.transform=state.isMaximized?"translate3d(0,10px,0)scale(0.985)":"translate3d(0,16px,0)scale(0.96)";state.frameHideTimer=window.setTimeout(function(){if(!state.frame||state.isOpen){return;}state.frame.style.visibility="hidden";},state.animationDuration);flushFrameState();}function destroyFrame(){if(!state.frame){return;}clearFrameTimers();state.frame.style.pointerEvents="none";state.frame.style.opacity="0";state.frame.style.transform="translate3d(0,18px,0)scale(0.94)";state.frame.style.visibility="hidden";state.frameDestroyTimer=window.setTimeout(function(){if(!state.frame){return;}if(state.frame.parentNode){state.frame.parentNode.removeChild(state.frame);}state.frame=null;state.frameLoaded=false;state.frameReady=false;state.initSent=false;state.isOpen=false;state.isMaximized=false;clearFrameTimers();},state.animationDuration);}function createFrame(){if(state.frame){return state.frame;}state.frame=document.createElement("iframe");state.frame.dataset.csAgentWidget="frame";state.frame.title=state.config.title||"在线客服";state.frame.src=state.frameUrl.toString();applyFrameLayout();state.frame.style.display="block";state.frame.style.visibility="hidden";state.frame.style.pointerEvents="none";state.frame.style.opacity="0";state.frame.style.transform="translate3d(0,18px,0)scale(0.96)";state.frame.addEventListener("load",function(){state.frameLoaded=true;syncFrameVisibility();});document.body.appendChild(state.frame);return state.frame;}function handleWindowMessage(event){if(!state.frame||event.source !==state.frame.contentWindow){return;}var data=event.data||{};if(data.type==="cs-agent:ready"){state.frameReady=true;flushFrameState();return;}if(data.type==="cs-agent:request-minimize"){state.isOpen=false;syncFrameVisibility();return;}if(data.type==="cs-agent:request-close"){destroyFrame();return;}if(data.type==="cs-agent:request-toggle-maximize"){state.isMaximized=!state.isMaximized;syncFrameVisibility();}}function createLauncher(){if(state.button){return state.button;}var config=state.config;var button=document.createElement("button");button.type="button";button.dataset.csAgentWidget="launcher";button.setAttribute("aria-label",config.title||"在线客服");button.textContent=config.title||"在线客服";button.style.position="fixed";button.style.bottom="24px";button.style.right=config.position==="left"?"":"24px";button.style.left=config.position==="left"?"24px":"";button.style.zIndex="2147483000";button.style.border="0";button.style.borderRadius="999px";button.style.padding="14px 18px";button.style.background=config.themeColor||"#0f6cbd";button.style.color="#fff";button.style.font="600 14px/1 sans-serif";button.style.boxShadow="0 18px 40px rgba(15,35,65,0.24)";button.style.cursor="pointer";button.addEventListener("click",function(){if(!state.frame){createFrame();}state.isOpen=!state.isOpen;syncFrameVisibility();});document.body.appendChild(button);state.button=button;return button;}function mount(config){state.config=normalizeConfig(config||window.CSAgentConfig||{});if(!state.config.channelId||!state.config.baseUrl){console.error("[cs-agent-widget]channelId and baseUrl are required");return;}state.frameUrl=createFrameUrl(state.config);createLauncher();}function destroy(){clearFrameTimers();if(state.frame&&state.frame.parentNode){state.frame.parentNode.removeChild(state.frame);}if(state.button&&state.button.parentNode){state.button.parentNode.removeChild(state.button);}state.button=null;state.frame=null;state.frameLoaded=false;state.frameReady=false;state.initSent=false;state.isOpen=false;state.isMaximized=false;}window.CSAgentWidget={mount:mount,destroy:destroy,open:function(){if(!state.frame){createFrame();}state.isOpen=true;syncFrameVisibility();},close:function(){state.isOpen=false;syncFrameVisibility();},};if(!state.listenerBound){window.addEventListener("message",handleWindowMessage);state.listenerBound=true;}if(window.CSAgentConfig){mount(window.CSAgentConfig);}})(); +(function () { + var DEFAULT_CONFIG = { + position: "right", + themeColor: "#0f6cbd", + width: "380px", + externalSource: "web_chat", + }; + + var state = window.__CS_AGENT_WIDGET_STATE__; + if (!state) { + state = { + button: null, + frame: null, + frameLoaded: false, + frameReady: false, + initSent: false, + isOpen: false, + isMaximized: false, + frameHideTimer: null, + frameDestroyTimer: null, + config: null, + frameUrl: null, + animationDuration: 260, + }; + window.__CS_AGENT_WIDGET_STATE__ = state; + } + + function normalizeConfig(config) { + var merged = {}; + var key; + for (key in DEFAULT_CONFIG) { + if (Object.prototype.hasOwnProperty.call(DEFAULT_CONFIG, key)) { + merged[key] = DEFAULT_CONFIG[key]; + } + } + config = config || {}; + for (key in config) { + if (Object.prototype.hasOwnProperty.call(config, key)) { + merged[key] = config[key]; + } + } + merged.baseUrl = String(merged.baseUrl || window.location.origin).replace(/\/$/, ""); + merged.apiBaseUrl = String(merged.apiBaseUrl || merged.baseUrl).replace(/\/$/, ""); + merged.channelId = String(merged.channelId || ""); + merged.externalSource = String(merged.externalSource || "web_chat"); + return merged; + } + + function resolveWidgetBaseUrl(config) { + var currentScript = document.currentScript; + if (currentScript && currentScript.src) { + return currentScript.src.replace(/\/sdk\/cs-ai-agent-sdk\.min\.js(?:\?.*)?$/, ""); + } + return String(config.widgetBaseUrl || config.baseUrl || window.location.origin).replace(/\/$/, ""); + } + + function createFrameUrl(config) { + var widgetBaseUrl = resolveWidgetBaseUrl(config); + var frameUrl = new URL(widgetBaseUrl + "/kefu/chat/"); + frameUrl.searchParams.set("channelId", config.channelId); + frameUrl.searchParams.set("baseUrl", config.baseUrl); + if (config.apiBaseUrl) frameUrl.searchParams.set("apiBaseUrl", config.apiBaseUrl); + if (config.externalSource) frameUrl.searchParams.set("externalSource", config.externalSource); + if (config.title) frameUrl.searchParams.set("title", config.title); + if (config.subtitle) frameUrl.searchParams.set("subtitle", config.subtitle); + if (config.position) frameUrl.searchParams.set("position", config.position); + if (config.themeColor) frameUrl.searchParams.set("themeColor", config.themeColor); + if (config.width) frameUrl.searchParams.set("width", config.width); + if (config.subject) frameUrl.searchParams.set("subject", config.subject); + return frameUrl; + } + + function clearFrameTimers() { + if (state.frameHideTimer) { + window.clearTimeout(state.frameHideTimer); + state.frameHideTimer = null; + } + if (state.frameDestroyTimer) { + window.clearTimeout(state.frameDestroyTimer); + state.frameDestroyTimer = null; + } + } + + function applyFrameLayout() { + var frame = state.frame; + var config = state.config; + if (!frame || !config) { + return; + } + + frame.style.position = "fixed"; + frame.style.border = "0"; + frame.style.overflow = "hidden"; + frame.style.background = "#fff"; + frame.style.zIndex = "2147483000"; + frame.style.boxShadow = "0 28px 80px rgba(15, 35, 65, 0.28)"; + frame.style.willChange = "top,right,bottom,left,width,height,opacity,transform,border-radius"; + frame.style.transition = + "top 260ms cubic-bezier(0.22, 1, 0.36, 1), right 260ms cubic-bezier(0.22, 1, 0.36, 1), bottom 260ms cubic-bezier(0.22, 1, 0.36, 1), left 260ms cubic-bezier(0.22, 1, 0.36, 1), width 260ms cubic-bezier(0.22, 1, 0.36, 1), height 260ms cubic-bezier(0.22, 1, 0.36, 1), opacity 220ms ease, transform 260ms cubic-bezier(0.22, 1, 0.36, 1), border-radius 260ms cubic-bezier(0.22, 1, 0.36, 1), box-shadow 260ms ease"; + frame.style.transformOrigin = + config.position === "left" ? "left bottom" : "right bottom"; + + if (state.isMaximized) { + frame.style.top = "20px"; + frame.style.right = "20px"; + frame.style.bottom = "20px"; + frame.style.left = "20px"; + frame.style.width = "calc(100vw - 40px)"; + frame.style.maxWidth = "none"; + frame.style.height = "calc(100vh - 40px)"; + frame.style.borderRadius = "24px"; + return; + } + + frame.style.top = ""; + frame.style.bottom = "88px"; + frame.style.right = config.position === "left" ? "" : "24px"; + frame.style.left = config.position === "left" ? "24px" : ""; + frame.style.width = config.width || "380px"; + frame.style.maxWidth = "calc(100vw - 24px)"; + frame.style.height = "min(760px, calc(100vh - 112px))"; + frame.style.borderRadius = "28px"; + } + + function postToFrame(message) { + if (!state.frame || !state.frame.contentWindow || !state.frameUrl) { + return; + } + try { + state.frame.contentWindow.postMessage(message, state.frameUrl.origin); + } catch (error) { + console.error("[cs-agent-widget] postMessage failed", error); + } + } + + function flushFrameState() { + if (!state.frame || !state.frameLoaded || !state.frameReady) { + return; + } + + if (!state.initSent) { + state.initSent = true; + postToFrame({ + type: "cs-agent:init", + payload: state.config, + }); + } + + postToFrame({ type: state.isOpen ? "cs-agent:open" : "cs-agent:minimize" }); + postToFrame({ + type: "cs-agent:maximized", + payload: { isMaximized: state.isMaximized }, + }); + } + + function syncFrameVisibility() { + var frame = state.frame; + if (!frame) { + return; + } + clearFrameTimers(); + applyFrameLayout(); + frame.style.display = "block"; + + if (state.isOpen) { + frame.style.visibility = "visible"; + frame.style.pointerEvents = "auto"; + state.frameHideTimer = window.setTimeout(function () { + if (!state.frame) { + return; + } + state.frame.style.opacity = "1"; + state.frame.style.transform = "translate3d(0, 0, 0) scale(1)"; + }, 16); + flushFrameState(); + return; + } + + frame.style.pointerEvents = "none"; + frame.style.opacity = "0"; + frame.style.transform = state.isMaximized + ? "translate3d(0, 10px, 0) scale(0.985)" + : "translate3d(0, 16px, 0) scale(0.96)"; + state.frameHideTimer = window.setTimeout(function () { + if (!state.frame || state.isOpen) { + return; + } + state.frame.style.visibility = "hidden"; + }, state.animationDuration); + flushFrameState(); + } + + function destroyFrame() { + if (!state.frame) { + return; + } + clearFrameTimers(); + state.frame.style.pointerEvents = "none"; + state.frame.style.opacity = "0"; + state.frame.style.transform = "translate3d(0, 18px, 0) scale(0.94)"; + state.frame.style.visibility = "hidden"; + state.frameDestroyTimer = window.setTimeout(function () { + if (!state.frame) { + return; + } + if (state.frame.parentNode) { + state.frame.parentNode.removeChild(state.frame); + } + state.frame = null; + state.frameLoaded = false; + state.frameReady = false; + state.initSent = false; + state.isOpen = false; + state.isMaximized = false; + clearFrameTimers(); + }, state.animationDuration); + } + + function createFrame() { + if (state.frame) { + return state.frame; + } + + state.frame = document.createElement("iframe"); + state.frame.dataset.csAgentWidget = "frame"; + state.frame.title = state.config.title || "在线客服"; + state.frame.src = state.frameUrl.toString(); + applyFrameLayout(); + state.frame.style.display = "block"; + state.frame.style.visibility = "hidden"; + state.frame.style.pointerEvents = "none"; + state.frame.style.opacity = "0"; + state.frame.style.transform = "translate3d(0, 18px, 0) scale(0.96)"; + state.frame.addEventListener("load", function () { + state.frameLoaded = true; + syncFrameVisibility(); + }); + + document.body.appendChild(state.frame); + return state.frame; + } + + function handleWindowMessage(event) { + if (!state.frame || event.source !== state.frame.contentWindow) { + return; + } + + var data = event.data || {}; + if (data.type === "cs-agent:ready") { + state.frameReady = true; + flushFrameState(); + return; + } + + if (data.type === "cs-agent:request-minimize") { + state.isOpen = false; + syncFrameVisibility(); + return; + } + + if (data.type === "cs-agent:request-close") { + destroyFrame(); + return; + } + + if (data.type === "cs-agent:request-toggle-maximize") { + state.isMaximized = !state.isMaximized; + syncFrameVisibility(); + } + } + + function createLauncher() { + if (state.button) { + return state.button; + } + + var config = state.config; + var button = document.createElement("button"); + button.type = "button"; + button.dataset.csAgentWidget = "launcher"; + button.setAttribute("aria-label", config.title || "在线客服"); + button.textContent = config.title || "在线客服"; + button.style.position = "fixed"; + button.style.bottom = "24px"; + button.style.right = config.position === "left" ? "" : "24px"; + button.style.left = config.position === "left" ? "24px" : ""; + button.style.zIndex = "2147483000"; + button.style.border = "0"; + button.style.borderRadius = "999px"; + button.style.padding = "14px 18px"; + button.style.background = config.themeColor || "#0f6cbd"; + button.style.color = "#fff"; + button.style.font = "600 14px/1 sans-serif"; + button.style.boxShadow = "0 18px 40px rgba(15, 35, 65, 0.24)"; + button.style.cursor = "pointer"; + + button.addEventListener("click", function () { + if (!state.frame) { + createFrame(); + } + state.isOpen = !state.isOpen; + syncFrameVisibility(); + }); + + document.body.appendChild(button); + state.button = button; + return button; + } + + function mount(config) { + state.config = normalizeConfig(config || window.CSAgentConfig || {}); + if (!state.config.channelId || !state.config.baseUrl) { + console.error("[cs-agent-widget] channelId and baseUrl are required"); + return; + } + + state.frameUrl = createFrameUrl(state.config); + createLauncher(); + } + + function destroy() { + clearFrameTimers(); + if (state.frame && state.frame.parentNode) { + state.frame.parentNode.removeChild(state.frame); + } + if (state.button && state.button.parentNode) { + state.button.parentNode.removeChild(state.button); + } + state.button = null; + state.frame = null; + state.frameLoaded = false; + state.frameReady = false; + state.initSent = false; + state.isOpen = false; + state.isMaximized = false; + } + + window.CSAgentWidget = { + mount: mount, + destroy: destroy, + open: function () { + if (!state.frame) { + createFrame(); + } + state.isOpen = true; + syncFrameVisibility(); + }, + close: function () { + state.isOpen = false; + syncFrameVisibility(); + }, + }; + + if (!state.listenerBound) { + window.addEventListener("message", handleWindowMessage); + state.listenerBound = true; + } + + if (window.CSAgentConfig) { + mount(window.CSAgentConfig); + } +})(); + diff --git a/web/scripts/build-sdk.mjs b/web/scripts/build-sdk.mjs index bb0189b..7a74fcf 100644 --- a/web/scripts/build-sdk.mjs +++ b/web/scripts/build-sdk.mjs @@ -1,4 +1,4 @@ -import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { cp, mkdir } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -8,19 +8,7 @@ const source = path.join(rootDir, "lib", "sdk", "cs-ai-agent-sdk.js"); const targetDir = path.join(rootDir, "public", "sdk"); const target = path.join(targetDir, "cs-ai-agent-sdk.min.js"); -function minifyJavaScript(sourceText) { - return sourceText - .replace(/\/\*[\s\S]*?\*\//g, "") - .replace(/(^|[^:])\/\/.*$/gm, "$1") - .replace(/\s+/g, " ") - .replace(/\s*([{}()[\];,:?=+\-*/<>|&])\s*/g, "$1") - .trim(); -} - -const sourceText = await readFile(source, "utf8"); -const output = `${minifyJavaScript(sourceText)}\n`; - await mkdir(targetDir, { recursive: true }); -await writeFile(target, output, "utf8"); +await cp(source, target); console.log(`sdk written to ${target}`);