"use strict"; /** * @class AIChat * @constructor * @param {?(Object.|Array.)} [inputs = nulls] * @returns {void} * @access public * @static */ export const AIChat = (function(){ /** * @callback aichat_init_callback * @param {!boolean} ok * @returns {boolean} */ /** * @callback aichat_preload_callback * @param {?HTMLElement} item * @param {!boolean} asynchronous * @param {!boolean} ok * @returns {void} */ /** * @typedef {HTMLElement|string|[string, Object.|null, HTMLElement|string|Array.|null]} aichat_html_item */ /** * @constructs AIChat * @param {?(Object.|Array.)} [inputs = nulls] * @returns {void} * @access private * @static */ const AIChat = function(inputs = null){ /** @type {AIChat} */ const self = this, /** @type {Object.} */ settings = {}, /** @type {Object.} */ secrets = {}; /** @type {boolean} */ let started = false, /** @type {number} */ preload_timeout = 2000, /** @type {number} */ frames_per_second = 60, /** @type {WebSocket|null} */ web_socket_client = null; /** * @returns {void} * @access private */ const constructor = () => { self.get("autostart") && self.start(); }; /** * @param {?aichat_init_callaback} [callback = null] * @returns {boolean} * @access public */ this.start = (callback = null) => { /** @type {aichat_init_callaback} */ const end = ok => typeof callback == "function" ? callback(ok) : ok; if(started) return end(false); started = true; preload_timeout = self.get(["preload_timeout", "timeout"], null, preload_timeout); frames_per_second = self.get(["frames_per_second", "fps"], null, frames_per_second); self.preload(self.get("position", null, "body"), (item, asynchronous, ok) => { if(ok){ build(item); try{ web_socket_client = new WebSocket(self.get("web_socket_url", null, "ws://localhost:18001/")); web_socket_client.onopen = on_web_socket_open; web_socket_client.onmessage = on_web_socket_message; web_socket_client.onerror = on_web_socket_error; web_socket_client.onclose = on_web_socket_close; }catch(exception){ console.error("Failed to connect to the WebSocket server:", exception); }; }; end(ok); }); return true; }; /** * @param {?aichat_init_callaback} [callback = null] * @returns {boolean} * @access public */ this.stop = (callback = null) => { /** @type {aichat_init_callaback} */ const end = ok => typeof callback == "function" ? callback(ok) : ok; if(!started) return end(false); started = false; return end(true); }; const on_web_socket_open = event => { web_socket_client.send("Hello, WebSocket server!"); }; const on_web_socket_message = event => {}; const on_web_socket_error = event => {}; const on_web_socket_close = event => {}; /** * @param {!(string|Array.)} keys * @param {!(Object.|Array.)} inputs * @param {?any} [_default = null] * @returns {any|null} * @access public */ this.get = (keys, subinputs, _default = null) => AIChat.get_value(keys, [ subinputs, inputs, secrets, settings, AIChat.DEFAULT_SETTINGS ], _default); /** * @param {!(string|HTMLElement)} selector * @param {!aichat_preload_callback} callback * @returns {void} * @access public */ this.preload = (selector, callback) => { if(typeof callback != "function") return; if(AIChat.is_html_item(selector)) return callback(selector, false, true); if(typeof selector != "string") return callback(null, false, false); /** @type {HTMLElement|null} */ let item; try{ if(item = document.querySelector(selector)) return callback(item, false, true); }catch(exception){ return callback(null, false, false); }; /** @type {number} */ const date = Date.now(); /** @type {number} */ let interval = setInterval(() => { if(item = document.querySelector(selector)){ clearInterval(interval); callback(item, true, true); }else if(Date.now() - date > preload_timeout){ clearInterval(interval); callback(null, true, false); }; }, 1000 / frames_per_second); }; /** * @param {!HTMLElement} item * @return {void} * @access private */ const build = item => { AIChat.HTML(item, ["div", {class: "aichat"}, [ ["header", null, [ ["h1", null, "AI Chat"] ]], ["main", null, [ ["fieldset", {class : "chat"}, [ ["legend", {data_i18n : "chat"}, "Chat"], ["section", {"class" : "messages"}], ["form", { action : "#", method : "post", on_submit : send }, [ ["div", null, [ ["textarea", { name : "message", data_i18n : "type_message", data_i18n_without : true, placeholder : "Type your message here..." }] ]], ["button", { type : "submit", data_i18n : "send", data_i18n_without : true, title : "Send" }, [ ["span", {data_icon : "send"}], ["span", {data_i18n : "send"}, "Send"] ]], ["button", { type : "reset", data_i18n : "clean", data_i18n_without : true, title : "Clean" }, [ ["span", {data_icon : "clean"}], ["span", {data_i18n : "clean"}, "Clean"] ]] ]] ]] ]], ["footer"] ]]); }; /** * @param {!HTMLElement} item * @param {!Event} event * @returns {any|null|void} * @access private */ const send = (item, event) => { event.preventDefault(); web_socket_client.send(document.querySelector(".aichat form textarea").value); return false; }; constructor(); }; /** @type {Object.} */ AIChat.DEFAULT_SETTINGS = { /** @type {boolean} */ autostart : true, /** @type {number} */ timeout : 2000, /** @type {number} */ preload_timeout : 2000, /** @type {number} */ frames_per_second : 60, /** @type {string} */ position : "body" }; /** * @param {?any} item * @returns {boolean} * @access public * @static */ AIChat.is_key = item => typeof item == "string" && /^[a-z_][a-z0-9_]*$/i.test(item); /** * @param {?any} item * @returns {boolean} * @access public * @static */ AIChat.is_dictionary = item => item && item.constructor === Object; /** * @param {?any} item * @returns {boolean} * @access public * @static */ AIChat.is_html_item = item => item && (item.tagName || item.nodeName); /** * @param {...?any} items * @returns {Array.} * @access public * @static */ AIChat.get_keys = (...items) => { /** @type {Array.} */ const keys = []; items.forEach(item => { if(AIChat.is_key(item)) keys.includes(item) || keys.push(item); else if(Array.isArray(items)) AIChat.get_keys(...item).forEach(key => keys.includes(key) || keys.push(key)); }); return keys; }; /** * @param {...?any} items * @returns {Array.>} * @access public * @static */ AIChat.get_dictionaries = (...items) => { /** @type {Array.>} */ const dictionaries = []; items.forEach(item => { if(AIChat.is_dictionary(item)) dictionaries.includes(item) || dictionaries.push(item); else if(item instanceof Array) AIChat.get_dictionaries(...item).forEach(dictionary => { dictionaries.includes(dictionary) || dictionaries.push(dictionary); }); }); return dictionaries; }; /** * @param {!(string|Array.)} keys * @param {!(Object.|Array.)} inputs * @param {?any} [_default = null] * @returns {any|null} * @access public * @static */ AIChat.get_value = (keys, inputs, _default = null) => { if((keys = AIChat.get_keys(keys)).length) for(const subinputs of AIChat.get_dictionaries(inputs)) for(const key of keys) if(key in subinputs) return subinputs[key]; return _default; }; /** * @param {!(HTMLElement|string)} item * @param {!Object.} attributes * @returns {void} * @access public * @static */ AIChat.set_attributes = (item, attributes) => { typeof item == "string" && (item = document.querySelector(item)); if(AIChat.is_html_item(item)) for(const key in attributes){ /** @type {any|null} */ const value = attributes[key]; if(/^on/i.test(key) && typeof value == "function") item.addEventListener(key.replace(/^on[-_]?|-/ig, ""), event => value(item, event)); else item.setAttribute(key.replace(/[^a-z0-9]+/ig, "-"), value); }; }; /** * @param {...aichat_html_item} items * @returns {Array.} * @access public * @static */ AIChat.HTML = (...items) => { /** @type {boolean} */ const has_html_item = AIChat.is_html_item(typeof items[0] == "string" ? items[0] = document.querySelector(items[0]) || items[0] : items[0]), /** @type {DocumentFragment} */ fragment = document.createDocumentFragment(); (has_html_item ? items.slice(1) : items).forEach(item => { if(AIChat.is_html_item(item)) fragment.appendChild(item); else if(typeof item == "string") fragment.appendChild(document.createTextNode(item)); else if(Array.isArray(item)){ /** @type {[string, Object.|null, HTMLElement|string|Array.|null]} */ const [tag, attributes, children] = item.concat(null, null), /** @type {!HTMLElement} */ child = fragment.appendChild(document.createElement(tag)); AIChat.is_dictionary(attributes) && AIChat.set_attributes(child, attributes); if(AIChat.is_html_item(children)) child.appendChild(children); else if(typeof children == "string") child.appendChild(document.createTextNode(children)); else if(Array.isArray(children)) AIChat.HTML(child, ...children); }; }); has_html_item && items[0].appendChild(fragment); return fragment.childNodes; }; return AIChat; })();