UNPKG

@yourgpt/widget-web-sdk

Version:

Official YourGPT SDK for JavaScript/TypeScript and React applications

848 lines (839 loc) 23.5 kB
'use strict'; var react = require('react'); var jsxRuntime = require('react/jsx-runtime'); /* YourGPT SDK - React Package - https://yourgpt.ai */ // src/types/core.ts var YourGPTError = class extends Error { constructor(message, code) { super(message); this.code = code; this.name = "YourGPTError"; } }; // src/utils/index.ts var isBrowser = () => { return typeof window !== "undefined" && typeof document !== "undefined"; }; var isDevelopment = () => { return typeof process !== "undefined" && process.env?.NODE_ENV === "development"; }; var createDebugLogger = (namespace) => { return { log: (message, ...args) => { if (isDevelopment()) { console.log(`[YourGPT SDK:${namespace}] ${message}`, ...args); } }, warn: (message, ...args) => { if (isDevelopment()) { console.warn(`[YourGPT SDK:${namespace}] ${message}`, ...args); } }, error: (message, ...args) => { if (isDevelopment()) { console.error(`[YourGPT SDK:${namespace}] ${message}`, ...args); } } }; }; var waitFor = async (condition, timeout = 5e3, interval = 100) => { const start = Date.now(); while (!condition()) { if (Date.now() - start > timeout) { throw new YourGPTError(`Timeout after ${timeout}ms waiting for condition`); } await new Promise((resolve) => setTimeout(resolve, interval)); } }; var validateWidgetId = (widgetId) => { const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; return uuidRegex.test(widgetId); }; var validateUrl = (url) => { try { new URL(url); return true; } catch { return false; } }; var loadScript = (src, async = true) => { return new Promise((resolve, reject) => { if (!isBrowser()) { reject(new YourGPTError("Cannot load script in non-browser environment")); return; } if (document.querySelector(`script[src="${src}"]`)) { resolve(); return; } const script = document.createElement("script"); script.src = src; script.async = async; script.type = "module"; script.onload = () => resolve(); script.onerror = () => reject(new YourGPTError(`Failed to load script: ${src}`)); document.head.appendChild(script); }); }; var EventEmitter = class { constructor() { this.events = {}; } on(event, callback) { if (!this.events[event]) { this.events[event] = []; } this.events[event].push(callback); return () => { this.off(event, callback); }; } off(event, callback) { if (!this.events[event]) return; if (callback) { this.events[event] = this.events[event].filter((cb) => cb !== callback); } else { delete this.events[event]; } } emit(event, data) { if (!this.events[event]) return; this.events[event].forEach((callback) => { try { callback(data); } catch (error) { console.error(`Error in event handler for ${String(event)}:`, error); } }); } removeAllListeners() { this.events = {}; } }; // src/core/YourGPT.ts var _YourGPTSDK = class _YourGPTSDK extends EventEmitter { constructor() { super(); this.config = null; this.isInitialized = false; this.logger = createDebugLogger("Core"); this.state = { isOpen: false, isVisible: true, isConnected: false, isLoaded: false, messageCount: 0, connectionRetries: 0 }; this.aiActionHandlers = /* @__PURE__ */ new Map(); this.setupGlobalAPI(); } /** * Get singleton instance */ static getInstance() { if (!_YourGPTSDK.instance) { _YourGPTSDK.instance = new _YourGPTSDK(); } return _YourGPTSDK.instance; } /** * Initialize the SDK */ async init(config) { window.YGC_WIDGET_RENDER_MODE = config.mode || "floating" /* floating */; if (this.isInitialized) { this.logger.warn("SDK already initialized"); return this; } this.validateConfig(config); this.config = config; this.logger.log("Initializing SDK with config:", config); this.setupGlobalVariables(); if (config.autoLoad !== false) { await this.loadWidget(); } this.isInitialized = true; this.logger.log("SDK initialized successfully"); return this; } /** * Validate configuration */ validateConfig(config) { if (!config.widgetId) { throw new YourGPTError("Widget ID is required", "MISSING_WIDGET_ID"); } if (!validateWidgetId(config.widgetId)) { throw new YourGPTError("Invalid widget ID format", "INVALID_WIDGET_ID"); } if (config.endpoint && !validateUrl(config.endpoint)) { throw new YourGPTError("Invalid endpoint URL", "INVALID_ENDPOINT"); } } /** * Set up global variables */ setupGlobalVariables() { if (!isBrowser()) return; window.YOURGPT_WIDGET_UID = this.config.widgetId; if (!window.$yourgptChatbot) { window.$yourgptChatbot = {}; } window.$yourgptChatbot.WIDGET_ENDPOINT = this.getEndpoint(); } /** * Get endpoint URL */ getEndpoint() { if (this.config?.endpoint) { return this.config.endpoint; } return this.config?.whitelabel ? "https://widget.d4ai.chat" : ""; } /** * Load widget assets */ async loadWidget() { if (!isBrowser()) { throw new YourGPTError("Cannot load widget in non-browser environment", "NOT_BROWSER"); } const endpoint = this.getEndpoint(); try { this.createRootContainer(); await loadScript(`${endpoint}/chatbot.js`); await waitFor(() => this.isWidgetReady(), 1e4); this.updateState({ isLoaded: true }); this.logger.log("Widget loaded successfully"); } catch (error) { this.logger.error("Failed to load widget:", error); throw new YourGPTError("Failed to load widget", "WIDGET_LOAD_FAILED"); } } /** * Create root container for widget */ createRootContainer() { if (document.getElementById("yourgpt_root")) { return; } const root = document.createElement("div"); root.id = "yourgpt_root"; root.style.cssText = ` position: fixed; z-index: 99999; `; document.body.appendChild(root); } /** * Check if widget is ready */ isWidgetReady() { return Boolean(window.$yourgptChatbot?.execute && window.$yourgptChatbot?.on && window.$yourgptChatbot?.set); } /** * Set up global API for backwards compatibility */ setupGlobalAPI() { if (!isBrowser()) return; if (!window.$yourgptChatbot) { window.$yourgptChatbot = { q: [], execute: (action, ...args) => { window.$yourgptChatbot.q.push(["execute", action, ...args]); }, on: (event, callback) => { window.$yourgptChatbot.q.push(["on", event, callback]); }, off: (event, callback) => { window.$yourgptChatbot.q.push(["off", event, callback]); }, set: (key, value) => { window.$yourgptChatbot.q.push(["set", key, value]); } }; } } /** * Update widget state */ updateState(updates) { this.state = { ...this.state, ...updates }; this.emit("stateChange", this.state); } /** * Execute widget command */ executeCommand(command, ...args) { if (!this.isWidgetReady()) { this.logger.warn("Widget not ready, queueing command:", command); window.$yourgptChatbot?.q?.push(["execute", command, ...args]); return; } window.$yourgptChatbot.execute(command, ...args); } /** * Register event listener */ registerEventListener(event, callback) { if (!this.isWidgetReady()) { this.logger.warn("Widget not ready, queueing event listener:", event); window.$yourgptChatbot?.q?.push(["on", event, callback]); return; } window.$yourgptChatbot.on(event, callback); } /** * Set widget data */ setWidgetData(key, value) { if (!this.isWidgetReady()) { this.logger.warn("Widget not ready, queueing data:", key); window.$yourgptChatbot?.q?.push(["set", key, value]); return; } window.$yourgptChatbot.set(key, value); } /** * Get current configuration */ getConfig() { return this.config; } /** * Get current state */ getState() { return { ...this.state }; } /** * Check if SDK is initialized */ isReady() { return this.isInitialized; } /** * Widget Controls */ open() { this.executeCommand("widget:open"); this.updateState({ isOpen: true }); } close() { this.executeCommand("widget:close"); this.updateState({ isOpen: false }); } toggle() { if (this.state.isOpen) { this.close(); } else { this.open(); } } show() { this.executeCommand("widget:show"); this.updateState({ isVisible: true }); } hide() { this.executeCommand("widget:hide"); this.updateState({ isVisible: false }); } /** * Messaging */ sendMessage(text, autoSend = true) { this.executeCommand("message:send", { text, send: autoSend }); } /** * Advanced Features */ openBottomSheet(url) { this.executeCommand("bottomSheet:open", { url }); } startGame(gameId, options = {}) { this.executeCommand("game:start", { id: gameId, ...options }); } /** * Data Management */ setSessionData(data) { this.setWidgetData("session:data", data); } setVisitorData(data) { this.setWidgetData("visitor:data", data); } setContactData(data) { this.setWidgetData("contact:data", data); } /** * Event Listeners */ onInit(callback) { const wrappedCallback = () => { this.updateState({ isConnected: true }); callback(); }; this.registerEventListener("init", wrappedCallback); return () => window.$yourgptChatbot?.off?.("init", wrappedCallback); } onMessageReceived(callback) { const wrappedCallback = (data) => { this.updateState({ messageCount: this.state.messageCount + 1 }); callback(data); }; this.registerEventListener("message:received", wrappedCallback); return () => window.$yourgptChatbot?.off?.("message:received", wrappedCallback); } onEscalatedToHuman(callback) { this.registerEventListener("escalatedToHuman", callback); return () => window.$yourgptChatbot?.off?.("escalatedToHuman", callback); } onWidgetPopup(callback) { const wrappedCallback = (isOpen) => { this.updateState({ isOpen }); callback(isOpen); }; this.registerEventListener("widget:popup", wrappedCallback); return () => window.$yourgptChatbot?.off?.("widget:popup", wrappedCallback); } /** * AI Actions */ registerAIAction(actionName, handler) { this.aiActionHandlers.set(actionName, handler); this.registerEventListener(`ai:action:${actionName}`, handler); } unregisterAIAction(actionName) { this.aiActionHandlers.delete(actionName); window.$yourgptChatbot?.off?.(`ai:action:${actionName}`); } getRegisteredAIActions() { return Array.from(this.aiActionHandlers.keys()); } /** * Create complete chatbot API */ createChatbotAPI() { return { // State ...this.getState(), // Widget Controls open: this.open.bind(this), close: this.close.bind(this), toggle: this.toggle.bind(this), show: this.show.bind(this), hide: this.hide.bind(this), // Messaging sendMessage: this.sendMessage.bind(this), // Advanced Features openBottomSheet: this.openBottomSheet.bind(this), startGame: this.startGame.bind(this), // Data Management setSessionData: this.setSessionData.bind(this), setVisitorData: this.setVisitorData.bind(this), setContactData: this.setContactData.bind(this), // Event Listeners onInit: this.onInit.bind(this), onMessageReceived: this.onMessageReceived.bind(this), onEscalatedToHuman: this.onEscalatedToHuman.bind(this), onWidgetPopup: this.onWidgetPopup.bind(this) }; } /** * Create AI Actions API */ createAIActionsAPI() { return { registerAction: this.registerAIAction.bind(this), unregisterAction: this.unregisterAIAction.bind(this), registerActions: (actions) => { Object.entries(actions).forEach(([name, handler]) => { this.registerAIAction(name, handler); }); }, getRegisteredActions: this.getRegisteredAIActions.bind(this), get registeredActions() { return this.getRegisteredActions(); } }; } /** * Cleanup */ destroy() { this.removeAllListeners(); this.aiActionHandlers.clear(); this.isInitialized = false; this.config = null; _YourGPTSDK.instance = null; } }; _YourGPTSDK.instance = null; var YourGPTSDK = _YourGPTSDK; var YourGPT = { /** * Initialize the SDK */ init: (config) => { const sdk = YourGPTSDK.getInstance(); return sdk.init(config); }, /** * Get the SDK instance */ getInstance: () => { return YourGPTSDK.getInstance(); } }; var YourGPT_default = YourGPT; // src/react/hooks/useYourGPT.ts function useYourGPT(options = {}) { const { config, autoInit = true } = options; const [sdk, setSdk] = react.useState(null); const [isInitialized, setIsInitialized] = react.useState(false); const [isLoading, setIsLoading] = react.useState(false); const [error, setError] = react.useState(null); const [state, setState] = react.useState({ isOpen: false, isVisible: true, isConnected: false, isLoaded: false, messageCount: 0, connectionRetries: 0 }); const sdkRef = react.useRef(null); const init = react.useCallback(async (initConfig) => { if (isInitialized) { return; } setIsLoading(true); setError(null); try { const sdkInstance = await YourGPT_default.init(initConfig); const unsubscribe = sdkInstance.on("stateChange", (newState) => { setState(newState); }); sdkRef.current = sdkInstance; setSdk(sdkInstance); setIsInitialized(true); setState(sdkInstance.getState()); sdkInstance._unsubscribeStateChange = unsubscribe; } catch (err) { const sdkError = err instanceof YourGPTError ? err : new YourGPTError(String(err)); setError(sdkError); } finally { setIsLoading(false); } }, [isInitialized]); const destroy = react.useCallback(() => { if (sdkRef.current) { if (sdkRef.current._unsubscribeStateChange) { sdkRef.current._unsubscribeStateChange(); } sdkRef.current.destroy(); sdkRef.current = null; setSdk(null); setIsInitialized(false); setState({ isOpen: false, isVisible: true, isConnected: false, isLoaded: false, messageCount: 0, connectionRetries: 0 }); } }, []); react.useEffect(() => { if (config && autoInit && !isInitialized && !isLoading) { init(config); } }, [config, autoInit, isInitialized, isLoading, init]); react.useEffect(() => { return () => { if (sdkRef.current) { if (sdkRef.current._unsubscribeStateChange) { sdkRef.current._unsubscribeStateChange(); } } }; }, []); return { sdk, isInitialized, isLoading, error, state, init, destroy }; } function useYourGPTChatbot() { const [state, setState] = react.useState({ isOpen: false, isVisible: true, isConnected: false, isLoaded: false, messageCount: 0, connectionRetries: 0 }); const sdkRef = react.useRef(null); const unsubscribersRef = react.useRef([]); react.useEffect(() => { const sdk = YourGPT_default.getInstance(); sdkRef.current = sdk; setState(sdk.getState()); const unsubscribe = sdk.on("stateChange", (newState) => { setState(newState); }); unsubscribersRef.current.push(unsubscribe); return () => { unsubscribersRef.current.forEach((unsub) => unsub()); unsubscribersRef.current = []; }; }, []); const widgetControls = react.useMemo(() => ({ open: () => sdkRef.current?.open(), close: () => sdkRef.current?.close(), toggle: () => sdkRef.current?.toggle(), show: () => sdkRef.current?.show(), hide: () => sdkRef.current?.hide() }), []); const messaging = react.useMemo(() => ({ sendMessage: (text, autoSend = true) => { sdkRef.current?.sendMessage(text, autoSend); } }), []); const advanced = react.useMemo(() => ({ openBottomSheet: (url) => { sdkRef.current?.openBottomSheet(url); }, startGame: (gameId, options = {}) => { sdkRef.current?.startGame(gameId, options); } }), []); const dataManagement = react.useMemo(() => ({ setSessionData: (data) => { sdkRef.current?.setSessionData(data); }, setVisitorData: (data) => { sdkRef.current?.setVisitorData(data); }, setContactData: (data) => { sdkRef.current?.setContactData(data); } }), []); const eventListeners = react.useMemo(() => ({ onInit: (callback) => { const unsubscribe = sdkRef.current?.onInit(callback); if (unsubscribe) { unsubscribersRef.current.push(unsubscribe); } return unsubscribe || (() => { }); }, onMessageReceived: (callback) => { const unsubscribe = sdkRef.current?.onMessageReceived(callback); if (unsubscribe) { unsubscribersRef.current.push(unsubscribe); } return unsubscribe || (() => { }); }, onEscalatedToHuman: (callback) => { const unsubscribe = sdkRef.current?.onEscalatedToHuman(callback); if (unsubscribe) { unsubscribersRef.current.push(unsubscribe); } return unsubscribe || (() => { }); }, onWidgetPopup: (callback) => { const unsubscribe = sdkRef.current?.onWidgetPopup(callback); if (unsubscribe) { unsubscribersRef.current.push(unsubscribe); } return unsubscribe || (() => { }); } }), []); return { // State ...state, // Functions ...widgetControls, ...messaging, ...advanced, ...dataManagement, ...eventListeners }; } function useAIActions() { const [registeredActions, setRegisteredActions] = react.useState([]); const handlersRef = react.useRef(/* @__PURE__ */ new Map()); const sdkRef = react.useRef(null); react.useEffect(() => { const sdk = YourGPT_default.getInstance(); sdkRef.current = sdk; setRegisteredActions(sdk.getRegisteredAIActions()); }, []); const registerAction = react.useCallback((actionName, handler) => { if (!sdkRef.current) return; handlersRef.current.set(actionName, handler); sdkRef.current.registerAIAction(actionName, handler); setRegisteredActions(Array.from(handlersRef.current.keys())); }, []); const unregisterAction = react.useCallback((actionName) => { if (!sdkRef.current) return; handlersRef.current.delete(actionName); sdkRef.current.unregisterAIAction(actionName); setRegisteredActions(Array.from(handlersRef.current.keys())); }, []); const getRegisteredActions = react.useCallback(() => { return Array.from(handlersRef.current.keys()); }, []); const registerActions = react.useCallback((actions) => { Object.entries(actions).forEach(([name, handler]) => { registerAction(name, handler); }); }, [registerAction]); react.useEffect(() => { return () => { handlersRef.current.forEach((_, actionName) => { sdkRef.current?.unregisterAIAction(actionName); }); handlersRef.current.clear(); }; }, []); return { registerAction, unregisterAction, registerActions, getRegisteredActions, registeredActions }; } var YourGPTContext = react.createContext(null); function YourGPTProvider({ children, config, onError, onInitialized }) { const [isBrowser2, setIsBrowser] = react.useState(false); const [sdk, setSdk] = react.useState(null); const [isInitialized, setIsInitialized] = react.useState(false); const [isLoading, setIsLoading] = react.useState(false); const [error, setError] = react.useState(null); const [state, setState] = react.useState({ isOpen: false, isVisible: true, isConnected: false, isLoaded: false, messageCount: 0, connectionRetries: 0 }); react.useEffect(() => { setIsBrowser(true); }, []); react.useEffect(() => { if (!isBrowser2) return; const initializeSDK = async () => { setIsLoading(true); setError(null); try { const sdkInstance = await YourGPT_default.init(config); const unsubscribe = sdkInstance.on("stateChange", (newState) => { setState(newState); }); setSdk(sdkInstance); setIsInitialized(true); setState(sdkInstance.getState()); sdkInstance._unsubscribeStateChange = unsubscribe; if (onInitialized) { onInitialized(sdkInstance); } } catch (err) { const sdkError = err instanceof YourGPTError ? err : new YourGPTError(String(err)); setError(sdkError); if (onError) { onError(sdkError); } } finally { setIsLoading(false); } }; initializeSDK(); return () => { if (sdk) { if (sdk._unsubscribeStateChange) { sdk._unsubscribeStateChange(); } } }; }, [config, onError, onInitialized, isBrowser2]); const value = { sdk, isInitialized, isLoading, error, state }; return /* @__PURE__ */ jsxRuntime.jsx(YourGPTContext.Provider, { value, children }); } function useYourGPT2() { const context = react.useContext(YourGPTContext); if (!context) { throw new YourGPTError("useYourGPT must be used within a YourGPTProvider"); } return context; } function YourGPTWidget({ className, style, onMount, onUnmount }) { const containerRef = react.useRef(null); const { isInitialized } = useYourGPT2(); const [isBrowser2, setIsBrowser] = react.useState(false); const mode = typeof window !== "undefined" ? window.YGC_WIDGET_RENDER_MODE : "floating" /* floating */; react.useEffect(() => { setIsBrowser(true); }, []); react.useEffect(() => { if (!isInitialized || !isBrowser2) return void 0; const container = containerRef.current; if (!container || mode !== "embedded") return void 0; window.YGC_WIDGET?.renderEmbedded(container); onMount?.(); return () => { window.YGC_WIDGET?.cleanup?.(); onUnmount?.(); }; }, [isInitialized, isBrowser2, mode]); if (!isBrowser2) { return null; } if (mode === "embedded") { return /* @__PURE__ */ jsxRuntime.jsx( "div", { ref: containerRef, id: `yourgpt-container-${Math.random()}`, className, style: { position: "relative", width: "100%", height: "100%", ...style } } ); } return null; } // src/react/index.ts var VERSION = "1.0.0"; exports.VERSION = VERSION; exports.YourGPT = YourGPT_default; exports.YourGPTError = YourGPTError; exports.YourGPTProvider = YourGPTProvider; exports.YourGPTSDK = YourGPTSDK; exports.YourGPTWidget = YourGPTWidget; exports.useAIActions = useAIActions; exports.useYourGPT = useYourGPT; exports.useYourGPTChatbot = useYourGPTChatbot; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map