feat: Implement main application shell with navigation and color scheme toggle

- Added Appshell component with responsive navbar and main content area
- Integrated ColorSchemeToggle for light/dark mode switching
- Created Welcome component with styled title and introductory text
- Developed ChatPage for LLM interaction with WebSocket support
- Implemented Biblioteca for managing notes with rich text editor
- Added LoginPage for user authentication with error handling
- Introduced MessageList and MessageBubble components for chat messages
- Styled components with CSS modules for consistent design
This commit is contained in:
2025-06-21 02:01:21 +02:00
parent 3d5deef0fb
commit aef8791151
101 changed files with 169 additions and 166 deletions
View File
+24
View File
@@ -0,0 +1,24 @@
from domains.Security.GenerarIDs import GeneradorIDUnico
class OpenAICredencial:
def __init__(self, titulo: str, api_key: str, organizacion: str = None, id: str = None):
"""
:param titulo: Nombre descriptivo para esta credencial.
:param api_key: Clave secreta de la API de OpenAI.
:param organizacion: (Opcional) ID de la organización asociada a la cuenta de OpenAI.
"""
self.id = id if id is not None else GeneradorIDUnico("OPAK").generar()
self.titulo = titulo
self.api_key = api_key
self.organizacion = organizacion
def get_headers(self) -> dict:
"""
Retorna los encabezados necesarios para autenticar una petición HTTP a OpenAI.
"""
headers = {
"Authorization": f"Bearer {self.api_key}"
}
if self.organizacion:
headers["OpenAI-Organization"] = self.organizacion
return headers
+107
View File
@@ -0,0 +1,107 @@
import os
import base64
from dotenv import load_dotenv
from sqlalchemy import Column, Integer, String
from domains.ConexionSql.Base_conexion import ConexionBase
from domains.base import Base
from domains.ApiKeys.openai_apikey import OpenAICredencial
from domains.Security.Encriptar import Encriptar_fernet
from entrypoint import ENV_PATH
from domains.ArquitectureLayer.Mapper import Mapper_base
from sqlalchemy import Column, String
from domains.ArquitectureLayer.Model import Model_base
from domains.ArquitectureLayer.Repo import Repo_base
# ----------------------
# Cargar clave maestra
# ----------------------
load_dotenv(ENV_PATH)
pssword = os.getenv('MASTER_PASSWORD')
if pssword is None:
raise ValueError("MASTER_PASSWORD no está definida en el archivo .env")
# ----------------------
# MODELO (SQLAlchemy)
# ----------------------
class OpenAICredencialModel(Base, Model_base):
__tablename__ = 'openai_credenciales'
id = Column(String, primary_key=True)
titulo = Column(String, nullable=False)
api_key = Column(String, nullable=False) # Encriptada como base64 string
organizacion = Column(String, nullable=True)
# ----------------------
# MAPPER
# ----------------------
class OpenAICredencialMapper(Mapper_base[OpenAICredencial, OpenAICredencialModel]):
@staticmethod
def to_model(obj: OpenAICredencial) -> OpenAICredencialModel:
return OpenAICredencialModel(
id=obj.id,
titulo=obj.titulo,
api_key=base64.b64encode(
Encriptar_fernet.encriptar(obj.api_key, pssword)
).decode("utf-8"),
organizacion=obj.organizacion
)
@staticmethod
def from_model(model: OpenAICredencialModel) -> OpenAICredencial:
return OpenAICredencial(
id=model.id,
titulo=model.titulo,
api_key=Encriptar_fernet.desencriptar(
base64.b64decode(model.api_key), pssword
),
organizacion=model.organizacion
)
@staticmethod
def to_dict(obj: OpenAICredencial) -> dict:
return {
"id": obj.id,
"titulo": obj.titulo,
"api_key": base64.b64encode(
Encriptar_fernet.encriptar(obj.api_key, pssword)
).decode("utf-8"),
"organizacion": obj.organizacion
}
@staticmethod
def from_dict(data: dict) -> OpenAICredencial:
return OpenAICredencial(
id=data["id"],
titulo=data["titulo"],
api_key=Encriptar_fernet.desencriptar(
base64.b64decode(data["api_key"]), pssword
),
organizacion=data.get("organizacion")
)
# ----------------------
# REPO
# ----------------------
class OpenAICredencialRepo(Repo_base[OpenAICredencialModel, OpenAICredencial]):
def __init__(self, conexion: ConexionBase):
super().__init__(
session=conexion.get_session(),
modelo=OpenAICredencialModel,
mapper=OpenAICredencialMapper
)
def get_by_titulo(self, titulo: str) -> OpenAICredencial | None:
model = (
self.session.query(self.Modelo)
.filter_by(titulo=titulo, sys_deleted_at=None)
.first()
)
return self.Mapper.from_model(model) if model else None