(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, configLoading: 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.subject) frameUrl.searchParams.set("subject", config.subject); return frameUrl; } function mergeWidgetConfig(config, remoteConfig) { if (!remoteConfig) { return config; } var merged = {}; var key; for (key in config) { if (Object.prototype.hasOwnProperty.call(config, key)) { merged[key] = config[key]; } } var remoteKeys = ["title", "subtitle", "welcomeText", "themeColor", "position", "width"]; for (var i = 0; i < remoteKeys.length; i += 1) { key = remoteKeys[i]; if ( Object.prototype.hasOwnProperty.call(remoteConfig, key) && remoteConfig[key] !== undefined && remoteConfig[key] !== null ) { merged[key] = remoteConfig[key]; } } return merged; } function fetchWidgetConfig(config) { var baseUrl = String(config.apiBaseUrl || config.baseUrl || "").replace(/\/$/, ""); if (!baseUrl || !config.channelId || typeof fetch !== "function") { return Promise.resolve(config); } var url = baseUrl + "/api/open/im/widget/config?channelId=" + encodeURIComponent(config.channelId); return fetch(url, { method: "GET", cache: "no-store", headers: { "X-Channel-Id": config.channelId, }, }) .then(function (response) { return response.json(); }) .then(function (payload) { if (!payload || payload.success === false) { return config; } return mergeWidgetConfig(config, payload.data || {}); }) .catch(function () { return config; }); } 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; } if (!state.frameUrl || !state.config) { return null; } 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) { var rawConfig = config || window.CSAgentConfig || {}; state.config = normalizeConfig(rawConfig); var widgetBaseUrl = resolveWidgetBaseUrl(state.config); if (!rawConfig.baseUrl) { state.config.baseUrl = widgetBaseUrl; } if (!rawConfig.apiBaseUrl) { state.config.apiBaseUrl = state.config.baseUrl; } if (!state.config.channelId) { console.error("[cs-agent-widget] channelId is required"); return; } state.configLoading = true; fetchWidgetConfig(state.config).then(function (nextConfig) { state.configLoading = false; state.config = normalizeConfig(nextConfig); state.frameUrl = createFrameUrl(state.config); if (state.button && state.button.parentNode) { state.button.parentNode.removeChild(state.button); state.button = null; } 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; state.configLoading = false; } window.CSAgentWidget = { mount: mount, destroy: destroy, open: function () { if (!state.frame) { createFrame(); } if (!state.frame) { return; } 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); } })();