#wip: AI client working.

This commit is contained in:
KyMAN 2026-06-08 08:07:37 +02:00
parent f1371e7389
commit c73884ef35
38 changed files with 1462 additions and 438 deletions

View File

@ -68,7 +68,7 @@
"AnP_RoutesManager_end" : null,
"AnP_WebSocketsServersManager_start" : null,
"default_web_sockets_servers2" : {
"default_web_sockets_servers" : {
"anp" : {
"type" : "WebSocketServerDriver",
"host" : "",

View File

@ -31,10 +31,10 @@
"AnP_RoutesManager_end" : null,
"AnP_WebSocketServerDriver_start" : null,
"web_socket_server_client_close_exception" : "El Web Socket Servidor '{host}:{port}' ha cerrado la conexión con el cliente '{key}'.",
"web_socket_server_client_connected" : "El cliente '{key}' se ha conectado al Web Socket Servidor '{host}:{port}' desde '{client_host}:{client_port}'.",
"web_socket_server_client_exception" : "Excepción al intentar procesar un mensaje de recepción del cliente '{key}' en el Web Socket Servidor '{host}:{port}'.",
"web_socket_server_client_disconnected" : "El cliente '{key}' se ha desconectado del Web Socket Servidor '{host}:{port}'.",
"web_socket_server_client_close_exception" : "El Web Socket Servidor '{host}:{port}' ha cerrado la conexión con el cliente '{id}'.",
"web_socket_server_client_connected" : "El cliente '{id}' se ha conectado al Web Socket Servidor '{host}:{port}' desde '{client_host}:{client_port}'.",
"web_socket_server_client_exception" : "Excepción al intentar procesar un mensaje de recepción del cliente '{id}' en el Web Socket Servidor '{host}:{port}'.",
"web_socket_server_client_disconnected" : "El cliente '{id}' se ha desconectado del Web Socket Servidor '{host}:{port}'.",
"AnP_WebSocketServerDriver_end" : null,
"AnP_TitlesManager_start" : null,

View File

@ -0,0 +1,146 @@
"use strict";
import {Event} from "../Application/Event.ecma.js";
import {Check} from "../Utils/Checks.ecma.js";
import {Common} from "../Utils/Common.ecma.js";
/**
* @typedef {import("../Application/AnP.ecma.js").AnP} AnP
*/
/**
* @class WebSocketsClientsAbstract
* @constructor
* @param {!AnP} anp
* @param {!string} key
* @param {!(Object.<string, any|null>|Array.<any|null>)} inputs
* @return {void}
* @access public
* @static
*/
export const WebSocketsClientsAbstract = (function(){
/**
* @constructs WebSocketsClientsAbstract
* @param {!AnP} anp
* @param {!string} key
* @param {!(Object.<string, any|null>|Array.<any|null>)} inputs
* @return {void}
* @access private
* @static
*/
const WebSocketsClientsAbstract = function(anp, key, inputs){
/** @type {WebSocketsClientsAbstract|Object.<string, any|null>} */
let self = this;
/** @type {string|null} */
this.key = null;
/** @type {string|null} */
this.url = null;
/** @type {string|null} */
this.id = null;
/** @type {Event} */
this.on_open = new Event();
/** @type {Event} */
this.on_message = new Event();
/** @type {Event} */
this.on_close = new Event();
/** @type {Event} */
this.on_error = new Event();
/**
* @param {!Object.<string, any|null>} item
* @returns {void}
* @access public
*/
this.change_self = item => {
self = item;
};
/**
* @return {void}
* @access private
*/
const constructor = () => {
self.key = key;
self.url = Common.get_value("url", (
Check.is_string(inputs) ? inputs = {url : inputs} :
inputs), "");
self.on_open.add(on_opened);
self.on_message.add(on_received);
self.on_close.add(on_closed);
self.on_error.add(on_errored);
};
/**
* @returns {void}
* @access private
*/
const on_opened = () => {};
/**
* @param {!string} inputs
* @returns {void}
* @access private
*/
const on_received = inputs => {
/** @type {Object.<string, any|null>} */
const data = Common.data_decode(inputs);
switch(data.controller + "." + data.action){
case "web_socket_client.set_id":
self.id = data.data;
console.log(self.id);
break;
default:
anp.controllers.execute(data.controller, data.action, data, data.code);
break;
};
};
/**
* @param {!string} controller
* @param {!string} action
* @param {?any} [data = null]
* @param {!number} [code = 200]
* @return {void}
* @access public
*/
this.send = (controller, action, data, code = 200) => {};
/**
* @return {void}
* @access public
*/
this.close = () => {};
/**
* @returns {void}
* @access private
*/
const on_closed = () => {
self.id = null;
};
/**
* @param {!string} error
* @returns {void}
* @access private
*/
const on_errored = error => {
console.error(error);
self.close();
};
constructor();
};
return WebSocketsClientsAbstract;
})();

View File

@ -8,6 +8,7 @@ import {ThreadsManager} from "../Managers/ThreadsManager.ecma.js";
import {UniqueKeysManager} from "../Managers/UniqueKeysManager.ecma.js";
import {SessionsManager} from "../Managers/SessionsManager.ecma.js";
import {ModelsManager} from "../Managers/ModelsManager.ecma.js";
import {ControllersManager} from "../Managers/ControllersManager.ecma.js";
import {ViewsManager} from "../Managers/ViewsManager.ecma.js";
import {RoutesManager} from "../Managers/RoutesManager.ecma.js";
import {WebSocketsClientsManager} from "../Managers/WebSocketsClientsManager.ecma.js";
@ -70,6 +71,8 @@ export const AnP = (function(){
this.models = new ModelsManager(self);
/** @type {Components} */
this.components = new Components(self);
/** @type {ControllersManager} */
this.controllers = new ControllersManager(self);
/** @type {ViewsManager} */
this.views = new ViewsManager(self);
/** @type {RoutesManager} */
@ -94,7 +97,7 @@ export const AnP = (function(){
*/
this.update = (callback = null) => {
Common.execute_array([
"files", "settings", "print_types", "i18n", "threads", "models", "views", "routes"
"files", "settings", "print_types", "i18n", "threads", "models", "controllers", "views", "routes", "web_sockets_clients"
], (key, next) => {
self[key].update(next);
}, callback, true);
@ -107,7 +110,7 @@ export const AnP = (function(){
*/
this.reset = (callback = null) => {
Common.execute_array([
"files", "settings", "print_types", "i18n", "threads", "models", "views", "routes"
"files", "settings", "print_types", "i18n", "threads", "models", "controllers", "views", "routes", "web_sockets_clients"
], (key, next) => {
self[key].reset(next);
}, callback, true);
@ -161,7 +164,11 @@ export const AnP = (function(){
};
started = false;
end(true);
Common.execute_array(["web_sockets_clients"], (key, next) => {
self[key].close(next);
}, () => {
end(true);
});
return true;
};

View File

@ -47,6 +47,8 @@ export const Components = (function(){
/** @type {Components} */
const self = this;
/** @type {number|null} */
let thread = null;
/** @type {BaseComponent} */
this.base = new BaseComponent(anp);
@ -71,7 +73,32 @@ export const Components = (function(){
* @returns {void}
* @access private
*/
const constructor = () => {};
const constructor = () => {
thread = anp.threads.add(thread_method, {
autostart : true,
bucle : true,
timer : 100
});
};
/**
* @returns {void}
* @access private
*/
const thread_method = () => {
document.querySelectorAll(".anp .input .field-information[data-i18n=length]").forEach(item => {
/** @type {HTMLSpanElement} */
const field = item.querySelector(".value"),
/** @type {HTMLInputElement|HTMLTextAreaElement} */
input = item.parentNode.parentNode.querySelector("input,textarea");
field.textContent != input.value.length && (field.textContent = input.value.length);
});
};
/**
* @param {!(string|Array.<string>)} keys
@ -117,6 +144,7 @@ export const Components = (function(){
},
on_error : (item, event) => {
/** @type {number} */
const i = Number(item.parentNode.getAttribute("data_i")) + 1;
item.setAttribute("src", Common.json_decode(Common.base64_decode(item.parentNode.getAttribute("data-data")))[i]);
@ -129,21 +157,45 @@ export const Components = (function(){
]);
};
/**
* @param {!string} i18n
* @param {!string} [tag = "span"]
* @param {?(Object.<string, any|null>|Array.<any|null>)} [variables = null]
* @returns {Array.<any|null>}
* @access public
*/
this.i18n = (i18n, tag = "span", variables = null) => [tag, {
data_i18n : i18n,
...(variables ? {data_i18n_variables : Common.data_encode(variables)} : {})
}, anp.i18n.get(i18n, variables)];
/**
* @param {!string} icon
* @param {!string} [tag = "span"]
* @returns {Array.<any|null>}
* @access public
*/
this.icon = (icon, tag = "span") => [tag, {data_icon : icon}];
/**
* @param {!string} name
* @param {!(string|event_callback)} action
* @param {?(Object.<string, any|null>|Array.<any|null>)} [inputs = null]
* @returns {Array.<any|null>}
* @access public
*/
this.button = (name, action, inputs = null) => {
/** @type {string} */
const text = anp.i18n.get(name, (
Check.is_bool(inputs) ? {toogled : inputs} :
Check.is_string(inputs) ? {type : inputs} :
inputs)),
/** @type {boolean} */
has_action = Check.is_function(action),
/** @type {string} */
type = Check.is_string(action) ? action : Common.get_value("type", inputs, "button"),
/** @type {boolean|null} */
toggled = Common.get_value("toggled", inputs, null);
has_action || (action = null);
@ -169,6 +221,12 @@ export const Components = (function(){
]);
};
/**
* @param {!string} name
* @param {?(Object.<string, any|null>|Array.<any|null>)} [inputs = null]
* @returns {Array.<any|null>}
* @access public
*/
this.checkbox = (name, inputs = null) => Label({
class : "checkbox",
data_i18n : name,
@ -188,8 +246,15 @@ export const Components = (function(){
self.i18n(name)
]);
/**
* @param {!string} name
* @param {?(Object.<string, any|null>|Array.<any|null>)} [inputs = null]
* @returns {Array.<any|null>}
* @access public
*/
this.radio = (name, inputs = null) => {
/** @type {string|null} */
const set = Common.get_value("set", inputs);
return Label({
@ -213,10 +278,36 @@ export const Components = (function(){
]);
};
/**
* @param {!string} name
* @param {?any} value
* @returns {Array.<any|null>}
* @access public
*/
const field_information = (name, value) => Span({
class : "field-information",
data_i18n : name,
data_i18n_without : true,
title : anp.i18n.get(name),
data_value : "" + value
}, [
self.i18n(name),
Span({class : "value"}, "" + value)
]);
/**
* @param {!string} name
* @param {?(Object.<string, any|null>|Array.<any|null>)} [inputs = null]
* @returns {Array.<any|null>}
* @access public
*/
this.text = (name, inputs = null) => {
/** @type {string} */
const text = anp.i18n.get(name),
/** @type {number|null} */
minimum_length = Common.get_value("minimum_length", inputs, null),
/** @type {number|null} */
maximum_length = Common.get_value("maximum_length", inputs, null);
return Span({
@ -249,17 +340,28 @@ export const Components = (function(){
"value"
], inputs)
}),
Span({class : "minimum", data_minimum : minimum_length}, "" + minimum_length),
Span({class : "length"}, "" + Common.get_value("value", inputs, "").length),
Span({class : "maximum", data_maximum : maximum_length}, "" + maximum_length)
Span({class : "information"}, [
field_information("minimum", minimum_length),
field_information("length", Common.get_value("value", inputs, "").length),
field_information("maximum", maximum_length),
])
]);
};
/**
* @param {!string} name
* @param {?(Object.<string, any|null>|Array.<any|null>)} [inputs = null]
* @returns {Array.<any|null>}
* @access public
*/
this.password = (name, inputs = null) => {
/** @type {string} */
const text = anp.i18n.get(name),
minimum_length = Common.get_value("minimum_length", inputs, 0),
maximum_length = Common.get_value("maximum_length", inputs, 0);
/** @type {number|null} */
minimum_length = Common.get_value("minimum_length", inputs, null),
/** @type {number|null} */
maximum_length = Common.get_value("maximum_length", inputs, null);
return Span({
data_i18n : name,
@ -279,14 +381,23 @@ export const Components = (function(){
"value"
], inputs)
}),
Span({class : "minimum", data_minimum : minimum_length}, "" + minimum_length),
Span({class : "length"}, "" + Common.get_value("value", inputs, "").length),
Span({class : "maximum", data_maximum : maximum_length}, "" + maximum_length)
Span({class : "information"}, [
field_information("minimum", minimum_length),
field_information("length", Common.get_value("value", inputs, "").length),
field_information("maximum", maximum_length),
])
]);
};
/**
* @param {!string} name
* @param {?(Object.<string, any|null>|Array.<any|null>)} [inputs = null]
* @returns {Array.<any|null>}
* @access public
*/
this.email = (name, inputs = null) => {
/** @type {string} */
const text = anp.i18n.get(name);
return Span({
@ -305,12 +416,22 @@ export const Components = (function(){
]);
};
/**
* @param {!string} name
* @param {?(Object.<string, any|null>|Array.<any|null>)} [inputs = null]
* @returns {Array.<any|null>}
* @access public
*/
this.number = (name, inputs = null) => {
/** @type {string} */
const text = anp.i18n.get(name),
minimum = Common.get_value("minimum", inputs, 0),
maximum = Common.get_value("maximum", inputs, 0),
value = Common.get_value("value", inputs, 0);
/** @type {number|null} */
minimum = Common.get_value("minimum", inputs, null),
/** @type {number|null} */
maximum = Common.get_value("maximum", inputs, null),
/** @type {number|null} */
value = Common.get_value("value", inputs, null);
return Span({
data_i18n : name,
@ -331,9 +452,11 @@ export const Components = (function(){
"value"
], inputs)
}),
Span({class : "minimum", data_minimum : minimum}, "" + minimum),
Span({class : "value"}, "" + value),
Span({class : "maximum", data_maximum : maximum}, "" + maximum)
Span({class : "information"}, [
field_information("minimum", minimum),
// field_information("value", value),
field_information("maximum", maximum),
])
]);
};
@ -341,6 +464,7 @@ export const Components = (function(){
* @param {!Array.<Array.<[string, string|event_callback, Object.<string, any|null>|Array.<any|null>]>>} buttons
* @param {?(Object.<string, any|null>|Array.<any|null>)} [inputs = null]
* @returns {Array.<any|null>}
* @access public
*/
this.buttons = (buttons, inputs = null) => Div({
class : ["buttons"].concat(Common.get_array(Common.get_value("class", inputs, []))).join(" ").trim(),

View File

@ -33,9 +33,9 @@ export const Event = (function(){
/** @type {Event} */
const self = this,
/** @type {Object.<number, event_callback>} */
events = {},
/** @type {number} */
id_i = 0;
events = {};
/** @type {number} */
let id_i = 0;
/** @type {boolean} */
this.once = once;
/** @type {boolean} */

View File

@ -1,6 +1,9 @@
"use strict";
import {Fieldset, Section, Form, Div} from "../Utils/HTMLDSL.ecma.js";
import {
Fieldset, Section, Form, Div, Nav, UL, LI, Span, Pre
} from "../Utils/HTMLDSL.ecma.js";
import {MarkDown} from "../Utils/MarkDown.ecma.js";
import {Common} from "../Utils/Common.ecma.js";
/**
@ -41,8 +44,6 @@ export const AIChatComponent = (function(){
const name = Common.get_value("name", inputs, "aichat");
web_socket_id = anp.web_sockets_clients.add("AAA", "https://localhost:18000/");
return Fieldset({class : "aichat"}, [
anp.components.i18n(name, "legend"),
Section({class : "messages"}),
@ -60,6 +61,86 @@ export const AIChatComponent = (function(){
]);
};
const build_data_icon = (name, value) => LI({
data_name : name,
data_i18n : value,
data_i18n_without : true,
title : anp.components.i18n(value)
}, [
anp.components.icon(value),
anp.components.i18n(value)
]);
const build_data_item = (name, value = null) => LI({
data_name : name,
data_i18n : name,
data_i18n_without : true,
title : anp.components.i18n(name)
}, [
anp.components.icon(name),
anp.components.i18n(name),
value === null ? null : Span({class : "value"}, "" + value)
]);
const get_message_box = item => {
while((!item.classList || !item.classList.contains("message")) && (item = item.parentElement));
return item;
};
const set_view = (item, event) => {
const box = get_message_box(item),
mode = item.getAttribute("data-i18n");
box.getAttribute("data-mode") != mode && box.setAttribute("data-mode", mode);
};
const format = content => {
const html = MarkDown.to_html(content);
return [html, html.replace(/</g, "&lt;").replace(/>/g, "&gt;"), content];
};
const build_message = (id, type, content = "", mode = "waiting") => {
const [html, raw_html, md] = format(content),
date = Date.now();
return Fieldset({
class : "message",
data_id : id,
data_type : type,
data_ok : true,
data_status : mode,
data_done : mode == "done",
data_mode : "html"
}, [
anp.components.i18n(type, "legend"),
Div({class : "message-box html-content"}, html),
Pre({class : "message-box raw-html-content"}, raw_html),
Pre({class : "message-box md-content"}, md),
Nav({class : "view buttons"}, [
anp.components.button("html", set_view),
anp.components.button("raw_html", set_view),
anp.components.button("md", set_view),
]),
UL({class : "data"}, [
build_data_icon("status", mode),
build_data_item("ok"),
build_data_item("done"),
build_data_item("date_from", date),
build_data_item("date_to", date),
build_data_item("time", 0),
build_data_item("length", content.length),
build_data_item("response_tokens", content.replace(/(?:[^a-z0-9]+|[\r\n]+)+/gi, " ").trim().split(" ").length)
])
]);
};
const send = (item, event) => {
const text_box = item.querySelector("[name=message]"),
@ -68,15 +149,21 @@ export const AIChatComponent = (function(){
event.preventDefault();
if(text){
Common.HTML(".aichat .messages", Fieldset({
class : "message",
data_type : "user",
data_id : anp.unique_keys.create()
}, [
anp.components.i18n("user", "legend"),
Div({class : "content"}, text)
]));
const data_id = anp.unique_keys.get();
Common.HTML(
".aichat .messages",
build_message(data_id, "user", text, "done"),
build_message(data_id, "bot")
);
text_box.value = "";
anp.web_sockets_clients.send("anp", "ai", "message", {
message_id : data_id,
message : text
});
};
return false;
@ -86,24 +173,35 @@ export const AIChatComponent = (function(){
event.key == "Enter" && !event.shiftKey && send(item, event);
};
this.write_response = (id, fragment) => {
this.write_response = (id, fragment, ok, done) => {
let box = document.querySelector(".aichat .messages>[data-type=bot][data-id='" + id + "']");
const box = document.querySelector(".aichat .messages>[data-type=bot][data-id='" + id + "']"),
status = (
!ok ? "error" :
done ? "done" :
"loading"),
status_text = anp.i18n.get(status),
status_box = box.querySelector("[data-name=status]"),
status_i18n_box = status_box.querySelector("[data-i18n]"),
date = Date.now(),
tokens_box = box.querySelector("[data-name=response_tokens] .value"),
[html, raw_html, md] = format(box.querySelector(".md-content").innerText += fragment);
if(!box){
if(!document.querySelector(".aichat .messages>[data-type=user][data-id='" + id + "']"))
return;
box = Common.HTML(".aichat .messages", Fieldset({
class : "message",
data_type : "box",
data_id : id
}, [
anp.components.i18n("assistant", "legend"),
Div({class : "content"}, "")
]), true)[0];
};
box.querySelector(".html-content").innerHTML = html;
box.querySelector(".raw-html-content").innerHTML = raw_html;
box.querySelector(".content").innerHTML += fragment;
box.setAttribute("data-ok", ok);
box.setAttribute("data-done", done);
box.setAttribute("data-status", status);
status_box.setAttribute("data-i18n", status);
status_box.setAttribute("title", status_text);
status_i18n_box.setAttribute("data-i18n", status_text);
status_i18n_box.innerHTML = status_text;
box.querySelector("[data-name=date_to]").querySelector(".value").innerHTML = date;
box.querySelector("[data-name=time]").querySelector(".value").innerHTML = date - Number(box.querySelector("[data-name=date_from] .value").innerText);
tokens_box.innerText = Number(tokens_box.innerText) + 1;
};

View File

@ -123,7 +123,7 @@ export const BaseComponent = (function(){
/** @type {string} */
logo = anp.settings.get(["application_logo", "logo"], inputs, "images/logo.webp"),
/** @type {string} */
id = anp.unique_keys.create(),
id = anp.unique_keys.get(),
/** @type {string} */
gui_mode = Check.is_dark_mode() ? "dark" : "light",
/** @type {boolean} */

View File

@ -1,39 +0,0 @@
"use strict";
/**
* @typedef {import("../Application/AnP.ecma.js").AnP} AnP
*/
/**
* @class AIChat
* @constructor
* @param {!AnP} anp
* @returns {void}
* @access private
* @static
*/
export const AIChat = (function(){
/**
* @constructs AIChat
* @param {!AnP} anp
* @returns {void}
* @access private
* @static
*/
const AIChat = function(anp){
/** @type {AIChat} */
const self = this;
/**
* @returns {void}
* @access private
*/
const constructor = () => {};
constructor();
};
return AIChat;
})();

View File

@ -0,0 +1,55 @@
"use strict";
/**
* @typedef {import("../Application/AnP.ecma.js").AnP} AnP
*/
/**
* @class AIController
* @constructor
* @param {!AnP} anp
* @returns {void}
* @access private
* @static
*/
export const AIController = (function(){
/**
* @constructs AIController
* @param {!AnP} anp
* @returns {void}
* @access private
* @static
*/
const AIController = function(anp){
/** @type {AIChat} */
const self = this;
/**
* @returns {void}
* @access private
*/
const constructor = () => {};
this.test = (...parameters) => {
console.log(parameters);
};
this.message = (data, code) => {
anp.components.aichat.write_response(data.data.data_id, data.data.response, data.data.ok, data.data.done);
// const box = document.querySelector(".aichat .messages [data-type=bot][data-id=" + data.data.data_id + "]");
// box.querySelector(".content").innerHTML += data.data.response;
// box.setAttribute("data-done", data.data.done);
// box.setAttribute("data-ok", data.data.ok);
};
constructor();
};
return AIController;
})();

View File

@ -196,6 +196,7 @@ export const FilesDriver = (function(){
date = Date.now();
ajax.open(method, root_urls[i] + url, asynchronous);
ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
ajax.timeout = timeout;
ajax.onreadystatechange = () => {
if(ended)

View File

@ -0,0 +1,173 @@
"use strict";
import {WebSocketsClientsAbstract} from "../Abstracts/WebSocketsClientsAbstract.ecma.js";
import {Common} from "../Utils/Common.ecma.js";
import {Event} from "../Application/Event.ecma.js";
/**
* @typedef {import("../Application/AnP.ecma.js").AnP} AnP
*/
/**
* @class WebSocketsDriver
* @constructor
* @extends WebSocketsClientsAbstract
* @param {!AnP} anp
* @param {!string} key
* @param {!(Object.<string, any|null>|Array.<any|null>)} inputs
* @returns {void}
* @access public
* @static
*/
export const WebSocketsClientsDriver = (function(){
/**
* @callback continue_callback
* @param {boolean} ok
* @return {void}
*/
/**
* @constructs WebSocketsClientsDriver
* @extends WebSocketsClientsAbstract
* @param {!AnP} anp
* @param {!string} key
* @param {!(Object.<string, any|null>|Array.<any|null>)} inputs
* @returns {void}
* @access private
* @static
*/
const WebSocketsClientsDriver = function(anp, key, inputs){
/** @type {WebSocketsClientsDriver} */
const self = this;
/** @type {WebSocketsClientsAbstract|null} */
let _super = null,
/** @type {WebSocket|null} */
client = null,
/** @type {boolean} */
started = false;
/**
* @returns {void}
* @access private
*/
const constructor = () => {
Common.extends(self, _super = new WebSocketsClientsAbstract(anp, key, inputs), false);
Common.get_value(["web_socket_autostart", "autostart"], inputs, true) && self.start();
};
/**
* @param {?continue_callback} callback
* @returns {boolean}
* @access public
*/
this.start = (callback = null) => {
/** @type {continue_callback} */
const end = ok => Common.execute(callback, ok);
if(started){
end(false);
return false;
};
started = true;
client = new WebSocket(self.url);
client.onopen = on_open;
client.onmessage = on_message
client.onerror = on_error;
client.onclose = on_close;
end(true);
return true;
};
/**
* @param {?continue_callback} callback
* @returns {boolean}
* @access public
*/
this.close = (callback = null) => {
/** @type {continue_callback} */
const end = ok => Common.execute(callback, ok);
if(!started){
end(false);
return false;
};
started = false;
client.close();
client = null;
return true;
};
/**
* @param {!Event} _
* @returns {void}
* @access private
*/
const on_open = _ => {
self.on_open.execute();
};
/**
* @param {!Event} event
* @returns {void}
* @access private
*/
const on_message = event => {
self.on_message.execute(event.data);
};
/**
* @param {!Event} event
* @returns {void}
* @access private
*/
const on_error = event => {
self.on_error.execute(event.error);
};
/**
* @param {!Event} _
* @returns {void}
* @access private
*/
const on_close = _ => {
self.on_close.execute();
};
/**
* @param {!string} controller
* @param {!string} action
* @param {?any} [data = null]
* @param {!number} [code = 200]
* @return {void}
* @access public
*/
this.send = (controller, action, data = null, code = 200) => {
client.send(Common.data_encode({
ok : code >= 200 && code < 300,
code : code,
controller : controller,
action : action,
data : data,
id : self.id
}));
};
constructor();
};
return WebSocketsClientsDriver;
})();

View File

@ -1,131 +0,0 @@
"use strict";
import {Common} from "../Utils/Common.ecma.js";
import {Event} from "../Application/Event.ecma.js";
/**
* @class WebSocketsDriver
* @constructor
* @param {!(Object.<string, any|null>|Array.<any|null>)} inputs
* @returns {void}
* @access public
* @static
*/
export const WebSocketsDriver = (function(){
/**
* @callback continue_callback
* @param {boolean} ok
* @return {void}
*/
/**
* @constructs WebSocketsDriver
* @param {!(Object.<string, any|null>|Array.<any|null>)} inputs
* @returns {void}
* @access private
* @static
*/
const WebSocketsDriver = function(inputs){
/** @type {WebSocketsDriver} */
const self = this;
/** @type {WebSocket|null} */
let client = null,
/** @type {string|null} */
url = null,
/** @type {boolean} */
started = false;
/** @type {Event} */
this.on_open = new Event();
/** @type {Event} */
this.on_message = new Event();
/** @type {Event} */
this.on_error = new Event();
/** @type {Event} */
this.on_close = new Event();
/**
* @returns {void}
* @access private
*/
const constructor = () => {
url = Common.get_string(inputs.url, "ws://localhost:8080");
};
/**
* @param {?continue_callback} callback
* @returns {boolean}
* @access public
*/
this.start = (callback = null) => {
/** @type {continue_callback} */
const end = ok => Common.execute(callback, ok);
if(started){
end(false);
return false;
};
started = true;
client = new WebSocket(url);
client.onopen = on_open;
client.onmessage = on_message
client.onerror = on_error;
client.onclose = on_close;
return true;
};
/**
* @param {?continue_callback} callback
* @returns {boolean}
* @access public
*/
this.close = (callback = null) => {
/** @type {continue_callback} */
const end = ok => Common.execute(callback, ok);
if(!started){
end(false);
return false;
};
started = false;
client.close();
client = null;
return true;
};
const on_open = event => {
console.log(["client", event]);
};
const on_message = event => {
console.log(["message", event]);
};
const on_error = event => {
console.log(["error", event]);
};
const on_close = event => {
console.log(["close", event]);
};
this.send = message => {
client.send(message);
};
constructor();
};
return WebSocketsDriver;
})();

View File

@ -55,9 +55,9 @@ export const ControllersManager = (function(){
* @access public
*/
this.update = (callback = null) => {
Common.execute(callback);
Common.execute_array(["default_controllers_files", "controllers_files", "default_controllers", "controllers"], (key, next) => {
self.add(anp.settings.get(key), true, next);
}, callback, true);
};
/**
@ -116,37 +116,50 @@ export const ControllersManager = (function(){
};
/**
*
* @param {?any} inputs
* @param {!boolean} [overwrite = false]
* @param {?default_callback} [callback = null]
* @return {void}
* @access public
*/
this.add = (inputs, overwrite = false, callback = null) => {
anp.files.load_json(inputs, data => {
Object.entries(data).forEach(([key, Controller]) => {
if(!Controller || (!overwrite && controllers[key]))
return;
if(Check.is_function(Controller))
controllers[key] = new Controller(anp);
else if(Check.is_object(Controller))
controllers[key] = Controller;
else if(Check.is_string(Controller)){
data.forEach(subinputs => {
Object.entries(subinputs).forEach(([key, Controller]) => {
if(!Controller || (!overwrite && controllers[key]))
return;
if(Check.is_function(Controller))
controllers[key] = new Controller(anp);
else if(Check.is_object(Controller))
controllers[key] = Controller;
else if(Check.is_string(Controller)){
/** @type {Object.<string, any|null>|Function|null} */
const Model = anp.models.get(Controller);
/** @type {Object.<string, any|null>|Function|null} */
const Model = anp.models.get(Controller);
if(Check.is_object(Model))
controllers[key] = Model;
else if(Check.is_function(Model))
controllers[key] = new Model(anp);
if(Check.is_object(Model))
controllers[key] = Model;
else if(Check.is_function(Model))
controllers[key] = new Model(anp);
};
};
});
});
Common.execute(callback);
}, true);
};
this.execute = (name, action, ...parameters) => {};
/**
* @param {!string} controller
* @param {!string} action
* @param {?any} [data = null]
* @param {!number} [code = 200]
* @return {void}
* @access public
*/
this.execute = (controller, action, data = null, code = 200) => {
controllers[controller] && controllers[controller][action] && controllers[controller][action](data, code);
};
constructor();

View File

@ -54,9 +54,9 @@ export const ModelsManager = (function(){
* @access public
*/
this.update = (callback = null) => {
Common.execute(callback);
Common.execute_array(["default_models_files", "models_files", "default_models", "models"], (key, next) => {
self.add(anp.settings.get(key), true, next);
}, callback, true);
};
/**
@ -123,11 +123,13 @@ export const ModelsManager = (function(){
*/
this.add = (inputs, overwrite = false, callback = null) => {
anp.files.load_json(inputs, data => {
Object.entries(data).forEach(([key, Model]) => {
Model &&
(overwrite || !models[key]) &&
(Check.is_function(Model) || Check.is_object(Model)) &&
(models[key] = Model);
data.forEach(subdata => {
Object.entries(subdata).forEach(([key, Model]) => {
Model &&
(overwrite || !models[key]) &&
(Check.is_function(Model) || Check.is_object(Model)) &&
(models[key] = Model);
});
});
Common.execute(callback);
}, true);

View File

@ -168,7 +168,7 @@ export const UniqueKeysManager = (function(){
* @return {string}
* @access public
*/
this.create = (is_html_item = false) => {
this.get = (is_html_item = false) => {
/** @type {string} */
let key;

View File

@ -1,6 +1,8 @@
"use strict";
import {WebSocketClientModel} from "../Models/WebSocketClientModel.ecma.js";
import {WebSocketsClientsAbstract} from "../Abstracts/WebSocketsClientsAbstract.ecma.js";
import {Common} from "../Utils/Common.ecma.js";
import {Check} from "../Utils/Checks.ecma.js";
/**
* @typedef {import("../Application/AnP.ecma.js").AnP} AnP
@ -18,6 +20,24 @@ import {WebSocketClientModel} from "../Models/WebSocketClientModel.ecma.js";
*/
export const WebSocketsClientsManager = (function(){
/**
* @callback continue_callback
* @param {boolean} ok
* @returns {void}
*/
/**
* @callback simple_callback
* @returns {void}
*/
/**
* @callback web_socket_constructor_callback
* @param {!AnP} anp
* @param {!(Object.<string, any|null>|Array.<any|null>)} inputs
* @returns {void}
*/
/**
* @constructs WebSocketsClientsManager
* @param {!AnP} anp
@ -31,7 +51,7 @@ export const WebSocketsClientsManager = (function(){
/** @type {WebSocketsClientsManager} */
const self = this,
/** @type {Object.<string, WebSocketClientModel} */
/** @type {Object.<string, WebSocketsClientsAbstract>} */
clients = {};
/**
@ -40,10 +60,110 @@ export const WebSocketsClientsManager = (function(){
*/
const constructor = () => {};
this.add = (key, url) => {
/**
* @param {?continue_callback} callback
* @returns {boolean}
* @access public
*/
this.update = (callback = null) => {
Common.execute_array(["default_web_sockets_clients_files", "web_sockets_clients_files", "default_web_sockets_clients", "web_sockets_clients"], (key, next) => {
self.add(anp.settings.get(key), true, next);
}, callback, true);
};
clients[key] = new WebSocketClientModel(anp, url);
/**
* @param {?continue_callback} callback
* @returns {boolean}
* @access public
*/
this.reset = (callback = null) => {
[settings, secrets].forEach(Common.clear_dictionary);
self.update(callback);
};
/**
* @param {?continue_callback} callback
* @returns {boolean}
* @access public
*/
this.start = (callback = null) => {
/** @type {continue_callback} */
const end = ok => Common.execute(callback, ok);
if(started){
end(false);
return false;
};
started = true;
self.update(() => end(true));
return true;
};
/**
* @param {?continue_callback} callback
* @returns {boolean}
* @access public
*/
this.close = (callback = null) => {
/** @type {continue_callback} */
const end = ok => Common.execute(callback, ok);
if(!started){
end(false);
return false;
};
started = false;
end(true);
return true;
};
/**
* @param {?any} inputs
* @param {!boolean} [overwrite = false]
* @param {?simple_callback} [callback = null]
* @return {void}
* @access public
*/
this.add = (inputs, overwrite = false, callback = null) => {
anp.files.load_json(inputs, data => {
for(let subinputs of data)
Object.entries(subinputs).filter(([key, _]) => (
overwrite || clients[key] === undefined
)).forEach(([key, value]) => {
try{
/** @type {string|web_socket_constructor_callback|WebSocketsClientsAbstract} */
const Type = Common.get_value("type", (
Check.is_string(value) ? value = {url : value} :
value), "WebSocketsDriver"),
/** @type {web_socket_constructor_callback|null} */
Model = Check.is_string(Type) ? anp.models.get(Type) : null,
/** @type {WebSocketsClientsAbstract} */
web_socket = (
Model ? new Model(anp, key, value) :
Check.is_function(Type) ? new Type(anp, key, value) :
Type instanceof WebSocketsClientsAbstract ? Type :
null);
web_socket && (clients[key] = web_socket);
}catch(exception){
anp.exception(exception, "anp_web_sockets_clients_manager_add", {
key : key
});
};
});
Common.execute(callback);
}, true);
};
this.remove = key => {
@ -55,7 +175,45 @@ export const WebSocketsClientsManager = (function(){
return false;
};
this.send = (key, data) => {};
this.send = (key, controller, action, data = null, code = 200) => {
if(key in clients){
clients[key].send(controller, action, data, code);
return true;
};
return false;
};
this.on_open = (key, callback) => {
if(key in clients){
clients[key].on_open.add(callback);
return true;
};
return false;
};
this.on_message = (key, callback) => {
if(key in clients){
clients[key].on_message.add(callback);
return true;
};
return false;
};
this.on_error = (key, callback) => {
if(key in clients){
clients[key].on_error.add(callback);
return true;
};
return false;
};
this.on_close = (key, callback) => {
if(key in clients){
clients[key].on_close.add(callback);
return true;
};
return false;
};
constructor();

View File

@ -1,92 +0,0 @@
"use strict";
import {Event} from "../Application/Event.ecma.js";
import {Check} from "../Utils/Checks.ecma.js";
import {Common} from "../Utils/Common.ecma.js";
/**
* @typedef {import("../Application/AnP.ecma.js").AnP} AnP
*/
export const WebSocketClientModel = (function(){
const WebSocketClientModel = function(anp, inputs){
const self = this;
let web_socket = null,
url = null,
id = null;
this.on_open = new Event();
this.on_message = new Event();
this.on_close = new Event();
this.on_error = new Event();
const constructor = () => {
Check.is_string(inputs) && (inputs = {url : inputs});
web_socket = new WebSocket(url = Common.get_value("url", inputs, ""));
web_socket.onopen = self.on_open.execute;
web_socket.onmessage = self.on_message.execute;
web_socket.onclose = self.on_close.execute;
web_socket.onerror = self.on_error.execute;
// self.on_open.add(opened);
self.on_message.add(reveive_message);
self.on_close.add(closed);
self.on_error.add(error);
};
// const opened = event => {
// self.send("web_socket_client", "get_id", null, 200);
// };
const reveive_message = event => {
console.log(event);
const data = Common.data_decode(event.data);
switch(data.controller + "." + data.method){
case "web_socket_client.set_id":
id = data.data;
break;
default:
anp.controllers.execute(data.controller, data.method, data.data, data);
break;
};
};
this.send = (controller, method, data, code = 200) => {
web_socket.send(Common.data_encode({
ok : code >= 200 && code < 300,
code : code,
id : id,
controller : controller,
method : method,
data : data
}));
};
this.close = () => {
web_socket.close();
};
const closed = event => {
id = null;
};
const error = event => {
console.error(event);
self.close();
};
constructor();
};
return WebSocketClientModel;
})();

View File

@ -467,5 +467,19 @@ export const Common = (function(){
"\\" + (Common.REGULAR_EXPRESSION.FROM[character] || character)
));
/**
* @param {!Object.<string, any|null>} main
* @param {!(Object.<string, any|null>|Array.<Object.<string, any|null>>)} items
* @param {!boolean} [overwrite = false]
* @returns {void}
* @access public
* @static
*/
Common.extends = (main, items, overwrite = false) => Common.get_array(items).forEach(item => {
item && Object.entries(item).forEach(([key, value]) => {
(overwrite || !(key in main)) && (main[key] = value);
});
});
return Common;
})();

View File

@ -347,3 +347,9 @@ export const Body = (attributes = {}, children = []) => ["body", attributes, chi
/** @type {element_callback} */
export const HTML = (attributes = {}, children = []) => ["html", attributes, children];
/** @type {element_callback} */
export const Pre = (attributes = {}, children = []) => ["pre", attributes, children];
/** @type {element_callback} */
export const Code = (attributes = {}, children = []) => ["code", attributes, children];

View File

@ -0,0 +1,101 @@
"use strict";
export const MarkDown = (function(){
const MarkDown = function(anp){};
MarkDown.Item = function(pattern, replace, block = false){
this.pattern = pattern;
this.replace = replace;
this.block = block;
this.match = null;
this.length = 0;
this.more = true;
this.index = -1;
};
MarkDown.Header = () => new MarkDown.Item(/^(#{1,6})\s*(.+)$/m, (match, hashes, content) => `<h${hashes.length}>${MarkDown.to_html(content, false)}</h${hashes.length}>`, true);
MarkDown.Block = () => new MarkDown.Item(/^```([a-z]*)\r?\n([\s\S]*?)\r?\n```/, (match, lang, content) => `<fieldset><legend>${lang}</legend><pre><code class="${lang}">${content}</code></pre></fieldset>`, true);
MarkDown.BlockQuote = () => new MarkDown.Item(/^>\s?(.+)$/m, (match, content) => `<blockquote>${MarkDown.to_html(content, false)}</blockquote>`, true);
MarkDown.Paragraph = () => new MarkDown.Item(/^((?:[^\r\n]+(?:\r\n|[\r\n])?)+)/m, (match, content) => `<p>${MarkDown.to_html(content, false)}</p>\n\n`, true);
MarkDown.Link = () => new MarkDown.Item(/\[([^\]]+)\]\(([^)]+)\)|([a-z]{2,12}:\/{2}[^ "']+)/, (match, text, url, link) => `<a href="${url || link}" target="_blank" title="${text || link}">${text || link}</a>`);
MarkDown.Bold = () => new MarkDown.Item(/\*{2}((?:(?!(?:\*{2})).)+)\*{2}/, (match, content) => `<b>${MarkDown.to_html(content, false)}</b>`);
MarkDown.Italic = () => new MarkDown.Item(/\*((?:(?!(?:\*)).|\*{2})+)\*(?!\*)/, (match, content) => `<i>${MarkDown.to_html(content, false)}</i>`);
MarkDown.to_html = (string, blocks = true) => {
let html = ``;
const matches = [
MarkDown.Header(),
MarkDown.Block(),
MarkDown.BlockQuote(),
MarkDown.Paragraph(),
MarkDown.Link(),
MarkDown.Bold(),
MarkDown.Italic()
];
do{
let index = 1 << 28,
i = null;
[...matches].forEach((item, j) => {
if(!item.more)
return;
if(item.block && !blocks){
item.more = false;
return;
};
if(item.index < 0){
const submatches = string.match(item.pattern);
if(!submatches){
item.more = false;
return;
};
item.match = submatches;
item.length = submatches[0].length;
item.index = submatches.index;
};
if(index > item.index){
index = item.index;
i = j;
};
});
if(i === null)
break;
const item = matches[i],
total = index + item.length;
html += string.substring(0, index) + item.replace(...item.match);
string = string.substring(total);
matches.forEach(item => {
item.index -= total;
});
}while(matches.length)
html += string;
return html;
}
return MarkDown;
})();

View File

@ -35,13 +35,15 @@
import {AnP} from "./ecma/Application/AnP.ecma.js";
import {SessionsController} from "./ecma/Controllers/SessionsController.ecma.js";
import {AIChat} from "./ecma/Controllers/AIChatController.ecma.js";
import {AIController} from "./ecma/Controllers/AIController.ecma.js";
import {WebSocketsClientsDriver} from "./ecma/Drivers/WebSocketsClientsDriver.ecma.js";
/** @type {AnP} */
const anp = new AnP({
models : {
"sessions" : SessionsController,
"aichat" : AIChat
SessionsController : SessionsController,
AIController : AIController,
WebSocketsClientsDriver : WebSocketsClientsDriver
}
});

View File

@ -24,5 +24,14 @@
["galego", "Galego", "/images/flags/galego.svg"],
["nihongo", "日本語", "/images/flags/nihongo.svg"],
["russkiy", "Русский", "/images/flags/russkiy.svg"]
]
],
"default_web_sockets_clients" : {
"anp" : {
"type" : "WebSocketsClientsDriver",
"url" : "ws://localhost:18765/"
}
},
"default_controllers" : {
"ai" : "AIController"
}
}

View File

@ -14,6 +14,23 @@
"gui_mode" : "Modo del GUI",
"aichat" : "AIChat",
"message" : "Mensaje",
"send" : "Enviar"
"send" : "Enviar",
"bot" : "Bot",
"waiting" : "Esperando",
"ok" : "OK",
"done" : "Hecho",
"date_from" : "Fecha de inicio",
"date_to" : "Fecha de fin",
"time" : "Tiempo",
"length" : "Longitud",
"response_tokens" : "Tokens de respuesta",
"loading" : "Cargando",
"error" : "Error",
"html" : "HTML",
"md" : "Markdown",
"status" : "Estado",
"raw_html" : "Código HTML",
"minimum" : "Mínimo",
"maximum" : "Máximo"
}
}

110
Public/scss/AnP.aichat.scss Normal file
View File

@ -0,0 +1,110 @@
@mixin aichar-color($mode){
.aichat>legend{border-bottom-color : map-deep-get($color, $mode, "fore")}
}
.anp{
&[data-forced-gui-mode=default][data-gui-mode=default]{
@include main_color_web(light);
}
@each $key in (dark, light){
&[data-forced-gui-mode=#{$key}],&[data-forced-gui-mode=default][data-gui-mode=#{$key}]{
@include main_color_web($key);
}
}
.aichat{
position : absolute;
top : 0em;
left : 0em;
width : 100%;
height : 100%;
border : none;
box-sizing : border-box;
&>legend{
padding-bottom : .1em;
border-bottom-width : .1em;
border-bottom-style : solid;
}
fieldset{
position : relative;
border : none;
}
legend{font-weight : 900;}
form{
display : flex;
flex-direction : row;
position : absolute;
left : 0em;
bottom : 0em;
width : 100%;
height : 5em;
box-sizing : border-box;
&>span{flex-grow : 0;}
&>button{flex-grow : 0;}
}
.messages{
position : absolute;
top : 0em;
left : 0em;
bottom : 5em;
width : 100%;
box-sizing : border-box;
overflow : auto;
}
.message{
clear : both;
margin : 2em 0em;
legend{
float : left;
width : 100%;
padding : 0em;
margin-bottom : .75em;
}
nav{
position : absolute;
top : 0em;
right : 0em;
}
pre{
width : 100%;
white-space : pre-wrap;
}
}
.message-box{display : none;}
.view.buttons button{
opacity : .5;
[data-icon]::before{margin : 0em;}
[data-i18n]{display : none;}
}
[data-mode=html] .html-content,
[data-mode=raw_html] .raw-html-content,
[data-mode=md] .md-content{display : block;}
[data-mode=html] button[data-i18n=html],
[data-mode=raw_html] button[data-i18n=raw_html],
[data-mode=md] button[data-i18n=md]{opacity : 1;}
.data{
position : absolute;
right : 0em;
bottom : 0em;
margin : 0em;
padding : 0em;
list-style-type : none;
opacity : .85;
li{
display : inline-block;
margin : 0em .5em;
font-size : .75em;
[data-i18n]{display : none;}
}
[data-name=date_from]{display : none;}
}
}
}

View File

@ -23,7 +23,10 @@
width : 100%;
height : 100%;
overflow : hidden;
&,input,textarea,select,button{font-family : $font-normal;}
&,input,textarea,select,button{
font-family : $font-normal;
box-sizing : border-box;
}
button,input,textarea,select{font-size : 1em;}
@ -100,6 +103,22 @@
min-height : 0%;
resize : none;
}
.information{
position : absolute;
bottom : 0em;
right : 0em;
font-size : .8em;
color : $color-grey;
span>[data-i18n],&>[data-value=null]{display : none;}
&>[data-i18n=minimum]::after{
content : "-";
margin : 0em .2em;
}
&>[data-i18n=maximum]::before{
content : "-";
margin : 0em .2em;
}
}
}
h1{

View File

@ -792,7 +792,8 @@
height: 100%;
overflow: hidden; }
.anp, .anp input, .anp textarea, .anp select, .anp button {
font-family: "Roboto"; }
font-family: "Roboto";
box-sizing: border-box; }
.anp button, .anp input, .anp textarea, .anp select {
font-size: 1em; }
.anp a[href] {
@ -889,6 +890,20 @@
min-width: 0%;
min-height: 0%;
resize: none; }
.anp .input .information {
position: absolute;
bottom: 0em;
right: 0em;
font-size: .8em;
color: #898989; }
.anp .input .information span > [data-i18n], .anp .input .information > [data-value=null] {
display: none; }
.anp .input .information > [data-i18n=minimum]::after {
content: "-";
margin: 0em .2em; }
.anp .input .information > [data-i18n=maximum]::before {
content: "-";
margin: 0em .2em; }
.anp h1 {
flex-grow: 0;
font-size: 1em;
@ -1053,5 +1068,137 @@
content: "\f689"; }
.anp [data-icon=gui_mode]::before {
content: "\f043"; }
.anp [data-icon=html]::before {
content: "\f13b";
font-family: "FA6FB"; }
.anp [data-icon=raw_html]::before {
content: "\f121"; }
.anp [data-icon=md]::before {
content: "\f1c9"; }
.anp[data-forced-gui-mode=default][data-gui-mode=default] {
background-color: #EFEFEF;
color: #222; }
.anp[data-forced-gui-mode=default][data-gui-mode=default] a[href], .anp[data-forced-gui-mode=default][data-gui-mode=default] [data-role=link], .anp[data-forced-gui-mode=default][data-gui-mode=default] button, .anp[data-forced-gui-mode=default][data-gui-mode=default] [type=submit], .anp[data-forced-gui-mode=default][data-gui-mode=default] [type=button], .anp[data-forced-gui-mode=default][data-gui-mode=default] [type=reset], .anp[data-forced-gui-mode=default][data-gui-mode=default] [role=button] {
color: #2262b0; }
.anp[data-forced-gui-mode=default][data-gui-mode=default] a[href]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=default] [data-role=link]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=default] button:hover, .anp[data-forced-gui-mode=default][data-gui-mode=default] [type=submit]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=default] [type=button]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=default] [type=reset]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=default] [role=button]:hover {
color: #b06222; }
.anp[data-forced-gui-mode=default][data-gui-mode=default] button, .anp[data-forced-gui-mode=default][data-gui-mode=default] [type=submit], .anp[data-forced-gui-mode=default][data-gui-mode=default] [type=button], .anp[data-forced-gui-mode=default][data-gui-mode=default] [type=reset], .anp[data-forced-gui-mode=default][data-gui-mode=default] [role=button] {
border-color: #2262b0;
box-shadow: 0em 0em 0.2em inset #2262b0; }
.anp[data-forced-gui-mode=default][data-gui-mode=default] button:hover, .anp[data-forced-gui-mode=default][data-gui-mode=default] [type=submit]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=default] [type=button]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=default] [type=reset]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=default] [role=button]:hover {
border-color: #b06222;
box-shadow: 0em 0em 0.2em inset #b06222; }
.anp[data-forced-gui-mode=dark], .anp[data-forced-gui-mode=default][data-gui-mode=dark] {
background-color: #222;
color: #EFEFEF; }
.anp[data-forced-gui-mode=dark] a[href], .anp[data-forced-gui-mode=dark] [data-role=link], .anp[data-forced-gui-mode=dark] button, .anp[data-forced-gui-mode=dark] [type=submit], .anp[data-forced-gui-mode=dark] [type=button], .anp[data-forced-gui-mode=dark] [type=reset], .anp[data-forced-gui-mode=dark] [role=button], .anp[data-forced-gui-mode=default][data-gui-mode=dark] a[href], .anp[data-forced-gui-mode=default][data-gui-mode=dark] [data-role=link], .anp[data-forced-gui-mode=default][data-gui-mode=dark] button, .anp[data-forced-gui-mode=default][data-gui-mode=dark] [type=submit], .anp[data-forced-gui-mode=default][data-gui-mode=dark] [type=button], .anp[data-forced-gui-mode=default][data-gui-mode=dark] [type=reset], .anp[data-forced-gui-mode=default][data-gui-mode=dark] [role=button] {
color: #4b8bd9; }
.anp[data-forced-gui-mode=dark] a[href]:hover, .anp[data-forced-gui-mode=dark] [data-role=link]:hover, .anp[data-forced-gui-mode=dark] button:hover, .anp[data-forced-gui-mode=dark] [type=submit]:hover, .anp[data-forced-gui-mode=dark] [type=button]:hover, .anp[data-forced-gui-mode=dark] [type=reset]:hover, .anp[data-forced-gui-mode=dark] [role=button]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=dark] a[href]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=dark] [data-role=link]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=dark] button:hover, .anp[data-forced-gui-mode=default][data-gui-mode=dark] [type=submit]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=dark] [type=button]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=dark] [type=reset]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=dark] [role=button]:hover {
color: #d98b4b; }
.anp[data-forced-gui-mode=dark] button, .anp[data-forced-gui-mode=dark] [type=submit], .anp[data-forced-gui-mode=dark] [type=button], .anp[data-forced-gui-mode=dark] [type=reset], .anp[data-forced-gui-mode=dark] [role=button], .anp[data-forced-gui-mode=default][data-gui-mode=dark] button, .anp[data-forced-gui-mode=default][data-gui-mode=dark] [type=submit], .anp[data-forced-gui-mode=default][data-gui-mode=dark] [type=button], .anp[data-forced-gui-mode=default][data-gui-mode=dark] [type=reset], .anp[data-forced-gui-mode=default][data-gui-mode=dark] [role=button] {
border-color: #4b8bd9;
box-shadow: 0em 0em 0.2em inset #4b8bd9; }
.anp[data-forced-gui-mode=dark] button:hover, .anp[data-forced-gui-mode=dark] [type=submit]:hover, .anp[data-forced-gui-mode=dark] [type=button]:hover, .anp[data-forced-gui-mode=dark] [type=reset]:hover, .anp[data-forced-gui-mode=dark] [role=button]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=dark] button:hover, .anp[data-forced-gui-mode=default][data-gui-mode=dark] [type=submit]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=dark] [type=button]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=dark] [type=reset]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=dark] [role=button]:hover {
border-color: #d98b4b;
box-shadow: 0em 0em 0.2em inset #d98b4b; }
.anp[data-forced-gui-mode=light], .anp[data-forced-gui-mode=default][data-gui-mode=light] {
background-color: #EFEFEF;
color: #222; }
.anp[data-forced-gui-mode=light] a[href], .anp[data-forced-gui-mode=light] [data-role=link], .anp[data-forced-gui-mode=light] button, .anp[data-forced-gui-mode=light] [type=submit], .anp[data-forced-gui-mode=light] [type=button], .anp[data-forced-gui-mode=light] [type=reset], .anp[data-forced-gui-mode=light] [role=button], .anp[data-forced-gui-mode=default][data-gui-mode=light] a[href], .anp[data-forced-gui-mode=default][data-gui-mode=light] [data-role=link], .anp[data-forced-gui-mode=default][data-gui-mode=light] button, .anp[data-forced-gui-mode=default][data-gui-mode=light] [type=submit], .anp[data-forced-gui-mode=default][data-gui-mode=light] [type=button], .anp[data-forced-gui-mode=default][data-gui-mode=light] [type=reset], .anp[data-forced-gui-mode=default][data-gui-mode=light] [role=button] {
color: #2262b0; }
.anp[data-forced-gui-mode=light] a[href]:hover, .anp[data-forced-gui-mode=light] [data-role=link]:hover, .anp[data-forced-gui-mode=light] button:hover, .anp[data-forced-gui-mode=light] [type=submit]:hover, .anp[data-forced-gui-mode=light] [type=button]:hover, .anp[data-forced-gui-mode=light] [type=reset]:hover, .anp[data-forced-gui-mode=light] [role=button]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=light] a[href]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=light] [data-role=link]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=light] button:hover, .anp[data-forced-gui-mode=default][data-gui-mode=light] [type=submit]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=light] [type=button]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=light] [type=reset]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=light] [role=button]:hover {
color: #b06222; }
.anp[data-forced-gui-mode=light] button, .anp[data-forced-gui-mode=light] [type=submit], .anp[data-forced-gui-mode=light] [type=button], .anp[data-forced-gui-mode=light] [type=reset], .anp[data-forced-gui-mode=light] [role=button], .anp[data-forced-gui-mode=default][data-gui-mode=light] button, .anp[data-forced-gui-mode=default][data-gui-mode=light] [type=submit], .anp[data-forced-gui-mode=default][data-gui-mode=light] [type=button], .anp[data-forced-gui-mode=default][data-gui-mode=light] [type=reset], .anp[data-forced-gui-mode=default][data-gui-mode=light] [role=button] {
border-color: #2262b0;
box-shadow: 0em 0em 0.2em inset #2262b0; }
.anp[data-forced-gui-mode=light] button:hover, .anp[data-forced-gui-mode=light] [type=submit]:hover, .anp[data-forced-gui-mode=light] [type=button]:hover, .anp[data-forced-gui-mode=light] [type=reset]:hover, .anp[data-forced-gui-mode=light] [role=button]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=light] button:hover, .anp[data-forced-gui-mode=default][data-gui-mode=light] [type=submit]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=light] [type=button]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=light] [type=reset]:hover, .anp[data-forced-gui-mode=default][data-gui-mode=light] [role=button]:hover {
border-color: #b06222;
box-shadow: 0em 0em 0.2em inset #b06222; }
.anp .aichat {
position: absolute;
top: 0em;
left: 0em;
width: 100%;
height: 100%;
border: none;
box-sizing: border-box; }
.anp .aichat > legend {
padding-bottom: .1em;
border-bottom-width: .1em;
border-bottom-style: solid; }
.anp .aichat fieldset {
position: relative;
border: none; }
.anp .aichat legend {
font-weight: 900; }
.anp .aichat form {
display: flex;
flex-direction: row;
position: absolute;
left: 0em;
bottom: 0em;
width: 100%;
height: 5em;
box-sizing: border-box; }
.anp .aichat form > span {
flex-grow: 0; }
.anp .aichat form > button {
flex-grow: 0; }
.anp .aichat .messages {
position: absolute;
top: 0em;
left: 0em;
bottom: 5em;
width: 100%;
box-sizing: border-box;
overflow: auto; }
.anp .aichat .message {
clear: both;
margin: 2em 0em; }
.anp .aichat .message legend {
float: left;
width: 100%;
padding: 0em;
margin-bottom: .75em; }
.anp .aichat .message nav {
position: absolute;
top: 0em;
right: 0em; }
.anp .aichat .message pre {
width: 100%;
white-space: pre-wrap; }
.anp .aichat .message-box {
display: none; }
.anp .aichat .view.buttons button {
opacity: .5; }
.anp .aichat .view.buttons button [data-icon]::before {
margin: 0em; }
.anp .aichat .view.buttons button [data-i18n] {
display: none; }
.anp .aichat [data-mode=html] .html-content,
.anp .aichat [data-mode=raw_html] .raw-html-content,
.anp .aichat [data-mode=md] .md-content {
display: block; }
.anp .aichat [data-mode=html] button[data-i18n=html],
.anp .aichat [data-mode=raw_html] button[data-i18n=raw_html],
.anp .aichat [data-mode=md] button[data-i18n=md] {
opacity: 1; }
.anp .aichat .data {
position: absolute;
right: 0em;
bottom: 0em;
margin: 0em;
padding: 0em;
list-style-type: none;
opacity: .85; }
.anp .aichat .data li {
display: inline-block;
margin: 0em .5em;
font-size: .75em; }
.anp .aichat .data li [data-i18n] {
display: none; }
.anp .aichat .data [data-name=date_from] {
display: none; }
/*# sourceMappingURL=AnP.css.map */

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,5 @@
.anp{
[data-icon]::before{
margin-right : .3em;
font-family : $font-icon;
@ -13,4 +14,10 @@
[data-icon=zoom]::before{content : unicode("f002");}
[data-icon=reset_zoom]::before{content : unicode("f689");}
[data-icon=gui_mode]::before{content : unicode("f043");}
// Icons for AI Chat
[data-icon=html]::before{content : unicode("f13b"); font-family : "FA6FB";}
[data-icon=raw_html]::before{content : unicode("f121");}
[data-icon=md]::before{content : unicode("f1c9");}
}

View File

@ -1 +1 @@
@import "AnP.fonts.scss", "AnP.settings.scss", "AnP.common.scss", "AnP.base.scss", "AnP.icons.scss";
@import "AnP.fonts.scss", "AnP.settings.scss", "AnP.common.scss", "AnP.base.scss", "AnP.icons.scss", "AnP.aichat.scss";

View File

@ -30,6 +30,8 @@ class AIInterpretersAbstract(ABC):
self.pool:str = self.anp.settings.get(("ai_interpreter_pool", "ai_pool", "pool"), inputs, self.key)
self.sessions:dict[int, list[int]] = {}
self.sessions_i:int = 0
self.think:bool = self.anp.settings.get(("ai_interpreter_think", "ai_think", "think"), inputs, False)
self.allow_contexts:bool = self.anp.settings.get(("ai_interpreter_allow_contexts", "ai_allow_contexts", "allow_contexts"), inputs, True)
def start(self:Self) -> None:
pass
@ -47,7 +49,7 @@ class AIInterpretersAbstract(ABC):
return id, self.sessions[id]
def save_context(self:Self, id:int, context:list[int]) -> None:
if id in self.sessions:
if self.allow_contexts and id in self.sessions:
self.sessions[id] = context
def close_session(self:Self, id:int) -> bool:
@ -56,12 +58,15 @@ class AIInterpretersAbstract(ABC):
return True
return False
def get_orders(self:Self, orders:str|list[str]) -> str:
def get_context(self:Self, id:int) -> list[int]:
return self.sessions[id] if id in self.sessions else []
def get_orders(self:Self, orders:Optional[str|list[str]] = None) -> str:
results:str = ""
i:int = 0
for block in (orders, self.orders):
for block in (self.orders, orders):
if block:
if Check.is_array(block):
@ -73,10 +78,13 @@ class AIInterpretersAbstract(ABC):
else:
results += ("\n\n" if results else "") + str(block).strip()
return results
@abstractmethod
def request(self:Self,
session:int|None,
message:str,
orders:list[str] = [],
callback:Optional[Callable[[int, AIResponseModel], None]] = None
callback:Optional[Callable[[int, AIResponseModel], None]] = None,
context:list[int] = []
) -> tuple[int|None, AIResponseModel]:pass

View File

@ -9,8 +9,11 @@ from Utils.Common import Common
class WebSocketServersAbstract(ABC):
def __init__(self:Self, anp:AnPInterface, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None:
def __init__(self:Self, anp:AnPInterface, key:str, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None:
self.anp:AnPInterface = anp
self.key:str = key
self.host:str = self.anp.settings.get(("web_socket_server_host", "host"), inputs, "")
self.port:int = self.anp.settings.get(("web_socket_server_port", "port"), inputs, 18765)
self.on_new_client:Event = Event()
self.on_message:Event = Event()
self.on_close:Event = Event()
@ -25,14 +28,15 @@ class WebSocketServersAbstract(ABC):
@abstractmethod
def close_client(self:Self, id:int) -> None:pass
def format_data(self:Self, controller:str, method:str, data:Any|None, id:str, code:int = 200) -> str:
def format_data(self:Self, controller:str, action:str, data:Any|None, id:str, code:int = 200) -> str:
return Common.data_encode({
"ok" : code >= 200 and code < 300,
"code" : code,
"controller": controller,
"method": method,
"action": action,
"data": data,
"id": id,
"code": code
"id": id
})
@abstractmethod
def send(self:Self, controller:str, method:str, data:Any|None, ids:Optional[int|Sequence[int]] = None, code:int = 200) -> None:pass
def send(self:Self, controller:str, action:str, data:Any|None, ids:Optional[str|Sequence[str]] = None, code:int = 200) -> None:pass

View File

@ -10,20 +10,23 @@ from Models.PseudoLoRAModel import PseudoLoRAModel
class AIController(ControllerAbstract, ModelAbstract):
def __test_execution(self:Self, end:Callable[[], None], request:RequestModel) -> None:
print("PASA")
self.anp.ai_interpreters.request(
"anp_titles",
"anp_responses",
None,
request.get("message", "Hola, Gemma. ¿Me puedes ayudar a instalar una impresora Canon?"),
lambda id, response: print((id, response.response)),
[
"Seleccionar títulos exactos relacionados con la consulta:" + "".join(
"\n - " + title for title in [
"Información, gestión e instalación de Cividas",
"Información, gestión e instalación de Impresoras/Fotocopiadoras/Multifuncionales Canon"
]
),
]
# lambda id, response: print((id, response.response, request.get("client_id"))),
lambda id, response: self.anp.web_socket_servers.send("anp", "ai", "test", {
"id" : id,
"response" : response.response
}, request.get("client_id")),
# [
# "# Títulos\n" + "".join(
# "\n - " + title for title in [
# "Información, gestión e instalación de Cividas",
# "Información, gestión e instalación de Impresoras/Fotocopiadoras/Multifuncionales Canon"
# ]
# ),
# ]
)
end()
@ -34,3 +37,26 @@ class AIController(ControllerAbstract, ModelAbstract):
"code" : 200,
"message" : "ok"
})
def __message_execution(self:Self, end:Callable[[], None], request:RequestModel) -> None:
self.anp.ai_interpreters.request(
"anp_responses",
None,
request.get("message"),
lambda id, response: self.anp.web_socket_servers.send("anp", "ai", "message", {
"id" : id,
"response" : response.response,
"ok" : response.ok,
"done" : response.done,
"data_id" : request.get("message_id")
}, request.get("client_id"))
)
end()
def message(self:Self, request:RequestModel) -> None:
self.anp.queues.add("anp", self.__message_execution, request)
request.set_response({
"ok" : True,
"code" : 200,
"message" : "ok"
})

View File

@ -24,7 +24,8 @@ class OllamaDriver(AIInterpretersAbstract, ModelAbstract):
session:int|None,
message:str,
callback:Optional[Callable[[int, AIResponseModel], None]] = None,
orders:str|list[str] = []
orders:str|list[str] = [],
custom_context:Optional[list[int]] = None
) -> tuple[int|None, AIResponseModel]:
response:Response
@ -38,14 +39,18 @@ class OllamaDriver(AIInterpretersAbstract, ModelAbstract):
if self.maximum_tokens_per_session is not None:
options["num_ctx"] = self.maximum_tokens_per_session
session, context = self.get_session(session)
session, context = custom_context or self.get_session(session)
orders = self.get_orders(orders)
if custom_context:
context = context.copy()
with Post(self.url, json = {
"model" : self.model,
"prompt": message,
**({"system": orders} if len(orders) else {}),
"stream": self.stream,
"think" : self.think,
**(
{"format" : self.format} if (
self.format == "json" or
@ -65,8 +70,9 @@ class OllamaDriver(AIInterpretersAbstract, ModelAbstract):
chunk:bytes
for chunk in response.iter_lines():
if not self.anp.working():
break
if chunk:
print(Common.json_decode(chunk))
results.update(Common.json_decode(chunk))
if results.done:
self.save_context(session, results.context)

View File

@ -12,19 +12,17 @@ from Utils.Checks import Check
class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract):
def __init__(self:Self, anp:AnPInterface, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None:
super().__init__(anp, inputs)
def __init__(self:Self, anp:AnPInterface, key:str, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None:
super().__init__(anp, key, inputs)
self.__server:WebSocketServer
self.__clients:dict[str, WebSocketClient] = {}
self.__host:str = anp.settings.get(("web_socket_server_host", "host"), inputs, "")
self.__port:int = anp.settings.get(("web_socket_server_port", "port"), inputs, 8765)
self.__thread:Thread = None
anp.settings.get(("web_socket_server_autostart", "autostart"), inputs, True) and self.start()
def __run(self:Self) -> None:
self.__server = server_serve(self.__handler, self.__host, self.__port)
self.__server = server_serve(self.__handler, self.host, self.port)
self.__server.serve_forever()
def start(self:Self) -> None:
@ -40,27 +38,34 @@ class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract):
self.__server.shutdown()
def close_client(self:Self, id:int) -> None:
if id in self.__clients:
try:
self.__clients[id].close()
except Exception as exception:
self.anp.exception(exception, "web_socket_server_client_close_exception", {
"client": id,
def close_client(self:Self, ids:Optional[str|list[str]] = None) -> None:
id:str
for id in (
ids if Check.is_array(ids) else
list(self.__clients.keys()) if ids is None else
[ids] if Check.is_string(ids) else
[]):
if id in self.__clients:
try:
self.__clients[id].close()
except Exception as exception:
self.anp.exception(exception, "web_socket_server_client_close_exception", {
"id": id,
"port": self.port,
"host": self.host
})
del self.__clients[id]
def __handler(self:Self, client:WebSocketClient) -> None:
id:int = self.anp.unique_keys.create()
id:str = self.anp.unique_keys.get()
self.__clients[id] = client
self.on_new_client.execute(id)
self.anp.print("info", "web_socket_server_client_connected", {
"client": id,
"id": id,
"port": self.port,
"host": self.host,
"client_host" : client.remote_address[0],
@ -76,26 +81,30 @@ class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract):
break
self.on_message.execute(id, message)
except Exception as exception:
self.anp.exception(exception, "web_socket_server_client_exception", {
"client": id,
self.anp.working() and self.anp.exception(exception, "web_socket_server_client_exception", {
"id": id,
"port": self.port,
"host": self.host
})
self.on_error.execute(id, exception)
finally:
self.close_client(id)
try:
self.__clients[id].close()
except Exception as _:
pass
del self.__clients[id]
self.anp.unique_keys.remove(id)
self.on_close.execute(id)
self.anp.print("info", "web_socket_server_client_disconnected", {
"client": id,
"id": id,
"port": self.port,
"host": self.host
})
def send(self:Self, controller:str, method:str, data:str, ids:Optional[int|Sequence[int]] = None, code:int = 200) -> bool:
def send(self:Self, controller:str, action:str, data:str, ids:Optional[str|Sequence[str]] = None, code:int = 200) -> bool:
success:bool = True
id:int
id:str
for id in (
list(self.__clients.keys()) if ids is None else
@ -103,10 +112,10 @@ class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract):
[ids]):
if id in self.__clients:
try:
self.__clients[id].send(self.format_data(controller, method, data, id, code))
self.__clients[id].send(self.format_data(controller, action, data, id, code))
except Exception as exception:
self.anp.exception(exception, "web_socket_server_client_send_exception", {
"client": id,
"id": id,
"port": self.port,
"host": self.host
})

View File

@ -49,10 +49,10 @@ class WebSocketServersManager:
self.__web_sockets[name] = web_socket
web_socket.on_new_client.add(lambda id:self.on_new_client(web_socket, id, name))
web_socket.on_message.add(lambda id, message:self.on_message(web_socket, id, message, name))
web_socket.on_close.add(lambda id:self.on_close(web_socket, id, name))
web_socket.on_error.add(lambda id, exception:self.on_error(web_socket, id, exception, name))
web_socket.on_new_client.add(lambda id:self.on_new_client.execute(web_socket, id, name))
web_socket.on_message.add(lambda id, message:self.on_message.execute(web_socket, id, message, name))
web_socket.on_close.add(lambda id:self.on_close.execute(web_socket, id, name))
web_socket.on_error.add(lambda id, exception:self.on_error.execute(web_socket, id, exception, name))
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
@ -77,7 +77,7 @@ class WebSocketServersManager:
Class:type[WebSocketServersAbstract] = self.anp.models.get(WebSocketServersAbstract, _type)
Class and issubclass(Class, WebSocketServersAbstract) and self.__set(key, Class(self.anp, value))
Class and issubclass(Class, WebSocketServersAbstract) and self.__set(key, Class(self.anp, key, value))
def remove(self:Self, names:str|Sequence[str]) -> None:
for name in Common.get_keys(names):
@ -94,20 +94,14 @@ class WebSocketServersManager:
def send(self:Self,
name:str,
controller:str,
method:str,
action:str,
data:Optional[Any] = None,
clients:Optional[int|Sequence[int]] = None,
clients:Optional[str|Sequence[str]] = None,
code:int = 200
) -> None:
if name in self.__web_sockets:
try:
self.__web_sockets[name].send(Common.data_encode({
"ok" : code >= 200 and code < 300,
"code" : code,
"controller" : controller,
"method" : method,
"data" : data
}), clients)
self.__web_sockets[name].send(controller, action, data, clients, code)
except Exception as exception:
self.anp.exception(exception, "web_socket_server_send_exception", {"name": name})
@ -115,10 +109,12 @@ class WebSocketServersManager:
data:dict[str, Any|None] = Common.data_decode(raw_data)
if "controller" in data and "method" in data:
if "controller" in data and "action" in data:
request:RequestModel = RequestModel()
"data" in data and Check.is_dictionary(data["data"]) and request.set_variables(data["data"])
request.data = data.get("data", None)
request.variables["web_socket"] = web_socket
request.variables["client_id"] = client
@ -126,6 +122,6 @@ class WebSocketServersManager:
self.anp.controllers.execute(
data["controller"],
data["method"],
data["action"],
request
)

View File

@ -1,8 +1,9 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Any, Self, Sequence
from typing import Any, Self
from re import Pattern as REPattern
from json import loads as json_decode
from Utils.Patterns import RE
class Check:
@ -11,6 +12,10 @@ class Check:
def is_string(item:Any|None) -> bool:
return isinstance(item, str)
@staticmethod
def is_binary(item:Any|None) -> bool:
return isinstance(item, bytes)
@classmethod
def is_key(cls:type[Self], item:Any|None) -> bool:
return isinstance(item, str) and RE.KEY.match(item) is not None
@ -38,3 +43,17 @@ class Check:
@staticmethod
def is_regular_expression(item:Any|None) -> bool:
return isinstance(item, REPattern)
@staticmethod
def is_json_data(item:Any|None, full:bool = False) -> bool:
if Check.is_dictionary(item) or Check.is_array(item):
return True
if Check.is_string(item) and len(item = item.strip()) >= 2 and item[0] + item[-1] in ("{}", "[]"):
if not full:
return True
try:
if json_decode(item) is not None:
return True
except Exception as _:
pass
return False

View File

@ -105,7 +105,7 @@ class Common:
key:str
value:Any|None
for key, value in cls.get_dictionaries(subinputs).items():
for key, value in cls.get_dictionary(subinputs).items():
if overwrite or key not in dictionary:
dictionary[key] = value
@ -286,7 +286,13 @@ class Common:
return None
@staticmethod
def json_decode(data:str) -> dict[str, Any|None]|Sequence[Any|None]|None:
def json_decode(data:str|bytes) -> dict[str, Any|None]|Sequence[Any|None]|None:
if Check.is_binary(data):
for charset in ("utf-8", "latin1"):
try:
return json_decode(data.decode(charset))
except Exception as exception:
pass
try:
return json_decode(data)
except Exception as exception:
@ -303,10 +309,11 @@ class Common:
@staticmethod
def base64_decode(data:str) -> bytes|None:
try:
return base64_decode(data.encode("utf-8"))
except Exception as exception:
pass
if Check.is_binary(data) or Check.is_string(data):
try:
return base64_decode(data)
except Exception as exception:
pass
return None
@classmethod
@ -319,9 +326,11 @@ class Common:
@classmethod
def data_decode(cls:type[Self], data:str) -> Any|None:
if Check.is_string(data):
data = cls.base64_decode(data)
if Check.is_string(data) or Check.is_binary(data):
if Check.is_string(data):
data = data.encode("latin1").decode("utf-8")
data = cls.base64_decode(data)
if Check.is_string(data) or Check.is_binary(data):
try:
return cls.json_decode(data)
except Exception as exception:
@ -333,8 +342,8 @@ class Common:
if Check.is_function(callback):
try:
return callback(*arguments)
except Exception as _:
pass
except Exception as exception:
print(exception)
return None
@staticmethod