wip: Fix AI Interpreter. Doing Cookies and Sessions Manager.

This commit is contained in:
mbruzon 2026-06-08 14:47:31 +02:00
parent c73884ef35
commit b4a1d09e3d
23 changed files with 1080 additions and 566 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@
/Python/websockets
__pycache__
.sass-cache
/Public/fonts/FontAwesome-6.7.2

View File

@ -6,6 +6,7 @@ import {SettingsManager} from "../Managers/SettingsManager.ecma.js";
import {I18NManager} from "../Managers/I18NManager.ecma.js";
import {ThreadsManager} from "../Managers/ThreadsManager.ecma.js";
import {UniqueKeysManager} from "../Managers/UniqueKeysManager.ecma.js";
import {CookiesManager} from "../Managers/CookiesManager.ecma.js";
import {SessionsManager} from "../Managers/SessionsManager.ecma.js";
import {ModelsManager} from "../Managers/ModelsManager.ecma.js";
import {ControllersManager} from "../Managers/ControllersManager.ecma.js";
@ -65,6 +66,8 @@ export const AnP = (function(){
this.threads = new ThreadsManager(self);
/** @type {UniqueKeysManager} */
this.unique_keys = new UniqueKeysManager(self);
/** @type {CookiesManager} */
this.cookies = new CookiesManager(self);
/** @type {SessionsManager} */
this.sessions = new SessionsManager(self);
/** @type {ModelsManager} */

View File

@ -65,7 +65,7 @@ export const AIChatComponent = (function(){
data_name : name,
data_i18n : value,
data_i18n_without : true,
title : anp.components.i18n(value)
title : anp.i18n.get(value)
}, [
anp.components.icon(value),
anp.components.i18n(value)
@ -75,7 +75,7 @@ export const AIChatComponent = (function(){
data_name : name,
data_i18n : name,
data_i18n_without : true,
title : anp.components.i18n(name)
title : anp.i18n.get(name)
}, [
anp.components.icon(name),
anp.components.i18n(name),
@ -174,6 +174,7 @@ export const AIChatComponent = (function(){
};
this.write_response = (id, fragment, ok, done) => {
console.log([id, fragment]);
const box = document.querySelector(".aichat .messages>[data-type=bot][data-id='" + id + "']"),
status = (
@ -185,17 +186,20 @@ export const AIChatComponent = (function(){
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);
[html, raw_html, md] = format(box.querySelector(".md-content").textContent += fragment);
box.querySelector(".html-content").innerHTML = html;
box.querySelector(".raw-html-content").innerHTML = raw_html;
box.querySelector("[data-name=length] .value").innerText = md.length;
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_box.querySelector("[data-icon]").setAttribute("data-icon", status);
status_i18n_box.setAttribute("data-i18n", status_text);
status_i18n_box.innerHTML = status_text;

View File

@ -0,0 +1,107 @@
"use strict";
import {Check} from "../Utils/Check.ecma.js";
import {Common} from "../Utils/Common.ecma.js";
/**
* @typedef {import("../Application/AnP.ecma.js").AnP} AnP
*/
/**
* @class CookiesManager
* @constructor
* @param {!AnP} anp
* @return {void}
* @access public
* @static
*/
export const CookiesManager = (function(){
/**
* @constructs CookiesManager
* @param {!AnP} anp
* @return {void}
* @access private
* @static
*/
const CookiesManager = function(anp){
/** @type {CookiesManager} */
const self = this;
/**
* @returns {void}
* @access private
*/
const constructor = () => {};
/**
* @returns {Object.<string, Any|null>}
* @access public
*/
this.to_dictionary = () => document.cookie.split(";").map(cookie => cookie.trim().split("=")).reduce((dictionary, [key, value]) => {
dictionary[key] = value;
return dictionary;
}, {});
/**
*
* @param {!string} name
* @param {any|null} value
* @param {!(boolean|Object.<string, Any|null>|Array.<any|null>)} options
* @returns
*/
this.set = (name, value, options = {}) => {
if(Common.get_value("overwrite", (
Check.is_boolean ? {overwrite : options} :
options), false) || !self.has(name)){
document.cookie = (
`${name}=${value}` +
(Common.get_value("expires", options) ? `; expires=${options.expires.toUTCString()}` : "") +
(Common.get_value("path", options) ? `; path=${options.path}` : "") +
(Common.get_value("domain", options) ? `; domain=${options.domain}` : "") +
(Common.get_value("secure", options) ? `; secure` : "")
);
return true;
};
return false;
};
/**
* @param {!string} name
* @returns {boolean}
* @access public
*/
this.has = name => name in self.to_dictionary();
/**
* @param {!string} name
* @returns {string|null}
* @access public
*/
this.get = name => self.to_dictionary()[name] || null;
/**
* @param {!string} name
* @param {!(boolean|Object.<string, any|null>|Array.<any|null>)} options
* @returns {boolean}
* @access public
*/
this.delete = (name, options = {}) => {
if(self.has(name)){
self.set(name, "", {
expires : new Date(0),
...Common.get_dictionary(options)
});
return true;
};
return false;
};
constructor();
};
return CookiesManager;
})();

View File

@ -18,9 +18,7 @@
<meta name="xdoc:author" content="KyMAN" />
<meta name="xdoc:access" content="public" />
<style type="text/css" data-type="text/css;charset=utf-8" data-css-map="/scss/AnP.css.map" data-scss="/scss/AnP.scss" data-crossorigin="anonymous" charset="utf-8">
@import url("/scss/AnP.css");
<style type="text/css" data-type="text/css;charset=utf-8" data-crossorigin="anonymous" charset="utf-8">
html,body{
height : 100%;
@ -30,6 +28,8 @@
</style>
<link type="text/css;charset=utf-8" data-language="SASS/CSS3" rel="stylesheet" href="/scss/AnP.css" data-css-map="/scss/AnP.css.map" data-scss="/scss/AnP.scss" charset="utf-8" crossorigin="anonymous" />
<script type="module" data-type="text/javascript;charset=utf-8" data-language="ECMAScript 2015" charset="utf-8">
"use strict";

View File

@ -1,19 +0,0 @@
// @use "sass:map";
// @use "sass:list";
// @use "sass:meta";
@function unicode($code){
@return unquote("\"") + unquote(str-insert($code, "\\", 1)) + unquote("\"");
}
@function map-deep-get($scope, $keys...){
$i : 1;
@while (type-of($scope) == map) and ($i <= length($keys)){
$scope : map-get($scope, nth($keys, $i));
$i : $i + 1;
}
@return $scope;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,23 +0,0 @@
.anp{
[data-icon]::before{
margin-right : .3em;
font-family : $font-icon;
}
[data-icon=home]::before{content : unicode("f015");}
[data-icon=git]::before{content : unicode("f841"); font-family : "FA6FB";}
[data-icon=user]::before{content : unicode("f007");}
[data-icon=ip]::before{content : "IP"; font-weight : 900; font-family : $font-normal;}
[data-icon=login]::before{content : unicode("f090");}
[data-icon=register]::before{content : unicode("f234");}
[data-icon=logout]::before{content : unicode("f08b");}
[data-icon=zoom]::before{content : unicode("f002");}
[data-icon=reset_zoom]::before{content : unicode("f689");}
[data-icon=gui_mode]::before{content : unicode("f043");}
// Icons for AI Chat
[data-icon=html]::before{content : unicode("f13b"); font-family : "FA6FB";}
[data-icon=raw_html]::before{content : unicode("f121");}
[data-icon=md]::before{content : unicode("f1c9");}
}

View File

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

View File

@ -1,14 +1,17 @@
@use "settings" as *;
@use "common" as *;
@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);
@include aichar-color(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);
@include aichar-color($key);
}
}

View File

@ -1,3 +1,6 @@
@use "settings" as *;
@use "common" as *;
@mixin main_color_web($mode){
background-color : map-deep-get($color, $mode, "back");
color : map-deep-get($color, $mode, "fore");
@ -13,6 +16,7 @@
box-shadow : 0em 0em .2em inset map-deep-get($color, $mode, "secondary");
}
}
.i18n-selector li{background-color : map-deep-get($color, $mode, "back");}
}
.anp{

20
Public/scss/_common.scss Normal file
View File

@ -0,0 +1,20 @@
@use "sass:map";
@use "sass:list";
@use "sass:meta";
@use "sass:string";
@function unicode($code){
@return string.unquote("\"") + string.unquote(string.insert($code, "\\", 1)) + string.unquote("\"");
}
@function map-deep-get($scope, $keys...){
$i : 1;
@while (meta.type-of($scope) == map) and ($i <= list.length($keys)){
$scope : map.get($scope, list.nth($keys, $i));
$i : $i + 1;
}
@return $scope;
}

View File

@ -1,3 +1,59 @@
// @font-face {
// font-family : "Font Awesome 6 Brands";
// font-style : normal;
// font-weight : 400;
// font-display : block;
// src :
// url("/fonts/FontAwesome-6.7.2/fa-brands-400.woff2") format("woff2"),
// url("/fonts/FontAwesome-6.7.2/fa-brands-400.ttf") format("truetype");
// }
// @font-face {
// font-family : "Font Awesome 6 Regular";
// font-style : normal;
// font-weight : 400;
// font-display : block;
// src :
// url("/fonts/FontAwesome-6.7.2/fa-regular-400.woff2") format("woff2"),
// url("/fonts/FontAwesome-6.7.2/fa-regular-400.ttf") format("truetype");
// }
// @font-face {
// font-family : "Font Awesome 6 Solid";
// font-style : normal;
// font-weight : 900;
// font-display : block;
// src :
// url("/fonts/FontAwesome-6.7.2/fa-solid-900.woff2") format("woff2"),
// url("/fonts/FontAwesome-6.7.2/fa-solid-900.ttf") format("truetype");
// }
// @font-face {
// font-family : "FA6FB";
// font-style : normal;
// font-weight : 400;
// font-display : block;
// src :
// url("/fonts/FontAwesome-6.7.2/fa-brands-400.woff2") format("woff2"),
// url("/fonts/FontAwesome-6.7.2/fa-brands-400.ttf") format("truetype");
// }
// @font-face {
// font-family : "FA6FR";
// font-style : normal;
// font-weight : 400;
// font-display : block;
// src :
// url("/fonts/FontAwesome-6.7.2/fa-regular-400.woff2") format("woff2"),
// url("/fonts/FontAwesome-6.7.2/fa-regular-400.ttf") format("truetype");
// }
// @font-face {
// font-family : "FA6FS";
// font-style : normal;
// font-weight : 900;
// font-display : block;
// src :
// url("/fonts/FontAwesome-6.7.2/fa-solid-900.woff2") format("woff2"),
// url("/fonts/FontAwesome-6.7.2/fa-solid-900.ttf") format("truetype");
// }
@font-face {
font-family : "FA6FB";
font-style : normal;
@ -26,6 +82,42 @@
url("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/webfonts/fa-solid-900.ttf") format("truetype");
}
// @font-face {
// font-family : "FA6FB";
// font-style : normal;
// font-weight : 400;
// font-display : block;
// src :
// url("https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.7.2/webfonts/fa-brands-400.woff2") format("woff2"),
// url("https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.7.2/webfonts/fa-brands-400.ttf") format("truetype");
// }
// @font-face {
// font-family : "FA6FR";
// font-style : normal;
// font-weight : 400;
// font-display : block;
// src :
// url("https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.7.2/webfonts/fa-regular-400.woff2") format("woff2"),
// url("https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.7.2/webfonts/fa-regular-400.ttf") format("truetype");
// }
// @font-face {
// font-family : "FA6FS";
// font-style : normal;
// font-weight : 900;
// font-display : block;
// src :
// url("https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.7.2/webfonts/fa-solid-900.woff2") format("woff2"),
// url("https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.7.2/webfonts/fa-solid-900.ttf") format("truetype");
// }
$FA6FB : "FA6FB";
$FA6FR : "FA6FR";
$FA6FS : "FA6FS";
// $FA6FB : "Font Awesome 6 Brands";
// $FA6FR : "Font Awesome 6 Regular";
// $FA6FS : "Font Awesome 6 Solid";
/* cyrillic-ext */
@font-face{
font-family : "Roboto";

40
Public/scss/_icons.scss Normal file
View File

@ -0,0 +1,40 @@
@use "settings" as *;
@use "fonts" as *;
@use "common" as *;
.anp{
[data-icon]::before{
margin-right : .3em;
font-family : $font-icon;
}
[data-icon=home]::before{content : unicode("f015");}
[data-icon=git]::before{content : unicode("f841"); font-family : $FA6FB;}
[data-icon=user]::before{content : unicode("f007");}
[data-icon=ip]::before{content : "IP"; font-weight : 900; font-family : $font-normal;}
[data-icon=login]::before{content : unicode("f090");}
[data-icon=register]::before{content : unicode("f234");}
[data-icon=logout]::before{content : unicode("f08b");}
[data-icon=zoom]::before{content : unicode("f002");}
[data-icon=reset_zoom]::before{content : unicode("f689");}
[data-icon=gui_mode]::before{content : unicode("f043");}
[data-icon=send]::before{content : unicode("f1d8"); font-family : $FA6FR;}
// 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");}
[data-icon=done]::before{content : unicode("f164"); font-family : $FA6FR;}
[data-icon=think]::before{content : unicode("f731"); font-family : $FA6FB;}
[data-icon=loading]::before{content : unicode("f110");}
[data-icon=waiting]::before{content : unicode("f252");}
[data-ok=true] [data-icon=ok]::before{content : unicode("f00c");}
[data-ok=false] [data-icon=ok]::before{content : unicode("f00d");}
[data-done=true] [data-icon=done]::before{content : unicode("f058"); font-family : $FA6FR;}
[data-done=false] [data-icon=done]::before{content : unicode("f071");}
[data-icon=date_from]::before{content : unicode("f783");}
[data-icon=date_to]::before{content : unicode("f1da");}
[data-icon=time]::before{content : unicode("f017"); font-family : $FA6FR;}
[data-icon=length]::before{content : unicode("f546");}
[data-icon=response_tokens]::before{content : unicode("f4ad"); font-family : $FA6FR;}
}

View File

@ -1,3 +1,6 @@
@use "sass:color";
@use "fonts" as *;
// Colors
$color-fore : #222;
$color-back : #EFEFEF;
@ -5,24 +8,24 @@ $color-primary : #2272D4;
$color-secondary : #D47222;
$color-full-fore : #000;
$color-full-back : #FFF;
$color-grey : mix($color-fore, $color-back, 50%);
$color-grey : color.mix($color-fore, $color-back, 50%);
$color : (
light : (
fore : $color-fore,
back : $color-back,
primary : mix($color-primary, $color-fore, 80%),
secondary : mix($color-secondary, $color-fore, 80%),
primary : color.mix($color-primary, $color-fore, 80%),
secondary : color.mix($color-secondary, $color-fore, 80%),
full : $color-full-back,
input-back : mix($color-back, $color-full-back, 80%),
input-back : color.mix($color-back, $color-full-back, 80%),
transparent : rgba(255, 255, 255, 0)
),
dark : (
fore : $color-back,
back : $color-fore,
primary : mix($color-primary, $color-back, 80%),
secondary : mix($color-secondary, $color-back, 80%),
primary : color.mix($color-primary, $color-back, 80%),
secondary : color.mix($color-secondary, $color-back, 80%),
full : $color-full-fore,
input-back : mix($color-back, $color-full-fore, 80%),
input-back : color.mix($color-back, $color-full-fore, 80%),
transparent : rgba(0, 0, 0, 0)
)
);
@ -39,7 +42,7 @@ $margin : .5em;
// $font-icon : Arial, Helvetica, Sans;
$font-normal : "Roboto";
$font-mono : "Roboto Mono";
$font-icon : "FA6FS";
$font-icon : $FA6FS;
// Transitions
$transition-in : .35s;

View File

@ -39,9 +39,12 @@ class AIController(ControllerAbstract, ModelAbstract):
})
def __message_execution(self:Self, end:Callable[[], None], request:RequestModel) -> None:
self.anp.ai_interpreters.request(
session:str|None = None
session, _ = self.anp.ai_interpreters.request(
"anp_responses",
None,
session,
request.get("message"),
lambda id, response: self.anp.web_socket_servers.send("anp", "ai", "message", {
"id" : id,
@ -51,6 +54,7 @@ class AIController(ControllerAbstract, ModelAbstract):
"data_id" : request.get("message_id")
}, request.get("client_id"))
)
end()
def message(self:Self, request:RequestModel) -> None:

View File

@ -1,11 +1,10 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Optional
from typing import Self, Optional, Any
from abc import ABC, abstractmethod
from Models.SessionModel import SessionModel
class SessionsManagerInterface(ABC):
class SessionsManagerInterfaces(ABC):
@abstractmethod
def update(self:Self) -> None:pass
@ -14,7 +13,10 @@ class SessionsManagerInterface(ABC):
def reset(self:Self) -> None:pass
@abstractmethod
def get(self:Self, id:Optional[str] = None) -> SessionModel|None:pass
def get(self:Self, id:str, key:str, default:Optional[Any] = None) -> Any|None:pass
@abstractmethod
def set(self:Self, id:str, key:str, value:Any|None) -> bool:pass
@abstractmethod
def remove(self:Self, id:str) -> bool:pass

View File

@ -1,7 +1,9 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Optional
from typing import Self, Optional, Any
from threading import Thread
from time import sleep, time as timestamp
from Interfaces.Application.AnPInterface import AnPInterface
from Models.SessionModel import SessionModel
@ -11,8 +13,12 @@ class SessionsManager:
self.anp:AnPInterface = anp
self.__sessions:dict[str, SessionModel] = {}
self.__thread:Thread = Thread(target = self.__autoclean)
self.__timeout:float|int = self.anp.settings.get(("sessions_timeout", "timeout"), None, 3600)
self.__clean_waiter:float|int = self.anp.settings.get(("sessions_clean_waiter", "clean_waiter"), None, 60)
self.update()
self.__thread.start()
def update(self:Self) -> None:pass
@ -22,15 +28,29 @@ class SessionsManager:
self.update()
def get(self:Self, id:Optional[str] = None) -> SessionModel|None:
if id is None:
def __autoclean(self:Self) -> None:
while self.anp.working():
session:SessionModel = SessionModel(self.anp.unique_keys.get())
id:str
session:SessionModel
time:float = timestamp()
self.__sessions[session.id] = session
for id, session in tuple(self.__sessions.items()):
if time - session.get("date_last", time) > self.__timeout:
self.remove(id)
return session
return self.__sessions.get(id, None)
sleep(self.__clean_waiter)
def get(self:Self, id:str, key:str, default:Optional[Any] = None) -> Any|None:
if id in self.__sessions:
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:
self.__sessions[id].set(key, value)
return True
return False
def remove(self:Self, id:str) -> bool:
if id in self.__sessions:

View File

@ -3,6 +3,7 @@
from typing import Any, Self, Callable, Sequence, Optional
from json import dumps as json_encode
from Models.SessionModel import SessionModel
from Abstracts.RouteAbstract import RouteAbstract
from Utils.Common import Common
@ -22,11 +23,12 @@ 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
def get(self:Self, key:str|Sequence[str], default:Optional[Any] = None) -> Any|None:
return Common.get_value(key, (
return self.session.get(key, Common.get_value(key, (
self.url_variables, self.get_variables, self.post_variables, self.variables
), default)
), default))
def set_variables(self:Self, inputs:dict[str, Any|None], on:Optional[str] = None) -> None:
(

View File

@ -2,9 +2,20 @@
# -*- coding: utf-8 -*-
from typing import Self, Any
from time import time as timestamp
class SessionModel:
def __init__(self:Self, id:str) -> None:
self.id:str = id
self.date_from:float = timestamp()
self.date_last:float = timestamp()
self.variables:dict[str, Any|None] = {}
def set(self:Self, key:str, value:Any|None) -> None:
self.date_last = timestamp()
self.variables[key] = value
def get(self:Self, key:str, default:Any|None = None) -> Any|None:
self.date_last = timestamp()
return self.variables.get(key, default)

View File

@ -258,6 +258,10 @@ class Common:
@staticmethod
def get_mime_from_path(path:str) -> str|None:
# if path[-6:] == ".woff2":
# return "font/woff2"
# if path[-4:] == ".ttf":
# return "font/ttf"
return get_mime_by_extension(path)[0]
@classmethod

3
Tools/sass.win.bat Normal file
View File

@ -0,0 +1,3 @@
@echo off
cd %~dp0
sass ..\Public\scss\AnP.scss ..\Public\scss\AnP.css