420 lines
13 KiB
JavaScript
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;
|
|
})(); |