From 36413778e75cbd120ec3ea10b7df693bfd00891c Mon Sep 17 00:00:00 2001 From: mbruzon Date: Wed, 10 Jun 2026 14:53:32 +0200 Subject: [PATCH] #wip: PseudoLoRAs building... --- JSON/AnP.pseudoloras.json | 15 +- JSON/I18N/AnP.i18n.espanol.json | 17 +- .../WebSocketsClientsAbstract.ecma.js | 1 - .../ecma/Components/AIChatComponent.ecma.js | 20 ++- .../Drivers/WebSocketsClientsDriver.ecma.js | 2 +- Public/ecma/Managers/CookiesManager.ecma.js | 2 +- .../ecma/Managers/UniqueKeysManager.ecma.js | 11 ++ Python/Abstracts/AIInterpretersAbstract.py | 50 ++++-- Python/Abstracts/HTTPServersAbstract.py | 42 ++++- Python/Abstracts/WebSocketServersAbstract.py | 14 +- Python/Application/AnP.py | 10 +- Python/Controllers/AIController.py | 156 +++++++++++++----- Python/Drivers/HTTPDriver.py | 35 +++- Python/Drivers/OllamaDriver.py | 24 +-- Python/Drivers/WebSocketServerDriver.py | 9 + Python/Interfaces/Application/AnPInterface.py | 5 +- .../AIInterpretersManagerInterface.py | 5 +- .../Managers/SessionsManagerInterface.py | 9 +- Python/Managers/AIInterpretersManager.py | 7 +- Python/Managers/PseudoLoRAsManager.py | 69 ++++---- Python/Managers/RoutesManager.py | 3 + Python/Managers/SessionsManager.py | 23 ++- Python/Managers/WebSocketServersManager.py | 3 +- Python/Models/AIResponseModel.py | 3 +- Python/Models/RequestModel.py | 18 +- Python/Models/SessionModel.py | 6 +- Python/Utils/Patterns.py | 3 +- 27 files changed, 424 insertions(+), 138 deletions(-) diff --git a/JSON/AnP.pseudoloras.json b/JSON/AnP.pseudoloras.json index 8452fb3..00a637b 100644 --- a/JSON/AnP.pseudoloras.json +++ b/JSON/AnP.pseudoloras.json @@ -1,4 +1,17 @@ [ ["Qué es AnP", "/MD/PseudoLoRAs/AnP.md", [], false], - ["Gestión de configuración", "/MD/AnP.Settings.md", [], false] + ["AnP - Gestores", "/MD/AnP.Managers.md", [], false], + ["AnP - Gestores - Configuraciones", "/MD/AnP.Settings.md", [], false], + ["AnP - Gestores - I18N", "/MD/AnP.i18n.md", [], false], + ["AnP - Gestores - Tipos de impresión", "/MD/AnP.PrintTypes.md", [], false], + ["AnP - Gestores - Terminal/Consola", "/MD/AnP.Terminal.md", [], false], + ["AnP - Gestores - Modelos", "/MD/AnP.Models.md", [], false], + ["AnP - Gestores - Controladores de IA", "/MD/AnP.Controllers.md", [], false], + ["AnP - Gestores - Índices", "/MD/AnP.Indexes.md", [], false], + ["AnP - Gestores - Rutas", "/MD/AnP.Routes.md", [], false], + ["AnP - Web Sockets - WebSocketServerDriver", "/MD/AnP.WebSocketServerDriver.md", [], false], + ["AnP - Intérpretes de IA - OllamaDriver", "/MD/AnP.OllamaDriver.md", [], false], + ["AnP - Servidores HTTP - HTTPDriver", "/MD/AnP.HTTPDriver.md", [], false], + ["AnP - Modelos - IA", "/MD/AnP.AIModel.md", [], false], + ["AnP - Controladores - IA", "/MD/AnP.AIController.md", [], false] ] \ No newline at end of file diff --git a/JSON/I18N/AnP.i18n.espanol.json b/JSON/I18N/AnP.i18n.espanol.json index 8c0ef2d..7f9ad72 100644 --- a/JSON/I18N/AnP.i18n.espanol.json +++ b/JSON/I18N/AnP.i18n.espanol.json @@ -41,7 +41,22 @@ "AnP_TitlesManager_end" : null, "AnP_AIModel_start" : null, - "AnP_AIModel_end" : null + "AnP_AIModel_end" : null, + + "AnP_AIController_start" : null, + "ai_controller_loras_system" : [ + "Actúa como un clasificador estricto. Se te proporcionará una lista de títulos y un Prompt.\n", + "Debes evaluar cada título uno por uno en base al Prompt del usuario.\n", + "Debes responder ÚNICAMENTE con un array JSON de booleanos que tenga exactamente {items} elementos.\n", + "Cada posición del array de salida debe corresponder textualmente a la posición del título en la lista original:\n\n", + "- true: Si el título cumple el criterio.\n", + "- false: Si el título NO cumple el criterio.\n", + "No añadas explicaciones, solo el JSON final.\n\n", + "Si ningún título cumple el criterio, responde con un array de booleanos con todos sus valores en false.\n", + "Lista de títulos a evaluar: \n{list}" + ], + "ai_controller_response_system" : "Usa las siguientes guías para responder y dar soporte.{loras}", + "AnP_AIController_end" : null } } \ No newline at end of file diff --git a/Public/ecma/Abstracts/WebSocketsClientsAbstract.ecma.js b/Public/ecma/Abstracts/WebSocketsClientsAbstract.ecma.js index db55f60..ab299be 100644 --- a/Public/ecma/Abstracts/WebSocketsClientsAbstract.ecma.js +++ b/Public/ecma/Abstracts/WebSocketsClientsAbstract.ecma.js @@ -95,7 +95,6 @@ export const WebSocketsClientsAbstract = (function(){ 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); diff --git a/Public/ecma/Components/AIChatComponent.ecma.js b/Public/ecma/Components/AIChatComponent.ecma.js index c23a06b..08781e4 100644 --- a/Public/ecma/Components/AIChatComponent.ecma.js +++ b/Public/ecma/Components/AIChatComponent.ecma.js @@ -1,7 +1,7 @@ "use strict"; import { - Fieldset, Section, Form, Div, Nav, UL, LI, Span, Pre + Fieldset, Section, Form, Div, Nav, UL, LI, Span, Pre, Article } from "../Utils/HTMLDSL.ecma.js"; import {MarkDown} from "../Utils/MarkDown.ecma.js"; import {Common} from "../Utils/Common.ecma.js"; @@ -46,7 +46,13 @@ export const AIChatComponent = (function(){ return Fieldset({class : "aichat"}, [ anp.components.i18n(name, "legend"), - Section({class : "messages"}), + Section({class : "conversations"}, [ + Article({ + class : "messages", + data_conversation : anp.unique_keys.get(), + data_selected : true + }) + ]), Form({ method : "post", action : "#", @@ -144,7 +150,8 @@ export const AIChatComponent = (function(){ const send = (item, event) => { const text_box = item.querySelector("[name=message]"), - text = text_box.value.trim(); + text = text_box.value.trim(), + conversation = document.querySelector(".aichat [data-conversation][data-selected=true]").getAttribute("data-conversation"); event.preventDefault(); @@ -153,7 +160,7 @@ export const AIChatComponent = (function(){ const data_id = anp.unique_keys.get(); Common.HTML( - ".aichat .messages", + ".aichat [data-conversation=" + conversation + "]", build_message(data_id, "user", text, "done"), build_message(data_id, "bot") ); @@ -161,7 +168,8 @@ export const AIChatComponent = (function(){ anp.web_sockets_clients.send("anp", "ai", "message", { message_id : data_id, - message : text + message : text, + conversation : conversation }); }; @@ -174,7 +182,7 @@ export const AIChatComponent = (function(){ }; this.write_response = (id, fragment, ok, done) => { - console.log([id, fragment]); + // console.log([id, fragment]); const box = document.querySelector(".aichat .messages>[data-type=bot][data-id='" + id + "']"), status = ( diff --git a/Public/ecma/Drivers/WebSocketsClientsDriver.ecma.js b/Public/ecma/Drivers/WebSocketsClientsDriver.ecma.js index 170dceb..0ec18f5 100644 --- a/Public/ecma/Drivers/WebSocketsClientsDriver.ecma.js +++ b/Public/ecma/Drivers/WebSocketsClientsDriver.ecma.js @@ -161,7 +161,7 @@ export const WebSocketsClientsDriver = (function(){ controller : controller, action : action, data : data, - id : self.id + id : _super.id })); }; diff --git a/Public/ecma/Managers/CookiesManager.ecma.js b/Public/ecma/Managers/CookiesManager.ecma.js index 2dbf306..ad67464 100644 --- a/Public/ecma/Managers/CookiesManager.ecma.js +++ b/Public/ecma/Managers/CookiesManager.ecma.js @@ -1,6 +1,6 @@ "use strict"; -import {Check} from "../Utils/Check.ecma.js"; +import {Check} from "../Utils/Checks.ecma.js"; import {Common} from "../Utils/Common.ecma.js"; /** diff --git a/Public/ecma/Managers/UniqueKeysManager.ecma.js b/Public/ecma/Managers/UniqueKeysManager.ecma.js index 82b7229..b44eed4 100644 --- a/Public/ecma/Managers/UniqueKeysManager.ecma.js +++ b/Public/ecma/Managers/UniqueKeysManager.ecma.js @@ -213,6 +213,17 @@ export const UniqueKeysManager = (function(){ }); }; + /** + * @param {!(string|Array.)} new_keys + * @returns {void} + * @access public + */ + this.set = new_keys => { + Common.get_keys(new_keys).forEach(key => { + keys.includes(key) || keys.push(key); + }); + }; + constructor(); }; diff --git a/Python/Abstracts/AIInterpretersAbstract.py b/Python/Abstracts/AIInterpretersAbstract.py index 1943492..654b61f 100644 --- a/Python/Abstracts/AIInterpretersAbstract.py +++ b/Python/Abstracts/AIInterpretersAbstract.py @@ -32,6 +32,7 @@ class AIInterpretersAbstract(ABC): 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) + self.temperature:float = self.anp.settings.get(("ai_interpreter_temperature", "ai_temperature", "temperature"), inputs, 0.7) def start(self:Self) -> None: pass @@ -39,18 +40,26 @@ class AIInterpretersAbstract(ABC): def close(self:Self) -> None: self.sessions = {} - def get_session(self:Self, id:int|None = None) -> tuple[int, list[int]]: + def get_session(self:Self, id:int|None = None, conversation:str|None = None) -> tuple[int, list[int]]: if id is None or id not in self.sessions: id = self.sessions_i self.sessions_i += 1 - self.sessions[id] = [] + self.sessions[id] = {} - return id, self.sessions[id] + return id, self.sessions[id][conversation] if conversation in self.sessions[id] else [] - def save_context(self:Self, id:int, context:list[int]) -> None: + def save_context(self:Self, id:int, conversation:str, context:list[int]) -> None: if self.allow_contexts and id in self.sessions: - self.sessions[id] = context + if conversation not in self.sessions[id]: + self.sessions[id][conversation] = context + self.sessions[id][conversation] = context + + def close_conversation(self:Self, id:int, conversation:str) -> bool: + if id in self.sessions and conversation in self.sessions[id]: + del self.sessions[id][conversation] + return True + return False def close_session(self:Self, id:int) -> bool: if id in self.sessions: @@ -62,29 +71,34 @@ class AIInterpretersAbstract(ABC): return self.sessions[id] if id in self.sessions else [] def get_orders(self:Self, orders:Optional[str|list[str]] = None) -> str: + return ( + orders if Check.is_string(orders) else + "\n".join(orders) if Check.is_array(orders) else + "") - results:str = "" - i:int = 0 + # results:str = "" + # i:int = 0 - for block in (self.orders, orders): - if block: - if Check.is_array(block): + # for block in (self.orders, 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)) + # 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() + # i += len(block) + # results += ("\n\n" if results else "") + block_string.strip() - else: - results += ("\n\n" if results else "") + str(block).strip() + # else: + # results += ("\n\n" if results else "") + str(block).strip() - return results + # return results @abstractmethod def request(self:Self, session:int|None, + conversation:str|None, message:str, - orders:list[str] = [], + orders:str|list[str] = [], callback:Optional[Callable[[int, AIResponseModel], None]] = None, - context:list[int] = [] + custom_context:list[int] = [] ) -> tuple[int|None, AIResponseModel]:pass \ No newline at end of file diff --git a/Python/Abstracts/HTTPServersAbstract.py b/Python/Abstracts/HTTPServersAbstract.py index 07cb132..e6d8c4e 100644 --- a/Python/Abstracts/HTTPServersAbstract.py +++ b/Python/Abstracts/HTTPServersAbstract.py @@ -3,6 +3,7 @@ from abc import ABC, abstractmethod from typing import Any, Self, Optional, Sequence +from re import Match as REMath from Interfaces.Application.AnPInterface import AnPInterface from Utils.Common import Common @@ -20,6 +21,7 @@ class HTTPServersAbstract(ABC): "port": self.port, "host": self.host } + self._session_timeout:int = anp.settings.get(("sessions_timeout", "timeout"), inputs, 3600) self.key:str = key self.update() @@ -50,4 +52,42 @@ class HTTPServersAbstract(ABC): self.host = self.DEFAULT_HOST self.__update_print_data() - self.update() \ No newline at end of file + self.update() + + def load_cookies(self:Self, cookies:str|None) -> dict[str, Any|None]: + if not cookies: + return {} + + cookie:str + results:dict[str, Any|None] = {} + + for cookie in cookies.split(";"): + if "=" in cookie: + + key:str + value:str + + key, value = cookie.split("=", 1) + results[key.strip()] = value.strip() + + return results + + @staticmethod + def get_variables_from(source:str|None) -> dict[str, Any|None]: + if not source: + return {} + + json:dict[str, Any|None] = Common.data_decode(source) + + if json is not None: + return Common.get_dictionary(json) + + json = {} + + def callback(matches:REMath) -> str: + json[matches.group(1)] = matches.group(2) + return matches.group(0) + + Common.replace(self.HTTP_VARIABLE, source, callback) + + return json \ No newline at end of file diff --git a/Python/Abstracts/WebSocketServersAbstract.py b/Python/Abstracts/WebSocketServersAbstract.py index ab7bdd3..7036fde 100644 --- a/Python/Abstracts/WebSocketServersAbstract.py +++ b/Python/Abstracts/WebSocketServersAbstract.py @@ -14,6 +14,7 @@ class WebSocketServersAbstract(ABC): 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.__sessions:dict[str, str] = {} self.on_new_client:Event = Event() self.on_message:Event = Event() self.on_close:Event = Event() @@ -39,4 +40,15 @@ class WebSocketServersAbstract(ABC): }) @abstractmethod - def send(self:Self, controller:str, action:str, data:Any|None, ids:Optional[str|Sequence[str]] = None, code:int = 200) -> None:pass \ No newline at end of file + def send(self:Self, controller:str, action:str, data:Any|None, ids:Optional[str|Sequence[str]] = None, code:int = 200) -> None:pass + + def set_session(self:Self, id:str, session:str) -> None: + if id not in self.__sessions: + self.__sessions[id] = session + + def get_session(self:Self, id:str) -> str|None: + return self.__sessions.get(id) + + def remove_session(self:Self, id:str) -> None: + if id in self.__sessions: + del self.__sessions[id] \ No newline at end of file diff --git a/Python/Application/AnP.py b/Python/Application/AnP.py index df76247..4dbd5df 100644 --- a/Python/Application/AnP.py +++ b/Python/Application/AnP.py @@ -5,6 +5,7 @@ from typing import Self, Any, Optional, Sequence import datetime from re import Match as REMatch from traceback import extract_tb as extract_traceback, format_stack as trace_format_stack +from time import time as timestamp, sleep from Managers.I18NManager import I18NManager from Managers.SettingsManager import SettingsManager from Managers.PrintTypesManager import PrintTypesManager @@ -170,4 +171,11 @@ class AnP: data["end"] = Common.string_variables(self.__exception_format, data) - message and self.print("exception", message, data, i + 2) \ No newline at end of file + message and self.print("exception", message, data, i + 2) + + def wait(self:Self, time:str|float) -> None: + + date:float = timestamp() + + while self.__working and timestamp() - date < float(time): + sleep(.1) \ No newline at end of file diff --git a/Python/Controllers/AIController.py b/Python/Controllers/AIController.py index 08bcefc..a95ec06 100644 --- a/Python/Controllers/AIController.py +++ b/Python/Controllers/AIController.py @@ -9,54 +9,128 @@ from Models.PseudoLoRAModel import PseudoLoRAModel class AIController(ControllerAbstract, ModelAbstract): - def __test_execution(self:Self, end:Callable[[], None], request:RequestModel) -> None: - self.anp.ai_interpreters.request( - "anp_responses", - None, - request.get("message", "Hola, Gemma. ¿Me puedes ayudar a instalar una impresora Canon?"), - # lambda id, response: print((id, response.response, request.get("client_id"))), - lambda id, response: self.anp.web_socket_servers.send("anp", "ai", "test", { - "id" : id, - "response" : response.response - }, request.get("client_id")), - # [ - # "# Títulos\n" + "".join( - # "\n - " + title for title in [ - # "Información, gestión e instalación de Cividas", - # "Información, gestión e instalación de Impresoras/Fotocopiadoras/Multifuncionales Canon" - # ] - # ), - # ] - ) - end() + # def __message_execution(self:Self, end:Callable[[], None], request:RequestModel) -> None: - def test(self:Self, request:RequestModel) -> None: - self.anp.queues.add("anp", self.__test_execution, request) - request.set_response({ - "ok" : True, - "code" : 200, - "message" : "ok" - }) + # session:str|None = request.session.get("anp_ai_session") + # message_id:str|None = request.get("message_id") + # client_id:str|None = request.get("client_id") + + # session, _ = self.anp.ai_interpreters.request( + # "anp_responses", + # session, + # request.get("conversation"), + # request.get("message"), + # [], + # lambda id, response: self.anp.web_socket_servers.send("anp", "ai", "message", { + # "id" : id, + # "conversation" : response.conversation, + # "response" : response.response, + # "ok" : response.ok, + # "done" : response.done, + # "data_id" : message_id + # }, client_id) + # ) + + # if session is not None: + # request.session.set("anp_ai_session", session) + + # end() + + def __analyse_loras(self:Self, data:dict[str, Any], request:RequestModel, loras:list[str]) -> list[tuple[str, str]]: + + session:str|None = request.session.get("anp_loras_session") + results:list[tuple[str, str]] = [] + + print(self.anp.i18n.get("ai_controller_loras_system", { + "items" : len(loras), + "list" : "".join("\n" + str(i) + ". " + title for i, title in enumerate(loras)) + })) + + # session, results = self.anp.ai_interpreters.request( + # "anp_loras", + # session, + # data["conversation"], + # data["message"], + # self.anp.i18n.get("ai_controller_loras_system", { + # "items" : len(loras), + # "list" : "".join("\n" + str(i) + ". " + title for i, title in enumerate(loras)) + # }) + # ) + data["i"] += 1 + + # if session is not None or data["i"] == 1: + # request.session.set("anp_loras_session", session) + + return results def __message_execution(self:Self, end:Callable[[], None], request:RequestModel) -> None: - session:str|None = None + session:str|None = request.session.get("anp_ai_session") + message_id:str|None = request.get("message_id") + client_id:str|None = request.get("client_id") + conversation:str = request.get("conversation") + message:str = request.get("message") + loras:list[tuple[str, str]] + loras_data:dict[str, Any] = { + "conversation" : conversation, + "message" : message, + "i" : 0 + } + + loras = self.anp.pseudoloras.get(lambda loras:self.__analyse_loras(loras_data, request, loras)) + + print([title for title, _ in loras]) + + # session, _ = self.anp.ai_interpreters.request( + # "anp_responses", + # session, + # conversation, + # message, + # ("Usa las siguientes guías para responder y dar soporte.\n\n" + "".join( + # "# " + title + "\n\n" + text + "\n\n" for title, text in loras + # ) if len(loras) else []), + # lambda id, response: self.anp.web_socket_servers.send("anp", "ai", "message", { + # "id" : id, + # "conversation" : response.conversation, + # "response" : response.response, + # "ok" : response.ok, + # "done" : response.done, + # "data_id" : message_id + # }, client_id) + # ) + + # if session is not None: + # request.session.set("anp_ai_session", session) - session, _ = self.anp.ai_interpreters.request( - "anp_responses", - session, - 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_by_themes_execution(self:Self, end:Callable[[], None], request:RequestModel) -> None: + + # session:str|None = request.session.get("anp_ai_session") + # message_id:str|None = request.get("message_id") + # client_id:str|None = request.get("client_id") + + # session, _ = self.anp.ai_interpreters.request( + # "anp_responses", + # session, + # request.get("conversation"), + # request.get("message"), + # [], + # lambda id, response: self.anp.web_socket_servers.send("anp", "ai", "message", { + # "id" : id, + # "conversation" : response.conversation, + # "response" : response.response, + # "ok" : response.ok, + # "done" : response.done, + # "data_id" : message_id + # }, client_id) + # ) + + # if session is not None: + # request.session.set("anp_ai_session", session) + + # end() + def message(self:Self, request:RequestModel) -> None: self.anp.queues.add("anp", self.__message_execution, request) request.set_response({ diff --git a/Python/Drivers/HTTPDriver.py b/Python/Drivers/HTTPDriver.py index 70b2c7b..35cabb9 100644 --- a/Python/Drivers/HTTPDriver.py +++ b/Python/Drivers/HTTPDriver.py @@ -7,6 +7,7 @@ from Abstracts.ModelAbstract import ModelAbstract from Models.RequestModel import RequestModel from threading import Thread from http.server import BaseHTTPRequestHandler, HTTPServer +from http.cookies import SimpleCookie from Abstracts.HTTPServersAbstract import HTTPServersAbstract class HTTPDriver(HTTPServersAbstract, ModelAbstract): @@ -16,19 +17,50 @@ class HTTPDriver(HTTPServersAbstract, ModelAbstract): def __process(self:Self, method:str) -> None: anp:AnPInterface = self.server.anp + server:HTTPServersAbstract = self.server.itself request:RequestModel = RequestModel() + session_id:str|None = None + cookies:SimpleCookie|None = None request.method = method - request.path = self.path + request.path = self.path.split("?")[0] request.headers = dict(self.headers) request.client_host = self.client_address[0] request.client_port = self.client_address[1] + request.get_variables = server.get_variables_from(self.path[self.path.find("?") + 1:] if "?" in self.path else None) + request.post_variables = server.get_variables_from(self.rfile.read(int(self.headers.get("Content-Length", 0)))) + request.cookies = server.load_cookies(self.headers.get("Cookie")) + + session_id = request.get("session") + request.session = anp.sessions._get_instance(session_id) + + if session_id is None or request.session is None: + + cookies = SimpleCookie() + + session_id = anp.sessions.create() + cookies["session"] = session_id + cookies["session"]["path"] = "/" + cookies["session"]["max-age"] = server._session_timeout + # cookies["session"]["HttpOnly"] = True + # cookies["session"]["Secure"] = True + + request.session = anp.sessions._get_instance(session_id) + anp.routes.go([self.server.key], method, self.path, request) self.send_response(request.response_code) self.send_header("Content-Type", request.response_mime) self.send_header("Content-Length", str(len(request.response))) + + if cookies is not None: + + morsel:str + + for morsel in cookies.values(): + self.send_header("Set-Cookie", morsel.OutputString()) + self.end_headers() self.wfile.write( @@ -113,5 +145,6 @@ class HTTPDriver(HTTPServersAbstract, ModelAbstract): def __run_service(self:Self) -> None: self.__server = HTTPServer((self.host, self.port), self.HTTPRequestHandler) self.__server.anp = self.anp + self.__server.itself = self self.__server.key = self.key self.__server.serve_forever() \ No newline at end of file diff --git a/Python/Drivers/OllamaDriver.py b/Python/Drivers/OllamaDriver.py index a7e1b33..00526ea 100644 --- a/Python/Drivers/OllamaDriver.py +++ b/Python/Drivers/OllamaDriver.py @@ -22,24 +22,24 @@ class OllamaDriver(AIInterpretersAbstract, ModelAbstract): def request(self:Self, session:int|None, + conversation:str|None, message:str, - callback:Optional[Callable[[int, AIResponseModel], None]] = None, orders:str|list[str] = [], - custom_context:Optional[list[int]] = None + callback:Optional[Callable[[int, AIResponseModel], None]] = None, + custom_context:Optional[list[int]] = None, + save_session:bool = True ) -> tuple[int|None, AIResponseModel]: response:Response context:list[int] - options:dict[str, Any] = {} - results:AIResponseModel = AIResponseModel() + options:dict[str, Any] = {key : value for key, value in { + "num_predict" : self.maximum_response_tokens, + "num_ctx" : self.maximum_tokens_per_session, + "temperature" : self.temperature + }.items() if value is not None} + results:AIResponseModel = AIResponseModel(conversation) - if self.maximum_response_tokens is not None: - options["num_predict"] = self.maximum_response_tokens - - if self.maximum_tokens_per_session is not None: - options["num_ctx"] = self.maximum_tokens_per_session - - session, context = custom_context or self.get_session(session) + session, context = custom_context or self.get_session(session, conversation) orders = self.get_orders(orders) if custom_context: @@ -75,7 +75,7 @@ class OllamaDriver(AIInterpretersAbstract, ModelAbstract): if chunk: results.update(Common.json_decode(chunk)) if results.done: - self.save_context(session, results.context) + save_session and self.save_context(session, conversation, results.context) break Common.execute(callback, session, results) diff --git a/Python/Drivers/WebSocketServerDriver.py b/Python/Drivers/WebSocketServerDriver.py index 2927032..bc80be4 100644 --- a/Python/Drivers/WebSocketServerDriver.py +++ b/Python/Drivers/WebSocketServerDriver.py @@ -3,6 +3,7 @@ from threading import Thread from typing import Any, Self, Sequence, Optional +from http.cookies import SimpleCookie from Abstracts.WebSocketServersAbstract import WebSocketServersAbstract from Abstracts.ModelAbstract import ModelAbstract from websockets.sync.server import serve as server_serve @@ -60,10 +61,14 @@ class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract): def __handler(self:Self, client:WebSocketClient) -> None: id:str = self.anp.unique_keys.get() + cookies:SimpleCookie = SimpleCookie() self.__clients[id] = client self.on_new_client.execute(id) + cookies.load(client.request.headers.get("Cookie", '')) + self.set_session(id, cookies.get("session").value) + self.anp.print("info", "web_socket_server_client_connected", { "id": id, "port": self.port, @@ -92,6 +97,7 @@ class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract): self.__clients[id].close() except Exception as _: pass + self.remove_session(id) del self.__clients[id] self.anp.unique_keys.remove(id) self.on_close.execute(id) @@ -106,6 +112,8 @@ class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract): success:bool = True id:str + # print([ids, list(self.__clients.keys())]) + for id in ( list(self.__clients.keys()) if ids is None else ids if Check.is_array(ids) else @@ -113,6 +121,7 @@ class WebSocketServerDriver(WebSocketServersAbstract, ModelAbstract): if id in self.__clients: try: self.__clients[id].send(self.format_data(controller, action, data, id, code)) + # print(["WS", controller, action, data, ids, code, list(self.__clients.keys())]) except Exception as exception: self.anp.exception(exception, "web_socket_server_client_send_exception", { "id": id, diff --git a/Python/Interfaces/Application/AnPInterface.py b/Python/Interfaces/Application/AnPInterface.py index 0d18688..5b7c4a3 100644 --- a/Python/Interfaces/Application/AnPInterface.py +++ b/Python/Interfaces/Application/AnPInterface.py @@ -66,4 +66,7 @@ class AnPInterface(ABC): message:str|Sequence[str], inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None, i:int = 0 - ) -> None:pass \ No newline at end of file + ) -> None:pass + + @abstractmethod + def wait(self:Self, time:str|float) -> None:pass \ No newline at end of file diff --git a/Python/Interfaces/Managers/AIInterpretersManagerInterface.py b/Python/Interfaces/Managers/AIInterpretersManagerInterface.py index 7cf3c61..78a35d8 100644 --- a/Python/Interfaces/Managers/AIInterpretersManagerInterface.py +++ b/Python/Interfaces/Managers/AIInterpretersManagerInterface.py @@ -27,8 +27,9 @@ class AIInterpretersManagerInterface(ABC): key:str, session:int|None, message:str, - callback:Callable[[int, AIResponseModel], None], - orders:list[str] = [] + orders:list[str] = [], + callback:Optional[Callable[[int, AIResponseModel], None]] = None, + custom_context:list[int] = [] ) -> int|None:pass @abstractmethod diff --git a/Python/Interfaces/Managers/SessionsManagerInterface.py b/Python/Interfaces/Managers/SessionsManagerInterface.py index 6c14751..759ae20 100644 --- a/Python/Interfaces/Managers/SessionsManagerInterface.py +++ b/Python/Interfaces/Managers/SessionsManagerInterface.py @@ -3,8 +3,9 @@ from typing import Self, Optional, Any from abc import ABC, abstractmethod +from Models.SessionModel import SessionModel -class SessionsManagerInterfaces(ABC): +class SessionsManagerInterface(ABC): @abstractmethod def update(self:Self) -> None:pass @@ -12,6 +13,12 @@ class SessionsManagerInterfaces(ABC): @abstractmethod def reset(self:Self) -> None:pass + @abstractmethod + def create(self:Self) -> SessionModel:pass + + @abstractmethod + def _get_instance(self:Self, id:str) -> SessionModel|None:pass + @abstractmethod def get(self:Self, id:str, key:str, default:Optional[Any] = None) -> Any|None:pass diff --git a/Python/Managers/AIInterpretersManager.py b/Python/Managers/AIInterpretersManager.py index 3595a02..d64acc4 100644 --- a/Python/Managers/AIInterpretersManager.py +++ b/Python/Managers/AIInterpretersManager.py @@ -75,13 +75,14 @@ class AIInterpretersManager: key:str, session:int|None, message:str, - callback:Callable[[int, AIResponseModel], None], - orders:list[str] = [] + orders:str|list[str] = [], + callback:Optional[Callable[[int, AIResponseModel], None]] = None, + custom_context:list[int] = [] ) -> tuple[int|None, AIResponseModel|None]: response:AIResponseModel|None = None if key in self.__interpreters: - session, response = self.__interpreters[key].request(session, message, callback, orders) + session, response = self.__interpreters[key].request(session, message, orders, callback, custom_context) return session, response \ No newline at end of file diff --git a/Python/Managers/PseudoLoRAsManager.py b/Python/Managers/PseudoLoRAsManager.py index 69565ff..2b54826 100644 --- a/Python/Managers/PseudoLoRAsManager.py +++ b/Python/Managers/PseudoLoRAsManager.py @@ -15,6 +15,7 @@ class PseudoLoRAsManager: self.__maximum_cache:int = (1 << 20) * 100 self.__memory_cached:int = 0 self.__cache:dict[int, PseudoLoRAModel] = {} + self.__cache_i:int = 0 self.__loras:list[PseudoLoRAModel] = [] self.update() @@ -89,58 +90,62 @@ class PseudoLoRAsManager: for lora in self.__loras: lora.clean_cache() - def get(self:Self, callback:Callable[[list[PseudoLoRAModel]], bool], keys:list[str] = []) -> list[tuple[str, str]]: + def get(self:Self, + callback:Callable[[list[PseudoLoRAModel]], bool], + loras:Optional[list[PseudoLoRAModel]] = None + ) -> list[tuple[str, str]]: - next:list[PseudoLoRAModel] = [] results:list[tuple[str, str]] = [] i:int ok:bool - has_keys:bool = len(keys) > 0 - for i, ok in enumerate(callback( - lora.title for lora in self.__loras if not has_keys or any( - key in lora.keys for key in keys - ) - )): + if loras is None: + loras = self.__loras.copy() + + loras_is:list[bool] = [(i, callback([lora.title])) for i, lora in enumerate(loras)] + + new_loras:list[PseudoLoRAModel] = [] + + for i, ok in enumerate(loras_is): if not ok: continue - lora:PseudoLoRAModel = self.__loras[i] + lora:PseudoLoRAModel = loras[i] if lora.path is not None: + results.append((lora.title, Common.load_file(lora.path, "r"))) - data:str|None + # data:str|None - if lora.cacheable: - if not lora.cache: + # if lora.cacheable: + # if not lora.cache: - lora.cache = Common.load_file(lora.path, "r") - lora.memory = len(lora.cache) - self.__memory_cached += lora.memory + # lora.cache = Common.load_file(lora.path, "r") + # lora.memory = len(lora.cache) + # self.__memory_cached += lora.memory - if self.__cache[lora.i]: - del self.__cache[lora.i] - lora.i += 1 - if lora.i in self.__cache: - self.__cache[lora.i].append(lora) - else: - self.__cache[lora.i] = [lora] + # if self.__cache[lora.i]: + # del self.__cache[lora.i] - if self.__memory_cached > self.__maximum_cache: + # lora.i = self.__cache_i + # self.__cache_i += 1 + # self.__cache[lora.i] = lora - i:int = min(self.__cache.keys()) + # if self.__memory_cached > self.__maximum_cache: - for lora in self.__cache[i]: - lora.clean_cache() - del self.__cache[i] + # i:int = min(self.__cache.keys()) - if (data := lora.cache or Common.load_file(lora.path, "r")): - results.append((lora.title, lora.path)) + # for lora in self.__cache[i]: + # lora.clean_cache() + # del self.__cache[i] + + # if (data := lora.cache or Common.load_file(lora.path, "r")): + # results.append((lora.title, lora.path)) else: - next.extend(lora.nested) + new_loras.extend(lora.nested) - if len(next): - results.extend(callback(next)) + if len(new_loras): + results.extend(self.get(callback, new_loras)) return results \ No newline at end of file diff --git a/Python/Managers/RoutesManager.py b/Python/Managers/RoutesManager.py index b2550ad..67d03fe 100644 --- a/Python/Managers/RoutesManager.py +++ b/Python/Managers/RoutesManager.py @@ -35,6 +35,9 @@ class RoutesManager: route:RouteModel + if request.session is None: + request.session = self.anp.sessions._get_instance(request.get("session")) or None + for route in self.__routes: if route.match(key, method, path, request): if not request.response: diff --git a/Python/Managers/SessionsManager.py b/Python/Managers/SessionsManager.py index 542b743..b758e1a 100644 --- a/Python/Managers/SessionsManager.py +++ b/Python/Managers/SessionsManager.py @@ -3,7 +3,7 @@ from typing import Self, Optional, Any from threading import Thread -from time import sleep, time as timestamp +from time import time as timestamp from Interfaces.Application.AnPInterface import AnPInterface from Models.SessionModel import SessionModel @@ -39,21 +39,36 @@ class SessionsManager: if time - session.get("date_last", time) > self.__timeout: self.remove(id) - sleep(self.__clean_waiter) + self.anp.wait(self.__clean_waiter) + + def create(self:Self) -> SessionModel: + + id:str = self.anp.unique_keys.get() + + self.__sessions[id] = SessionModel(id) + + return id + + def _get_instance(self:Self, id:str) -> SessionModel|None: + + if id in self.__sessions and not self.__sessions[id].removed: + return self.__sessions[id] + return None def get(self:Self, id:str, key:str, default:Optional[Any] = None) -> Any|None: - if id in self.__sessions: + if id in self.__sessions and not self.__sessions[id].removed: return self.__sessions[id].get(key, default) return default def set(self:Self, id:str, key:str, value:Any|None) -> bool: - if id in self.__sessions: + if id in self.__sessions and not self.__sessions[id].removed: self.__sessions[id].set(key, value) return True return False def remove(self:Self, id:str) -> bool: if id in self.__sessions: + self.__sessions[id].remove() del self.__sessions[id] return True return False \ No newline at end of file diff --git a/Python/Managers/WebSocketServersManager.py b/Python/Managers/WebSocketServersManager.py index d2d30f2..8236f6c 100644 --- a/Python/Managers/WebSocketServersManager.py +++ b/Python/Managers/WebSocketServersManager.py @@ -105,7 +105,7 @@ class WebSocketServersManager: except Exception as exception: self.anp.exception(exception, "web_socket_server_send_exception", {"name": name}) - def __receive(self:Self, web_socket:WebSocketServersAbstract, client:int, raw_data:str, name:str) -> None: + def __receive(self:Self, web_socket:WebSocketServersAbstract, client:str, raw_data:str, name:str) -> None: data:dict[str, Any|None] = Common.data_decode(raw_data) @@ -119,6 +119,7 @@ class WebSocketServersManager: request.variables["web_socket"] = web_socket request.variables["client_id"] = client request.variables["web_socket_name"] = name + request.session = self.anp.sessions._get_instance(web_socket.get_session(client)) self.anp.controllers.execute( data["controller"], diff --git a/Python/Models/AIResponseModel.py b/Python/Models/AIResponseModel.py index afe6ed7..cb2bcf2 100644 --- a/Python/Models/AIResponseModel.py +++ b/Python/Models/AIResponseModel.py @@ -6,7 +6,8 @@ from time import time as timestamp class AIResponseModel: - def __init__(self:Self) -> None: + def __init__(self:Self, conversation:str) -> None: + self.conversation:str = conversation self.start:float = timestamp() self.model:str = "" self.response:str = "" diff --git a/Python/Models/RequestModel.py b/Python/Models/RequestModel.py index 05ed019..da67f0e 100644 --- a/Python/Models/RequestModel.py +++ b/Python/Models/RequestModel.py @@ -9,10 +9,11 @@ from Utils.Common import Common class RequestModel: - def __init__(self:Self) -> None: + def __init__(self:Self, session:Optional[SessionModel] = None) -> None: self.post_variables:dict[str, Any|None] = {} self.get_variables:dict[str, Any|None] = {} self.url_variables:dict[str, Any|None] = {} + self.cookies:dict[str, Any|None] = {} self.variables:dict[str, Any|None] = {} self.request_headers:dict[str, Any|None] = {} self.method:str|None = None @@ -23,15 +24,22 @@ class RequestModel: self.response_headers:dict[str, Any|None] = {} self.callback:Callable[[RequestModel, str|bytes|None], None]|None = None self.data:Any|None = None - self.session:SessionModel|None = None + self.session:SessionModel|None = session def get(self:Self, key:str|Sequence[str], default:Optional[Any] = None) -> Any|None: - return self.session.get(key, Common.get_value(key, ( - self.url_variables, self.get_variables, self.post_variables, self.variables - ), default)) + if self.session is not None: + + results:Any|None = self.session.get(key) + + if results is not None: + return results + return Common.get_value(key, ( + self.cookies, self.url_variables, self.get_variables, self.post_variables, self.variables + ), default) def set_variables(self:Self, inputs:dict[str, Any|None], on:Optional[str] = None) -> None: ( + self.cookies if on == "cookies" else self.url_variables if on == "url" else self.get_variables if on == "get" else self.post_variables if on == "post" else diff --git a/Python/Models/SessionModel.py b/Python/Models/SessionModel.py index af80c1d..9ca8648 100644 --- a/Python/Models/SessionModel.py +++ b/Python/Models/SessionModel.py @@ -11,6 +11,7 @@ class SessionModel: self.date_from:float = timestamp() self.date_last:float = timestamp() self.variables:dict[str, Any|None] = {} + self.removed:bool = False def set(self:Self, key:str, value:Any|None) -> None: self.date_last = timestamp() @@ -18,4 +19,7 @@ class SessionModel: def get(self:Self, key:str, default:Any|None = None) -> Any|None: self.date_last = timestamp() - return self.variables.get(key, default) \ No newline at end of file + return self.variables.get(key, default) + + def remove(self:Self) -> None: + self.removed = True \ No newline at end of file diff --git a/Python/Utils/Patterns.py b/Python/Utils/Patterns.py index fdc5a9e..ded5760 100644 --- a/Python/Utils/Patterns.py +++ b/Python/Utils/Patterns.py @@ -13,4 +13,5 @@ class RE: TO_REGULAR_EXPRESSION:REPattern = re_compile(r'[\(\)\{\}\/\\\.\-\+\*\^\$\?\|\!\<\>\r\n\t]') ROUTE_KEY:REPattern = re_compile(r'\\\{([a-z_][a-z0-9_]*)\\\}', RE_IGNORECASE) EXCEPTION:REPattern = re_compile(r'^\s*File "([^"]+)", line ([0-9]+), in ([^\n]+)(.*|[\r\n]*)*$') - NEW_LINE:REPattern = re_compile(r'\r\n|[\r\n]') \ No newline at end of file + NEW_LINE:REPattern = re_compile(r'\r\n|[\r\n]') + HTTP_VARIABLE:REPattern = re_compile(r'([^=&]+)=([^&]*)') \ No newline at end of file