#wip: Web Service communication.

This commit is contained in:
mbruzon 2026-06-05 15:00:29 +02:00
parent 178f678b69
commit f1371e7389
9 changed files with 232 additions and 18 deletions

View File

@ -71,7 +71,7 @@
"default_web_sockets_servers2" : { "default_web_sockets_servers2" : {
"anp" : { "anp" : {
"type" : "WebSocketServerDriver", "type" : "WebSocketServerDriver",
"host" : "localhost", "host" : "",
"port" : 18765 "port" : 18765
} }
}, },

View File

@ -10,6 +10,7 @@ import {SessionsManager} from "../Managers/SessionsManager.ecma.js";
import {ModelsManager} from "../Managers/ModelsManager.ecma.js"; import {ModelsManager} from "../Managers/ModelsManager.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 {Components} from "./Components.ecma.js"; import {Components} from "./Components.ecma.js";
import {Common} from "../Utils/Common.ecma.js"; import {Common} from "../Utils/Common.ecma.js";
import {Check} from "../Utils/Checks.ecma.js"; import {Check} from "../Utils/Checks.ecma.js";
@ -73,6 +74,8 @@ export const AnP = (function(){
this.views = new ViewsManager(self); this.views = new ViewsManager(self);
/** @type {RoutesManager} */ /** @type {RoutesManager} */
this.routes = new RoutesManager(self); this.routes = new RoutesManager(self);
/** @type {WebSocketsClientsManager} */
this.web_sockets_clients = new WebSocketsClientsManager(self);
/** /**
* @returns {void} * @returns {void}

View File

@ -28,7 +28,9 @@ export const AIChatComponent = (function(){
/** @type {AIChatComponent} */ /** @type {AIChatComponent} */
const self = this; const self = this;
/** @type {string|null} */
let web_socket_id = null;
/** /**
* @returns {void} * @returns {void}
* @access private * @access private
@ -39,6 +41,8 @@ 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"}),
@ -66,7 +70,8 @@ export const AIChatComponent = (function(){
if(text){ if(text){
Common.HTML(".aichat .messages", Fieldset({ Common.HTML(".aichat .messages", Fieldset({
class : "message", class : "message",
data_type : "user" data_type : "user",
data_id : anp.unique_keys.create()
}, [ }, [
anp.components.i18n("user", "legend"), anp.components.i18n("user", "legend"),
Div({class : "content"}, text) Div({class : "content"}, text)
@ -81,6 +86,27 @@ 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) => {
let box = document.querySelector(".aichat .messages>[data-type=bot][data-id='" + id + "']");
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(".content").innerHTML += fragment;
};
constructor(); constructor();
}; };

View File

@ -0,0 +1,65 @@
"use strict";
import {WebSocketClientModel} from "../Models/WebSocketClientModel.ecma.js";
/**
* @typedef {import("../Application/AnP.ecma.js").AnP} AnP
*/
/**
* @class WebSocketsClientsManager
* @constructor
* @param {!AnP} anp
* @param {!string} key
* @param {!(Object.<string, any|null>|Array.<any|null>)} inputs
* @returns {void}
* @access private
* @static
*/
export const WebSocketsClientsManager = (function(){
/**
* @constructs WebSocketsClientsManager
* @param {!AnP} anp
* @param {!string} key
* @param {!(Object.<string, any|null>|Array.<any|null>)} inputs
* @returns {void}
* @access private
* @static
*/
const WebSocketsClientsManager = function(anp, key, inputs){
/** @type {WebSocketsClientsManager} */
const self = this,
/** @type {Object.<string, WebSocketClientModel} */
clients = {};
/**
* @returns {void}
* @access private
*/
const constructor = () => {};
this.add = (key, url) => {
clients[key] = new WebSocketClientModel(anp, url);
};
this.remove = key => {
if(key in clients){
clients[key].close();
delete clients[key];
return true;
};
return false;
};
this.send = (key, data) => {};
constructor();
};
return WebSocketsClientsManager;
})();

View File

@ -0,0 +1,92 @@
"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

@ -18,7 +18,7 @@ class AIInterpretersAbstract(ABC):
self.key:str = key self.key:str = key
self.url:str = self.anp.settings.get(("ai_interpreter_url", "ai_url", "url"), inputs, "") self.url:str = self.anp.settings.get(("ai_interpreter_url", "ai_url", "url"), inputs, "")
self.stream:bool = self.anp.settings.get(("ai_interpreter_stream", "ai_stream", "stream"), inputs, False) self.stream:bool = self.anp.settings.get(("ai_interpreter_stream", "ai_stream", "stream"), inputs, False)
self.orders:list[str] = self.anp.settings.get(("ai_interpreter_orders", "ai_orders", "orders"), inputs, []) self.orders:str|list[str] = self.anp.settings.get(("ai_interpreter_orders", "ai_orders", "orders"), inputs, [])
self.maximum_tokens_per_session:int = self.anp.settings.get(( self.maximum_tokens_per_session:int = self.anp.settings.get((
"ai_interpreter_maximum_tokens_per_session", "ai_maximum_tokens_per_session", "maximum_tokens_per_session" "ai_interpreter_maximum_tokens_per_session", "ai_maximum_tokens_per_session", "maximum_tokens_per_session"
), inputs, None) ), inputs, None)
@ -55,6 +55,23 @@ class AIInterpretersAbstract(ABC):
del self.sessions[id] del self.sessions[id]
return True return True
return False return False
def get_orders(self:Self, orders:str|list[str]) -> str:
results:str = ""
i:int = 0
for block in (orders, self.orders):
if block:
if Check.is_array(block):
block_string:str = "".join(str(i + 1 + j) + ". " + str(order) + "\n" for j, order in enumerate(block))
i += len(block)
results += ("\n\n" if results else "") + block_string.strip()
else:
results += ("\n\n" if results else "") + str(block).strip()
@abstractmethod @abstractmethod
def request(self:Self, def request(self:Self,

View File

@ -5,6 +5,7 @@ from typing import Any, Self, Optional, Sequence
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from Interfaces.Application.AnPInterface import AnPInterface from Interfaces.Application.AnPInterface import AnPInterface
from Application.Event import Event from Application.Event import Event
from Utils.Common import Common
class WebSocketServersAbstract(ABC): class WebSocketServersAbstract(ABC):
@ -24,5 +25,14 @@ 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:
return Common.data_encode({
"controller": controller,
"method": method,
"data": data,
"id": id,
"code": code
})
@abstractmethod @abstractmethod
def send(self:Self, data:Any|None, ids:Optional[int|Sequence[int]] = None) -> None:pass def send(self:Self, controller:str, method:str, data:Any|None, ids:Optional[int|Sequence[int]] = None, code:int = 200) -> None:pass

View File

@ -24,7 +24,7 @@ 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:list[str] = [] orders:str|list[str] = []
) -> tuple[int|None, AIResponseModel]: ) -> tuple[int|None, AIResponseModel]:
response:Response response:Response
@ -38,15 +38,13 @@ 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
orders += self.orders
session, context = self.get_session(session) session, context = self.get_session(session)
orders = self.get_orders(orders)
with Post(self.url, json = { with Post(self.url, json = {
"model" : self.model, "model" : self.model,
"prompt": message, "prompt": message,
**({"system": "\\n".join( **({"system": orders} if len(orders) else {}),
str(i + 1) + ". " + order for i, order in enumerate(orders, 1)
)} if len(orders) else {}),
"stream": self.stream, "stream": self.stream,
**( **(
{"format" : self.format} if ( {"format" : self.format} if (

View File

@ -16,10 +16,9 @@ class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract):
super().__init__(anp, inputs) super().__init__(anp, inputs)
self.__server:WebSocketServer self.__server:WebSocketServer
self.__clients:dict[int, WebSocketClient] = {} self.__clients:dict[str, WebSocketClient] = {}
self.__host:str = anp.settings.get(("web_socket_server_host", "host"), inputs, "") 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.__port:int = anp.settings.get(("web_socket_server_port", "port"), inputs, 8765)
self.__client_i:int = 0
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()
@ -55,9 +54,7 @@ class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract):
def __handler(self:Self, client:WebSocketClient) -> None: def __handler(self:Self, client:WebSocketClient) -> None:
id:int = self.__client_i id:int = self.anp.unique_keys.create()
self.__client_i += 1
self.__clients[id] = client self.__clients[id] = client
self.on_new_client.execute(id) self.on_new_client.execute(id)
@ -70,6 +67,8 @@ class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract):
"client_port" : client.remote_address[1] "client_port" : client.remote_address[1]
}) })
self.send("web_socket_client", "set_id", id, id)
try: try:
while self.anp.working(): while self.anp.working():
message:str = client.recv() message:str = client.recv()
@ -85,6 +84,7 @@ class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract):
self.on_error.execute(id, exception) self.on_error.execute(id, exception)
finally: finally:
self.close_client(id) self.close_client(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, "client": id,
@ -92,15 +92,18 @@ class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract):
"host": self.host "host": self.host
}) })
def send(self:Self, data:str, ids:int|Sequence[int]) -> bool: def send(self:Self, controller:str, method:str, data:str, ids:Optional[int|Sequence[int]] = None, code:int = 200) -> bool:
success:bool = True success:bool = True
id:int id:int
for id in ids if Check.is_array(ids) else [ids]: for id in (
list(self.__clients.keys()) if ids is None else
ids if Check.is_array(ids) else
[ids]):
if id in self.__clients: if id in self.__clients:
try: try:
self.__clients[id].send(data) self.__clients[id].send(self.format_data(controller, method, 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, "client": id,