#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_RoutesManager_end" : null,
"AnP_WebSocketsServersManager_start" : null, "AnP_WebSocketsServersManager_start" : null,
"default_web_sockets_servers2" : { "default_web_sockets_servers" : {
"anp" : { "anp" : {
"type" : "WebSocketServerDriver", "type" : "WebSocketServerDriver",
"host" : "", "host" : "",

View File

@ -31,10 +31,10 @@
"AnP_RoutesManager_end" : null, "AnP_RoutesManager_end" : null,
"AnP_WebSocketServerDriver_start" : 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_close_exception" : "El Web Socket Servidor '{host}:{port}' ha cerrado la conexión con el cliente '{id}'.",
"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_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 '{key}' en el Web Socket Servidor '{host}:{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 '{key}' se ha desconectado del 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_WebSocketServerDriver_end" : null,
"AnP_TitlesManager_start" : 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 {UniqueKeysManager} from "../Managers/UniqueKeysManager.ecma.js";
import {SessionsManager} from "../Managers/SessionsManager.ecma.js"; import {SessionsManager} from "../Managers/SessionsManager.ecma.js";
import {ModelsManager} from "../Managers/ModelsManager.ecma.js"; import {ModelsManager} from "../Managers/ModelsManager.ecma.js";
import {ControllersManager} from "../Managers/ControllersManager.ecma.js";
import {ViewsManager} from "../Managers/ViewsManager.ecma.js"; import {ViewsManager} from "../Managers/ViewsManager.ecma.js";
import {RoutesManager} from "../Managers/RoutesManager.ecma.js"; import {RoutesManager} from "../Managers/RoutesManager.ecma.js";
import {WebSocketsClientsManager} from "../Managers/WebSocketsClientsManager.ecma.js"; import {WebSocketsClientsManager} from "../Managers/WebSocketsClientsManager.ecma.js";
@ -70,6 +71,8 @@ export const AnP = (function(){
this.models = new ModelsManager(self); this.models = new ModelsManager(self);
/** @type {Components} */ /** @type {Components} */
this.components = new Components(self); this.components = new Components(self);
/** @type {ControllersManager} */
this.controllers = new ControllersManager(self);
/** @type {ViewsManager} */ /** @type {ViewsManager} */
this.views = new ViewsManager(self); this.views = new ViewsManager(self);
/** @type {RoutesManager} */ /** @type {RoutesManager} */
@ -94,7 +97,7 @@ export const AnP = (function(){
*/ */
this.update = (callback = null) => { this.update = (callback = null) => {
Common.execute_array([ 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) => { ], (key, next) => {
self[key].update(next); self[key].update(next);
}, callback, true); }, callback, true);
@ -107,7 +110,7 @@ export const AnP = (function(){
*/ */
this.reset = (callback = null) => { this.reset = (callback = null) => {
Common.execute_array([ 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) => { ], (key, next) => {
self[key].reset(next); self[key].reset(next);
}, callback, true); }, callback, true);
@ -161,7 +164,11 @@ export const AnP = (function(){
}; };
started = false; started = false;
Common.execute_array(["web_sockets_clients"], (key, next) => {
self[key].close(next);
}, () => {
end(true); end(true);
});
return true; return true;
}; };

View File

@ -47,6 +47,8 @@ export const Components = (function(){
/** @type {Components} */ /** @type {Components} */
const self = this; const self = this;
/** @type {number|null} */
let thread = null;
/** @type {BaseComponent} */ /** @type {BaseComponent} */
this.base = new BaseComponent(anp); this.base = new BaseComponent(anp);
@ -71,7 +73,32 @@ export const Components = (function(){
* @returns {void} * @returns {void}
* @access private * @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 * @param {!(string|Array.<string>)} keys
@ -117,6 +144,7 @@ export const Components = (function(){
}, },
on_error : (item, event) => { on_error : (item, event) => {
/** @type {number} */
const i = Number(item.parentNode.getAttribute("data_i")) + 1; const i = Number(item.parentNode.getAttribute("data_i")) + 1;
item.setAttribute("src", Common.json_decode(Common.base64_decode(item.parentNode.getAttribute("data-data")))[i]); 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, { this.i18n = (i18n, tag = "span", variables = null) => [tag, {
data_i18n : i18n, data_i18n : i18n,
...(variables ? {data_i18n_variables : Common.data_encode(variables)} : {}) ...(variables ? {data_i18n_variables : Common.data_encode(variables)} : {})
}, anp.i18n.get(i18n, 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}]; 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) => { this.button = (name, action, inputs = null) => {
/** @type {string} */
const text = anp.i18n.get(name, ( const text = anp.i18n.get(name, (
Check.is_bool(inputs) ? {toogled : inputs} : Check.is_bool(inputs) ? {toogled : inputs} :
Check.is_string(inputs) ? {type : inputs} : Check.is_string(inputs) ? {type : inputs} :
inputs)), inputs)),
/** @type {boolean} */
has_action = Check.is_function(action), has_action = Check.is_function(action),
/** @type {string} */
type = Check.is_string(action) ? action : Common.get_value("type", inputs, "button"), type = Check.is_string(action) ? action : Common.get_value("type", inputs, "button"),
/** @type {boolean|null} */
toggled = Common.get_value("toggled", inputs, null); toggled = Common.get_value("toggled", inputs, null);
has_action || (action = 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({ this.checkbox = (name, inputs = null) => Label({
class : "checkbox", class : "checkbox",
data_i18n : name, data_i18n : name,
@ -188,8 +246,15 @@ export const Components = (function(){
self.i18n(name) 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) => { this.radio = (name, inputs = null) => {
/** @type {string|null} */
const set = Common.get_value("set", inputs); const set = Common.get_value("set", inputs);
return Label({ 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) => { this.text = (name, inputs = null) => {
/** @type {string} */
const text = anp.i18n.get(name), const text = anp.i18n.get(name),
/** @type {number|null} */
minimum_length = Common.get_value("minimum_length", inputs, null), minimum_length = Common.get_value("minimum_length", inputs, null),
/** @type {number|null} */
maximum_length = Common.get_value("maximum_length", inputs, null); maximum_length = Common.get_value("maximum_length", inputs, null);
return Span({ return Span({
@ -249,17 +340,28 @@ export const Components = (function(){
"value" "value"
], inputs) ], inputs)
}), }),
Span({class : "minimum", data_minimum : minimum_length}, "" + minimum_length), Span({class : "information"}, [
Span({class : "length"}, "" + Common.get_value("value", inputs, "").length), field_information("minimum", minimum_length),
Span({class : "maximum", data_maximum : maximum_length}, "" + maximum_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) => { this.password = (name, inputs = null) => {
/** @type {string} */
const text = anp.i18n.get(name), const text = anp.i18n.get(name),
minimum_length = Common.get_value("minimum_length", inputs, 0), /** @type {number|null} */
maximum_length = Common.get_value("maximum_length", inputs, 0); minimum_length = Common.get_value("minimum_length", inputs, null),
/** @type {number|null} */
maximum_length = Common.get_value("maximum_length", inputs, null);
return Span({ return Span({
data_i18n : name, data_i18n : name,
@ -279,14 +381,23 @@ export const Components = (function(){
"value" "value"
], inputs) ], inputs)
}), }),
Span({class : "minimum", data_minimum : minimum_length}, "" + minimum_length), Span({class : "information"}, [
Span({class : "length"}, "" + Common.get_value("value", inputs, "").length), field_information("minimum", minimum_length),
Span({class : "maximum", data_maximum : maximum_length}, "" + maximum_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) => { this.email = (name, inputs = null) => {
/** @type {string} */
const text = anp.i18n.get(name); const text = anp.i18n.get(name);
return Span({ 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) => { this.number = (name, inputs = null) => {
/** @type {string} */
const text = anp.i18n.get(name), const text = anp.i18n.get(name),
minimum = Common.get_value("minimum", inputs, 0), /** @type {number|null} */
maximum = Common.get_value("maximum", inputs, 0), minimum = Common.get_value("minimum", inputs, null),
value = Common.get_value("value", inputs, 0); /** @type {number|null} */
maximum = Common.get_value("maximum", inputs, null),
/** @type {number|null} */
value = Common.get_value("value", inputs, null);
return Span({ return Span({
data_i18n : name, data_i18n : name,
@ -331,9 +452,11 @@ export const Components = (function(){
"value" "value"
], inputs) ], inputs)
}), }),
Span({class : "minimum", data_minimum : minimum}, "" + minimum), Span({class : "information"}, [
Span({class : "value"}, "" + value), field_information("minimum", minimum),
Span({class : "maximum", data_maximum : maximum}, "" + maximum) // 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 {!Array.<Array.<[string, string|event_callback, Object.<string, any|null>|Array.<any|null>]>>} buttons
* @param {?(Object.<string, any|null>|Array.<any|null>)} [inputs = null] * @param {?(Object.<string, any|null>|Array.<any|null>)} [inputs = null]
* @returns {Array.<any|null>} * @returns {Array.<any|null>}
* @access public
*/ */
this.buttons = (buttons, inputs = null) => Div({ this.buttons = (buttons, inputs = null) => Div({
class : ["buttons"].concat(Common.get_array(Common.get_value("class", inputs, []))).join(" ").trim(), 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} */ /** @type {Event} */
const self = this, const self = this,
/** @type {Object.<number, event_callback>} */ /** @type {Object.<number, event_callback>} */
events = {}, events = {};
/** @type {number} */ /** @type {number} */
id_i = 0; let id_i = 0;
/** @type {boolean} */ /** @type {boolean} */
this.once = once; this.once = once;
/** @type {boolean} */ /** @type {boolean} */

View File

@ -1,6 +1,9 @@
"use strict"; "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"; import {Common} from "../Utils/Common.ecma.js";
/** /**
@ -41,8 +44,6 @@ export const AIChatComponent = (function(){
const name = Common.get_value("name", inputs, "aichat"); const name = Common.get_value("name", inputs, "aichat");
web_socket_id = anp.web_sockets_clients.add("AAA", "https://localhost:18000/");
return Fieldset({class : "aichat"}, [ return Fieldset({class : "aichat"}, [
anp.components.i18n(name, "legend"), anp.components.i18n(name, "legend"),
Section({class : "messages"}), 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 send = (item, event) => {
const text_box = item.querySelector("[name=message]"), const text_box = item.querySelector("[name=message]"),
@ -68,15 +149,21 @@ export const AIChatComponent = (function(){
event.preventDefault(); event.preventDefault();
if(text){ if(text){
Common.HTML(".aichat .messages", Fieldset({
class : "message", const data_id = anp.unique_keys.get();
data_type : "user",
data_id : anp.unique_keys.create() Common.HTML(
}, [ ".aichat .messages",
anp.components.i18n("user", "legend"), build_message(data_id, "user", text, "done"),
Div({class : "content"}, text) build_message(data_id, "bot")
])); );
text_box.value = ""; text_box.value = "";
anp.web_sockets_clients.send("anp", "ai", "message", {
message_id : data_id,
message : text
});
}; };
return false; return false;
@ -86,24 +173,35 @@ export const AIChatComponent = (function(){
event.key == "Enter" && !event.shiftKey && send(item, event); 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){ box.querySelector(".html-content").innerHTML = html;
if(!document.querySelector(".aichat .messages>[data-type=user][data-id='" + id + "']")) box.querySelector(".raw-html-content").innerHTML = raw_html;
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(".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} */ /** @type {string} */
logo = anp.settings.get(["application_logo", "logo"], inputs, "images/logo.webp"), logo = anp.settings.get(["application_logo", "logo"], inputs, "images/logo.webp"),
/** @type {string} */ /** @type {string} */
id = anp.unique_keys.create(), id = anp.unique_keys.get(),
/** @type {string} */ /** @type {string} */
gui_mode = Check.is_dark_mode() ? "dark" : "light", gui_mode = Check.is_dark_mode() ? "dark" : "light",
/** @type {boolean} */ /** @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(); date = Date.now();
ajax.open(method, root_urls[i] + url, asynchronous); ajax.open(method, root_urls[i] + url, asynchronous);
ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
ajax.timeout = timeout; ajax.timeout = timeout;
ajax.onreadystatechange = () => { ajax.onreadystatechange = () => {
if(ended) 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 * @access public
*/ */
this.update = (callback = null) => { this.update = (callback = null) => {
Common.execute_array(["default_controllers_files", "controllers_files", "default_controllers", "controllers"], (key, next) => {
Common.execute(callback); self.add(anp.settings.get(key), true, next);
}, callback, true);
}; };
/** /**
@ -116,14 +116,16 @@ export const ControllersManager = (function(){
}; };
/** /**
*
* @param {?any} inputs * @param {?any} inputs
* @param {!boolean} [overwrite = false] * @param {!boolean} [overwrite = false]
* @param {?default_callback} [callback = null] * @param {?default_callback} [callback = null]
* @return {void}
* @access public
*/ */
this.add = (inputs, overwrite = false, callback = null) => { this.add = (inputs, overwrite = false, callback = null) => {
anp.files.load_json(inputs, data => { anp.files.load_json(inputs, data => {
Object.entries(data).forEach(([key, Controller]) => { data.forEach(subinputs => {
Object.entries(subinputs).forEach(([key, Controller]) => {
if(!Controller || (!overwrite && controllers[key])) if(!Controller || (!overwrite && controllers[key]))
return; return;
if(Check.is_function(Controller)) if(Check.is_function(Controller))
@ -142,11 +144,22 @@ export const ControllersManager = (function(){
}; };
}); });
});
Common.execute(callback); Common.execute(callback);
}, true); }, 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(); constructor();

View File

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

View File

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

View File

@ -1,6 +1,8 @@
"use strict"; "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 * @typedef {import("../Application/AnP.ecma.js").AnP} AnP
@ -18,6 +20,24 @@ import {WebSocketClientModel} from "../Models/WebSocketClientModel.ecma.js";
*/ */
export const WebSocketsClientsManager = (function(){ 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 * @constructs WebSocketsClientsManager
* @param {!AnP} anp * @param {!AnP} anp
@ -31,7 +51,7 @@ export const WebSocketsClientsManager = (function(){
/** @type {WebSocketsClientsManager} */ /** @type {WebSocketsClientsManager} */
const self = this, const self = this,
/** @type {Object.<string, WebSocketClientModel} */ /** @type {Object.<string, WebSocketsClientsAbstract>} */
clients = {}; clients = {};
/** /**
@ -40,10 +60,110 @@ export const WebSocketsClientsManager = (function(){
*/ */
const constructor = () => {}; 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 => { this.remove = key => {
@ -55,7 +175,45 @@ export const WebSocketsClientsManager = (function(){
return false; 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(); 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) "\\" + (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; return Common;
})(); })();

View File

@ -347,3 +347,9 @@ export const Body = (attributes = {}, children = []) => ["body", attributes, chi
/** @type {element_callback} */ /** @type {element_callback} */
export const HTML = (attributes = {}, children = []) => ["html", attributes, children]; 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 {AnP} from "./ecma/Application/AnP.ecma.js";
import {SessionsController} from "./ecma/Controllers/SessionsController.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} */ /** @type {AnP} */
const anp = new AnP({ const anp = new AnP({
models : { models : {
"sessions" : SessionsController, SessionsController : SessionsController,
"aichat" : AIChat AIController : AIController,
WebSocketsClientsDriver : WebSocketsClientsDriver
} }
}); });

View File

@ -24,5 +24,14 @@
["galego", "Galego", "/images/flags/galego.svg"], ["galego", "Galego", "/images/flags/galego.svg"],
["nihongo", "日本語", "/images/flags/nihongo.svg"], ["nihongo", "日本語", "/images/flags/nihongo.svg"],
["russkiy", "Русский", "/images/flags/russkiy.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", "gui_mode" : "Modo del GUI",
"aichat" : "AIChat", "aichat" : "AIChat",
"message" : "Mensaje", "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%; width : 100%;
height : 100%; height : 100%;
overflow : hidden; 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;} button,input,textarea,select{font-size : 1em;}
@ -100,6 +103,22 @@
min-height : 0%; min-height : 0%;
resize : none; 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{ h1{

View File

@ -792,7 +792,8 @@
height: 100%; height: 100%;
overflow: hidden; } overflow: hidden; }
.anp, .anp input, .anp textarea, .anp select, .anp button { .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 { .anp button, .anp input, .anp textarea, .anp select {
font-size: 1em; } font-size: 1em; }
.anp a[href] { .anp a[href] {
@ -889,6 +890,20 @@
min-width: 0%; min-width: 0%;
min-height: 0%; min-height: 0%;
resize: none; } 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 { .anp h1 {
flex-grow: 0; flex-grow: 0;
font-size: 1em; font-size: 1em;
@ -1053,5 +1068,137 @@
content: "\f689"; } content: "\f689"; }
.anp [data-icon=gui_mode]::before { .anp [data-icon=gui_mode]::before {
content: "\f043"; } 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 */ /*# sourceMappingURL=AnP.css.map */

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,5 @@
.anp{ .anp{
[data-icon]::before{ [data-icon]::before{
margin-right : .3em; margin-right : .3em;
font-family : $font-icon; font-family : $font-icon;
@ -13,4 +14,10 @@
[data-icon=zoom]::before{content : unicode("f002");} [data-icon=zoom]::before{content : unicode("f002");}
[data-icon=reset_zoom]::before{content : unicode("f689");} [data-icon=reset_zoom]::before{content : unicode("f689");}
[data-icon=gui_mode]::before{content : unicode("f043");} [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.pool:str = self.anp.settings.get(("ai_interpreter_pool", "ai_pool", "pool"), inputs, self.key)
self.sessions:dict[int, list[int]] = {} self.sessions:dict[int, list[int]] = {}
self.sessions_i:int = 0 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: def start(self:Self) -> None:
pass pass
@ -47,7 +49,7 @@ class AIInterpretersAbstract(ABC):
return id, self.sessions[id] return id, self.sessions[id]
def save_context(self:Self, id:int, context:list[int]) -> None: 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 self.sessions[id] = context
def close_session(self:Self, id:int) -> bool: def close_session(self:Self, id:int) -> bool:
@ -56,12 +58,15 @@ class AIInterpretersAbstract(ABC):
return True return True
return False 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 = "" results:str = ""
i:int = 0 i:int = 0
for block in (orders, self.orders): for block in (self.orders, orders):
if block: if block:
if Check.is_array(block): if Check.is_array(block):
@ -73,10 +78,13 @@ class AIInterpretersAbstract(ABC):
else: else:
results += ("\n\n" if results else "") + str(block).strip() results += ("\n\n" if results else "") + str(block).strip()
return results
@abstractmethod @abstractmethod
def request(self:Self, def request(self:Self,
session:int|None, session:int|None,
message:str, message:str,
orders:list[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 ) -> tuple[int|None, AIResponseModel]:pass

View File

@ -9,8 +9,11 @@ from Utils.Common import Common
class WebSocketServersAbstract(ABC): 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.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_new_client:Event = Event()
self.on_message:Event = Event() self.on_message:Event = Event()
self.on_close:Event = Event() self.on_close:Event = Event()
@ -25,14 +28,15 @@ class WebSocketServersAbstract(ABC):
@abstractmethod @abstractmethod
def close_client(self:Self, id:int) -> None:pass 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({ return Common.data_encode({
"ok" : code >= 200 and code < 300,
"code" : code,
"controller": controller, "controller": controller,
"method": method, "action": action,
"data": data, "data": data,
"id": id, "id": id
"code": code
}) })
@abstractmethod @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): class AIController(ControllerAbstract, ModelAbstract):
def __test_execution(self:Self, end:Callable[[], None], request:RequestModel) -> None: def __test_execution(self:Self, end:Callable[[], None], request:RequestModel) -> None:
print("PASA")
self.anp.ai_interpreters.request( self.anp.ai_interpreters.request(
"anp_titles", "anp_responses",
None, None,
request.get("message", "Hola, Gemma. ¿Me puedes ayudar a instalar una impresora Canon?"), request.get("message", "Hola, Gemma. ¿Me puedes ayudar a instalar una impresora Canon?"),
lambda id, response: print((id, response.response)), # lambda id, response: print((id, response.response, request.get("client_id"))),
[ lambda id, response: self.anp.web_socket_servers.send("anp", "ai", "test", {
"Seleccionar títulos exactos relacionados con la consulta:" + "".join( "id" : id,
"\n - " + title for title in [ "response" : response.response
"Información, gestión e instalación de Cividas", }, request.get("client_id")),
"Información, gestión e instalación de Impresoras/Fotocopiadoras/Multifuncionales Canon" # [
] # "# 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() end()
@ -34,3 +37,26 @@ class AIController(ControllerAbstract, ModelAbstract):
"code" : 200, "code" : 200,
"message" : "ok" "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, session:int|None,
message:str, message:str,
callback:Optional[Callable[[int, AIResponseModel], None]] = None, 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]: ) -> tuple[int|None, AIResponseModel]:
response:Response response:Response
@ -38,14 +39,18 @@ class OllamaDriver(AIInterpretersAbstract, ModelAbstract):
if self.maximum_tokens_per_session is not None: if self.maximum_tokens_per_session is not None:
options["num_ctx"] = self.maximum_tokens_per_session 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) orders = self.get_orders(orders)
if custom_context:
context = context.copy()
with Post(self.url, json = { with Post(self.url, json = {
"model" : self.model, "model" : self.model,
"prompt": message, "prompt": message,
**({"system": orders} if len(orders) else {}), **({"system": orders} if len(orders) else {}),
"stream": self.stream, "stream": self.stream,
"think" : self.think,
**( **(
{"format" : self.format} if ( {"format" : self.format} if (
self.format == "json" or self.format == "json" or
@ -65,8 +70,9 @@ class OllamaDriver(AIInterpretersAbstract, ModelAbstract):
chunk:bytes chunk:bytes
for chunk in response.iter_lines(): for chunk in response.iter_lines():
if not self.anp.working():
break
if chunk: if chunk:
print(Common.json_decode(chunk))
results.update(Common.json_decode(chunk)) results.update(Common.json_decode(chunk))
if results.done: if results.done:
self.save_context(session, results.context) self.save_context(session, results.context)

View File

@ -12,19 +12,17 @@ from Utils.Checks import Check
class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract): class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract):
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:
super().__init__(anp, inputs) super().__init__(anp, key, inputs)
self.__server:WebSocketServer self.__server:WebSocketServer
self.__clients:dict[str, WebSocketClient] = {} 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 self.__thread:Thread = None
anp.settings.get(("web_socket_server_autostart", "autostart"), inputs, True) and self.start() anp.settings.get(("web_socket_server_autostart", "autostart"), inputs, True) and self.start()
def __run(self:Self) -> None: 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() self.__server.serve_forever()
def start(self:Self) -> None: def start(self:Self) -> None:
@ -40,27 +38,34 @@ class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract):
self.__server.shutdown() self.__server.shutdown()
def close_client(self:Self, id:int) -> None: 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: if id in self.__clients:
try: try:
self.__clients[id].close() self.__clients[id].close()
except Exception as exception: except Exception as exception:
self.anp.exception(exception, "web_socket_server_client_close_exception", { self.anp.exception(exception, "web_socket_server_client_close_exception", {
"client": id, "id": id,
"port": self.port, "port": self.port,
"host": self.host "host": self.host
}) })
del self.__clients[id]
def __handler(self:Self, client:WebSocketClient) -> None: 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.__clients[id] = client
self.on_new_client.execute(id) self.on_new_client.execute(id)
self.anp.print("info", "web_socket_server_client_connected", { self.anp.print("info", "web_socket_server_client_connected", {
"client": id, "id": id,
"port": self.port, "port": self.port,
"host": self.host, "host": self.host,
"client_host" : client.remote_address[0], "client_host" : client.remote_address[0],
@ -76,26 +81,30 @@ class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract):
break break
self.on_message.execute(id, message) self.on_message.execute(id, message)
except Exception as exception: except Exception as exception:
self.anp.exception(exception, "web_socket_server_client_exception", { self.anp.working() and self.anp.exception(exception, "web_socket_server_client_exception", {
"client": id, "id": id,
"port": self.port, "port": self.port,
"host": self.host "host": self.host
}) })
self.on_error.execute(id, exception) self.on_error.execute(id, exception)
finally: 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.anp.unique_keys.remove(id)
self.on_close.execute(id) self.on_close.execute(id)
self.anp.print("info", "web_socket_server_client_disconnected", { self.anp.print("info", "web_socket_server_client_disconnected", {
"client": id, "id": id,
"port": self.port, "port": self.port,
"host": self.host "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 success:bool = True
id:int id:str
for id in ( for id in (
list(self.__clients.keys()) if ids is None else list(self.__clients.keys()) if ids is None else
@ -103,10 +112,10 @@ class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract):
[ids]): [ids]):
if id in self.__clients: if id in self.__clients:
try: 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: except Exception as exception:
self.anp.exception(exception, "web_socket_server_client_send_exception", { self.anp.exception(exception, "web_socket_server_client_send_exception", {
"client": id, "id": id,
"port": self.port, "port": self.port,
"host": self.host "host": self.host
}) })

View File

@ -49,10 +49,10 @@ class WebSocketServersManager:
self.__web_sockets[name] = web_socket 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_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(web_socket, id, message, 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(web_socket, id, 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(web_socket, id, exception, 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: 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: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: def remove(self:Self, names:str|Sequence[str]) -> None:
for name in Common.get_keys(names): for name in Common.get_keys(names):
@ -94,20 +94,14 @@ class WebSocketServersManager:
def send(self:Self, def send(self:Self,
name:str, name:str,
controller:str, controller:str,
method:str, action:str,
data:Optional[Any] = None, data:Optional[Any] = None,
clients:Optional[int|Sequence[int]] = None, clients:Optional[str|Sequence[str]] = None,
code:int = 200 code:int = 200
) -> None: ) -> None:
if name in self.__web_sockets: if name in self.__web_sockets:
try: try:
self.__web_sockets[name].send(Common.data_encode({ self.__web_sockets[name].send(controller, action, data, clients, code)
"ok" : code >= 200 and code < 300,
"code" : code,
"controller" : controller,
"method" : method,
"data" : data
}), clients)
except Exception as exception: except Exception as exception:
self.anp.exception(exception, "web_socket_server_send_exception", {"name": name}) 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) 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() 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.data = data.get("data", None)
request.variables["web_socket"] = web_socket request.variables["web_socket"] = web_socket
request.variables["client_id"] = client request.variables["client_id"] = client
@ -126,6 +122,6 @@ class WebSocketServersManager:
self.anp.controllers.execute( self.anp.controllers.execute(
data["controller"], data["controller"],
data["method"], data["action"],
request request
) )

View File

@ -1,8 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from typing import Any, Self, Sequence from typing import Any, Self
from re import Pattern as REPattern from re import Pattern as REPattern
from json import loads as json_decode
from Utils.Patterns import RE from Utils.Patterns import RE
class Check: class Check:
@ -11,6 +12,10 @@ class Check:
def is_string(item:Any|None) -> bool: def is_string(item:Any|None) -> bool:
return isinstance(item, str) return isinstance(item, str)
@staticmethod
def is_binary(item:Any|None) -> bool:
return isinstance(item, bytes)
@classmethod @classmethod
def is_key(cls:type[Self], item:Any|None) -> bool: def is_key(cls:type[Self], item:Any|None) -> bool:
return isinstance(item, str) and RE.KEY.match(item) is not None return isinstance(item, str) and RE.KEY.match(item) is not None
@ -38,3 +43,17 @@ class Check:
@staticmethod @staticmethod
def is_regular_expression(item:Any|None) -> bool: def is_regular_expression(item:Any|None) -> bool:
return isinstance(item, REPattern) 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 key:str
value:Any|None 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: if overwrite or key not in dictionary:
dictionary[key] = value dictionary[key] = value
@ -286,7 +286,13 @@ class Common:
return None return None
@staticmethod @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: try:
return json_decode(data) return json_decode(data)
except Exception as exception: except Exception as exception:
@ -303,8 +309,9 @@ class Common:
@staticmethod @staticmethod
def base64_decode(data:str) -> bytes|None: def base64_decode(data:str) -> bytes|None:
if Check.is_binary(data) or Check.is_string(data):
try: try:
return base64_decode(data.encode("utf-8")) return base64_decode(data)
except Exception as exception: except Exception as exception:
pass pass
return None return None
@ -319,9 +326,11 @@ class Common:
@classmethod @classmethod
def data_decode(cls:type[Self], data:str) -> Any|None: def data_decode(cls:type[Self], data:str) -> Any|None:
if Check.is_string(data) or Check.is_binary(data):
if Check.is_string(data): if Check.is_string(data):
data = data.encode("latin1").decode("utf-8")
data = cls.base64_decode(data) data = cls.base64_decode(data)
if Check.is_string(data): if Check.is_string(data) or Check.is_binary(data):
try: try:
return cls.json_decode(data) return cls.json_decode(data)
except Exception as exception: except Exception as exception:
@ -333,8 +342,8 @@ class Common:
if Check.is_function(callback): if Check.is_function(callback):
try: try:
return callback(*arguments) return callback(*arguments)
except Exception as _: except Exception as exception:
pass print(exception)
return None return None
@staticmethod @staticmethod