AIDockerCompose/Public/ecma/AIChat.ecma.js
2026-05-06 18:58:50 +02:00

420 lines
13 KiB
JavaScript

"use strict";
/**
* @class AIChat
* @constructor
* @param {?(Object.<string, any|null>|Array.<any|null>)} [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.<string, any|null>|null, HTMLElement|string|Array.<aichat_html_item>|null]} aichat_html_item
*/
/**
* @constructs AIChat
* @param {?(Object.<string, any|null>|Array.<any|null>)} [inputs = nulls]
* @returns {void}
* @access private
* @static
*/
const AIChat = function(inputs = null){
/** @type {AIChat} */
const self = this,
/** @type {Object.<string, any|null>} */
settings = {},
/** @type {Object.<string, any|null>} */
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.<string>)} keys
* @param {!(Object.<string, any|null>|Array.<any|null>)} 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.<string, any|null>} */
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.<string>}
* @access public
* @static
*/
AIChat.get_keys = (...items) => {
/** @type {Array.<string>} */
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.<Object.<string, any|null>>}
* @access public
* @static
*/
AIChat.get_dictionaries = (...items) => {
/** @type {Array.<Object.<string, any|null>>} */
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.<string>)} keys
* @param {!(Object.<string, any|null>|Array.<any|null>)} 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.<string, any|null>} 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.<HTMLElement>}
* @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.<string, any|null>|null, HTMLElement|string|Array.<aichat_html_item>|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;
})();