#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" : {
"anp" : {
"type" : "WebSocketServerDriver",
"host" : "localhost",
"host" : "",
"port" : 18765
}
},

View File

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

View File

@ -28,7 +28,9 @@ export const AIChatComponent = (function(){
/** @type {AIChatComponent} */
const self = this;
/** @type {string|null} */
let web_socket_id = null;
/**
* @returns {void}
* @access private
@ -39,6 +41,8 @@ export const AIChatComponent = (function(){
const name = Common.get_value("name", inputs, "aichat");
web_socket_id = anp.web_sockets_clients.add("AAA", "https://localhost:18000/");
return Fieldset({class : "aichat"}, [
anp.components.i18n(name, "legend"),
Section({class : "messages"}),
@ -66,7 +70,8 @@ export const AIChatComponent = (function(){
if(text){
Common.HTML(".aichat .messages", Fieldset({
class : "message",
data_type : "user"
data_type : "user",
data_id : anp.unique_keys.create()
}, [
anp.components.i18n("user", "legend"),
Div({class : "content"}, text)
@ -81,6 +86,27 @@ export const AIChatComponent = (function(){
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();
};

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.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.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((
"ai_interpreter_maximum_tokens_per_session", "ai_maximum_tokens_per_session", "maximum_tokens_per_session"
), inputs, None)
@ -55,6 +55,23 @@ class AIInterpretersAbstract(ABC):
del self.sessions[id]
return True
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
def request(self:Self,

View File

@ -5,6 +5,7 @@ from typing import Any, Self, Optional, Sequence
from abc import ABC, abstractmethod
from Interfaces.Application.AnPInterface import AnPInterface
from Application.Event import Event
from Utils.Common import Common
class WebSocketServersAbstract(ABC):
@ -24,5 +25,14 @@ class WebSocketServersAbstract(ABC):
@abstractmethod
def close_client(self:Self, id:int) -> None:pass
def format_data(self:Self, controller:str, method:str, data:Any|None, id:str, code:int = 200) -> str:
return Common.data_encode({
"controller": controller,
"method": method,
"data": data,
"id": id,
"code": code
})
@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,
message:str,
callback:Optional[Callable[[int, AIResponseModel], None]] = None,
orders:list[str] = []
orders:str|list[str] = []
) -> tuple[int|None, AIResponseModel]:
response:Response
@ -38,15 +38,13 @@ class OllamaDriver(AIInterpretersAbstract, ModelAbstract):
if self.maximum_tokens_per_session is not None:
options["num_ctx"] = self.maximum_tokens_per_session
orders += self.orders
session, context = self.get_session(session)
orders = self.get_orders(orders)
with Post(self.url, json = {
"model" : self.model,
"prompt": message,
**({"system": "\\n".join(
str(i + 1) + ". " + order for i, order in enumerate(orders, 1)
)} if len(orders) else {}),
**({"system": orders} if len(orders) else {}),
"stream": self.stream,
**(
{"format" : self.format} if (

View File

@ -16,10 +16,9 @@ class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract):
super().__init__(anp, inputs)
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.__port:int = anp.settings.get(("web_socket_server_port", "port"), inputs, 8765)
self.__client_i:int = 0
self.__thread:Thread = None
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:
id:int = self.__client_i
self.__client_i += 1
id:int = self.anp.unique_keys.create()
self.__clients[id] = client
self.on_new_client.execute(id)
@ -70,6 +67,8 @@ class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract):
"client_port" : client.remote_address[1]
})
self.send("web_socket_client", "set_id", id, id)
try:
while self.anp.working():
message:str = client.recv()
@ -85,6 +84,7 @@ class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract):
self.on_error.execute(id, exception)
finally:
self.close_client(id)
self.anp.unique_keys.remove(id)
self.on_close.execute(id)
self.anp.print("info", "web_socket_server_client_disconnected", {
"client": id,
@ -92,15 +92,18 @@ class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract):
"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
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:
try:
self.__clients[id].send(data)
self.__clients[id].send(self.format_data(controller, method, data, id, code))
except Exception as exception:
self.anp.exception(exception, "web_socket_server_client_send_exception", {
"client": id,