Compare commits
16 Commits
20173e9042
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 9db2f70009 | |||
| 95c1762ca7 | |||
| 6e716f8f98 | |||
| c13240b481 | |||
| bf1814bb8e | |||
| 712bd877b8 | |||
| b34d52036e | |||
| c47b9474f4 | |||
| c646bc1fef | |||
| f7879e9557 | |||
| 41b307f4bb | |||
| 1022e23a0d | |||
| 0d1ffcd1ff | |||
| b087271255 | |||
| 2becc8bc7c | |||
| 8d35da1972 |
@@ -4,6 +4,6 @@ from fastapi import APIRouter
|
|||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
@router.get("/ping")
|
@router.get("/")
|
||||||
async def ping():
|
async def ping():
|
||||||
return {"message": "pong"}
|
return {"message": "pong"}
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
|
from fastapi import Path
|
||||||
|
|
||||||
|
from backend.schemas.text_manager_schema import BibliotecaInput, NotaInput
|
||||||
|
|
||||||
|
from fastapi.concurrency import run_in_threadpool
|
||||||
|
from backend.db.conexion import get_conexion
|
||||||
|
from backend.services.text_manager_srvc import *
|
||||||
|
from src.ConexionSql.Postgres_conexion import PostgresConexion
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/biblioteca", summary="Crear una nueva biblioteca")
|
||||||
|
async def crear_biblioteca_endpoint(
|
||||||
|
data: BibliotecaInput,
|
||||||
|
conexion: PostgresConexion = Depends(get_conexion)
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return await run_in_threadpool(
|
||||||
|
crear_biblioteca,
|
||||||
|
data.nombre_biblioteca,
|
||||||
|
conexion,
|
||||||
|
data.descripcion,
|
||||||
|
)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"Error interno al crear la biblioteca: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/list", summary="Listar todas las bibliotecas")
|
||||||
|
def listar_todas_bibliotecas(
|
||||||
|
conexion: PostgresConexion = Depends(get_conexion)
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return listar_bibliotecas(conexion=conexion)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail="Error interno al listar las bibliotecas")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/nota/{biblioteca_id}", summary="Agregar una nota a una biblioteca")
|
||||||
|
def agregar_nota(
|
||||||
|
biblioteca_id: str = Path(..., description="ID de la biblioteca a la que se agregará la nota"),
|
||||||
|
nota: NotaInput = ..., # viene del body
|
||||||
|
conexion: PostgresConexion = Depends(get_conexion)
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return agregar_nota_a_biblioteca(
|
||||||
|
conexion=conexion,
|
||||||
|
biblioteca_id=biblioteca_id,
|
||||||
|
titulo=nota.titulo,
|
||||||
|
texto=nota.texto,
|
||||||
|
tags=nota.tags,
|
||||||
|
conexiones=nota.conexiones,
|
||||||
|
resumen=nota.resumen
|
||||||
|
)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail="Error interno al agregar la nota")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/nota/list/{biblioteca_id}", summary="Listar todas las notas de una biblioteca")
|
||||||
|
def listar_notas(
|
||||||
|
biblioteca_id: str,
|
||||||
|
conexion: PostgresConexion = Depends(get_conexion)
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return listar_notas_de_biblioteca(conexion, biblioteca_id)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail="Error interno al listar las notas")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/nota/{biblioteca_id}/{nota_id}", summary="Eliminar una nota por ID")
|
||||||
|
def eliminar_nota_endpoint(
|
||||||
|
biblioteca_id: str = Path(..., description="ID de la biblioteca"),
|
||||||
|
nota_id: str = Path(..., description="ID de la nota a eliminar"),
|
||||||
|
conexion: PostgresConexion = Depends(get_conexion)
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return eliminar_nota(conexion=conexion, biblioteca_id=biblioteca_id, nota_id=nota_id)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
|
except Exception:
|
||||||
|
raise HTTPException(status_code=500, detail="Error interno al eliminar la nota")
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/nota/{biblioteca_id}/{nota_id}", summary="Actualizar una nota por ID")
|
||||||
|
def actualizar_nota_endpoint(
|
||||||
|
biblioteca_id: str = Path(..., description="ID de la biblioteca"),
|
||||||
|
nota_id: str = Path(..., description="ID de la nota a actualizar"),
|
||||||
|
nota: NotaInput = ..., # body
|
||||||
|
conexion: PostgresConexion = Depends(get_conexion)
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return actualizar_nota(conexion=conexion, biblioteca_id=biblioteca_id, nota_id=nota_id, nota_input=nota)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
|
except Exception:
|
||||||
|
raise HTTPException(status_code=500, detail="Error interno al actualizar la nota")
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
# backend/api/router.py
|
# backend/api/router.py
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
from backend.api.v1.endpoints import ping
|
from backend.api.v1.endpoints import ping, text_manager_endpoint
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
router.include_router(ping.router, prefix="/api/v1")
|
router.include_router(ping.router, prefix="/api/v1/ping")
|
||||||
|
router.include_router(text_manager_endpoint.router, prefix="/api/v1/text_manager")
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# backend/db/conexion.py
|
||||||
|
from entrypoint.init_db import db_credencial
|
||||||
|
from src.ConexionSql.Postgres_conexion import PostgresConexion
|
||||||
|
|
||||||
|
def get_conexion():
|
||||||
|
conexion = PostgresConexion(db_credencial)
|
||||||
|
try:
|
||||||
|
yield conexion
|
||||||
|
finally:
|
||||||
|
conexion.close()
|
||||||
+1
-1
@@ -13,7 +13,7 @@ app = FastAPI(
|
|||||||
# Configuración de CORS
|
# Configuración de CORS
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=["http://localhost:5173"], # Solo permite tu frontend local
|
allow_origins=["http://localhost:5173", "http://0.0.0.0:5173"], # Solo permite tu frontend local
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class NotaInput(BaseModel):
|
||||||
|
titulo: str
|
||||||
|
texto: str = ""
|
||||||
|
tags: Optional[List[str]] = []
|
||||||
|
conexiones: Optional[List[str]] = []
|
||||||
|
resumen: Optional[str] = ""
|
||||||
|
|
||||||
|
|
||||||
|
class BibliotecaInput(BaseModel):
|
||||||
|
nombre_biblioteca: str
|
||||||
|
descripcion: str
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
from src.TextManager.biblioteca import Biblioteca
|
||||||
|
from src.TextManager.biblioteca_mmr import BibliotecaRepo
|
||||||
|
from src.Llms.Embedders.Openai_embedder import OpenAIEmbedder
|
||||||
|
from src.ApiKeys.openai_apikey_mmr import OpenAICredencialRepo
|
||||||
|
from src.ConexionSql.Postgres_conexion import PostgresConexion
|
||||||
|
from src.TextManager.nota import Nota
|
||||||
|
from src.TextManager.notas_mmr import generar_tabla_nota_para_biblioteca, NotaRepo
|
||||||
|
from sqlalchemy import MetaData
|
||||||
|
from backend.schemas.text_manager_schema import NotaInput
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def crear_biblioteca(nombre_biblioteca: str, conexion: PostgresConexion, descripcion: str = None):
|
||||||
|
print("[INICIO] Creando biblioteca...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
print("[Paso 1] Obteniendo credencial...")
|
||||||
|
cred_repo = OpenAICredencialRepo(conexion)
|
||||||
|
credencial = cred_repo.get_by_id("OPAK20250513-61b29978b7604031014")
|
||||||
|
print("[OK] Credencial obtenida:", credencial.titulo if credencial else "❌ None")
|
||||||
|
|
||||||
|
print("[Paso 2] Instanciando embedder...")
|
||||||
|
embedder = OpenAIEmbedder(credencial, model="text-embedding-3-large")
|
||||||
|
print("[OK] Embedder instanciado")
|
||||||
|
|
||||||
|
print("[Paso 3] Instanciando biblioteca...")
|
||||||
|
biblioteca = Biblioteca(
|
||||||
|
nombre=nombre_biblioteca,
|
||||||
|
embedder=embedder,
|
||||||
|
descripcion=descripcion
|
||||||
|
)
|
||||||
|
print(f"[OK] Biblioteca instanciada con ID: {biblioteca.id}")
|
||||||
|
|
||||||
|
print("[Paso 4] Guardando en base de datos...")
|
||||||
|
repo = BibliotecaRepo(conexion)
|
||||||
|
repo.add(biblioteca=biblioteca)
|
||||||
|
print("[OK] Biblioteca guardada")
|
||||||
|
|
||||||
|
print("[Paso 5] Generando modelo de notas...")
|
||||||
|
biblioteca.generar_modelo_notas(conexion)
|
||||||
|
print("[OK] Modelo de notas generado")
|
||||||
|
|
||||||
|
print("[FIN] Biblioteca creada correctamente")
|
||||||
|
return {
|
||||||
|
"mensaje": f"Biblioteca '{nombre_biblioteca}' creada con éxito.",
|
||||||
|
"id": biblioteca.id
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print("[ERROR] Ocurrió una excepción:", str(e))
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def listar_bibliotecas(conexion: PostgresConexion) -> list[dict]:
|
||||||
|
repo = BibliotecaRepo(conexion)
|
||||||
|
bibliotecas: list[Biblioteca] = repo.get_all()
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"id": b.id,
|
||||||
|
"nombre": b.nombre,
|
||||||
|
"descripcion": b.descripcion,
|
||||||
|
"vector_dim": b.vector_dim
|
||||||
|
}
|
||||||
|
for b in bibliotecas
|
||||||
|
]
|
||||||
|
|
||||||
|
def agregar_nota_a_biblioteca(
|
||||||
|
conexion: PostgresConexion,
|
||||||
|
biblioteca_id: str,
|
||||||
|
titulo: str,
|
||||||
|
texto: str = "",
|
||||||
|
tags: list[str] = None,
|
||||||
|
conexiones: list[str] = None,
|
||||||
|
resumen: str = ""
|
||||||
|
):
|
||||||
|
|
||||||
|
# Obtener la biblioteca
|
||||||
|
repo_biblioteca = BibliotecaRepo(conexion)
|
||||||
|
biblioteca = repo_biblioteca.get_by_id(biblioteca_id)
|
||||||
|
if biblioteca is None:
|
||||||
|
raise ValueError(f"No se encontró la biblioteca con ID {biblioteca_id}")
|
||||||
|
|
||||||
|
# Crear objeto Nota
|
||||||
|
nota = Nota(
|
||||||
|
titulo=titulo,
|
||||||
|
texto=texto,
|
||||||
|
tags=tags or [],
|
||||||
|
conexiones=conexiones or [],
|
||||||
|
resumen=resumen or "",
|
||||||
|
# vector=biblioteca.embedder.embed_text(texto),
|
||||||
|
# vector_resumen=biblioteca.embedder.embed_text(resumen) if resumen else None
|
||||||
|
)
|
||||||
|
# Mostrar atributos seguros
|
||||||
|
print(
|
||||||
|
f"[DEBUG] Nota creada: titulo='{nota.titulo}', "
|
||||||
|
f"texto_len={len(nota.texto)}, "
|
||||||
|
f"tags={len(nota.tags)}, "
|
||||||
|
f"conexiones={len(nota.conexiones)}, "
|
||||||
|
f"resumen_len={len(nota.resumen)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Preparar tabla y modelo de nota
|
||||||
|
metadata = MetaData()
|
||||||
|
tabla, ModeloNota = generar_tabla_nota_para_biblioteca(
|
||||||
|
biblioteca.nombre,
|
||||||
|
biblioteca.vector_dim,
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
metadata.create_all(conexion.get_engine())
|
||||||
|
|
||||||
|
# Guardar la nota
|
||||||
|
repo_nota = NotaRepo(conexion.get_session(), ModeloNota)
|
||||||
|
nota_id = repo_nota.add(nota)
|
||||||
|
|
||||||
|
resultado = {
|
||||||
|
"mensaje": f"Nota '{titulo}' agregada con éxito a la biblioteca '{biblioteca.nombre}'.",
|
||||||
|
"nota_id": nota_id
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"[SUCCESS] {resultado['mensaje']}")
|
||||||
|
return resultado
|
||||||
|
|
||||||
|
|
||||||
|
def listar_notas_de_biblioteca(conexion: PostgresConexion, biblioteca_id: str) -> list[dict]:
|
||||||
|
repo_biblioteca = BibliotecaRepo(conexion)
|
||||||
|
biblioteca = repo_biblioteca.get_by_id(biblioteca_id)
|
||||||
|
if not biblioteca:
|
||||||
|
raise ValueError(f"No se encontró la biblioteca con ID: {biblioteca_id}")
|
||||||
|
|
||||||
|
metadata = MetaData()
|
||||||
|
tabla, ModeloNota = generar_tabla_nota_para_biblioteca(biblioteca.nombre, biblioteca.vector_dim, metadata)
|
||||||
|
metadata.create_all(conexion.get_engine())
|
||||||
|
|
||||||
|
repo_nota = NotaRepo(conexion.get_session(), ModeloNota)
|
||||||
|
notas = repo_nota.get_all()
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"id": n.id,
|
||||||
|
"titulo": n.titulo,
|
||||||
|
"tags": n.tags,
|
||||||
|
"texto": n.texto,
|
||||||
|
"resumen": n.resumen,
|
||||||
|
"conexiones": n.conexiones
|
||||||
|
}
|
||||||
|
for n in notas
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def eliminar_nota(conexion: PostgresConexion, biblioteca_id: str, nota_id: str) -> dict:
|
||||||
|
repo_biblioteca = BibliotecaRepo(conexion)
|
||||||
|
biblioteca = repo_biblioteca.get_by_id(biblioteca_id)
|
||||||
|
if not biblioteca:
|
||||||
|
raise ValueError(f"No se encontró la biblioteca con ID: {biblioteca_id}")
|
||||||
|
|
||||||
|
metadata = MetaData()
|
||||||
|
_, ModeloNota = generar_tabla_nota_para_biblioteca(biblioteca.nombre, biblioteca.vector_dim, metadata)
|
||||||
|
metadata.create_all(conexion.get_engine())
|
||||||
|
|
||||||
|
repo_nota = NotaRepo(conexion.get_session(), ModeloNota)
|
||||||
|
fue_eliminada = repo_nota.delete_by_id(nota_id)
|
||||||
|
|
||||||
|
if fue_eliminada:
|
||||||
|
return {"mensaje": f"Nota '{nota_id}' eliminada correctamente."}
|
||||||
|
else:
|
||||||
|
raise ValueError(f"No se encontró la nota con ID: {nota_id}")
|
||||||
|
|
||||||
|
|
||||||
|
def actualizar_nota(conexion: PostgresConexion, biblioteca_id: str, nota_id: str, nota_input: NotaInput) -> dict:
|
||||||
|
repo_biblioteca = BibliotecaRepo(conexion)
|
||||||
|
biblioteca = repo_biblioteca.get_by_id(biblioteca_id)
|
||||||
|
if not biblioteca:
|
||||||
|
raise ValueError(f"No se encontró la biblioteca con ID: {biblioteca_id}")
|
||||||
|
|
||||||
|
metadata = MetaData()
|
||||||
|
_, ModeloNota = generar_tabla_nota_para_biblioteca(biblioteca.nombre, biblioteca.vector_dim, metadata)
|
||||||
|
metadata.create_all(conexion.get_engine())
|
||||||
|
|
||||||
|
repo_nota = NotaRepo(conexion.get_session(), ModeloNota)
|
||||||
|
nota_actualizada = Nota(
|
||||||
|
titulo=nota_input.titulo,
|
||||||
|
texto=nota_input.texto,
|
||||||
|
tags=nota_input.tags or [],
|
||||||
|
conexiones=nota_input.conexiones or [],
|
||||||
|
resumen=nota_input.resumen or ""
|
||||||
|
)
|
||||||
|
fue_actualizada = repo_nota.update(nota_id, nota_actualizada)
|
||||||
|
|
||||||
|
if fue_actualizada:
|
||||||
|
return {"mensaje": f"Nota '{nota_id}' actualizada correctamente."}
|
||||||
|
else:
|
||||||
|
raise ValueError(f"No se encontró la nota con ID: {nota_id}")
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
def eliminar_pycache(directorio):
|
||||||
|
eliminados = 0
|
||||||
|
for root, dirs, files in os.walk(directorio):
|
||||||
|
for d in dirs:
|
||||||
|
if d == "__pycache__":
|
||||||
|
ruta = os.path.join(root, d)
|
||||||
|
print(f"🗑️ Eliminando: {ruta}")
|
||||||
|
shutil.rmtree(ruta)
|
||||||
|
eliminados += 1
|
||||||
|
print(f"\n✅ Eliminados {eliminados} directorios '__pycache__'.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
ruta_raiz = os.path.dirname(os.path.abspath(__file__)) # Carpeta actual
|
||||||
|
eliminar_pycache(ruta_raiz)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import asyncio
|
||||||
|
from fastmcp.client import Client
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with Client("http://127.0.0.1:4300") as client:
|
||||||
|
is_alive = await client.ping()
|
||||||
|
print("Ping exitoso:", is_alive)
|
||||||
|
|
||||||
|
asyncio.run(main())
|
||||||
-14
@@ -1,14 +0,0 @@
|
|||||||
# Tipo de base de datos
|
|
||||||
DB_TITLE=Production_Fitz_db
|
|
||||||
DB_USER=postgres
|
|
||||||
DB_PASSWORD=7Souw9SFD5P5RYpRWuTVvFkY7zlxATcN
|
|
||||||
DB_HOST=localhost
|
|
||||||
DB_PORT=5432
|
|
||||||
DB_NAME=fitz_db
|
|
||||||
|
|
||||||
|
|
||||||
# Contraseña maestra de la aplicacion
|
|
||||||
MASTER_PASSWORD=¤òAíÀÇ®8IgÄËïºÅ2a3duÎ4Ô¯5¦ï¤ç··sôÃmL9xWß
|
|
||||||
|
|
||||||
#Ruta del proyecto
|
|
||||||
PROJECT_PATH= E:\Fitz_Studio
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Tipo de base de datos
|
||||||
|
DB_TITLE=
|
||||||
|
DB_USER=
|
||||||
|
DB_PASSWORD=
|
||||||
|
DB_HOST=
|
||||||
|
DB_PORT=
|
||||||
|
DB_NAME=
|
||||||
|
|
||||||
|
|
||||||
|
# Contraseña maestra de la aplicacion
|
||||||
|
MASTER_PASSWORD=
|
||||||
|
|
||||||
|
#Ruta del proyecto
|
||||||
|
PROJECT_PATH=
|
||||||
+42
-1
@@ -41,6 +41,7 @@ E:\Fitz_Studio
|
|||||||
│ │ ├── 0e
|
│ │ ├── 0e
|
||||||
│ │ ├── 10
|
│ │ ├── 10
|
||||||
│ │ ├── 11
|
│ │ ├── 11
|
||||||
|
│ │ ├── 13
|
||||||
│ │ ├── 15
|
│ │ ├── 15
|
||||||
│ │ ├── 17
|
│ │ ├── 17
|
||||||
│ │ ├── 18
|
│ │ ├── 18
|
||||||
@@ -55,9 +56,11 @@ E:\Fitz_Studio
|
|||||||
│ │ ├── 22
|
│ │ ├── 22
|
||||||
│ │ ├── 23
|
│ │ ├── 23
|
||||||
│ │ ├── 24
|
│ │ ├── 24
|
||||||
|
│ │ ├── 25
|
||||||
│ │ ├── 26
|
│ │ ├── 26
|
||||||
│ │ ├── 27
|
│ │ ├── 27
|
||||||
│ │ ├── 28
|
│ │ ├── 28
|
||||||
|
│ │ ├── 2b
|
||||||
│ │ ├── 2c
|
│ │ ├── 2c
|
||||||
│ │ ├── 2d
|
│ │ ├── 2d
|
||||||
│ │ ├── 2f
|
│ │ ├── 2f
|
||||||
@@ -65,11 +68,14 @@ E:\Fitz_Studio
|
|||||||
│ │ ├── 32
|
│ │ ├── 32
|
||||||
│ │ ├── 33
|
│ │ ├── 33
|
||||||
│ │ ├── 34
|
│ │ ├── 34
|
||||||
|
│ │ ├── 36
|
||||||
│ │ ├── 39
|
│ │ ├── 39
|
||||||
|
│ │ ├── 3a
|
||||||
│ │ ├── 3c
|
│ │ ├── 3c
|
||||||
│ │ ├── 3d
|
│ │ ├── 3d
|
||||||
│ │ ├── 3e
|
│ │ ├── 3e
|
||||||
│ │ ├── 3f
|
│ │ ├── 3f
|
||||||
|
│ │ ├── 40
|
||||||
│ │ ├── 41
|
│ │ ├── 41
|
||||||
│ │ ├── 42
|
│ │ ├── 42
|
||||||
│ │ ├── 43
|
│ │ ├── 43
|
||||||
@@ -83,10 +89,14 @@ E:\Fitz_Studio
|
|||||||
│ │ ├── 4d
|
│ │ ├── 4d
|
||||||
│ │ ├── 4e
|
│ │ ├── 4e
|
||||||
│ │ ├── 4f
|
│ │ ├── 4f
|
||||||
|
│ │ ├── 50
|
||||||
│ │ ├── 51
|
│ │ ├── 51
|
||||||
│ │ ├── 52
|
│ │ ├── 52
|
||||||
│ │ ├── 55
|
│ │ ├── 55
|
||||||
|
│ │ ├── 56
|
||||||
│ │ ├── 57
|
│ │ ├── 57
|
||||||
|
│ │ ├── 58
|
||||||
|
│ │ ├── 59
|
||||||
│ │ ├── 5a
|
│ │ ├── 5a
|
||||||
│ │ ├── 5b
|
│ │ ├── 5b
|
||||||
│ │ ├── 5c
|
│ │ ├── 5c
|
||||||
@@ -100,6 +110,7 @@ E:\Fitz_Studio
|
|||||||
│ │ ├── 65
|
│ │ ├── 65
|
||||||
│ │ ├── 67
|
│ │ ├── 67
|
||||||
│ │ ├── 69
|
│ │ ├── 69
|
||||||
|
│ │ ├── 6b
|
||||||
│ │ ├── 6c
|
│ │ ├── 6c
|
||||||
│ │ ├── 6d
|
│ │ ├── 6d
|
||||||
│ │ ├── 6e
|
│ │ ├── 6e
|
||||||
@@ -109,27 +120,33 @@ E:\Fitz_Studio
|
|||||||
│ │ ├── 75
|
│ │ ├── 75
|
||||||
│ │ ├── 76
|
│ │ ├── 76
|
||||||
│ │ ├── 77
|
│ │ ├── 77
|
||||||
|
│ │ ├── 79
|
||||||
│ │ ├── 7b
|
│ │ ├── 7b
|
||||||
│ │ ├── 7c
|
│ │ ├── 7c
|
||||||
│ │ ├── 7d
|
│ │ ├── 7d
|
||||||
│ │ ├── 7f
|
│ │ ├── 7f
|
||||||
│ │ ├── 80
|
│ │ ├── 80
|
||||||
│ │ ├── 81
|
│ │ ├── 81
|
||||||
|
│ │ ├── 82
|
||||||
│ │ ├── 83
|
│ │ ├── 83
|
||||||
│ │ ├── 84
|
│ │ ├── 84
|
||||||
│ │ ├── 85
|
│ │ ├── 85
|
||||||
|
│ │ ├── 86
|
||||||
│ │ ├── 87
|
│ │ ├── 87
|
||||||
│ │ ├── 89
|
│ │ ├── 89
|
||||||
│ │ ├── 8a
|
│ │ ├── 8a
|
||||||
│ │ ├── 8b
|
│ │ ├── 8b
|
||||||
│ │ ├── 8c
|
│ │ ├── 8c
|
||||||
|
│ │ ├── 8d
|
||||||
│ │ ├── 90
|
│ │ ├── 90
|
||||||
|
│ │ ├── 92
|
||||||
│ │ ├── 94
|
│ │ ├── 94
|
||||||
│ │ ├── 95
|
│ │ ├── 95
|
||||||
│ │ ├── 97
|
│ │ ├── 97
|
||||||
│ │ ├── 98
|
│ │ ├── 98
|
||||||
│ │ ├── 99
|
│ │ ├── 99
|
||||||
│ │ ├── 9a
|
│ │ ├── 9a
|
||||||
|
│ │ ├── 9b
|
||||||
│ │ ├── 9c
|
│ │ ├── 9c
|
||||||
│ │ ├── 9d
|
│ │ ├── 9d
|
||||||
│ │ ├── a0
|
│ │ ├── a0
|
||||||
@@ -148,7 +165,9 @@ E:\Fitz_Studio
|
|||||||
│ │ ├── ad
|
│ │ ├── ad
|
||||||
│ │ ├── ae
|
│ │ ├── ae
|
||||||
│ │ ├── af
|
│ │ ├── af
|
||||||
|
│ │ ├── b0
|
||||||
│ │ ├── b1
|
│ │ ├── b1
|
||||||
|
│ │ ├── b2
|
||||||
│ │ ├── b3
|
│ │ ├── b3
|
||||||
│ │ ├── b4
|
│ │ ├── b4
|
||||||
│ │ ├── b5
|
│ │ ├── b5
|
||||||
@@ -158,6 +177,7 @@ E:\Fitz_Studio
|
|||||||
│ │ ├── ba
|
│ │ ├── ba
|
||||||
│ │ ├── bb
|
│ │ ├── bb
|
||||||
│ │ ├── bc
|
│ │ ├── bc
|
||||||
|
│ │ ├── bd
|
||||||
│ │ ├── bf
|
│ │ ├── bf
|
||||||
│ │ ├── c0
|
│ │ ├── c0
|
||||||
│ │ ├── c3
|
│ │ ├── c3
|
||||||
@@ -171,6 +191,7 @@ E:\Fitz_Studio
|
|||||||
│ │ ├── ce
|
│ │ ├── ce
|
||||||
│ │ ├── cf
|
│ │ ├── cf
|
||||||
│ │ ├── d1
|
│ │ ├── d1
|
||||||
|
│ │ ├── d3
|
||||||
│ │ ├── d4
|
│ │ ├── d4
|
||||||
│ │ ├── d5
|
│ │ ├── d5
|
||||||
│ │ ├── d6
|
│ │ ├── d6
|
||||||
@@ -184,6 +205,8 @@ E:\Fitz_Studio
|
|||||||
│ │ ├── de
|
│ │ ├── de
|
||||||
│ │ ├── df
|
│ │ ├── df
|
||||||
│ │ ├── e0
|
│ │ ├── e0
|
||||||
|
│ │ ├── e1
|
||||||
|
│ │ ├── e2
|
||||||
│ │ ├── e3
|
│ │ ├── e3
|
||||||
│ │ ├── e4
|
│ │ ├── e4
|
||||||
│ │ ├── e5
|
│ │ ├── e5
|
||||||
@@ -202,6 +225,7 @@ E:\Fitz_Studio
|
|||||||
│ │ ├── f6
|
│ │ ├── f6
|
||||||
│ │ ├── f7
|
│ │ ├── f7
|
||||||
│ │ ├── f9
|
│ │ ├── f9
|
||||||
|
│ │ ├── fa
|
||||||
│ │ ├── fb
|
│ │ ├── fb
|
||||||
│ │ ├── fc
|
│ │ ├── fc
|
||||||
│ │ ├── fd
|
│ │ ├── fd
|
||||||
@@ -298,6 +322,7 @@ E:\Fitz_Studio
|
|||||||
│ ├── jupyter
|
│ ├── jupyter
|
||||||
│ └── man
|
│ └── man
|
||||||
├── Apikeys.ipynb
|
├── Apikeys.ipynb
|
||||||
|
├── Apikeys_embedding.ipynb
|
||||||
├── Credenciales.ipynb
|
├── Credenciales.ipynb
|
||||||
├── Encriptacion.ipynb
|
├── Encriptacion.ipynb
|
||||||
├── README.md
|
├── README.md
|
||||||
@@ -310,6 +335,9 @@ E:\Fitz_Studio
|
|||||||
│ │ ├── __init__.py
|
│ │ ├── __init__.py
|
||||||
│ │ ├── __pycache__
|
│ │ ├── __pycache__
|
||||||
│ │ └── v1
|
│ │ └── v1
|
||||||
|
│ ├── db
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ └── conexion.py
|
||||||
│ ├── deps
|
│ ├── deps
|
||||||
│ │ ├── __init__.py
|
│ │ ├── __init__.py
|
||||||
│ │ └── auth.py
|
│ │ └── auth.py
|
||||||
@@ -721,16 +749,19 @@ E:\Fitz_Studio
|
|||||||
│ │ ├── queue-microtask
|
│ │ ├── queue-microtask
|
||||||
│ │ ├── range-parser
|
│ │ ├── range-parser
|
||||||
│ │ ├── raw-body
|
│ │ ├── raw-body
|
||||||
|
│ │ ├── re-resizable
|
||||||
│ │ ├── react
|
│ │ ├── react
|
||||||
│ │ ├── react-docgen
|
│ │ ├── react-docgen
|
||||||
│ │ ├── react-docgen-typescript
|
│ │ ├── react-docgen-typescript
|
||||||
│ │ ├── react-dom
|
│ │ ├── react-dom
|
||||||
|
│ │ ├── react-draggable
|
||||||
│ │ ├── react-is
|
│ │ ├── react-is
|
||||||
│ │ ├── react-number-format
|
│ │ ├── react-number-format
|
||||||
│ │ ├── react-reconciler
|
│ │ ├── react-reconciler
|
||||||
│ │ ├── react-refresh
|
│ │ ├── react-refresh
|
||||||
│ │ ├── react-remove-scroll
|
│ │ ├── react-remove-scroll
|
||||||
│ │ ├── react-remove-scroll-bar
|
│ │ ├── react-remove-scroll-bar
|
||||||
|
│ │ ├── react-rnd
|
||||||
│ │ ├── react-router
|
│ │ ├── react-router
|
||||||
│ │ ├── react-router-dom
|
│ │ ├── react-router-dom
|
||||||
│ │ ├── react-style-singleton
|
│ │ ├── react-style-singleton
|
||||||
@@ -892,9 +923,9 @@ E:\Fitz_Studio
|
|||||||
│ │ ├── Router.tsx
|
│ │ ├── Router.tsx
|
||||||
│ │ ├── assets
|
│ │ ├── assets
|
||||||
│ │ ├── components
|
│ │ ├── components
|
||||||
|
│ │ ├── data
|
||||||
│ │ ├── main.tsx
|
│ │ ├── main.tsx
|
||||||
│ │ ├── pages
|
│ │ ├── pages
|
||||||
│ │ ├── public
|
|
||||||
│ │ ├── theme.ts
|
│ │ ├── theme.ts
|
||||||
│ │ ├── types
|
│ │ ├── types
|
||||||
│ │ └── vite-env.d.ts
|
│ │ └── vite-env.d.ts
|
||||||
@@ -905,6 +936,7 @@ E:\Fitz_Studio
|
|||||||
│ ├── vite.config.js
|
│ ├── vite.config.js
|
||||||
│ ├── vitest.setup.mjs
|
│ ├── vitest.setup.mjs
|
||||||
│ └── yarn.lock
|
│ └── yarn.lock
|
||||||
|
├── github_tutorial.ipynb
|
||||||
├── main.py
|
├── main.py
|
||||||
├── notebooks
|
├── notebooks
|
||||||
│ └── hacer_script_nombres.ipynb
|
│ └── hacer_script_nombres.ipynb
|
||||||
@@ -958,6 +990,7 @@ E:\Fitz_Studio
|
|||||||
│ │ └── postgres_credencial_mmr.py
|
│ │ └── postgres_credencial_mmr.py
|
||||||
│ ├── Llms
|
│ ├── Llms
|
||||||
│ │ ├── Agente.py
|
│ │ ├── Agente.py
|
||||||
|
│ │ ├── Embedders
|
||||||
│ │ ├── MCPs
|
│ │ ├── MCPs
|
||||||
│ │ ├── Memory
|
│ │ ├── Memory
|
||||||
│ │ ├── Modelos
|
│ │ ├── Modelos
|
||||||
@@ -965,8 +998,16 @@ E:\Fitz_Studio
|
|||||||
│ │ └── __pycache__
|
│ │ └── __pycache__
|
||||||
│ ├── Security
|
│ ├── Security
|
||||||
│ │ ├── Encriptar.py
|
│ │ ├── Encriptar.py
|
||||||
|
│ │ ├── GenerarIDs.py
|
||||||
│ │ ├── __init__.py
|
│ │ ├── __init__.py
|
||||||
│ │ └── __pycache__
|
│ │ └── __pycache__
|
||||||
|
│ ├── TextManager
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── __pycache__
|
||||||
|
│ │ ├── biblioteca.py
|
||||||
|
│ │ ├── biblioteca_mmr.py
|
||||||
|
│ │ ├── nota.py
|
||||||
|
│ │ └── notas_biblioteca_mmr.py
|
||||||
│ ├── __init__.py
|
│ ├── __init__.py
|
||||||
│ ├── __pycache__
|
│ ├── __pycache__
|
||||||
│ │ ├── __init__.cpython-311.pyc
|
│ │ ├── __init__.cpython-311.pyc
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ from src.Credenciales.postgres_credencial import PostgresCredencial # Asegúrat
|
|||||||
from src.Credenciales.postgres_credencial_mmr import PostgresCredencialModel
|
from src.Credenciales.postgres_credencial_mmr import PostgresCredencialModel
|
||||||
from src.ApiKeys.openai_apikey_mmr import OpenAICredencialModel
|
from src.ApiKeys.openai_apikey_mmr import OpenAICredencialModel
|
||||||
from src.Llms.Modelos.Openai_model_mmr import ModeloOpenAIConfigModel
|
from src.Llms.Modelos.Openai_model_mmr import ModeloOpenAIConfigModel
|
||||||
|
from src.Llms.Embedders.Openai_embedder_mmr import OpenAIEmbedderModel
|
||||||
|
from src.TextManager.biblioteca_mmr import BibliotecaModel
|
||||||
|
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import os
|
import os
|
||||||
@@ -41,7 +44,7 @@ def init_db():
|
|||||||
|
|
||||||
# Crear engine desde la clase de conexión PostgreSQL
|
# Crear engine desde la clase de conexión PostgreSQL
|
||||||
conexion = PostgresConexion(db_credencial)
|
conexion = PostgresConexion(db_credencial)
|
||||||
engine = conexion.engine # Recuperamos el engine directamente
|
engine = conexion.get_engine() # Recuperamos el engine directamente
|
||||||
|
|
||||||
print("Creando tablas...")
|
print("Creando tablas...")
|
||||||
Base.metadata.create_all(engine)
|
Base.metadata.create_all(engine)
|
||||||
|
|||||||
Generated
+156
-19
@@ -13,8 +13,10 @@
|
|||||||
"@react-three/fiber": "^9.1.2",
|
"@react-three/fiber": "^9.1.2",
|
||||||
"@tabler/icons": "^3.31.0",
|
"@tabler/icons": "^3.31.0",
|
||||||
"@tabler/icons-react": "^3.31.0",
|
"@tabler/icons-react": "^3.31.0",
|
||||||
|
"axios": "^1.9.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
"react-rnd": "^10.5.2",
|
||||||
"react-router-dom": "^7.4.0"
|
"react-router-dom": "^7.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -3303,6 +3305,12 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/available-typed-arrays": {
|
"node_modules/available-typed-arrays": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||||
@@ -3329,6 +3337,17 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
|
||||||
|
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/axobject-query": {
|
"node_modules/axobject-query": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||||
@@ -3551,7 +3570,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
@@ -3712,6 +3730,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@@ -4052,6 +4082,15 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
@@ -4126,7 +4165,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.1",
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
@@ -4278,7 +4316,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -4288,7 +4325,6 @@
|
|||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -4333,7 +4369,6 @@
|
|||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0"
|
"es-errors": "^1.3.0"
|
||||||
@@ -4346,7 +4381,6 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
@@ -5060,6 +5094,26 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||||
|
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/for-each": {
|
"node_modules/for-each": {
|
||||||
"version": "0.3.5",
|
"version": "0.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||||
@@ -5093,6 +5147,42 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data/node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data/node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/forwarded": {
|
"node_modules/forwarded": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||||
@@ -5132,7 +5222,6 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
@@ -5183,7 +5272,6 @@
|
|||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.2",
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
@@ -5217,7 +5305,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dunder-proto": "^1.0.1",
|
"dunder-proto": "^1.0.1",
|
||||||
@@ -5412,7 +5499,6 @@
|
|||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -5491,7 +5577,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -5504,7 +5589,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-symbols": "^1.0.3"
|
"has-symbols": "^1.0.3"
|
||||||
@@ -5520,7 +5604,6 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
@@ -6268,7 +6351,6 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
@@ -6513,7 +6595,6 @@
|
|||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
@@ -6580,7 +6661,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -6842,7 +6922,6 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -7531,7 +7610,6 @@
|
|||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
@@ -7543,7 +7621,6 @@
|
|||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/proxy-addr": {
|
"node_modules/proxy-addr": {
|
||||||
@@ -7560,6 +7637,12 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
@@ -7633,6 +7716,16 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/re-resizable": {
|
||||||
|
"version": "6.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.11.2.tgz",
|
||||||
|
"integrity": "sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react": {
|
"node_modules/react": {
|
||||||
"version": "19.1.0",
|
"version": "19.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||||
@@ -7699,6 +7792,29 @@
|
|||||||
"react": "^19.1.0"
|
"react": "^19.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-draggable": {
|
||||||
|
"version": "4.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz",
|
||||||
|
"integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": "^1.1.1",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.3.0",
|
||||||
|
"react-dom": ">= 16.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-draggable/node_modules/clsx": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "17.0.2",
|
"version": "17.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||||
@@ -7794,6 +7910,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-rnd": {
|
||||||
|
"version": "10.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-rnd/-/react-rnd-10.5.2.tgz",
|
||||||
|
"integrity": "sha512-0Tm4x7k7pfHf2snewJA8x7Nwgt3LV+58MVEWOVsFjk51eYruFEa6Wy7BNdxt4/lH0wIRsu7Gm3KjSXY2w7YaNw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"re-resizable": "6.11.2",
|
||||||
|
"react-draggable": "4.4.6",
|
||||||
|
"tslib": "2.6.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.3.0",
|
||||||
|
"react-dom": ">=16.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-rnd/node_modules/tslib": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/react-router": {
|
"node_modules/react-router": {
|
||||||
"version": "7.5.3",
|
"version": "7.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.3.tgz",
|
||||||
|
|||||||
@@ -25,8 +25,10 @@
|
|||||||
"@react-three/fiber": "^9.1.2",
|
"@react-three/fiber": "^9.1.2",
|
||||||
"@tabler/icons": "^3.31.0",
|
"@tabler/icons": "^3.31.0",
|
||||||
"@tabler/icons-react": "^3.31.0",
|
"@tabler/icons-react": "^3.31.0",
|
||||||
|
"axios": "^1.9.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
"react-rnd": "^10.5.2",
|
||||||
"react-router-dom": "^7.4.0"
|
"react-router-dom": "^7.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
+10
-3
@@ -2,7 +2,8 @@ import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
|||||||
import { HomePage } from './pages/Home.page';
|
import { HomePage } from './pages/Home.page';
|
||||||
import { Consulta_API } from './pages/Consulta_api';
|
import { Consulta_API } from './pages/Consulta_api';
|
||||||
import { Error_404 } from './pages/404'; // Ajusta si está en otra carpeta
|
import { Error_404 } from './pages/404'; // Ajusta si está en otra carpeta
|
||||||
import { Prueba_appshell } from './pages/Prueba_appshell'; // Ajusta si está en otra carpeta
|
import { Grid_Dashboard } from './pages/Grid_dashboard'; // Ajusta si está en otra carpeta
|
||||||
|
import { Biblioteca } from './pages/Biblioteca';
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@@ -14,9 +15,15 @@ const router = createBrowserRouter([
|
|||||||
element: <Consulta_API />,
|
element: <Consulta_API />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/prueba_appshell',
|
path: '/Grid_Dashboard',
|
||||||
element: <Prueba_appshell />,
|
element: <Grid_Dashboard />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/Biblioteca',
|
||||||
|
element: <Biblioteca />,
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
element: <Error_404 />,
|
element: <Error_404 />,
|
||||||
|
|||||||
@@ -16,9 +16,9 @@
|
|||||||
var(--mantine-font-family);
|
var(--mantine-font-family);
|
||||||
margin-bottom: var(--mantine-spacing-sm);
|
margin-bottom: var(--mantine-spacing-sm);
|
||||||
background-color: var(--mantine-color-body);
|
background-color: var(--mantine-color-body);
|
||||||
padding: var(--mantine-spacing-md);
|
padding: var(--mantine-spacing-xs);
|
||||||
padding-top: 18px;
|
padding-top: 15px;
|
||||||
height: px;
|
height: 50px;
|
||||||
border-bottom: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-7));
|
border-bottom: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-7));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
&,
|
&,
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--mantine-color-brand-7);
|
background-color: var(--mantine-color-brand-7);
|
||||||
color: linear-gradient(90deg, var(--mantine-color-brand-7), var(--mantine-color-brand-4));
|
color: var(--mantine-color-brand-2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,150 +2,190 @@ import {
|
|||||||
AppShell,
|
AppShell,
|
||||||
Burger,
|
Burger,
|
||||||
Group,
|
Group,
|
||||||
Skeleton,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
UnstyledButton,
|
UnstyledButton,
|
||||||
ActionIcon,
|
|
||||||
Title,
|
Title,
|
||||||
|
useMantineTheme,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
|
import { useDisclosure, useMediaQuery } from '@mantine/hooks';
|
||||||
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { default as LogoIcon } from '../../assets/icons/favicon'; // ruta relativa ajusta según tu estructura
|
import { default as LogoIcon } from '../../assets/icons/favicon';
|
||||||
|
import { mainLinksdata } from '../../data/navigationsLinks_1';
|
||||||
|
import { submenuLinks } from '../../data/submenuLinks_1';
|
||||||
|
|
||||||
import { useMantineTheme } from '@mantine/core';
|
import classes from './Appshell.module.css';
|
||||||
|
|
||||||
import { mainLinksdata } from '../../data/navigationsLinks_1'; // ajusta la ruta
|
type AppShellWithMenuProps = {
|
||||||
import { submenuLinks } from '../../data/submenuLinks_1'; // ajusta la ruta según tu estructura
|
children?: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Persistencia en localStorage
|
||||||
|
const STORAGE_KEY = 'lastSubmenuRoutes';
|
||||||
|
|
||||||
import { useDisclosure, useMediaQuery } from '@mantine/hooks';
|
function getLastSubmenuRoute(section: string): string | null {
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
try {
|
||||||
import { Link, useLocation } from 'react-router-dom';
|
const raw = localStorage.getItem(STORAGE_KEY);
|
||||||
import classes from './Appshell.module.css';
|
const parsed = raw ? JSON.parse(raw) : {};
|
||||||
|
return parsed[section] ?? null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
type AppShellWithMenuProps = {
|
|
||||||
children?: React.ReactNode; // <- ahora es opcional
|
|
||||||
};
|
|
||||||
|
|
||||||
export function AppShellWithMenu({ children }: AppShellWithMenuProps) {
|
|
||||||
|
|
||||||
const theme = useMantineTheme();
|
|
||||||
|
|
||||||
|
|
||||||
const location = useLocation();
|
|
||||||
const isMobile = useMediaQuery('(max-width: 768px)');
|
|
||||||
const [mobileOpened, { toggle: toggleMobile, close: closeMobile }] = useDisclosure(false);
|
|
||||||
const [desktopOpened, { toggle: toggleDesktop, open: openDesktop }] = useDisclosure(true);
|
|
||||||
const isCollapsed = useMemo(() => (isMobile ? !mobileOpened : !desktopOpened), [isMobile, mobileOpened, desktopOpened]);
|
|
||||||
|
|
||||||
const [manualActiveTab, setManualActiveTab] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const matchedMain = Object.entries(submenuLinks).find(([mainKey, items]) =>
|
|
||||||
items.some((item) => location.pathname.startsWith(item.to))
|
|
||||||
);
|
|
||||||
|
|
||||||
const routeBasedActive = matchedMain?.[0] ?? 'Home';
|
|
||||||
const active = manualActiveTab ?? routeBasedActive;
|
|
||||||
const activeLink = submenuLinks[active as keyof typeof submenuLinks]?.find((item) => location.pathname === item.to)?.label ?? '';
|
|
||||||
|
|
||||||
const mainLinks = mainLinksdata.map((link) => (
|
|
||||||
<Tooltip
|
|
||||||
label={link.label}
|
|
||||||
position="right"
|
|
||||||
withArrow
|
|
||||||
transitionProps={{ duration: 0 }}
|
|
||||||
key={link.label}
|
|
||||||
>
|
|
||||||
<UnstyledButton
|
|
||||||
onClick={() => {
|
|
||||||
setManualActiveTab(link.label);
|
|
||||||
|
|
||||||
}}
|
|
||||||
className={classes.mainLink}
|
|
||||||
data-active={link.label === active || undefined}
|
|
||||||
>
|
|
||||||
<link.icon />
|
|
||||||
</UnstyledButton>
|
|
||||||
</Tooltip>
|
|
||||||
));
|
|
||||||
|
|
||||||
const links = (submenuLinks[active as keyof typeof submenuLinks] || []).map((item) => (
|
|
||||||
<Link
|
|
||||||
className={classes.link}
|
|
||||||
data-active={activeLink === item.label || undefined}
|
|
||||||
to={item.to}
|
|
||||||
key={item.label}
|
|
||||||
style={{ display: isCollapsed ? 'none' : 'block' }}
|
|
||||||
onClick={() => {
|
|
||||||
if (isMobile) closeMobile();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.label}
|
|
||||||
</Link>
|
|
||||||
));
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setManualActiveTab(null);
|
|
||||||
}, [location.pathname]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isMobile) openDesktop();
|
|
||||||
}, [isMobile, openDesktop]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AppShell
|
|
||||||
header={{ height: 60 }}
|
|
||||||
navbar={{
|
|
||||||
width: isCollapsed ? 60 : 300,
|
|
||||||
breakpoint: 'sm',
|
|
||||||
collapsed: { mobile: !mobileOpened, desktop: !desktopOpened },
|
|
||||||
}}
|
|
||||||
padding="md"
|
|
||||||
>
|
|
||||||
|
|
||||||
{/* Header */}
|
|
||||||
|
|
||||||
<AppShell.Header>
|
|
||||||
<Group h="100%" px="sm">
|
|
||||||
<Burger opened={mobileOpened} onClick={toggleMobile} hiddenFrom="sm" size="sm" />
|
|
||||||
<Burger opened={desktopOpened} onClick={toggleDesktop} visibleFrom="sm" size="sm" />
|
|
||||||
<LogoIcon
|
|
||||||
style={{ width: 30, height: 30 }}
|
|
||||||
circleFill={theme.colors.brand[9]}
|
|
||||||
pathFill={theme.colors.secondary[2]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</Group>
|
|
||||||
</AppShell.Header>
|
|
||||||
|
|
||||||
|
|
||||||
{/* Navbar */}
|
|
||||||
|
|
||||||
<AppShell.Navbar>
|
|
||||||
<div className={classes.wrapper}>
|
|
||||||
<div className={classes.aside}>
|
|
||||||
<div className={classes.topSection}>
|
|
||||||
|
|
||||||
{mainLinks}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div className={classes.main}>
|
|
||||||
{!isCollapsed && <Title order={4} className={classes.title}>{active}</Title>}
|
|
||||||
{links}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AppShell.Navbar>
|
|
||||||
|
|
||||||
{/* Main Content */}
|
|
||||||
|
|
||||||
<AppShell.Main>
|
|
||||||
{children}
|
|
||||||
</AppShell.Main>
|
|
||||||
|
|
||||||
|
|
||||||
</AppShell>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLastSubmenuRoute(section: string, route: string) {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(STORAGE_KEY);
|
||||||
|
const parsed = raw ? JSON.parse(raw) : {};
|
||||||
|
parsed[section] = route;
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(parsed));
|
||||||
|
} catch {
|
||||||
|
// fallback silencioso
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppShellWithMenu({ children }: AppShellWithMenuProps) {
|
||||||
|
const theme = useMantineTheme();
|
||||||
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const isMobile = useMediaQuery('(max-width: 768px)');
|
||||||
|
const [mobileOpened, { toggle: toggleMobile, close: closeMobile }] = useDisclosure(false);
|
||||||
|
const [desktopOpened, { toggle: toggleDesktop, open: openDesktop }] = useDisclosure(true);
|
||||||
|
|
||||||
|
const isCollapsed = useMemo(
|
||||||
|
() => (isMobile ? !mobileOpened : !desktopOpened),
|
||||||
|
[isMobile, mobileOpened, desktopOpened]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Estado para el main link activo
|
||||||
|
const [activeMain, setActiveMain] = useState<string>('Home');
|
||||||
|
|
||||||
|
// Ref para saber si el usuario ha hecho clic manualmente en el main link
|
||||||
|
const userClickedMainRef = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const currentPath = location.pathname.toLowerCase().replace(/\/$/, '');
|
||||||
|
|
||||||
|
let matchedMain: string | null = null;
|
||||||
|
let maxMatchLength = 0;
|
||||||
|
|
||||||
|
Object.entries(submenuLinks).forEach(([main, items]) => {
|
||||||
|
items.forEach((item) => {
|
||||||
|
const itemPath = item.to.toLowerCase().replace(/\/$/, '');
|
||||||
|
if (
|
||||||
|
currentPath === itemPath ||
|
||||||
|
currentPath.startsWith(itemPath + '/')
|
||||||
|
) {
|
||||||
|
if (itemPath.length > maxMatchLength) {
|
||||||
|
matchedMain = main;
|
||||||
|
maxMatchLength = itemPath.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (matchedMain) {
|
||||||
|
setActiveMain(matchedMain);
|
||||||
|
}
|
||||||
|
}, [location.pathname]);
|
||||||
|
|
||||||
|
const activeLink =
|
||||||
|
submenuLinks[activeMain as keyof typeof submenuLinks]?.find(
|
||||||
|
(item) => item.to === location.pathname
|
||||||
|
)?.label ?? '';
|
||||||
|
|
||||||
|
const mainLinks = mainLinksdata.map((link) => (
|
||||||
|
<Tooltip
|
||||||
|
label={link.label}
|
||||||
|
position="right"
|
||||||
|
withArrow
|
||||||
|
transitionProps={{ duration: 0 }}
|
||||||
|
key={link.label}
|
||||||
|
>
|
||||||
|
<UnstyledButton
|
||||||
|
onClick={() => {
|
||||||
|
userClickedMainRef.current = true;
|
||||||
|
setActiveMain(link.label);
|
||||||
|
|
||||||
|
const remembered = getLastSubmenuRoute(link.label);
|
||||||
|
const fallback = submenuLinks[link.label as keyof typeof submenuLinks]?.[0]?.to;
|
||||||
|
|
||||||
|
if (isCollapsed && (remembered || fallback)) {
|
||||||
|
navigate(remembered ?? fallback);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={classes.mainLink}
|
||||||
|
data-active={link.label === activeMain || undefined}
|
||||||
|
>
|
||||||
|
<link.icon />
|
||||||
|
</UnstyledButton>
|
||||||
|
</Tooltip>
|
||||||
|
));
|
||||||
|
|
||||||
|
const links = (submenuLinks[activeMain as keyof typeof submenuLinks] || []).map((item) => (
|
||||||
|
<Link
|
||||||
|
className={classes.link}
|
||||||
|
data-active={activeLink === item.label || undefined}
|
||||||
|
to={item.to}
|
||||||
|
key={item.label}
|
||||||
|
style={{ display: isCollapsed ? 'none' : 'block' }}
|
||||||
|
onClick={() => {
|
||||||
|
setLastSubmenuRoute(activeMain, item.to);
|
||||||
|
if (isMobile) closeMobile();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isMobile) openDesktop();
|
||||||
|
}, [isMobile, openDesktop]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppShell
|
||||||
|
header={{ height: 60 }}
|
||||||
|
navbar={{
|
||||||
|
width: isCollapsed ? 60 : 300,
|
||||||
|
breakpoint: 'sm',
|
||||||
|
collapsed: { mobile: !mobileOpened, desktop: !desktopOpened },
|
||||||
|
}}
|
||||||
|
padding="md"
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<AppShell.Header>
|
||||||
|
<Group h="100%" px="sm">
|
||||||
|
<Burger opened={mobileOpened} onClick={toggleMobile} hiddenFrom="sm" size="sm" />
|
||||||
|
<Burger opened={desktopOpened} onClick={toggleDesktop} visibleFrom="sm" size="sm" />
|
||||||
|
<LogoIcon
|
||||||
|
style={{ width: 30, height: 30 }}
|
||||||
|
circleFill={theme.colors.brand[9]}
|
||||||
|
pathFill={theme.colors.secondary[2]}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
</AppShell.Header>
|
||||||
|
|
||||||
|
{/* Navbar */}
|
||||||
|
<AppShell.Navbar>
|
||||||
|
<div className={classes.wrapper}>
|
||||||
|
<div className={classes.aside}>
|
||||||
|
<div className={classes.topSection}>{mainLinks}</div>
|
||||||
|
</div>
|
||||||
|
<div className={classes.main}>
|
||||||
|
{!isCollapsed && (
|
||||||
|
<Title order={4} className={classes.title}>
|
||||||
|
{activeMain}
|
||||||
|
</Title>
|
||||||
|
)}
|
||||||
|
{links}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AppShell.Navbar>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<AppShell.Main>{children}</AppShell.Main>
|
||||||
|
</AppShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import { Card, Text, Switch, Group, useMantineTheme, useComputedColorScheme } from '@mantine/core';
|
||||||
|
import { Rnd } from 'react-rnd';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
const GRID_SIZE = 30;
|
||||||
|
|
||||||
|
function hexToRgba(hex: string, alpha: number): string {
|
||||||
|
const sanitized = hex.replace('#', '');
|
||||||
|
const bigint = parseInt(sanitized, 16);
|
||||||
|
|
||||||
|
const r = (bigint >> 16) & 255;
|
||||||
|
const g = (bigint >> 8) & 255;
|
||||||
|
const b = bigint & 255;
|
||||||
|
|
||||||
|
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CardData {
|
||||||
|
id: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialCards: CardData[] = [
|
||||||
|
{ id: '1', x: 0, y: 0, width: GRID_SIZE * 2, height: GRID_SIZE * 2 },
|
||||||
|
{ id: '2', x: GRID_SIZE * 2, y: 0, width: GRID_SIZE * 3, height: GRID_SIZE * 2 },
|
||||||
|
{ id: '3', x: GRID_SIZE * 3, y: 0, width: GRID_SIZE * 3, height: GRID_SIZE * 2 },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const GridDashboard = () => {
|
||||||
|
const theme = useMantineTheme();
|
||||||
|
const colorScheme = useComputedColorScheme(); // ✅ directamente 'light' o 'dark'
|
||||||
|
const isDark = colorScheme === 'dark';
|
||||||
|
|
||||||
|
// Color de la rejilla adaptado al modo del tema
|
||||||
|
const gridBaseColor = isDark ? theme.colors.dark[4] : theme.colors.gray[3];
|
||||||
|
const gridColor = hexToRgba(gridBaseColor, 0.25); // Ajusta la opacidad aquí
|
||||||
|
|
||||||
|
const [cards, setCards] = useState<CardData[]>(initialCards);
|
||||||
|
const [showGrid, setShowGrid] = useState(true);
|
||||||
|
|
||||||
|
const updateCard = (id: string, updates: Partial<CardData>) => {
|
||||||
|
setCards((prev) =>
|
||||||
|
prev.map((card) => (card.id === id ? { ...card, ...updates } : card))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Group mb="xs">
|
||||||
|
<Switch
|
||||||
|
checked={showGrid}
|
||||||
|
onChange={(event) => setShowGrid(event.currentTarget.checked)}
|
||||||
|
label="Mostrar cuadrícula"
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '700px',
|
||||||
|
backgroundSize: `${GRID_SIZE}px ${GRID_SIZE}px`,
|
||||||
|
backgroundImage: showGrid
|
||||||
|
? `linear-gradient(to right, ${gridColor} 1px, transparent 1px),
|
||||||
|
linear-gradient(to bottom, ${gridColor} 1px, transparent 1px)`
|
||||||
|
: 'none',
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cards.map((card) => (
|
||||||
|
<Rnd
|
||||||
|
key={card.id}
|
||||||
|
size={{
|
||||||
|
width: Math.round(card.width / GRID_SIZE) * GRID_SIZE,
|
||||||
|
height: Math.round(card.height / GRID_SIZE) * GRID_SIZE,
|
||||||
|
}}
|
||||||
|
position={{ x: card.x, y: card.y }}
|
||||||
|
minWidth={GRID_SIZE * 8}
|
||||||
|
minHeight={GRID_SIZE * 8}
|
||||||
|
bounds="parent"
|
||||||
|
grid={[GRID_SIZE, GRID_SIZE]}
|
||||||
|
onDragStop={(_, d) =>
|
||||||
|
updateCard(card.id, {
|
||||||
|
x: Math.round(d.x / GRID_SIZE) * GRID_SIZE,
|
||||||
|
y: Math.round(d.y / GRID_SIZE) * GRID_SIZE,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onResizeStop={(_, __, ref, ___, pos) =>
|
||||||
|
updateCard(card.id, {
|
||||||
|
width: Math.round(ref.offsetWidth / GRID_SIZE) * GRID_SIZE,
|
||||||
|
height: Math.round(ref.offsetHeight / GRID_SIZE) * GRID_SIZE,
|
||||||
|
x: Math.round(pos.x / GRID_SIZE) * GRID_SIZE,
|
||||||
|
y: Math.round(pos.y / GRID_SIZE) * GRID_SIZE,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
shadow="sm"
|
||||||
|
padding="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
style={{ height: '100%', userSelect: 'none' }}
|
||||||
|
>
|
||||||
|
<Text fw={500}>Card {card.id}</Text>
|
||||||
|
<Text size="sm">Mueve o redimensiona</Text>
|
||||||
|
</Card>
|
||||||
|
</Rnd>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -13,7 +13,7 @@ import { MetodoSelect } from './MetodoSelect';
|
|||||||
import { useMantineTheme } from '@mantine/core';
|
import { useMantineTheme } from '@mantine/core';
|
||||||
|
|
||||||
export function LlamadorAPI() {
|
export function LlamadorAPI() {
|
||||||
const [direccion, setDireccion] = useState('http://localhost:8000/api/saludo');
|
const [direccion, setDireccion] = useState('http://localhost:8000/api/v1/ping/');
|
||||||
const [metodo, setMetodo] = useState('GET');
|
const [metodo, setMetodo] = useState('GET');
|
||||||
const [contenido, setContenido] = useState('');
|
const [contenido, setContenido] = useState('');
|
||||||
const [respuesta, setRespuesta] = useState('');
|
const [respuesta, setRespuesta] = useState('');
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function Welcome() {
|
|||||||
<Title className={classes.title} ta="center" mt={100}>
|
<Title className={classes.title} ta="center" mt={100}>
|
||||||
Hola! {' '}
|
Hola! {' '}
|
||||||
<Text inherit variant="gradient" component="span" gradient={{ from: theme.colors.brand[7], to: theme.colors.secondary[4], }} style={{ letterSpacing: '1px' }}>
|
<Text inherit variant="gradient" component="span" gradient={{ from: theme.colors.brand[7], to: theme.colors.secondary[4], }} style={{ letterSpacing: '1px' }}>
|
||||||
Egutierrez
|
Holooooo
|
||||||
</Text>
|
</Text>
|
||||||
</Title>
|
</Title>
|
||||||
<Text c="dimmed" ta="left" size="lg" maw={580} mx="auto" mt="xl">
|
<Text c="dimmed" ta="left" size="lg" maw={580} mx="auto" mt="xl">
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ export const submenuLinks = {
|
|||||||
Home: [
|
Home: [
|
||||||
{ label: 'Inicio', to: '/' },
|
{ label: 'Inicio', to: '/' },
|
||||||
{ label: 'Consulta Api', to: '/Consulta_API' },
|
{ label: 'Consulta Api', to: '/Consulta_API' },
|
||||||
{ label: 'Prueba_appshell', to: '/prueba_appshell' },
|
{ label: 'Biblioteca', to: '/Biblioteca' },
|
||||||
|
|
||||||
],
|
],
|
||||||
Dashboard: [
|
Dashboard: [
|
||||||
{ label: 'Resumen', to: '/dashboard/resumen' },
|
{ label: 'Resumen', to: '/dashboard/resumen' },
|
||||||
|
{ label: 'Grid_Dashboard', to: '/Grid_Dashboard' },
|
||||||
{ label: 'Estadísticas', to: '/dashboard/estadisticas' },
|
{ label: 'Estadísticas', to: '/dashboard/estadisticas' },
|
||||||
{ label: 'Usuarios', to: '/dashboard/usuarios' },
|
{ label: 'Usuarios', to: '/dashboard/usuarios' },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -0,0 +1,347 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
AppShell,
|
||||||
|
Stack,
|
||||||
|
Card,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
ScrollArea,
|
||||||
|
Group,
|
||||||
|
Button,
|
||||||
|
TextInput,
|
||||||
|
Modal,
|
||||||
|
Box,
|
||||||
|
Loader,
|
||||||
|
Textarea
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { AppShellWithMenu } from '../components/Appshell/Appshell';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
type Nota = {
|
||||||
|
id: string;
|
||||||
|
titulo: string;
|
||||||
|
texto: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Biblioteca = {
|
||||||
|
id: string;
|
||||||
|
nombre: string;
|
||||||
|
descripcion: string;
|
||||||
|
notas: Nota[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Biblioteca() {
|
||||||
|
const [bibliotecas, setBibliotecas] = useState<Biblioteca[]>([]);
|
||||||
|
const [bibliotecaSeleccionada, setBibliotecaSeleccionada] = useState<Biblioteca | null>(null);
|
||||||
|
const [modalAbierto, setModalAbierto] = useState(false);
|
||||||
|
const [tituloNota, setTituloNota] = useState('');
|
||||||
|
const [contenidoNota, setContenidoNota] = useState('');
|
||||||
|
const [loadingNotas, setLoadingNotas] = useState(false);
|
||||||
|
const [notaEnEdicion, setNotaEnEdicion] = useState<Nota | null>(null);
|
||||||
|
const [modalEditarAbierto, setModalEditarAbierto] = useState(false);
|
||||||
|
const [modalNuevaBiblio, setModalNuevaBiblio] = useState(false);
|
||||||
|
const [nombreBiblio, setNombreBiblio] = useState('');
|
||||||
|
const [descripcionBiblio, setDescripcionBiblio] = useState('');
|
||||||
|
const [loadingNuevaBiblio, setLoadingNuevaBiblio] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
const fetchBibliotecas = async () => {
|
||||||
|
try {
|
||||||
|
const res = await axios.get('/api/v1/text_manager/list');
|
||||||
|
console.log('📦 Respuesta del backend:', res.data);
|
||||||
|
|
||||||
|
if (!Array.isArray(res.data)) {
|
||||||
|
console.error('❌ La respuesta no es un array:', res.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bibliotecasConNotas = await Promise.all(
|
||||||
|
res.data.map(async (biblio: Omit<Biblioteca, 'notas'>) => {
|
||||||
|
const notas = await axios.get(`/api/v1/text_manager/nota/list/${biblio.id}`);
|
||||||
|
return { ...biblio, notas: notas.data as Nota[] };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setBibliotecas(bibliotecasConNotas);
|
||||||
|
setBibliotecaSeleccionada(bibliotecasConNotas[0] || null);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error al cargar bibliotecas:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const crearBiblioteca = async () => {
|
||||||
|
setLoadingNuevaBiblio(true); // 🔄 Activa el loader en el botón
|
||||||
|
try {
|
||||||
|
// Llamada a backend
|
||||||
|
await axios.post('/api/v1/text_manager/biblioteca', {
|
||||||
|
nombre_biblioteca: nombreBiblio,
|
||||||
|
descripcion: descripcionBiblio,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 🧼 Limpia formularios
|
||||||
|
setNombreBiblio('');
|
||||||
|
setDescripcionBiblio('');
|
||||||
|
|
||||||
|
// 🔒 Cierra el modal
|
||||||
|
setModalNuevaBiblio(false);
|
||||||
|
|
||||||
|
// 🔄 Refresca la lista de bibliotecas
|
||||||
|
await fetchBibliotecas();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error al crear biblioteca:', error);
|
||||||
|
} finally {
|
||||||
|
setLoadingNuevaBiblio(false); // ✅ Apaga el loader
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const agregarNota = async () => {
|
||||||
|
if (!bibliotecaSeleccionada) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post(`/api/v1/text_manager/nota/${bibliotecaSeleccionada.id}`, {
|
||||||
|
titulo: tituloNota,
|
||||||
|
texto: contenidoNota,
|
||||||
|
tags: [],
|
||||||
|
conexiones: [],
|
||||||
|
resumen: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
setLoadingNotas(true);
|
||||||
|
const nuevasNotas = await axios.get(`/api/v1/text_manager/nota/list/${bibliotecaSeleccionada.id}`);
|
||||||
|
const nuevasBibliotecas = bibliotecas.map((b) =>
|
||||||
|
b.id === bibliotecaSeleccionada.id ? { ...b, notas: nuevasNotas.data as Nota[] } : b
|
||||||
|
);
|
||||||
|
setBibliotecas(nuevasBibliotecas);
|
||||||
|
setBibliotecaSeleccionada(nuevasBibliotecas.find((b) => b.id === bibliotecaSeleccionada.id) || null);
|
||||||
|
setTituloNota('');
|
||||||
|
setContenidoNota('');
|
||||||
|
setModalAbierto(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error al agregar nota:', error);
|
||||||
|
} finally {
|
||||||
|
setLoadingNotas(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchBibliotecas();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Eliminar nota
|
||||||
|
const eliminarNota = async (notaId: string) => {
|
||||||
|
if (!bibliotecaSeleccionada) return;
|
||||||
|
try {
|
||||||
|
await axios.delete(`/api/v1/text_manager/nota/${bibliotecaSeleccionada.id}/${notaId}`);
|
||||||
|
|
||||||
|
// Solo actualiza la biblioteca actual
|
||||||
|
const nuevasNotas = await axios.get(`/api/v1/text_manager/nota/list/${bibliotecaSeleccionada.id}`);
|
||||||
|
const nuevasBibliotecas = bibliotecas.map((b) =>
|
||||||
|
b.id === bibliotecaSeleccionada.id ? { ...b, notas: nuevasNotas.data as Nota[] } : b
|
||||||
|
);
|
||||||
|
setBibliotecas(nuevasBibliotecas);
|
||||||
|
setBibliotecaSeleccionada(nuevasBibliotecas.find(b => b.id === bibliotecaSeleccionada.id) || null);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al eliminar nota:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Editar nota
|
||||||
|
const abrirModalEditar = (nota: Nota) => {
|
||||||
|
setNotaEnEdicion(nota);
|
||||||
|
setModalEditarAbierto(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Guardar cambios de edición
|
||||||
|
const guardarEdicionNota = async () => {
|
||||||
|
if (!notaEnEdicion || !bibliotecaSeleccionada) return;
|
||||||
|
try {
|
||||||
|
await axios.put(`/api/v1/text_manager/nota/${bibliotecaSeleccionada.id}/${notaEnEdicion.id}`, {
|
||||||
|
titulo: notaEnEdicion.titulo,
|
||||||
|
texto: notaEnEdicion.texto,
|
||||||
|
tags: [],
|
||||||
|
conexiones: [],
|
||||||
|
resumen: ""
|
||||||
|
});
|
||||||
|
setModalEditarAbierto(false);
|
||||||
|
setNotaEnEdicion(null);
|
||||||
|
await fetchBibliotecas();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al actualizar nota:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppShellWithMenu>
|
||||||
|
<Box display="flex" h="100%">
|
||||||
|
<Box w={240} p="md">
|
||||||
|
<ScrollArea h="100%">
|
||||||
|
<Stack>
|
||||||
|
<Button color="teal" onClick={fetchBibliotecas}>🔄 Recuperar bibliotecas</Button>
|
||||||
|
<Button color="grape" variant="outline" onClick={() => setModalNuevaBiblio(true)}>➕ Nueva biblioteca</Button>
|
||||||
|
|
||||||
|
|
||||||
|
{bibliotecas.map((biblio) => (
|
||||||
|
<Button
|
||||||
|
key={biblio.id}
|
||||||
|
size="xs"
|
||||||
|
fullWidth
|
||||||
|
variant={biblio.id === bibliotecaSeleccionada?.id ? 'filled' : 'light'}
|
||||||
|
color="blue"
|
||||||
|
onClick={() => setBibliotecaSeleccionada(biblio)}
|
||||||
|
>
|
||||||
|
{biblio.nombre}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</ScrollArea>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box p="md" style={{ flex: 1 }}>
|
||||||
|
{bibliotecaSeleccionada ? (
|
||||||
|
<Stack>
|
||||||
|
<Title order={2}>{bibliotecaSeleccionada.nombre}</Title>
|
||||||
|
<Group>
|
||||||
|
<Button onClick={() => setModalAbierto(true)}>Agregar nota</Button>
|
||||||
|
</Group>
|
||||||
|
<Group>
|
||||||
|
{loadingNotas ? (
|
||||||
|
<Loader />
|
||||||
|
) : (
|
||||||
|
bibliotecaSeleccionada.notas.map((nota) => (
|
||||||
|
|
||||||
|
// Cards de notas
|
||||||
|
|
||||||
|
<Card
|
||||||
|
key={nota.id}
|
||||||
|
shadow="sm"
|
||||||
|
padding="lg"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
style={{
|
||||||
|
width: 300,
|
||||||
|
height: 250, // Altura fija para asegurar la separación
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Title order={4} style={{ marginBottom: 10 }}>{nota.titulo}</Title>
|
||||||
|
<Text>{nota.texto}</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Box mt="md" style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() => abrirModalEditar(nota)}
|
||||||
|
>
|
||||||
|
Editar
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
// Fin de notas en cards
|
||||||
|
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<Stack>
|
||||||
|
<Text>Selecciona una biblioteca</Text>
|
||||||
|
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Modal para agregar */}
|
||||||
|
<Modal opened={modalAbierto} onClose={() => setModalAbierto(false)} title="Agregar nueva nota">
|
||||||
|
<Stack>
|
||||||
|
<TextInput
|
||||||
|
label="Título"
|
||||||
|
value={tituloNota}
|
||||||
|
onChange={(event) => setTituloNota(event.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<Textarea
|
||||||
|
label="Contenido"
|
||||||
|
minRows={6}
|
||||||
|
autosize
|
||||||
|
value={contenidoNota}
|
||||||
|
onChange={(event) => setContenidoNota(event.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<Button onClick={agregarNota}>Guardar</Button>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{/* Modal para editar */}
|
||||||
|
<Modal opened={modalEditarAbierto} onClose={() => setModalEditarAbierto(false)} title="Editar nota">
|
||||||
|
<Stack>
|
||||||
|
<TextInput
|
||||||
|
label="Título"
|
||||||
|
value={notaEnEdicion?.titulo || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
setNotaEnEdicion((prev) => (prev ? { ...prev, titulo: e.currentTarget.value } : null))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Textarea
|
||||||
|
label="Contenido"
|
||||||
|
minRows={6}
|
||||||
|
autosize
|
||||||
|
value={notaEnEdicion?.texto || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
setNotaEnEdicion((prev) => (prev ? { ...prev, texto: e.currentTarget.value } : null))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Group grow>
|
||||||
|
<Button color="blue" onClick={guardarEdicionNota}>
|
||||||
|
💾 Guardar cambios
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
onClick={async () => {
|
||||||
|
if (!notaEnEdicion || !bibliotecaSeleccionada) return;
|
||||||
|
await eliminarNota(notaEnEdicion.id);
|
||||||
|
setModalEditarAbierto(false);
|
||||||
|
setNotaEnEdicion(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
🗑️ Eliminar nota
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{/* Modal para crear una biblioteca */}
|
||||||
|
<Modal
|
||||||
|
opened={modalNuevaBiblio}
|
||||||
|
onClose={() => setModalNuevaBiblio(false)}
|
||||||
|
title="Crear nueva biblioteca"
|
||||||
|
>
|
||||||
|
<Stack>
|
||||||
|
<TextInput
|
||||||
|
label="Nombre"
|
||||||
|
value={nombreBiblio}
|
||||||
|
onChange={(e) => setNombreBiblio(e.currentTarget.value)}
|
||||||
|
disabled={loadingNuevaBiblio}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Descripción"
|
||||||
|
value={descripcionBiblio}
|
||||||
|
onChange={(e) => setDescripcionBiblio(e.currentTarget.value)}
|
||||||
|
disabled={loadingNuevaBiblio}
|
||||||
|
/>
|
||||||
|
<Button onClick={crearBiblioteca} loading={loadingNuevaBiblio}>
|
||||||
|
Crear
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
|
||||||
|
</AppShellWithMenu>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { Grid } from '@mantine/core';
|
||||||
|
import { AppShellWithMenu } from '../components/Appshell/Appshell';
|
||||||
|
import { GridDashboard } from '../components/Grid_dashboard';
|
||||||
|
|
||||||
|
export function Grid_Dashboard() {
|
||||||
|
return (
|
||||||
|
<AppShellWithMenu>
|
||||||
|
|
||||||
|
|
||||||
|
<GridDashboard></GridDashboard>
|
||||||
|
|
||||||
|
</AppShellWithMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { AppShellWithMenu } from '../components/Appshell/Appshell';
|
import { AppShellWithMenu } from '../components/Appshell/Appshell';
|
||||||
import { Welcome } from '@/components/Welcome/Welcome';
|
import { Welcome } from '@/components/Welcome/Welcome';
|
||||||
|
import { ColorSchemeToggle } from '@/components/ColorSchemeToggle/ColorSchemeToggle';
|
||||||
|
|
||||||
|
|
||||||
export function HomePage() {
|
export function HomePage() {
|
||||||
return (
|
return (
|
||||||
@@ -13,6 +15,8 @@ export function HomePage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<ColorSchemeToggle></ColorSchemeToggle>
|
||||||
|
|
||||||
</AppShellWithMenu>
|
</AppShellWithMenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { AppShellWithMenu } from '../components/Appshell/Appshell';
|
|
||||||
|
|
||||||
export function Prueba_appshell() {
|
|
||||||
return (
|
|
||||||
<AppShellWithMenu>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</AppShellWithMenu>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,15 @@ import svgr from 'vite-plugin-svgr';
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react(), tsconfigPaths(), svgr()],
|
plugins: [react(), tsconfigPaths(), svgr()],
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:8000',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
test: {
|
test: {
|
||||||
globals: true,
|
globals: true,
|
||||||
environment: 'jsdom',
|
environment: 'jsdom',
|
||||||
|
|||||||
+92
-2
@@ -1224,6 +1224,11 @@ async-function@^1.0.0:
|
|||||||
resolved "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz"
|
resolved "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz"
|
||||||
integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==
|
integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==
|
||||||
|
|
||||||
|
asynckit@^0.4.0:
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
|
||||||
|
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
|
||||||
|
|
||||||
available-typed-arrays@^1.0.7:
|
available-typed-arrays@^1.0.7:
|
||||||
version "1.0.7"
|
version "1.0.7"
|
||||||
resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz"
|
resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz"
|
||||||
@@ -1236,6 +1241,15 @@ axe-core@^4.10.0:
|
|||||||
resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz"
|
resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz"
|
||||||
integrity sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==
|
integrity sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==
|
||||||
|
|
||||||
|
axios@^1.9.0:
|
||||||
|
version "1.9.0"
|
||||||
|
resolved "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz"
|
||||||
|
integrity sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==
|
||||||
|
dependencies:
|
||||||
|
follow-redirects "^1.15.6"
|
||||||
|
form-data "^4.0.0"
|
||||||
|
proxy-from-env "^1.1.0"
|
||||||
|
|
||||||
axobject-query@^4.1.0:
|
axobject-query@^4.1.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz"
|
resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz"
|
||||||
@@ -1419,6 +1433,11 @@ check-error@^2.1.1:
|
|||||||
resolved "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz"
|
resolved "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz"
|
||||||
integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==
|
integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==
|
||||||
|
|
||||||
|
clsx@^1.1.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz"
|
||||||
|
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
|
||||||
|
|
||||||
clsx@^2.1.1:
|
clsx@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz"
|
resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz"
|
||||||
@@ -1441,6 +1460,13 @@ colord@^2.9.3:
|
|||||||
resolved "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz"
|
resolved "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz"
|
||||||
integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==
|
integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==
|
||||||
|
|
||||||
|
combined-stream@^1.0.8:
|
||||||
|
version "1.0.8"
|
||||||
|
resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
|
||||||
|
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
||||||
|
dependencies:
|
||||||
|
delayed-stream "~1.0.0"
|
||||||
|
|
||||||
concat-map@0.0.1:
|
concat-map@0.0.1:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
|
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
|
||||||
@@ -1636,6 +1662,11 @@ define-properties@^1.1.3, define-properties@^1.2.1:
|
|||||||
has-property-descriptors "^1.0.0"
|
has-property-descriptors "^1.0.0"
|
||||||
object-keys "^1.1.1"
|
object-keys "^1.1.1"
|
||||||
|
|
||||||
|
delayed-stream@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
|
||||||
|
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
|
||||||
|
|
||||||
depd@^2.0.0, depd@2.0.0:
|
depd@^2.0.0, depd@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz"
|
resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz"
|
||||||
@@ -2268,6 +2299,11 @@ flatted@^3.2.9, flatted@^3.3.3:
|
|||||||
resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz"
|
resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz"
|
||||||
integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==
|
integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==
|
||||||
|
|
||||||
|
follow-redirects@^1.15.6:
|
||||||
|
version "1.15.9"
|
||||||
|
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz"
|
||||||
|
integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
|
||||||
|
|
||||||
for-each@^0.3.3, for-each@^0.3.5:
|
for-each@^0.3.3, for-each@^0.3.5:
|
||||||
version "0.3.5"
|
version "0.3.5"
|
||||||
resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz"
|
resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz"
|
||||||
@@ -2283,6 +2319,16 @@ foreground-child@^3.1.0:
|
|||||||
cross-spawn "^7.0.6"
|
cross-spawn "^7.0.6"
|
||||||
signal-exit "^4.0.1"
|
signal-exit "^4.0.1"
|
||||||
|
|
||||||
|
form-data@^4.0.0:
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz"
|
||||||
|
integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==
|
||||||
|
dependencies:
|
||||||
|
asynckit "^0.4.0"
|
||||||
|
combined-stream "^1.0.8"
|
||||||
|
es-set-tostringtag "^2.1.0"
|
||||||
|
mime-types "^2.1.12"
|
||||||
|
|
||||||
forwarded@0.2.0:
|
forwarded@0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz"
|
resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz"
|
||||||
@@ -3157,6 +3203,18 @@ mime-db@^1.54.0:
|
|||||||
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz"
|
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz"
|
||||||
integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
|
integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
|
||||||
|
|
||||||
|
mime-db@1.52.0:
|
||||||
|
version "1.52.0"
|
||||||
|
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"
|
||||||
|
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
||||||
|
|
||||||
|
mime-types@^2.1.12:
|
||||||
|
version "2.1.35"
|
||||||
|
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"
|
||||||
|
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||||
|
dependencies:
|
||||||
|
mime-db "1.52.0"
|
||||||
|
|
||||||
mime-types@^3.0.0, mime-types@^3.0.1:
|
mime-types@^3.0.0, mime-types@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz"
|
resolved "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz"
|
||||||
@@ -3581,6 +3639,11 @@ proxy-addr@^2.0.7:
|
|||||||
forwarded "0.2.0"
|
forwarded "0.2.0"
|
||||||
ipaddr.js "1.9.1"
|
ipaddr.js "1.9.1"
|
||||||
|
|
||||||
|
proxy-from-env@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
|
||||||
|
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||||
|
|
||||||
punycode@^2.1.0, punycode@^2.3.1:
|
punycode@^2.1.0, punycode@^2.3.1:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz"
|
resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz"
|
||||||
@@ -3613,6 +3676,11 @@ raw-body@^3.0.0:
|
|||||||
iconv-lite "0.6.3"
|
iconv-lite "0.6.3"
|
||||||
unpipe "1.0.0"
|
unpipe "1.0.0"
|
||||||
|
|
||||||
|
re-resizable@6.11.2:
|
||||||
|
version "6.11.2"
|
||||||
|
resolved "https://registry.npmjs.org/re-resizable/-/re-resizable-6.11.2.tgz"
|
||||||
|
integrity sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==
|
||||||
|
|
||||||
react-docgen-typescript@^2.2.2:
|
react-docgen-typescript@^2.2.2:
|
||||||
version "2.2.2"
|
version "2.2.2"
|
||||||
resolved "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz"
|
resolved "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz"
|
||||||
@@ -3634,13 +3702,21 @@ react-docgen@^7.0.0:
|
|||||||
resolve "^1.22.1"
|
resolve "^1.22.1"
|
||||||
strip-indent "^4.0.0"
|
strip-indent "^4.0.0"
|
||||||
|
|
||||||
"react-dom@^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom@^18.0.0 || ^19.0.0", "react-dom@^18.x || ^19.x", react-dom@^19.0.0, react-dom@^19.1.0, react-dom@>=16.13, react-dom@>=16.8.0, react-dom@>=18:
|
"react-dom@^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom@^18.0.0 || ^19.0.0", "react-dom@^18.x || ^19.x", react-dom@^19.0.0, react-dom@^19.1.0, "react-dom@>= 16.3.0", react-dom@>=16.13, react-dom@>=16.3.0, react-dom@>=16.8.0, react-dom@>=18:
|
||||||
version "19.1.0"
|
version "19.1.0"
|
||||||
resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz"
|
resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz"
|
||||||
integrity sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==
|
integrity sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==
|
||||||
dependencies:
|
dependencies:
|
||||||
scheduler "^0.26.0"
|
scheduler "^0.26.0"
|
||||||
|
|
||||||
|
react-draggable@4.4.6:
|
||||||
|
version "4.4.6"
|
||||||
|
resolved "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz"
|
||||||
|
integrity sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==
|
||||||
|
dependencies:
|
||||||
|
clsx "^1.1.1"
|
||||||
|
prop-types "^15.8.1"
|
||||||
|
|
||||||
react-is@^16.13.1:
|
react-is@^16.13.1:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
||||||
@@ -3687,6 +3763,15 @@ react-remove-scroll@^2.6.2:
|
|||||||
use-callback-ref "^1.3.3"
|
use-callback-ref "^1.3.3"
|
||||||
use-sidecar "^1.1.3"
|
use-sidecar "^1.1.3"
|
||||||
|
|
||||||
|
react-rnd@^10.5.2:
|
||||||
|
version "10.5.2"
|
||||||
|
resolved "https://registry.npmjs.org/react-rnd/-/react-rnd-10.5.2.tgz"
|
||||||
|
integrity sha512-0Tm4x7k7pfHf2snewJA8x7Nwgt3LV+58MVEWOVsFjk51eYruFEa6Wy7BNdxt4/lH0wIRsu7Gm3KjSXY2w7YaNw==
|
||||||
|
dependencies:
|
||||||
|
re-resizable "6.11.2"
|
||||||
|
react-draggable "4.4.6"
|
||||||
|
tslib "2.6.2"
|
||||||
|
|
||||||
react-router-dom@^7.4.0:
|
react-router-dom@^7.4.0:
|
||||||
version "7.5.3"
|
version "7.5.3"
|
||||||
resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.3.tgz"
|
resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.3.tgz"
|
||||||
@@ -3725,7 +3810,7 @@ react-use-measure@^2.1.7:
|
|||||||
resolved "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz"
|
resolved "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz"
|
||||||
integrity sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==
|
integrity sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==
|
||||||
|
|
||||||
"react@^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react@^18.0.0 || ^19.0.0", "react@^18.x || ^19.x", react@^19.0.0, react@^19.1.0, "react@>= 16", react@>=16.13, react@>=16.8.0, react@>=17.0, react@>=18, react@>=18.0.0:
|
"react@^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react@^18.0.0 || ^19.0.0", "react@^18.x || ^19.x", react@^19.0.0, react@^19.1.0, "react@>= 16", "react@>= 16.3.0", react@>=16.13, react@>=16.3.0, react@>=16.8.0, react@>=17.0, react@>=18, react@>=18.0.0:
|
||||||
version "19.1.0"
|
version "19.1.0"
|
||||||
resolved "https://registry.npmjs.org/react/-/react-19.1.0.tgz"
|
resolved "https://registry.npmjs.org/react/-/react-19.1.0.tgz"
|
||||||
integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==
|
integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==
|
||||||
@@ -4535,6 +4620,11 @@ tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0:
|
|||||||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"
|
resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"
|
||||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||||
|
|
||||||
|
tslib@2.6.2:
|
||||||
|
version "2.6.2"
|
||||||
|
resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz"
|
||||||
|
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
|
||||||
|
|
||||||
turbo-stream@2.4.0:
|
turbo-stream@2.4.0:
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz"
|
resolved "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz"
|
||||||
|
|||||||
@@ -5,15 +5,7 @@
|
|||||||
"execution_count": 1,
|
"execution_count": 1,
|
||||||
"id": "26aa8e2b",
|
"id": "26aa8e2b",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [],
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"✅ Credencial: Credencial_enmanuel\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
"source": [
|
||||||
"from src.ApiKeys.openai_apikey import OpenAICredencial\n",
|
"from src.ApiKeys.openai_apikey import OpenAICredencial\n",
|
||||||
"from src.ApiKeys.openai_apikey_mmr import OpenAICredencialRepo # Ajusta si está en otro módulo\n",
|
"from src.ApiKeys.openai_apikey_mmr import OpenAICredencialRepo # Ajusta si está en otro módulo\n",
|
||||||
@@ -24,14 +16,64 @@
|
|||||||
"conexion_admin = PostgresConexion(db_credencial)\n",
|
"conexion_admin = PostgresConexion(db_credencial)\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# 3. Guardar la credencial en la base de datos\n",
|
"# 3. Guardar la credencial en la base de datos\n",
|
||||||
"repo = OpenAICredencialRepo(conexion_admin)\n",
|
"repo = OpenAICredencialRepo(conexion_admin)\n"
|
||||||
"credencial_openai = repo.get_by_id(1)\n",
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "4c232ecd",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"'OPAK20250513-61b29978b7604031014'"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 2,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"apikey_gpt = OpenAICredencial(titulo=\"Credencial_enmanuel_gpt\",\n",
|
||||||
|
" api_key=\"\")\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"repo.add(apikey_gpt)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 3,
|
||||||
|
"id": "32552452",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"✅ Credencial: Credencial_enmanuel_gpt\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"credencial_openai = repo.get_by_id('OPAK20250513-61b29978b7604031014')\n",
|
||||||
"print(f\"✅ Credencial: {credencial_openai.titulo}\")"
|
"print(f\"✅ Credencial: {credencial_openai.titulo}\")"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 2,
|
"execution_count": null,
|
||||||
|
"id": "7464fa65",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 4,
|
||||||
"id": "e5b665a6",
|
"id": "e5b665a6",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@
|
|||||||
"cells": [
|
"cells": [
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": 1,
|
||||||
"id": "5206b9c6",
|
"id": "5206b9c6",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 3,
|
"execution_count": 2,
|
||||||
"id": "63a0b954",
|
"id": "63a0b954",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": 3,
|
||||||
"id": "0575f424",
|
"id": "0575f424",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
@@ -38,17 +38,17 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 3,
|
"execution_count": 4,
|
||||||
"id": "a5266309",
|
"id": "a5266309",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"1"
|
"'PGCR20250510-02f3cf9610127084237'"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 3,
|
"execution_count": 4,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"modelo_cred = repo_cred.get_by_id(1)\n",
|
"modelo_cred = repo_cred.get_by_id(\"PGCR20250510-02f3cf9610127084237\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
"print(modelo_cred.titulo)"
|
"print(modelo_cred.titulo)"
|
||||||
]
|
]
|
||||||
@@ -0,0 +1,277 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 1,
|
||||||
|
"id": "255345d5",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"from src.TextManager.biblioteca import Biblioteca\n",
|
||||||
|
"from src.TextManager.biblioteca_mmr import BibliotecaRepo\n",
|
||||||
|
"from src.Llms.Embedders.Openai_embedder import OpenAIEmbedder\n",
|
||||||
|
"from src.ApiKeys.openai_apikey_mmr import OpenAICredencialRepo\n",
|
||||||
|
"from src.ConexionSql.Postgres_conexion import PostgresConexion\n",
|
||||||
|
"from src.TextManager.nota import Nota\n",
|
||||||
|
"from src.TextManager.notas_biblioteca_mmr import generar_tabla_nota_para_biblioteca, NotaRepo\n",
|
||||||
|
"from sqlalchemy import MetaData\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 2,
|
||||||
|
"id": "b414a66c",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def agregar_nota_a_biblioteca(\n",
|
||||||
|
" conexion: PostgresConexion,\n",
|
||||||
|
" biblioteca_id: str,\n",
|
||||||
|
" titulo: str,\n",
|
||||||
|
" texto: str = \"\",\n",
|
||||||
|
" tags: list[str] = None,\n",
|
||||||
|
" conexiones: list[str] = None,\n",
|
||||||
|
" resumen: str = \"\"\n",
|
||||||
|
"):\n",
|
||||||
|
" print(\"[INFO] Iniciando el proceso de agregar nota a la biblioteca...\")\n",
|
||||||
|
"\n",
|
||||||
|
" # Obtener la biblioteca\n",
|
||||||
|
" print(f\"[INFO] Buscando biblioteca con ID: {biblioteca_id}\")\n",
|
||||||
|
" repo_biblioteca = BibliotecaRepo(conexion)\n",
|
||||||
|
" biblioteca = repo_biblioteca.get_by_id(biblioteca_id)\n",
|
||||||
|
" if biblioteca is None:\n",
|
||||||
|
" print(f\"[ERROR] No se encontró la biblioteca con ID {biblioteca_id}\")\n",
|
||||||
|
" raise ValueError(f\"No se encontró la biblioteca con ID {biblioteca_id}\")\n",
|
||||||
|
" print(f\"[INFO] Biblioteca encontrada: {biblioteca.nombre} (vector_dim={biblioteca.vector_dim})\")\n",
|
||||||
|
"\n",
|
||||||
|
" # Crear objeto Nota\n",
|
||||||
|
" print(f\"[INFO] Creando objeto Nota con título: {titulo}\")\n",
|
||||||
|
" nota = Nota(\n",
|
||||||
|
" titulo=titulo,\n",
|
||||||
|
" texto=texto,\n",
|
||||||
|
" tags=tags or [],\n",
|
||||||
|
" conexiones=conexiones or [],\n",
|
||||||
|
" resumen=resumen or \"\",\n",
|
||||||
|
" # vector=biblioteca.embedder.embed_text(texto),\n",
|
||||||
|
" # vector_resumen=biblioteca.embedder.embed_text(resumen) if resumen else None\n",
|
||||||
|
" )\n",
|
||||||
|
" # Mostrar atributos seguros\n",
|
||||||
|
" print(\n",
|
||||||
|
" f\"[DEBUG] Nota creada: titulo='{nota.titulo}', \"\n",
|
||||||
|
" f\"texto_len={len(nota.texto)}, \"\n",
|
||||||
|
" f\"tags={len(nota.tags)}, \"\n",
|
||||||
|
" f\"conexiones={len(nota.conexiones)}, \"\n",
|
||||||
|
" f\"resumen_len={len(nota.resumen)}\"\n",
|
||||||
|
" )\n",
|
||||||
|
"\n",
|
||||||
|
" # Preparar tabla y modelo de nota\n",
|
||||||
|
" print(f\"[INFO] Generando tabla y modelo de Nota para la biblioteca: {biblioteca.nombre}\")\n",
|
||||||
|
" metadata = MetaData()\n",
|
||||||
|
" tabla, ModeloNota = generar_tabla_nota_para_biblioteca(\n",
|
||||||
|
" biblioteca.nombre,\n",
|
||||||
|
" biblioteca.vector_dim,\n",
|
||||||
|
" metadata\n",
|
||||||
|
" )\n",
|
||||||
|
" print(f\"[INFO] Creando tabla en la base de datos si no existe...\")\n",
|
||||||
|
" metadata.create_all(conexion.get_engine())\n",
|
||||||
|
" print(f\"[INFO] Tabla '{tabla.name}' verificada/creada.\")\n",
|
||||||
|
"\n",
|
||||||
|
" # Guardar la nota\n",
|
||||||
|
" print(f\"[INFO] Guardando nota en la base de datos...\")\n",
|
||||||
|
" repo_nota = NotaRepo(conexion.get_session(), ModeloNota)\n",
|
||||||
|
" nota_id = repo_nota.add(nota)\n",
|
||||||
|
" print(f\"[INFO] Nota guardada con ID: {nota_id}\")\n",
|
||||||
|
"\n",
|
||||||
|
" resultado = {\n",
|
||||||
|
" \"mensaje\": f\"Nota '{titulo}' agregada con éxito a la biblioteca '{biblioteca.nombre}'.\",\n",
|
||||||
|
" \"nota_id\": nota_id\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" print(f\"[SUCCESS] {resultado['mensaje']}\")\n",
|
||||||
|
" return resultado\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 9,
|
||||||
|
"id": "8e57e511",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"[INFO] Iniciando el proceso de agregar nota a la biblioteca...\n",
|
||||||
|
"[INFO] Buscando biblioteca con ID: BBLI20250511-a91dbb2168172979414\n",
|
||||||
|
"[INFO] Biblioteca encontrada: biblio_Pruebas_1 (vector_dim=3072)\n",
|
||||||
|
"[INFO] Creando objeto Nota con título: fdsfdsfsdfewww\n",
|
||||||
|
"[DEBUG] Nota creada: titulo='fdsfdsfsdfewww', texto_len=0, tags=0, conexiones=0, resumen_len=0\n",
|
||||||
|
"[INFO] Generando tabla y modelo de Nota para la biblioteca: biblio_Pruebas_1\n",
|
||||||
|
"[INFO] Creando tabla en la base de datos si no existe...\n",
|
||||||
|
"[INFO] Tabla 'biblio_pruebas_1' verificada/creada.\n",
|
||||||
|
"[INFO] Guardando nota en la base de datos...\n",
|
||||||
|
"[INFO] Nota guardada con ID: NOTA20250511-0cf0187e58905045667\n",
|
||||||
|
"[SUCCESS] Nota 'fdsfdsfsdfewww' agregada con éxito a la biblioteca 'biblio_Pruebas_1'.\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"{'mensaje': \"Nota 'fdsfdsfsdfewww' agregada con éxito a la biblioteca 'biblio_Pruebas_1'.\",\n",
|
||||||
|
" 'nota_id': 'NOTA20250511-0cf0187e58905045667'}"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 9,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"from entrypoint.init_db import db_credencial\n",
|
||||||
|
"conexion_admin = PostgresConexion(db_credencial)\n",
|
||||||
|
"\n",
|
||||||
|
"agregar_nota_a_biblioteca(\n",
|
||||||
|
" conexion=conexion_admin,\n",
|
||||||
|
" biblioteca_id=\"BBLI20250511-a91dbb2168172979414\",\n",
|
||||||
|
" titulo=\"fdsfdsfsdfewww\"\n",
|
||||||
|
")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 12,
|
||||||
|
"id": "431f24f1",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def listar_notas_de_biblioteca(conexion: PostgresConexion, biblioteca_id: str) -> list[dict]:\n",
|
||||||
|
" repo_biblioteca = BibliotecaRepo(conexion)\n",
|
||||||
|
" biblioteca = repo_biblioteca.get_by_id(biblioteca_id)\n",
|
||||||
|
" if not biblioteca:\n",
|
||||||
|
" raise ValueError(f\"No se encontró la biblioteca con ID: {biblioteca_id}\")\n",
|
||||||
|
"\n",
|
||||||
|
" metadata = MetaData()\n",
|
||||||
|
" tabla, ModeloNota = generar_tabla_nota_para_biblioteca(biblioteca.nombre, biblioteca.vector_dim, metadata)\n",
|
||||||
|
" metadata.create_all(conexion.get_engine())\n",
|
||||||
|
"\n",
|
||||||
|
" repo_nota = NotaRepo(conexion.get_session(), ModeloNota)\n",
|
||||||
|
" notas = repo_nota.get_all()\n",
|
||||||
|
" return [\n",
|
||||||
|
" {\n",
|
||||||
|
" \"id\": n.id,\n",
|
||||||
|
" \"titulo\": n.titulo,\n",
|
||||||
|
" \"tags\": n.tags,\n",
|
||||||
|
" \"texto\": n.texto,\n",
|
||||||
|
" \"resumen\": n.resumen,\n",
|
||||||
|
" \"conexiones\": n.conexiones\n",
|
||||||
|
" }\n",
|
||||||
|
" for n in notas\n",
|
||||||
|
" ]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 13,
|
||||||
|
"id": "ae4f2994",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"[{'id': 'NOTA20250511-6cf9b177a2249549641',\n",
|
||||||
|
" 'titulo': 'Hola que tal',\n",
|
||||||
|
" 'tags': [],\n",
|
||||||
|
" 'texto': '',\n",
|
||||||
|
" 'resumen': '',\n",
|
||||||
|
" 'conexiones': []},\n",
|
||||||
|
" {'id': 'NOTA20250511-664831e1bd118315114',\n",
|
||||||
|
" 'titulo': 'Holoooo',\n",
|
||||||
|
" 'tags': [],\n",
|
||||||
|
" 'texto': '',\n",
|
||||||
|
" 'resumen': '',\n",
|
||||||
|
" 'conexiones': []},\n",
|
||||||
|
" {'id': 'NOTA20250511-04dbb203a9126228444',\n",
|
||||||
|
" 'titulo': 'sajdhasjdhasjdh',\n",
|
||||||
|
" 'tags': [],\n",
|
||||||
|
" 'texto': '',\n",
|
||||||
|
" 'resumen': '',\n",
|
||||||
|
" 'conexiones': []},\n",
|
||||||
|
" {'id': 'NOTA20250511-668f68e425271569668',\n",
|
||||||
|
" 'titulo': 'fsdfdf',\n",
|
||||||
|
" 'tags': [],\n",
|
||||||
|
" 'texto': '',\n",
|
||||||
|
" 'resumen': '',\n",
|
||||||
|
" 'conexiones': []},\n",
|
||||||
|
" {'id': 'NOTA20250511-a6ad7166d5660286632',\n",
|
||||||
|
" 'titulo': 'fsdfdf',\n",
|
||||||
|
" 'tags': [],\n",
|
||||||
|
" 'texto': '',\n",
|
||||||
|
" 'resumen': '',\n",
|
||||||
|
" 'conexiones': []},\n",
|
||||||
|
" {'id': 'NOTA20250511-639fa883c2266940759',\n",
|
||||||
|
" 'titulo': 'fsdfdf',\n",
|
||||||
|
" 'tags': [],\n",
|
||||||
|
" 'texto': '',\n",
|
||||||
|
" 'resumen': '',\n",
|
||||||
|
" 'conexiones': []},\n",
|
||||||
|
" {'id': 'NOTA20250511-27e86a8da6529002860',\n",
|
||||||
|
" 'titulo': 'fsdfdf',\n",
|
||||||
|
" 'tags': [],\n",
|
||||||
|
" 'texto': '',\n",
|
||||||
|
" 'resumen': '',\n",
|
||||||
|
" 'conexiones': []},\n",
|
||||||
|
" {'id': 'NOTA20250511-06479aca23973772356',\n",
|
||||||
|
" 'titulo': 'fsdfdf',\n",
|
||||||
|
" 'tags': [],\n",
|
||||||
|
" 'texto': '',\n",
|
||||||
|
" 'resumen': '',\n",
|
||||||
|
" 'conexiones': []},\n",
|
||||||
|
" {'id': 'NOTA20250511-0d577e3336174429305',\n",
|
||||||
|
" 'titulo': 'fdsfdsfsdfewww',\n",
|
||||||
|
" 'tags': [],\n",
|
||||||
|
" 'texto': '',\n",
|
||||||
|
" 'resumen': '',\n",
|
||||||
|
" 'conexiones': []},\n",
|
||||||
|
" {'id': 'NOTA20250511-0cf0187e58905045667',\n",
|
||||||
|
" 'titulo': 'fdsfdsfsdfewww',\n",
|
||||||
|
" 'tags': [],\n",
|
||||||
|
" 'texto': '',\n",
|
||||||
|
" 'resumen': '',\n",
|
||||||
|
" 'conexiones': []}]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 13,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"listar_notas_de_biblioteca(\n",
|
||||||
|
" conexion=conexion_admin,\n",
|
||||||
|
" biblioteca_id=\"BBLI20250511-a91dbb2168172979414\"\n",
|
||||||
|
")"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": ".venv",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.11.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 1,
|
||||||
|
"id": "b02bfb00",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Your branch is up to date with 'origin/main'.\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stderr",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Switched to branch 'main'\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"!git checkout main"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 2,
|
||||||
|
"id": "92d482e6",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Updating 234639a..20173e9\n",
|
||||||
|
"Fast-forward\n",
|
||||||
|
" .gitignore | 11 +\n",
|
||||||
|
" Apikeys.ipynb | 128 +--\n",
|
||||||
|
" {llms/MCPs => backend}/__init__.py | 0\n",
|
||||||
|
" {llms/Modelos => backend/api}/__init__.py | 0\n",
|
||||||
|
" {llms => backend/api/v1}/__init__.py | 0\n",
|
||||||
|
" backend/api/v1/endpoints/ping.py | 9 +\n",
|
||||||
|
" backend/api/v1/router.py | 7 +\n",
|
||||||
|
" {security => backend/deps}/__init__.py | 0\n",
|
||||||
|
" backend/deps/auth.py | 0\n",
|
||||||
|
" backend/main.py | 24 +\n",
|
||||||
|
" data/files/txt/tree.txt | 980 +++++++++++++++++++++\n",
|
||||||
|
" entrypoint/init_db.py | 2 +-\n",
|
||||||
|
" frontend/.gitignore | 3 +\n",
|
||||||
|
" frontend/index.html | 4 +-\n",
|
||||||
|
" frontend/package-lock.json | 345 ++++++++\n",
|
||||||
|
" frontend/package.json | 2 +\n",
|
||||||
|
" frontend/src/assets/icons/favicon.svg | 55 ++\n",
|
||||||
|
" frontend/src/assets/icons/favicon.tsx | 37 +\n",
|
||||||
|
" frontend/src/assets/icons/index.ts | 18 +\n",
|
||||||
|
" .../components/{ => Appshell}/Appshell.module.css | 25 +-\n",
|
||||||
|
" .../src/components/{ => Appshell}/Appshell.tsx | 97 +-\n",
|
||||||
|
" frontend/src/components/HoloShader.tsx | 30 +-\n",
|
||||||
|
" frontend/src/components/Links/MainlLinks.tsx | 0\n",
|
||||||
|
" frontend/src/components/LlamadorAPI.tsx | 11 +-\n",
|
||||||
|
" frontend/src/components/MetodoSelect.tsx | 4 +-\n",
|
||||||
|
" frontend/src/components/Welcome/Welcome.tsx | 6 +-\n",
|
||||||
|
" frontend/src/components/botoncito.tsx | 16 -\n",
|
||||||
|
" frontend/src/data/navigationsLinks_1.ts | 20 +\n",
|
||||||
|
" frontend/src/data/submenuLinks_1.ts | 35 +\n",
|
||||||
|
" frontend/src/favicon.svg | 1 -\n",
|
||||||
|
" frontend/src/pages/404.tsx | 55 +-\n",
|
||||||
|
" frontend/src/pages/Analitica.tsx | 6 +-\n",
|
||||||
|
" frontend/src/pages/Consulta_api.tsx | 4 +-\n",
|
||||||
|
" frontend/src/pages/Home.page.tsx | 12 +-\n",
|
||||||
|
" frontend/src/pages/Plantilla.tsx | 2 +-\n",
|
||||||
|
" frontend/src/pages/Prueba_appshell.tsx | 7 +-\n",
|
||||||
|
" frontend/src/theme.ts | 57 +-\n",
|
||||||
|
" frontend/src/types/svg.d.ts | 7 +\n",
|
||||||
|
" frontend/src/types/vite-env.d.ts | 1 +\n",
|
||||||
|
" frontend/tsconfig.json | 2 +-\n",
|
||||||
|
" frontend/{vite.config.mjs => vite.config.js} | 5 +-\n",
|
||||||
|
" frontend/yarn.lock | 160 +++-\n",
|
||||||
|
" llms/Agente.py | 122 ---\n",
|
||||||
|
" llms/Modelos/Openai_model.py | 64 --\n",
|
||||||
|
" main.py | 25 +-\n",
|
||||||
|
" prueba_loop_agente.py | 94 +-\n",
|
||||||
|
" src/ApiKeys/openai_apikey_mmr.py | 2 +-\n",
|
||||||
|
" src/ConexionApis/OpenAi_conexion.py | 15 +-\n",
|
||||||
|
" src/Credenciales/postgres_credencial_mmr.py | 2 +-\n",
|
||||||
|
" src/Llms/Agente.py | 196 +++++\n",
|
||||||
|
" {llms => src/Llms}/MCPs/MCPStdioServer.py | 0\n",
|
||||||
|
" src/Llms/MCPs/__init__.py | 0\n",
|
||||||
|
" {llms => src/Llms}/Memory/Base_MemoryConv.py | 0\n",
|
||||||
|
" {llms => src/Llms}/Memory/postgres_MemoryConv.py | 2 +-\n",
|
||||||
|
" {llms => src/Llms}/Modelos/Base_model.py | 2 +-\n",
|
||||||
|
" src/Llms/Modelos/Openai_model.py | 82 ++\n",
|
||||||
|
" {llms => src/Llms}/Modelos/Openai_model_mmr.py | 2 +-\n",
|
||||||
|
" src/Llms/Modelos/__init__.py | 0\n",
|
||||||
|
" src/Llms/__init__.py | 0\n",
|
||||||
|
" {security => src/Security}/Encriptar.py | 0\n",
|
||||||
|
" src/Security/__init__.py | 0\n",
|
||||||
|
" 61 files changed, 2343 insertions(+), 453 deletions(-)\n",
|
||||||
|
" rename {llms/MCPs => backend}/__init__.py (100%)\n",
|
||||||
|
" rename {llms/Modelos => backend/api}/__init__.py (100%)\n",
|
||||||
|
" rename {llms => backend/api/v1}/__init__.py (100%)\n",
|
||||||
|
" create mode 100644 backend/api/v1/endpoints/ping.py\n",
|
||||||
|
" create mode 100644 backend/api/v1/router.py\n",
|
||||||
|
" rename {security => backend/deps}/__init__.py (100%)\n",
|
||||||
|
" create mode 100644 backend/deps/auth.py\n",
|
||||||
|
" create mode 100644 backend/main.py\n",
|
||||||
|
" create mode 100644 frontend/src/assets/icons/favicon.svg\n",
|
||||||
|
" create mode 100644 frontend/src/assets/icons/favicon.tsx\n",
|
||||||
|
" create mode 100644 frontend/src/assets/icons/index.ts\n",
|
||||||
|
" rename frontend/src/components/{ => Appshell}/Appshell.module.css (82%)\n",
|
||||||
|
" rename frontend/src/components/{ => Appshell}/Appshell.tsx (62%)\n",
|
||||||
|
" create mode 100644 frontend/src/components/Links/MainlLinks.tsx\n",
|
||||||
|
" delete mode 100644 frontend/src/components/botoncito.tsx\n",
|
||||||
|
" create mode 100644 frontend/src/data/navigationsLinks_1.ts\n",
|
||||||
|
" create mode 100644 frontend/src/data/submenuLinks_1.ts\n",
|
||||||
|
" delete mode 100644 frontend/src/favicon.svg\n",
|
||||||
|
" create mode 100644 frontend/src/types/svg.d.ts\n",
|
||||||
|
" create mode 100644 frontend/src/types/vite-env.d.ts\n",
|
||||||
|
" rename frontend/{vite.config.mjs => vite.config.js} (74%)\n",
|
||||||
|
" delete mode 100644 llms/Agente.py\n",
|
||||||
|
" delete mode 100644 llms/Modelos/Openai_model.py\n",
|
||||||
|
" create mode 100644 src/Llms/Agente.py\n",
|
||||||
|
" rename {llms => src/Llms}/MCPs/MCPStdioServer.py (100%)\n",
|
||||||
|
" create mode 100644 src/Llms/MCPs/__init__.py\n",
|
||||||
|
" rename {llms => src/Llms}/Memory/Base_MemoryConv.py (100%)\n",
|
||||||
|
" rename {llms => src/Llms}/Memory/postgres_MemoryConv.py (97%)\n",
|
||||||
|
" rename {llms => src/Llms}/Modelos/Base_model.py (95%)\n",
|
||||||
|
" create mode 100644 src/Llms/Modelos/Openai_model.py\n",
|
||||||
|
" rename {llms => src/Llms}/Modelos/Openai_model_mmr.py (97%)\n",
|
||||||
|
" create mode 100644 src/Llms/Modelos/__init__.py\n",
|
||||||
|
" create mode 100644 src/Llms/__init__.py\n",
|
||||||
|
" rename {security => src/Security}/Encriptar.py (100%)\n",
|
||||||
|
" create mode 100644 src/Security/__init__.py\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"!git merge cambios_frontend\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 3,
|
||||||
|
"id": "91a704a5",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stderr",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"remote: . Processing 1 references \n",
|
||||||
|
"remote: Processed 1 references in total \n",
|
||||||
|
"To http://10.8.0.6:3123/egutierrez/Fitz_Studio.git\n",
|
||||||
|
" 234639a..20173e9 main -> main\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"!git push origin main"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": ".venv",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.11.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# client.py
|
||||||
|
import asyncio
|
||||||
|
from src.Llms.MCPs.Mcp_client import MCPClient
|
||||||
|
from src.Llms.MCPs.Http_mcp_server import HttpMCPServer
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
client = MCPClient()
|
||||||
|
|
||||||
|
client.register_server(HttpMCPServer(
|
||||||
|
name="tools",
|
||||||
|
path="IGNORED_IN_CLIENT", # no importa aquí
|
||||||
|
host="127.0.0.1",
|
||||||
|
port=4300,
|
||||||
|
path_http="/tools"
|
||||||
|
))
|
||||||
|
|
||||||
|
await client.connect_all()
|
||||||
|
|
||||||
|
result = await client.call_tool({
|
||||||
|
"server": "tools",
|
||||||
|
"tool": "get_hostname",
|
||||||
|
"input": {}
|
||||||
|
})
|
||||||
|
print("RESULT:", result)
|
||||||
|
|
||||||
|
await client.disconnect_all()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
+100
-42
@@ -7,53 +7,111 @@ from src.ConexionApis.OpenAi_conexion import OpenAICliente
|
|||||||
from src.Llms.Modelos.Openai_model import ModeloOpenAI
|
from src.Llms.Modelos.Openai_model import ModeloOpenAI
|
||||||
from src.Llms.Agente import AgenteAI
|
from src.Llms.Agente import AgenteAI
|
||||||
from src.Llms.Memory.postgres_MemoryConv import MemoryConvPostgres
|
from src.Llms.Memory.postgres_MemoryConv import MemoryConvPostgres
|
||||||
|
from fastmcp.client.transports import StreamableHttpTransport
|
||||||
|
from fastmcp.client import Client
|
||||||
|
from src.Llms.MCPs.McpClient import MCPClient # ya tienes esta clase
|
||||||
|
from src.Llms.MCPs.McpClient_Registry import ClientRegistry # o ajusta según tu estructura
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
|
||||||
|
# Usar Credencial openai
|
||||||
|
|
||||||
|
conexion_admin = PostgresConexion(db_credencial)
|
||||||
|
repo = OpenAICredencialRepo(conexion_admin)
|
||||||
|
credencial_openai = repo.get_by_id("OPAK20250513-61b29978b7604031014")
|
||||||
|
cliente = OpenAICliente(credencial_openai)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
conexion_admin = PostgresConexion(db_credencial)
|
# crea el modelo (openai)
|
||||||
|
|
||||||
repo = OpenAICredencialRepo(conexion_admin)
|
modelo = ModeloOpenAI(
|
||||||
credencial_openai = repo.get_by_id(1)
|
cliente=cliente,
|
||||||
|
model="gpt-4o",
|
||||||
cliente = OpenAICliente(credencial_openai)
|
temperature=1,
|
||||||
|
top_p=1.0
|
||||||
modelo = ModeloOpenAI(
|
|
||||||
cliente=cliente,
|
|
||||||
model="gpt-4o",
|
|
||||||
temperature=1,
|
|
||||||
top_p=1.0
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
memoria = MemoryConvPostgres(
|
|
||||||
credencial=db_credencial,
|
|
||||||
nombre_tabla="memoria_conversacion_pruebas",
|
|
||||||
k=10
|
|
||||||
)
|
|
||||||
|
|
||||||
agente2 = AgenteAI(
|
|
||||||
modelo=modelo,
|
|
||||||
nombre="Experto en Astronomía",
|
|
||||||
descripcion="Un experto en astronomía que responde preguntas sobre el universo.",
|
|
||||||
system_prompt="Actúa como un experto en astronomía y astrofísica con experiencia académica y práctica en observación astronómica, física estelar, cosmología, mecánica orbital y análisis de datos astronómicos. Cuando respondas, utiliza lenguaje técnico pero accesible para alguien con conocimientos intermedios en física y matemáticas. Siempre que sea posible, incluye explicaciones detalladas, ejemplos numéricos y referencias a teorías o descubrimientos relevantes (por ejemplo, relatividad general, evolución estelar, espectroscopía, etc.). No simplifiques en exceso. Si la pregunta tiene múltiples dimensiones (como observacional y teórica), aborda todas. ¿Estás listo para empezar?",
|
|
||||||
rol="astronomo",
|
|
||||||
max_iterations=5,
|
|
||||||
memoria=memoria,
|
|
||||||
objetivos=["Responder preguntas sobre astronomía y astrofísica", "Proporcionar explicaciones detalladas y ejemplos numéricos"],
|
|
||||||
)
|
|
||||||
|
|
||||||
async def probar_interaccion_stream():
|
|
||||||
print("Respuesta en streaming:\n")
|
|
||||||
|
|
||||||
# Paso 1: espera la corutina para obtener el generador
|
|
||||||
respuesta_gen = await agente2.interactuar_en_bucle(
|
|
||||||
"¿Hacia qué va orbitando cada astro del espacio? responde jerárquicamente",
|
|
||||||
stream=True
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Paso 2: itera sobre el generador
|
# Le otorga memoria
|
||||||
async for token in respuesta_gen:
|
|
||||||
print(token, end="", flush=True)
|
memoria = MemoryConvPostgres(
|
||||||
|
credencial=db_credencial,
|
||||||
|
nombre_tabla="memoria_conversacion_pruebas",
|
||||||
|
k=10
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Cargamos las herramientas
|
||||||
|
|
||||||
|
herramientas = MCPClient.from_http(
|
||||||
|
name="tools",
|
||||||
|
url="http://127.0.0.1:4300/tools/"
|
||||||
|
)
|
||||||
|
|
||||||
|
math = MCPClient.from_http(
|
||||||
|
name="math",
|
||||||
|
url="http://127.0.0.1:4200/math/"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Las añadimos al registro de herramientas
|
||||||
|
|
||||||
|
registry = ClientRegistry()
|
||||||
|
|
||||||
|
|
||||||
|
registry.add("tools", herramientas)
|
||||||
|
registry.add("math", math)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# --- INICIALIZACIÓN DEL AGENTE ---
|
||||||
|
agente2 = AgenteAI(
|
||||||
|
modelo=modelo,
|
||||||
|
nombre="Asistente Inteligente",
|
||||||
|
descripcion="Un asistente conversacional versátil, capaz de resolver problemas, acceder a herramientas y proporcionar respuestas útiles.",
|
||||||
|
system_prompt=(
|
||||||
|
"Eres un asistente inteligente que ayuda al usuario a resolver tareas, responder preguntas y usar herramientas disponibles si es necesario. "
|
||||||
|
"Debes razonar paso a paso, y si se detecta que una herramienta MCP es útil, actúa generando el bloque MCP apropiado sin dar más explicaciones. "
|
||||||
|
"Siempre estructura tus respuestas con claridad, y termina con <END> cuando creas haber completado la tarea."
|
||||||
|
),
|
||||||
|
rol="asistente",
|
||||||
|
objetivos=[
|
||||||
|
"Resolver tareas del usuario",
|
||||||
|
"Usar herramientas MCP si es útil",
|
||||||
|
"Responder de forma clara y útil"
|
||||||
|
],
|
||||||
|
|
||||||
|
# max_iterations=3,
|
||||||
|
# memoria=memoria,
|
||||||
|
|
||||||
|
mcp=registry # ← ✅ Integración del cliente MCP
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- FUNCIÓN DE EJECUCIÓN ---
|
||||||
|
async def probar_interaccion_stream():
|
||||||
|
# # 🔌 Conectar a los servidores MCP registrados
|
||||||
|
# await mcp_client.connect_all()
|
||||||
|
|
||||||
|
print("Respuesta en streaming:\n")
|
||||||
|
respuesta_gen = await agente2.interactuar_en_bucle(
|
||||||
|
"¿Cuál es mi nombre de usuario en este sistema?",
|
||||||
|
stream=True
|
||||||
|
)
|
||||||
|
|
||||||
|
async for token in respuesta_gen:
|
||||||
|
print(token, end="", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
await probar_interaccion_stream()
|
||||||
|
|
||||||
|
|
||||||
|
# Ejecutar
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
asyncio.run(probar_interaccion_stream())
|
|
||||||
|
|||||||
+19
-64
@@ -1,74 +1,29 @@
|
|||||||
import asyncio
|
|
||||||
from llms.MCPs.MCPStdioServer import MCPStdioServer
|
|
||||||
import os
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
prueba = MCPStdioServer(
|
|
||||||
name="prueba_server_mcp",
|
|
||||||
command="C:/Users/lucas/Desktop/mcps/.venv/Scripts/python.exe",
|
|
||||||
args=["C:/Users/lucas/Desktop/mcps/server_mcp_python/server_mcp.py"],
|
|
||||||
)
|
|
||||||
await prueba.start()
|
|
||||||
print("Herramientas:", prueba.get_tool_names())
|
|
||||||
|
|
||||||
await prueba.stop() # <- esto previene el error
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# Asegura compatibilidad para subprocess en Windows
|
|
||||||
if os.name == "nt":
|
|
||||||
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
asyncio.run(main())
|
async def test_registry(registry: ClientRegistry):
|
||||||
|
tools = await registry.listar_tools_por_cliente()
|
||||||
|
prompts = await registry.listar_prompts_por_cliente()
|
||||||
|
resources = await registry.listar_resources_por_cliente()
|
||||||
|
|
||||||
|
print("\n🔧 Herramientas:", tools)
|
||||||
|
|
||||||
|
|
||||||
|
print("\n📋 Prompts:", prompts)
|
||||||
|
|
||||||
|
print("\n📂 Resources:", resources)
|
||||||
|
|
||||||
|
|
||||||
|
asyncio.run(test_registry(registry))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def test_wrapper():
|
||||||
|
|
||||||
from src.ApiKeys.openai_apikey import OpenAICredencial
|
# 2. Llamar a una herramienta de prueba
|
||||||
from src.ApiKeys.openai_apikey_mmr import OpenAICredencialRepo # Ajusta si está en otro módulo
|
result = await herramientas.call_tool("generate_uuid")
|
||||||
from src.ConexionSql.Postgres_conexion import PostgresConexion
|
print("\n🆔 UUID generado:", result[0].text) # Accedemos al contenido directamente
|
||||||
|
|
||||||
# 1. Crear instancia de conexión (asegúrate de configurar bien tu conexión en Base_conexion)
|
|
||||||
from entrypoint.init_db import db_credencial
|
|
||||||
conexion_admin = PostgresConexion(db_credencial)
|
|
||||||
|
|
||||||
# 3. Guardar la credencial en la base de datos
|
|
||||||
repo = OpenAICredencialRepo(conexion_admin)
|
|
||||||
credencial_openai = repo.get_by_id(1)
|
|
||||||
print(f"✅ Credencial: {credencial_openai.titulo}")
|
|
||||||
|
|
||||||
from src.ConexionApis.OpenAi_conexion import OpenAICliente
|
|
||||||
|
|
||||||
cliente = OpenAICliente(credencial_openai)
|
|
||||||
|
|
||||||
from llms.Modelos.Openai_model import ModeloOpenAI
|
|
||||||
|
|
||||||
modelo = ModeloOpenAI(
|
|
||||||
cliente=cliente,
|
|
||||||
model="gpt-4o",
|
|
||||||
temperature=1,
|
|
||||||
top_p=1.0
|
|
||||||
)
|
|
||||||
|
|
||||||
from llms.Agente import AgenteAI
|
|
||||||
|
|
||||||
|
|
||||||
agente_con_herramientas = AgenteAI(
|
# asyncio.run(test_wrapper())
|
||||||
modelo=modelo,
|
|
||||||
nombre="Agente con herramientas",
|
|
||||||
descripcion="Un agente que puede usar herramientas",
|
|
||||||
system_prompt="Eres un asistente que puede usar herramientas para responder preguntas.",
|
|
||||||
rol="asistente",
|
|
||||||
objetivos=["Asistir al usuario en tareas complejas", "usar herramientas para obtener información adicional"]
|
|
||||||
# tools=
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
respuesta = agente_con_herramientas.interactuar(
|
|
||||||
prompt="Hola como estas?",
|
|
||||||
)
|
|
||||||
|
|
||||||
print(respuesta)
|
|
||||||
@@ -28,3 +28,4 @@ def save_tree_to_file(start_path='.', max_depth=2, output_file='tree.txt'):
|
|||||||
# Ejemplo de uso:
|
# Ejemplo de uso:
|
||||||
# Puedes cambiar estos valores según lo necesites
|
# Puedes cambiar estos valores según lo necesites
|
||||||
save_tree_to_file(start_path=r'E:\Fitz_Studio', max_depth=3, output_file=r'E:\Fitz_Studio\data\files\txt\tree.txt')
|
save_tree_to_file(start_path=r'E:\Fitz_Studio', max_depth=3, output_file=r'E:\Fitz_Studio\data\files\txt\tree.txt')
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
from src.Security.GenerarIDs import GeneradorIDUnico
|
||||||
|
|
||||||
class OpenAICredencial:
|
class OpenAICredencial:
|
||||||
def __init__(self, titulo: str, api_key: str, organizacion: str = None):
|
def __init__(self, titulo: str, api_key: str, organizacion: str = None, id: str = None):
|
||||||
"""
|
"""
|
||||||
:param titulo: Nombre descriptivo para esta credencial.
|
:param titulo: Nombre descriptivo para esta credencial.
|
||||||
:param api_key: Clave secreta de la API de OpenAI.
|
:param api_key: Clave secreta de la API de OpenAI.
|
||||||
:param organizacion: (Opcional) ID de la organización asociada a la cuenta 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.titulo = titulo
|
||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
self.organizacion = organizacion
|
self.organizacion = organizacion
|
||||||
|
|||||||
@@ -8,6 +8,13 @@ from src.base import Base
|
|||||||
from src.ApiKeys.openai_apikey import OpenAICredencial
|
from src.ApiKeys.openai_apikey import OpenAICredencial
|
||||||
from src.Security.Encriptar import Encriptar_fernet
|
from src.Security.Encriptar import Encriptar_fernet
|
||||||
from entrypoint import ENV_PATH
|
from entrypoint import ENV_PATH
|
||||||
|
from src.ArquitectureLayer.Mapper import Mapper_base
|
||||||
|
|
||||||
|
|
||||||
|
from sqlalchemy import Column, String
|
||||||
|
from src.ArquitectureLayer.Model import Model_base
|
||||||
|
from src.ArquitectureLayer.Repo import Repo_base
|
||||||
|
|
||||||
|
|
||||||
# ----------------------
|
# ----------------------
|
||||||
# Cargar clave maestra
|
# Cargar clave maestra
|
||||||
@@ -21,10 +28,10 @@ if pssword is None:
|
|||||||
# MODELO (SQLAlchemy)
|
# MODELO (SQLAlchemy)
|
||||||
# ----------------------
|
# ----------------------
|
||||||
|
|
||||||
class OpenAICredencialModel(Base):
|
class OpenAICredencialModel(Base, Model_base):
|
||||||
__tablename__ = 'openai_credenciales'
|
__tablename__ = 'openai_credenciales'
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(String, primary_key=True)
|
||||||
titulo = Column(String, nullable=False)
|
titulo = Column(String, nullable=False)
|
||||||
api_key = Column(String, nullable=False) # Encriptada como base64 string
|
api_key = Column(String, nullable=False) # Encriptada como base64 string
|
||||||
organizacion = Column(String, nullable=True)
|
organizacion = Column(String, nullable=True)
|
||||||
@@ -33,30 +40,23 @@ class OpenAICredencialModel(Base):
|
|||||||
# MAPPER
|
# MAPPER
|
||||||
# ----------------------
|
# ----------------------
|
||||||
|
|
||||||
class OpenAICredencialMapper:
|
class OpenAICredencialMapper(Mapper_base[OpenAICredencial, OpenAICredencialModel]):
|
||||||
@staticmethod
|
|
||||||
def to_dict(obj: OpenAICredencial) -> dict:
|
|
||||||
return {
|
|
||||||
"titulo": obj.titulo,
|
|
||||||
"api_key": base64.b64encode(
|
|
||||||
Encriptar_fernet.encriptar(obj.api_key, pssword)
|
|
||||||
).decode('utf-8'),
|
|
||||||
"organizacion": obj.organizacion
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_dict(data: dict) -> OpenAICredencial:
|
def to_model(obj: OpenAICredencial) -> OpenAICredencialModel:
|
||||||
return OpenAICredencial(
|
return OpenAICredencialModel(
|
||||||
titulo=data["titulo"],
|
id=obj.id,
|
||||||
api_key=Encriptar_fernet.desencriptar(
|
titulo=obj.titulo,
|
||||||
base64.b64decode(data["api_key"]), pssword
|
api_key=base64.b64encode(
|
||||||
),
|
Encriptar_fernet.encriptar(obj.api_key, pssword)
|
||||||
organizacion=data.get("organizacion")
|
).decode("utf-8"),
|
||||||
|
organizacion=obj.organizacion
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_model(model: OpenAICredencialModel) -> OpenAICredencial:
|
def from_model(model: OpenAICredencialModel) -> OpenAICredencial:
|
||||||
return OpenAICredencial(
|
return OpenAICredencial(
|
||||||
|
id=model.id,
|
||||||
titulo=model.titulo,
|
titulo=model.titulo,
|
||||||
api_key=Encriptar_fernet.desencriptar(
|
api_key=Encriptar_fernet.desencriptar(
|
||||||
base64.b64decode(model.api_key), pssword
|
base64.b64decode(model.api_key), pssword
|
||||||
@@ -64,29 +64,44 @@ class OpenAICredencialMapper:
|
|||||||
organizacion=model.organizacion
|
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
|
# REPO
|
||||||
# ----------------------
|
# ----------------------
|
||||||
|
|
||||||
class OpenAICredencialRepo:
|
class OpenAICredencialRepo(Repo_base[OpenAICredencialModel, OpenAICredencial]):
|
||||||
def __init__(self, conexion: ConexionBase):
|
def __init__(self, conexion: ConexionBase):
|
||||||
self.session = conexion.get_session()
|
super().__init__(
|
||||||
|
session=conexion.get_session(),
|
||||||
def add(self, credencial: OpenAICredencial) -> int:
|
modelo=OpenAICredencialModel,
|
||||||
data = OpenAICredencialMapper.to_dict(credencial)
|
mapper=OpenAICredencialMapper
|
||||||
model = OpenAICredencialModel(**data)
|
)
|
||||||
self.session.add(model)
|
|
||||||
self.session.commit()
|
|
||||||
return model.id
|
|
||||||
|
|
||||||
def get_all(self) -> list[OpenAICredencial]:
|
|
||||||
models = self.session.query(OpenAICredencialModel).all()
|
|
||||||
return [OpenAICredencialMapper.from_model(m) for m in models]
|
|
||||||
|
|
||||||
def get_by_titulo(self, titulo: str) -> OpenAICredencial | None:
|
def get_by_titulo(self, titulo: str) -> OpenAICredencial | None:
|
||||||
model = self.session.query(OpenAICredencialModel).filter_by(titulo=titulo).first()
|
model = (
|
||||||
return OpenAICredencialMapper.from_model(model) if model else None
|
self.session.query(self.Modelo)
|
||||||
|
.filter_by(titulo=titulo, sys_deleted_at=None)
|
||||||
def get_by_id(self, id_: int) -> OpenAICredencial | None:
|
.first()
|
||||||
model = self.session.get(OpenAICredencialModel, id_)
|
)
|
||||||
return OpenAICredencialMapper.from_model(model) if model else None
|
return self.Mapper.from_model(model) if model else None
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
# src\ArquitectureLayer\Mapper.py
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import TypeVar, Generic, Type
|
||||||
|
import json
|
||||||
|
|
||||||
|
TDominio = TypeVar("TDominio")
|
||||||
|
TModelo = TypeVar("TModelo")
|
||||||
|
|
||||||
|
class Mapper_base(ABC, Generic[TDominio, TModelo]):
|
||||||
|
# ----------------------------
|
||||||
|
# Conversiones individuales
|
||||||
|
# ----------------------------
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@abstractmethod
|
||||||
|
def to_model(obj: TDominio) -> TModelo:
|
||||||
|
"""Convierte objeto de dominio a modelo ORM"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@abstractmethod
|
||||||
|
def from_model(model: TModelo) -> TDominio:
|
||||||
|
"""Convierte modelo ORM a objeto de dominio"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@abstractmethod
|
||||||
|
def to_dict(obj: TDominio) -> dict:
|
||||||
|
"""Convierte objeto de dominio a diccionario plano"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@abstractmethod
|
||||||
|
def from_dict(d: dict) -> TDominio:
|
||||||
|
"""Convierte diccionario plano a objeto de dominio"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_json(cls, obj: TDominio) -> str:
|
||||||
|
"""Convierte objeto de dominio a JSON string"""
|
||||||
|
return json.dumps(cls.to_dict(obj), default=str)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json(cls, json_str: str) -> TDominio:
|
||||||
|
"""Convierte JSON string a objeto de dominio"""
|
||||||
|
return cls.from_dict(json.loads(json_str))
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# Conversiones en lote (bulk)
|
||||||
|
# ----------------------------
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_model_list(cls, objs: list[TDominio]) -> list[TModelo]:
|
||||||
|
return [cls.to_model(o) for o in objs]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_model_list(cls, models: list[TModelo]) -> list[TDominio]:
|
||||||
|
return [cls.from_model(m) for m in models]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_dict_list(cls, objs: list[TDominio]) -> list[dict]:
|
||||||
|
return [cls.to_dict(o) for o in objs]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict_list(cls, dicts: list[dict]) -> list[TDominio]:
|
||||||
|
return [cls.from_dict(d) for d in dicts]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_json_list(cls, objs: list[TDominio]) -> str:
|
||||||
|
return json.dumps(cls.to_dict_list(objs), default=str)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json_list(cls, json_str: str) -> list[TDominio]:
|
||||||
|
return cls.from_dict_list(json.loads(json_str))
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# src\ArquitectureLayer\Model.py
|
||||||
|
|
||||||
|
from sqlalchemy import Column, DateTime, String, Integer, Text, func
|
||||||
|
from sqlalchemy.ext.declarative import declared_attr, as_declarative
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
@as_declarative()
|
||||||
|
class Model_base:
|
||||||
|
__abstract__ = True
|
||||||
|
|
||||||
|
@declared_attr
|
||||||
|
def sys_created_at(cls):
|
||||||
|
return Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
||||||
|
|
||||||
|
@declared_attr
|
||||||
|
def sys_created_by(cls):
|
||||||
|
return Column(String, nullable=True)
|
||||||
|
|
||||||
|
@declared_attr
|
||||||
|
def sys_updated_at(cls):
|
||||||
|
return Column(DateTime(timezone=True), onupdate=func.now(), nullable=True)
|
||||||
|
|
||||||
|
@declared_attr
|
||||||
|
def sys_updated_by(cls):
|
||||||
|
return Column(String, nullable=True)
|
||||||
|
|
||||||
|
@declared_attr
|
||||||
|
def sys_version(cls):
|
||||||
|
return Column(Integer, default=1, nullable=False)
|
||||||
|
|
||||||
|
@declared_attr
|
||||||
|
def sys_notes(cls):
|
||||||
|
return Column(Text, nullable=True)
|
||||||
|
|
||||||
|
@declared_attr
|
||||||
|
def sys_deleted_at(cls):
|
||||||
|
return Column(DateTime(timezone=True), nullable=True)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
id_val = getattr(self, "id", None)
|
||||||
|
return f"<{self.__class__.__name__} id={id_val}>"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
cls = self.__class__.__name__
|
||||||
|
id_val = getattr(self, "id", None)
|
||||||
|
return f"{cls}(id={id_val})"
|
||||||
|
|
||||||
|
def __json__(self) -> dict:
|
||||||
|
"""Devuelve una representación JSON serializable (dict plano)."""
|
||||||
|
out = {}
|
||||||
|
|
||||||
|
# Prevención de error: solo ejecuta si __table__ existe
|
||||||
|
if not hasattr(self, "__table__"):
|
||||||
|
return out
|
||||||
|
|
||||||
|
for attr in self.__table__.columns:
|
||||||
|
val = getattr(self, attr.name, None)
|
||||||
|
if isinstance(val, datetime):
|
||||||
|
out[attr.name] = val.isoformat()
|
||||||
|
else:
|
||||||
|
out[attr.name] = val
|
||||||
|
|
||||||
|
return out
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
# src\ArquitectureLayer\Repo.py
|
||||||
|
|
||||||
|
from abc import ABC
|
||||||
|
from typing import Type, TypeVar, Generic, Optional
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from sqlalchemy import func
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from src.ArquitectureLayer.Mapper import Mapper_base # Asegúrate de importar tu ABC base
|
||||||
|
|
||||||
|
TModelo = TypeVar("TModelo")
|
||||||
|
TDominio = TypeVar("TDominio")
|
||||||
|
|
||||||
|
class Repo_base(ABC, Generic[TModelo, TDominio]):
|
||||||
|
def __init__(self, session: Session, modelo: type[TModelo], mapper: type[Mapper_base[TDominio, TModelo]]):
|
||||||
|
self.session = session
|
||||||
|
self.Modelo = modelo
|
||||||
|
self.Mapper = mapper
|
||||||
|
|
||||||
|
# ----------------------
|
||||||
|
# ADD
|
||||||
|
# ----------------------
|
||||||
|
|
||||||
|
def add(self, dominio: TDominio, created_by: Optional[str] = None, notes: Optional[str] = None) -> str:
|
||||||
|
data = self.Mapper.to_dict(dominio)
|
||||||
|
data.update({
|
||||||
|
"sys_created_by": created_by,
|
||||||
|
"sys_notes": notes,
|
||||||
|
"sys_version": 1
|
||||||
|
})
|
||||||
|
model = self.Modelo(**data)
|
||||||
|
self.session.add(model)
|
||||||
|
self.session.commit()
|
||||||
|
return model.id
|
||||||
|
|
||||||
|
def add_many(self, dominios: list[TDominio], created_by: Optional[str] = None, notes: Optional[str] = None) -> list[str]:
|
||||||
|
ids = []
|
||||||
|
for dominio in dominios:
|
||||||
|
data = self.Mapper.to_dict(dominio)
|
||||||
|
data.update({
|
||||||
|
"sys_created_by": created_by,
|
||||||
|
"sys_notes": notes,
|
||||||
|
"sys_version": 1
|
||||||
|
})
|
||||||
|
model = self.Modelo(**data)
|
||||||
|
self.session.add(model)
|
||||||
|
ids.append(model.id)
|
||||||
|
self.session.commit()
|
||||||
|
return ids
|
||||||
|
|
||||||
|
# ----------------------
|
||||||
|
# GET
|
||||||
|
# ----------------------
|
||||||
|
|
||||||
|
def get_by_id(self, id_: str) -> Optional[TDominio]:
|
||||||
|
model = self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first()
|
||||||
|
return self.Mapper.from_model(model) if model else None
|
||||||
|
|
||||||
|
def get_all(self) -> list[TDominio]:
|
||||||
|
models = self.session.query(self.Modelo).filter_by(sys_deleted_at=None).all()
|
||||||
|
return self.Mapper.from_model_list(models)
|
||||||
|
|
||||||
|
def get_paginated(self, offset: int = 0, limit: int = 10) -> list[TDominio]:
|
||||||
|
models = self.session.query(self.Modelo).filter_by(sys_deleted_at=None).offset(offset).limit(limit).all()
|
||||||
|
return self.Mapper.from_model_list(models)
|
||||||
|
|
||||||
|
def get_deleted(self) -> list[TDominio]:
|
||||||
|
models = self.session.query(self.Modelo).filter(self.Modelo.sys_deleted_at.isnot(None)).all()
|
||||||
|
return self.Mapper.from_model_list(models)
|
||||||
|
|
||||||
|
# ----------------------
|
||||||
|
# UPDATE
|
||||||
|
# ----------------------
|
||||||
|
|
||||||
|
def update(self, id_: str, new_data: dict, updated_by: Optional[str] = None, notes: Optional[str] = None) -> bool:
|
||||||
|
model = self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first()
|
||||||
|
if not model:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for key, value in new_data.items():
|
||||||
|
if hasattr(model, key):
|
||||||
|
setattr(model, key, value)
|
||||||
|
|
||||||
|
model.sys_updated_by = updated_by
|
||||||
|
model.sys_notes = notes
|
||||||
|
model.sys_version = (model.sys_version or 1) + 1
|
||||||
|
self.session.commit()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def bulk_update(self, updates: list[tuple[str, dict]], updated_by: Optional[str] = None, notes: Optional[str] = None) -> int:
|
||||||
|
count = 0
|
||||||
|
for id_, data in updates:
|
||||||
|
if self.update(id_, data, updated_by=updated_by, notes=notes):
|
||||||
|
count += 1
|
||||||
|
return count
|
||||||
|
|
||||||
|
# ----------------------
|
||||||
|
# DELETE
|
||||||
|
# ----------------------
|
||||||
|
|
||||||
|
def delete_by_id(self, id_: str) -> bool:
|
||||||
|
model = self.session.query(self.Modelo).filter_by(id=id_).first()
|
||||||
|
if model:
|
||||||
|
self.session.delete(model)
|
||||||
|
self.session.commit()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete_all(self) -> int:
|
||||||
|
deleted = self.session.query(self.Modelo).delete()
|
||||||
|
self.session.commit()
|
||||||
|
return deleted
|
||||||
|
|
||||||
|
# ----------------------
|
||||||
|
# SOFT DELETE
|
||||||
|
# ----------------------
|
||||||
|
|
||||||
|
def soft_delete(self, id_: str, deleted_by: Optional[str] = None, notes: Optional[str] = None) -> bool:
|
||||||
|
model = self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first()
|
||||||
|
if model:
|
||||||
|
model.sys_deleted_at = datetime.now()
|
||||||
|
model.sys_updated_by = deleted_by
|
||||||
|
model.sys_notes = notes
|
||||||
|
model.sys_version = (model.sys_version or 1) + 1
|
||||||
|
self.session.commit()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def soft_restore(self, id_: str, restored_by: Optional[str] = None, notes: Optional[str] = None) -> bool:
|
||||||
|
model = self.session.query(self.Modelo).filter_by(id=id_).first()
|
||||||
|
if model and model.sys_deleted_at is not None:
|
||||||
|
model.sys_deleted_at = None
|
||||||
|
model.sys_updated_by = restored_by
|
||||||
|
model.sys_notes = notes
|
||||||
|
model.sys_version = (model.sys_version or 1) + 1
|
||||||
|
self.session.commit()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
# ----------------------
|
||||||
|
# OTROS
|
||||||
|
# ----------------------
|
||||||
|
|
||||||
|
def exists(self, id_: str) -> bool:
|
||||||
|
return self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first() is not None
|
||||||
|
|
||||||
|
def count(self) -> int:
|
||||||
|
return self.session.query(self.Modelo).filter_by(sys_deleted_at=None).count()
|
||||||
@@ -1,7 +1,39 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from sqlalchemy.orm import Session
|
from datetime import datetime, timezone
|
||||||
|
from sqlalchemy.orm import Session, sessionmaker
|
||||||
|
from sqlalchemy.engine import Engine
|
||||||
|
from sqlalchemy import create_engine, text
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
|
||||||
class ConexionBase(ABC):
|
class ConexionBase(ABC):
|
||||||
|
def __init__(self, uri: str):
|
||||||
|
self.estado = "pendiente"
|
||||||
|
self.timestamp = datetime.now(timezone.utc)
|
||||||
|
self._engine: Engine = create_engine(uri)
|
||||||
|
self._Session = sessionmaker(bind=self._engine)
|
||||||
|
self._session_instance: Session | None = None
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_session(self) -> Session:
|
def get_session(self) -> Session:
|
||||||
pass
|
if self._session_instance is None:
|
||||||
|
self._session_instance = self._Session()
|
||||||
|
return self._session_instance
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_engine(self) -> Engine:
|
||||||
|
return self._engine
|
||||||
|
|
||||||
|
def probar_conexion(self) -> bool:
|
||||||
|
try:
|
||||||
|
with self._engine.connect() as connection:
|
||||||
|
connection.execute(text("SELECT 1"))
|
||||||
|
self.estado = "exito"
|
||||||
|
return True
|
||||||
|
except SQLAlchemyError:
|
||||||
|
self.estado = "fallo"
|
||||||
|
return False
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self._session_instance is not None:
|
||||||
|
self._session_instance.close()
|
||||||
|
self._session_instance = None
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from sqlalchemy import create_engine, text
|
from sqlalchemy import create_engine, text
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker, Session
|
||||||
|
from sqlalchemy.engine import Engine
|
||||||
|
|
||||||
from src.ConexionSql.Base_conexion import ConexionBase
|
from src.ConexionSql.Base_conexion import ConexionBase
|
||||||
from src.Credenciales.postgres_credencial import PostgresCredencial
|
from src.Credenciales.postgres_credencial import PostgresCredencial
|
||||||
|
|
||||||
|
|
||||||
class PostgresConexion(ConexionBase):
|
class PostgresConexion(ConexionBase):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.estado = "pendiente"
|
self.estado = "pendiente"
|
||||||
@@ -18,28 +18,41 @@ class PostgresConexion(ConexionBase):
|
|||||||
self.port = credencial.port
|
self.port = credencial.port
|
||||||
self.dbname = credencial.dbname
|
self.dbname = credencial.dbname
|
||||||
self.user = credencial.user
|
self.user = credencial.user
|
||||||
self.password = credencial.password # se guarda la contraseña
|
self.password = credencial.password
|
||||||
uri = credencial.get_uri()
|
uri = credencial.get_uri()
|
||||||
else:
|
else:
|
||||||
self.user = kwargs.get("user")
|
self.user = kwargs.get("user")
|
||||||
self.password = kwargs.get("password") # se guarda la contraseña
|
self.password = kwargs.get("password")
|
||||||
self.host = kwargs.get("host")
|
self.host = kwargs.get("host")
|
||||||
self.port = kwargs.get("port", 5432)
|
self.port = kwargs.get("port", 5432)
|
||||||
self.dbname = kwargs.get("db") or kwargs.get("dbname")
|
self.dbname = kwargs.get("db") or kwargs.get("dbname")
|
||||||
uri = f"postgresql://{self.user}:{self.password}@{self.host}:{self.port}/{self.dbname}"
|
uri = f"postgresql://{self.user}:{self.password}@{self.host}:{self.port}/{self.dbname}"
|
||||||
|
|
||||||
self.engine = create_engine(uri)
|
self._engine: Engine = create_engine(uri)
|
||||||
self.SessionLocal = sessionmaker(bind=self.engine)
|
self._Session = sessionmaker(bind=self._engine)
|
||||||
|
|
||||||
def get_session(self):
|
# ✅ INICIALIZAR LA SESIÓN AQUÍ
|
||||||
return self.SessionLocal()
|
self._session_instance: Session | None = None
|
||||||
|
|
||||||
|
def get_session(self) -> Session:
|
||||||
|
if self._session_instance is None:
|
||||||
|
self._session_instance = self._Session()
|
||||||
|
return self._session_instance
|
||||||
|
|
||||||
|
def get_engine(self) -> Engine:
|
||||||
|
return self._engine
|
||||||
|
|
||||||
def probar_conexion(self) -> bool:
|
def probar_conexion(self) -> bool:
|
||||||
try:
|
try:
|
||||||
with self.engine.connect() as connection:
|
with self._engine.connect() as connection:
|
||||||
connection.execute(text("SELECT 1"))
|
connection.execute(text("SELECT 1"))
|
||||||
self.estado = "exito"
|
self.estado = "exito"
|
||||||
return True
|
return True
|
||||||
except SQLAlchemyError:
|
except SQLAlchemyError:
|
||||||
self.estado = "fallo"
|
self.estado = "fallo"
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self._session_instance is not None:
|
||||||
|
self._session_instance.close()
|
||||||
|
self._session_instance = None
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
from src.Security.GenerarIDs import GeneradorIDUnico
|
||||||
|
|
||||||
class PostgresCredencial:
|
class PostgresCredencial:
|
||||||
def __init__(self, titulo: str, host: str, port: int, dbname: str, user: str, password: str):
|
def __init__(self, titulo: str, host: str, port: int, dbname: str, user: str, password: str, id: str = None):
|
||||||
|
self.id = id if id is not None else GeneradorIDUnico("PGCR").generar()
|
||||||
self.titulo = titulo
|
self.titulo = titulo
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
@@ -7,6 +10,7 @@ class PostgresCredencial:
|
|||||||
self.user = user
|
self.user = user
|
||||||
self.password = password
|
self.password = password
|
||||||
|
|
||||||
|
|
||||||
def get_uri(self) -> str:
|
def get_uri(self) -> str:
|
||||||
"""
|
"""
|
||||||
Retorna una URI de conexión para PostgreSQL.
|
Retorna una URI de conexión para PostgreSQL.
|
||||||
|
|||||||
@@ -2,6 +2,13 @@ import os
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from sqlalchemy import Column, Integer, String
|
from sqlalchemy import Column, Integer, String
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
from sqlalchemy import DateTime, Text, func
|
||||||
|
import base64
|
||||||
|
|
||||||
|
|
||||||
|
from src.ArquitectureLayer.Mapper import Mapper_base
|
||||||
|
from src.ArquitectureLayer.Model import Model_base
|
||||||
|
from src.ArquitectureLayer.Repo import Repo_base
|
||||||
|
|
||||||
from src.ConexionSql.Base_conexion import ConexionBase
|
from src.ConexionSql.Base_conexion import ConexionBase
|
||||||
from src.base import Base
|
from src.base import Base
|
||||||
@@ -21,10 +28,10 @@ if pssword is None:
|
|||||||
# MODELO (SQLAlchemy)
|
# MODELO (SQLAlchemy)
|
||||||
# ----------------------
|
# ----------------------
|
||||||
|
|
||||||
class PostgresCredencialModel(Base):
|
class PostgresCredencialModel(Base, Model_base):
|
||||||
__tablename__ = 'postgres_credenciales'
|
__tablename__ = 'postgres_credenciales'
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(String, primary_key=True)
|
||||||
titulo = Column(String, nullable=False)
|
titulo = Column(String, nullable=False)
|
||||||
host = Column(String, nullable=False)
|
host = Column(String, nullable=False)
|
||||||
port = Column(Integer, nullable=False)
|
port = Column(Integer, nullable=False)
|
||||||
@@ -36,12 +43,40 @@ class PostgresCredencialModel(Base):
|
|||||||
# MAPPER
|
# MAPPER
|
||||||
# ----------------------
|
# ----------------------
|
||||||
|
|
||||||
import base64
|
class PostgresCredencialMapper(Mapper_base[PostgresCredencial, PostgresCredencialModel]):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_model(obj: PostgresCredencial) -> PostgresCredencialModel:
|
||||||
|
return PostgresCredencialModel(
|
||||||
|
id=obj.id,
|
||||||
|
titulo=obj.titulo,
|
||||||
|
host=obj.host,
|
||||||
|
port=obj.port,
|
||||||
|
dbname=obj.dbname,
|
||||||
|
user=obj.user,
|
||||||
|
password=base64.b64encode(
|
||||||
|
Encriptar_fernet.encriptar(obj.password, pssword)
|
||||||
|
).decode('utf-8')
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_model(model: PostgresCredencialModel) -> PostgresCredencial:
|
||||||
|
return PostgresCredencial(
|
||||||
|
id=model.id,
|
||||||
|
titulo=model.titulo,
|
||||||
|
host=model.host,
|
||||||
|
port=model.port,
|
||||||
|
dbname=model.dbname,
|
||||||
|
user=model.user,
|
||||||
|
password=Encriptar_fernet.desencriptar(
|
||||||
|
base64.b64decode(model.password), pssword
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
class PostgresCredencialMapper:
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def to_dict(obj: PostgresCredencial) -> dict:
|
def to_dict(obj: PostgresCredencial) -> dict:
|
||||||
return {
|
return {
|
||||||
|
"id": obj.id,
|
||||||
"titulo": obj.titulo,
|
"titulo": obj.titulo,
|
||||||
"host": obj.host,
|
"host": obj.host,
|
||||||
"port": obj.port,
|
"port": obj.port,
|
||||||
@@ -55,6 +90,7 @@ class PostgresCredencialMapper:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def from_dict(data: dict) -> PostgresCredencial:
|
def from_dict(data: dict) -> PostgresCredencial:
|
||||||
return PostgresCredencial(
|
return PostgresCredencial(
|
||||||
|
id=data["id"],
|
||||||
titulo=data["titulo"],
|
titulo=data["titulo"],
|
||||||
host=data["host"],
|
host=data["host"],
|
||||||
port=data["port"],
|
port=data["port"],
|
||||||
@@ -65,42 +101,22 @@ class PostgresCredencialMapper:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_model(model: PostgresCredencialModel) -> PostgresCredencial:
|
|
||||||
return PostgresCredencial(
|
|
||||||
titulo=model.titulo,
|
|
||||||
host=model.host,
|
|
||||||
port=model.port,
|
|
||||||
dbname=model.dbname,
|
|
||||||
user=model.user,
|
|
||||||
password=Encriptar_fernet.desencriptar(
|
|
||||||
base64.b64decode(model.password), pssword
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# ----------------------
|
# ----------------------
|
||||||
# REPO
|
# REPO
|
||||||
# ----------------------
|
# ----------------------
|
||||||
|
|
||||||
class PostgresCredencialRepo:
|
class PostgresCredencialRepo(Repo_base[PostgresCredencialModel, PostgresCredencial]):
|
||||||
def __init__(self, conexion: ConexionBase):
|
def __init__(self, conexion: ConexionBase):
|
||||||
self.session = conexion.get_session()
|
super().__init__(
|
||||||
|
session=conexion.get_session(),
|
||||||
def add(self, credencial: PostgresCredencial) -> int:
|
modelo=PostgresCredencialModel,
|
||||||
data = PostgresCredencialMapper.to_dict(credencial)
|
mapper=PostgresCredencialMapper
|
||||||
model = PostgresCredencialModel(**data)
|
)
|
||||||
self.session.add(model)
|
|
||||||
self.session.commit()
|
|
||||||
return model.id
|
|
||||||
|
|
||||||
def get_all(self) -> list[PostgresCredencial]:
|
|
||||||
models = self.session.query(PostgresCredencialModel).all()
|
|
||||||
return [PostgresCredencialMapper.from_model(m) for m in models]
|
|
||||||
|
|
||||||
def get_by_titulo(self, titulo: str) -> PostgresCredencial | None:
|
def get_by_titulo(self, titulo: str) -> PostgresCredencial | None:
|
||||||
model = self.session.query(PostgresCredencialModel).filter_by(titulo=titulo).first()
|
model = (
|
||||||
return PostgresCredencialMapper.from_model(model) if model else None
|
self.session.query(self.Modelo)
|
||||||
|
.filter_by(titulo=titulo, sys_deleted_at=None)
|
||||||
def get_by_id(self, id_: int) -> PostgresCredencial | None:
|
.first()
|
||||||
model = self.session.get(PostgresCredencialModel, id_)
|
)
|
||||||
return PostgresCredencialMapper.from_model(model) if model else None
|
return self.Mapper.from_model(model) if model else None
|
||||||
+128
-49
@@ -1,6 +1,6 @@
|
|||||||
from src.Llms.Modelos.Base_model import ModeloABC
|
from src.Llms.Modelos.Base_model import ModeloABC
|
||||||
from src.Llms.Memory.Base_MemoryConv import MemoryConvABC
|
from src.Llms.Memory.Base_MemoryConv import MemoryConvABC
|
||||||
|
from src.Llms.MCPs.McpClient_Registry import ClientRegistry
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional, List, Union, AsyncGenerator
|
from typing import Optional, List, Union, AsyncGenerator
|
||||||
|
|
||||||
@@ -17,12 +17,11 @@ class AgenteAI:
|
|||||||
max_iterations: int = 1,
|
max_iterations: int = 1,
|
||||||
memoria: Optional[MemoryConvABC] = None,
|
memoria: Optional[MemoryConvABC] = None,
|
||||||
version: str = "1.0.0",
|
version: str = "1.0.0",
|
||||||
tools: Optional[List] = None,
|
mcp: ClientRegistry = None,
|
||||||
output_schema: Optional[dict] = None,
|
output_schema: Optional[dict] = None,
|
||||||
):
|
):
|
||||||
self.modelo = modelo
|
self.modelo = modelo
|
||||||
self.memoria = memoria
|
self.memoria = memoria
|
||||||
self.tools = tools or []
|
|
||||||
self.output_schema = output_schema
|
self.output_schema = output_schema
|
||||||
|
|
||||||
self.nombre = nombre
|
self.nombre = nombre
|
||||||
@@ -36,6 +35,9 @@ class AgenteAI:
|
|||||||
self.created_at = datetime.now()
|
self.created_at = datetime.now()
|
||||||
self.updated_at = self.created_at
|
self.updated_at = self.created_at
|
||||||
self.numero_interacciones = 0
|
self.numero_interacciones = 0
|
||||||
|
self.mcp = mcp # <-- Aquí guardamos el registry
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def actualizar_configuracion(self, **kwargs):
|
def actualizar_configuracion(self, **kwargs):
|
||||||
for clave, valor in kwargs.items():
|
for clave, valor in kwargs.items():
|
||||||
@@ -43,56 +45,105 @@ class AgenteAI:
|
|||||||
setattr(self, clave, valor)
|
setattr(self, clave, valor)
|
||||||
self.updated_at = datetime.now()
|
self.updated_at = datetime.now()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def full_system_prompt(self) -> str:
|
async def full_system_prompt(self) -> str:
|
||||||
partes = [
|
tools_str = await self._obtener_herramientas_disponibles_str()
|
||||||
f"Tu nombre es: {self.nombre}",
|
return f"""
|
||||||
f"Tu descripción: {self.descripcion}",
|
Eres un agente conversacional con acceso a herramientas MCP (Model Context Protocol).
|
||||||
f"Tu Rol: {self.rol}",
|
|
||||||
f"Tus Objetivos: {', '.join(self.objetivos)}",
|
|
||||||
""
|
|
||||||
]
|
|
||||||
|
|
||||||
herramientas = self._obtener_descripcion_tools()
|
Tu comportamiento sigue este flujo:
|
||||||
if herramientas:
|
|
||||||
partes.append("Estas son tus herramientas disponibles:")
|
|
||||||
partes.extend(herramientas)
|
|
||||||
partes.append(
|
|
||||||
"Cuando consideres necesario, utiliza las herramientas disponibles "
|
|
||||||
"para responder de manera más precisa o realizar tareas específicas. "
|
|
||||||
"Indica claramente qué herramienta estás utilizando y por qué."
|
|
||||||
)
|
|
||||||
|
|
||||||
partes.append(self.system_prompt)
|
1. **Piensa** para razonar tu decisión.
|
||||||
|
2. **Decide** si:
|
||||||
|
- puedes responder tú mismo,
|
||||||
|
- necesitas más información del usuario,
|
||||||
|
- o necesitas una herramienta MCP.
|
||||||
|
3. **Actúa**:
|
||||||
|
- Cuando uses MCP, termina **solo** con un bloque de código MCP y **nada más**.
|
||||||
|
- Ten en cuenta EXACTAMENTE los parámetros especificados.
|
||||||
|
- **No expliques, no hables después del bloque. Termina tu turno.**
|
||||||
|
|
||||||
if self.output_schema:
|
---
|
||||||
partes.append("SIEMPRE formatea la respuesta final siguiendo estrictamente el siguiente esquema JSON:")
|
|
||||||
partes.append(f"```json\n{self.output_schema}\n```")
|
|
||||||
|
|
||||||
return "\n".join(partes)
|
# Formato MCP
|
||||||
|
|
||||||
def _obtener_descripcion_tools(self) -> List[str]:
|
```mcp
|
||||||
descripciones = []
|
{{
|
||||||
if not hasattr(self, "mcp_servers"):
|
"tool": "<nombre_de_la_herramienta>",
|
||||||
return descripciones
|
"input": {{
|
||||||
|
"clave": "valor"
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
Reglas clave:
|
||||||
|
|
||||||
for server in self.mcp_servers:
|
Razonas antes de actuar.
|
||||||
if hasattr(server, "tools") and server.tools:
|
|
||||||
for tool in server.tools:
|
Nunca hables después de un bloque MCP.
|
||||||
if isinstance(tool, str):
|
|
||||||
descripciones.append(f"- {tool}: [sin descripción]")
|
No combines respuestas y herramientas.
|
||||||
elif isinstance(tool, dict):
|
|
||||||
nombre = tool.get("name", "¿?")
|
Piensa. Decide. Actúa.
|
||||||
descripcion = tool.get("description", "[sin descripción]")
|
|
||||||
descripciones.append(f"- {nombre}: {descripcion}")
|
Herramientas disponibles para usar con MCP:
|
||||||
elif hasattr(tool, "name"):
|
{tools_str}
|
||||||
descripcion = getattr(tool, "description", "[sin descripción]")
|
|
||||||
descripciones.append(f"- {tool.name}: {descripcion}")
|
""".strip()
|
||||||
return descripciones
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Conseguir las herramientas disponibles
|
||||||
|
|
||||||
|
async def _obtener_herramientas_disponibles_str(self) -> str:
|
||||||
|
if not self.mcp:
|
||||||
|
return "No se han definido herramientas disponibles."
|
||||||
|
|
||||||
|
herramientas = []
|
||||||
|
tools_por_cliente = await self.mcp.listar_tools_por_cliente()
|
||||||
|
|
||||||
|
for name, tools in tools_por_cliente.items():
|
||||||
|
if not tools:
|
||||||
|
continue
|
||||||
|
herramientas.append(f"\n🔌 Cliente: {name}")
|
||||||
|
for tool in tools:
|
||||||
|
props = tool.inputSchema.get("properties", {})
|
||||||
|
parametros = "\n ".join(f"- {k} ({v.get('type', '?')})" for k, v in props.items())
|
||||||
|
herramientas.append(f"""Nombre: {tool.name}
|
||||||
|
Descripción: {tool.description}
|
||||||
|
Parámetros:
|
||||||
|
{parametros}
|
||||||
|
""")
|
||||||
|
return "\n".join(herramientas) or "No hay herramientas disponibles actualmente."
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Formatear prompt para agentes
|
||||||
|
|
||||||
def _formatear_prompt(self, mensajes: List[dict]) -> str:
|
def _formatear_prompt(self, mensajes: List[dict]) -> str:
|
||||||
return "\n".join([f"{msg['role']}: {msg['content']}" for msg in mensajes])
|
return "\n".join([f"{msg['role']}: {msg['content']}" for msg in mensajes])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###----------- Funcion para interactuar
|
||||||
|
|
||||||
async def interactuar(self, prompt: str, stream: bool = False) -> Union[str, AsyncGenerator[str, None]]:
|
async def interactuar(self, prompt: str, stream: bool = False) -> Union[str, AsyncGenerator[str, None]]:
|
||||||
historial = self.memoria.cargar_historial_chat() if self.memoria else []
|
historial = self.memoria.cargar_historial_chat() if self.memoria else []
|
||||||
contexto = historial + [{"role": "user", "content": prompt}]
|
contexto = historial + [{"role": "user", "content": prompt}]
|
||||||
@@ -100,12 +151,11 @@ class AgenteAI:
|
|||||||
|
|
||||||
respuesta = await self.modelo.responder(
|
respuesta = await self.modelo.responder(
|
||||||
prompt=prompt_final,
|
prompt=prompt_final,
|
||||||
system_prompt=self.full_system_prompt,
|
system_prompt=await self.full_system_prompt, # ✅ correcto
|
||||||
stream=stream
|
stream=stream
|
||||||
)
|
)
|
||||||
|
|
||||||
if stream:
|
if stream:
|
||||||
# stream es un generador asincrónico
|
|
||||||
async def wrapper():
|
async def wrapper():
|
||||||
buffer_respuesta = ""
|
buffer_respuesta = ""
|
||||||
async for token in respuesta:
|
async for token in respuesta:
|
||||||
@@ -125,31 +175,45 @@ class AgenteAI:
|
|||||||
self.updated_at = datetime.now()
|
self.updated_at = datetime.now()
|
||||||
return respuesta
|
return respuesta
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###----------- Funcion para interactuar en bucle
|
||||||
|
|
||||||
async def interactuar_en_bucle(self, prompt: str, stream: bool = False) -> Union[List[str], AsyncGenerator[str, None]]:
|
async def interactuar_en_bucle(self, prompt: str, stream: bool = False) -> Union[List[str], AsyncGenerator[str, None]]:
|
||||||
|
print("🚀 [interactuar_en_bucle] Iniciando interacción")
|
||||||
historial = self.memoria.cargar_historial_chat() if self.memoria else []
|
historial = self.memoria.cargar_historial_chat() if self.memoria else []
|
||||||
|
print(f"📜 [interactuar_en_bucle] Historial cargado: {historial}")
|
||||||
respuestas = [] if not stream else None
|
respuestas = [] if not stream else None
|
||||||
respuesta_anterior = None
|
respuesta_anterior = None
|
||||||
iteration = 0
|
iteration = 0
|
||||||
prompt_original = prompt.strip()
|
prompt_original = prompt.strip()
|
||||||
|
print(f"✏️ [interactuar_en_bucle] Prompt original: {prompt_original}")
|
||||||
|
|
||||||
async def generador():
|
async def generador():
|
||||||
nonlocal iteration, respuesta_anterior
|
nonlocal iteration, respuesta_anterior
|
||||||
prompt_actual = prompt_original
|
prompt_actual = prompt_original
|
||||||
|
|
||||||
while self.max_iterations == 0 or iteration < self.max_iterations:
|
while self.max_iterations == 0 or iteration < self.max_iterations:
|
||||||
|
print(f"\n🔁 [generador] Iteración: {iteration}")
|
||||||
|
|
||||||
if iteration == 0:
|
if iteration == 0:
|
||||||
prompt_actual += (
|
prompt_actual += (
|
||||||
"\n\nIMPORTANTE:\n"
|
"\n\nIMPORTANTE:\n"
|
||||||
"Si al revisar tu última respuesta y mi pregunta inicial consideras que has terminado, "
|
"Si al revisar tu última respuesta y mi pregunta inicial consideras que has terminado, "
|
||||||
"di alguna de estas frases: <FIN>"
|
"di alguna de estas frases: <END>"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
prompt_actual = (
|
prompt_actual = (
|
||||||
f"Esta es la pregunta original:\n{prompt_original}\n\n"
|
f"Esta es la pregunta original:\n{prompt_original}\n\n"
|
||||||
f"Esto fue lo último que dijiste:\n{respuesta_anterior}\n"
|
f"Esto fue lo último que dijiste:\n{respuesta_anterior}\n"
|
||||||
|
|
||||||
"\n\nIMPORTANTE:\n"
|
"\n\nIMPORTANTE:\n"
|
||||||
"Si al revisar tu última respuesta y mi pregunta inicial consideras que has terminado, "
|
"Si al revisar tu última respuesta y mi pregunta inicial consideras que has terminado, "
|
||||||
"di alguna de estas frases: <FIN>"
|
"di alguna de estas frases: <END>"
|
||||||
"En caso contrario, responde a la pregunta original "
|
"En caso contrario, responde a la pregunta original "
|
||||||
"y añade información relevante que no hayas mencionado antes.\n\n"
|
"y añade información relevante que no hayas mencionado antes.\n\n"
|
||||||
)
|
)
|
||||||
@@ -157,30 +221,40 @@ class AgenteAI:
|
|||||||
contexto = historial + [{"role": "user", "content": prompt_actual}]
|
contexto = historial + [{"role": "user", "content": prompt_actual}]
|
||||||
prompt_final = self._formatear_prompt(contexto)
|
prompt_final = self._formatear_prompt(contexto)
|
||||||
|
|
||||||
|
print(f"📨 [generador] Prompt final enviado al modelo:\n{prompt_final}")
|
||||||
|
|
||||||
|
print("🤖 [generador] Esperando respuesta del modelo...")
|
||||||
respuesta = await self.modelo.responder(
|
respuesta = await self.modelo.responder(
|
||||||
prompt=prompt_final,
|
prompt=prompt_final,
|
||||||
system_prompt=self.full_system_prompt,
|
system_prompt=await self.full_system_prompt,
|
||||||
stream=stream
|
stream=stream
|
||||||
)
|
)
|
||||||
|
print("✅ [generador] Respuesta recibida")
|
||||||
|
|
||||||
if stream:
|
if stream:
|
||||||
buffer_respuesta = ""
|
buffer_respuesta = ""
|
||||||
async for token in respuesta:
|
async for token in respuesta:
|
||||||
buffer_respuesta += token
|
buffer_respuesta += token
|
||||||
|
# print(f"🔹 [stream] Token: {token}")
|
||||||
yield token
|
yield token
|
||||||
respuesta_anterior = buffer_respuesta
|
respuesta_anterior = buffer_respuesta
|
||||||
|
# print(f"📦 [stream] Respuesta completa:\n{respuesta_anterior}")
|
||||||
else:
|
else:
|
||||||
respuestas.append(respuesta)
|
respuestas.append(respuesta)
|
||||||
respuesta_anterior = respuesta
|
respuesta_anterior = respuesta
|
||||||
|
# print(f"📦 [generador] Respuesta completa:\n{respuesta_anterior}")
|
||||||
|
|
||||||
if self.memoria:
|
if self.memoria:
|
||||||
|
print("💾 [memoria] Guardando turno en la memoria...")
|
||||||
self.memoria.guardar_turno("user", prompt_actual)
|
self.memoria.guardar_turno("user", prompt_actual)
|
||||||
self.memoria.guardar_turno("assistant", respuesta_anterior)
|
self.memoria.guardar_turno("assistant", respuesta_anterior)
|
||||||
|
|
||||||
self.numero_interacciones += 1
|
self.numero_interacciones += 1
|
||||||
self.updated_at = datetime.now()
|
self.updated_at = datetime.now()
|
||||||
|
print(f"📊 [generador] Interacción #{self.numero_interacciones} registrada")
|
||||||
|
|
||||||
if "<fin>" in respuesta_anterior.lower():
|
if "<end>" in respuesta_anterior.lower():
|
||||||
|
print("🛑 [generador] Detectado <end>. Terminando bucle.")
|
||||||
break
|
break
|
||||||
|
|
||||||
iteration += 1
|
iteration += 1
|
||||||
@@ -188,6 +262,11 @@ class AgenteAI:
|
|||||||
|
|
||||||
return generador() if stream else await generador_to_list(generador)
|
return generador() if stream else await generador_to_list(generador)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Helper para consumir generador asincrónico si no es stream
|
# Helper para consumir generador asincrónico si no es stream
|
||||||
async def generador_to_list(gen: AsyncGenerator[str, None]) -> List[str]:
|
async def generador_to_list(gen: AsyncGenerator[str, None]) -> List[str]:
|
||||||
buffer = ""
|
buffer = ""
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
class EmbedderABC(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def encoder(self, text: str) -> List[float]:
|
||||||
|
"""Genera los embeddings para un texto dado."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def dimension_number(self) -> int:
|
||||||
|
"""Devuelve la dimensión del modelo de embedding."""
|
||||||
|
pass
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
from typing import List
|
||||||
|
from src.Llms.Embedders.Base_Embedder import EmbedderABC # Asegúrate de que EmbedderABC esté en este módulo
|
||||||
|
from src.ApiKeys.openai_apikey import OpenAICredencial
|
||||||
|
from src.ConexionApis.OpenAi_conexion import OpenAICliente
|
||||||
|
from src.Security.GenerarIDs import GeneradorIDUnico
|
||||||
|
|
||||||
|
class OpenAIEmbedder(EmbedderABC):
|
||||||
|
def __init__(self, credencial: OpenAICredencial,
|
||||||
|
model: str,
|
||||||
|
id: str = None):
|
||||||
|
self.model = model
|
||||||
|
self.client = OpenAICliente(credencial)
|
||||||
|
self._dimension = None # Lazy loading
|
||||||
|
self.id = id if id is not None else GeneradorIDUnico("OAMB").generar()
|
||||||
|
|
||||||
|
def encoder(self, text: str) -> List[float]:
|
||||||
|
"""
|
||||||
|
Genera los embeddings para un texto dado utilizando el modelo de OpenAI.
|
||||||
|
"""
|
||||||
|
response = self.client.embedding(model=self.model, input=text)
|
||||||
|
embedding = response.data[0].embedding
|
||||||
|
if self._dimension is None:
|
||||||
|
self._dimension = len(embedding)
|
||||||
|
return embedding
|
||||||
|
|
||||||
|
def dimension_number(self) -> int:
|
||||||
|
"""
|
||||||
|
Devuelve la dimensión del modelo de embedding, generando un embedding si no se ha calculado aún.
|
||||||
|
"""
|
||||||
|
if self._dimension is None:
|
||||||
|
_ = self.encoder("dimension_check")
|
||||||
|
return self._dimension
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from sqlalchemy import Column, String
|
||||||
|
from sqlalchemy import Column, String, ForeignKey
|
||||||
|
|
||||||
|
from src.ArquitectureLayer.Mapper import Mapper_base
|
||||||
|
from src.ArquitectureLayer.Model import Model_base
|
||||||
|
from src.ArquitectureLayer.Repo import Repo_base
|
||||||
|
|
||||||
|
from src.ConexionSql.Base_conexion import ConexionBase
|
||||||
|
from src.base import Base
|
||||||
|
from src.Security.GenerarIDs import GeneradorIDUnico
|
||||||
|
from src.Llms.Embedders.Openai_embedder import OpenAIEmbedder
|
||||||
|
from src.ApiKeys.openai_apikey import OpenAICredencial
|
||||||
|
|
||||||
|
# ----------------------
|
||||||
|
# Cargar configuración desde .env si se requiere
|
||||||
|
# ----------------------
|
||||||
|
from entrypoint import ENV_PATH
|
||||||
|
load_dotenv(ENV_PATH)
|
||||||
|
|
||||||
|
# ----------------------
|
||||||
|
# MODELO (SQLAlchemy)
|
||||||
|
# ----------------------
|
||||||
|
|
||||||
|
class OpenAIEmbedderModel(Base, Model_base):
|
||||||
|
__tablename__ = "openai_embedders"
|
||||||
|
|
||||||
|
id = Column(String, primary_key=True)
|
||||||
|
|
||||||
|
api_key_id = Column(String, ForeignKey("openai_credenciales.id"), nullable=False)
|
||||||
|
model = Column(String, nullable=False)
|
||||||
|
|
||||||
|
# ----------------------
|
||||||
|
# MAPPER
|
||||||
|
# ----------------------
|
||||||
|
|
||||||
|
class OpenAIEmbedderMapper(Mapper_base[OpenAIEmbedder, OpenAIEmbedderModel]):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_model(obj: OpenAIEmbedder) -> OpenAIEmbedderModel:
|
||||||
|
return OpenAIEmbedderModel(
|
||||||
|
id=obj.id,
|
||||||
|
api_key_id=obj.client.credencial.id,
|
||||||
|
model=obj.model
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_model(model: OpenAIEmbedderModel, credencial: OpenAICredencial) -> OpenAIEmbedder:
|
||||||
|
return OpenAIEmbedder(
|
||||||
|
id=model.id,
|
||||||
|
credencial=credencial,
|
||||||
|
model=model.model
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_dict(obj: OpenAIEmbedder) -> dict:
|
||||||
|
return {
|
||||||
|
"id": obj.id,
|
||||||
|
"api_key_id": obj.client.credencial.id,
|
||||||
|
"model": obj.model
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(data: dict, credencial: OpenAICredencial) -> OpenAIEmbedder:
|
||||||
|
return OpenAIEmbedder(
|
||||||
|
id=data["id"],
|
||||||
|
credencial=credencial,
|
||||||
|
model=data["model"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# ----------------------
|
||||||
|
# REPO
|
||||||
|
# ----------------------
|
||||||
|
|
||||||
|
class OpenAIEmbedderRepo(Repo_base[OpenAIEmbedderModel, OpenAIEmbedder]):
|
||||||
|
def __init__(self, conexion: ConexionBase):
|
||||||
|
super().__init__(
|
||||||
|
session=conexion.get_session(),
|
||||||
|
modelo=OpenAIEmbedderModel,
|
||||||
|
mapper=OpenAIEmbedderMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_by_id(self, id_: str, credencial: OpenAICredencial) -> OpenAIEmbedder | None:
|
||||||
|
model = self.session.get(self.Modelo, id_)
|
||||||
|
return self.Mapper.from_model(model, credencial) if model else None
|
||||||
|
|
||||||
|
def get_all(self, credencial_loader: callable) -> list[OpenAIEmbedder]:
|
||||||
|
"""
|
||||||
|
:param credencial_loader: función que recibe un api_key_id y devuelve una instancia de OpenAICredencial
|
||||||
|
"""
|
||||||
|
models = self.session.query(self.Modelo).all()
|
||||||
|
return [
|
||||||
|
self.Mapper.from_model(m, credencial_loader(m.api_key_id))
|
||||||
|
for m in models
|
||||||
|
]
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
import os
|
|
||||||
from typing import Optional, List, Dict
|
|
||||||
from contextlib import AsyncExitStack
|
|
||||||
from mcp import ClientSession, StdioServerParameters
|
|
||||||
from mcp.client.stdio import stdio_client
|
|
||||||
from mcp.types import Tool
|
|
||||||
|
|
||||||
class MCPStdioServer:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
command: str,
|
|
||||||
args: List[str],
|
|
||||||
env: Optional[Dict[str, str]] = None
|
|
||||||
):
|
|
||||||
self.name = name
|
|
||||||
self.command = command
|
|
||||||
self.args = args
|
|
||||||
self.env = env or os.environ.copy()
|
|
||||||
self.exit_stack = AsyncExitStack()
|
|
||||||
self.session: Optional[ClientSession] = None
|
|
||||||
self.tools: List[Tool] = []
|
|
||||||
|
|
||||||
async def start(self):
|
|
||||||
# Configurar el bucle de eventos Proactor en Windows si es necesario
|
|
||||||
if os.name == "nt":
|
|
||||||
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
|
|
||||||
|
|
||||||
server_params = StdioServerParameters(
|
|
||||||
command=self.command,
|
|
||||||
args=self.args,
|
|
||||||
env=self.env
|
|
||||||
)
|
|
||||||
|
|
||||||
# Iniciar el transporte y establecer la sesión
|
|
||||||
stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
|
|
||||||
read, write = stdio_transport
|
|
||||||
|
|
||||||
self.session = await self.exit_stack.enter_async_context(ClientSession(read, write))
|
|
||||||
await self.session.initialize()
|
|
||||||
response = await self.session.list_tools()
|
|
||||||
self.tools = response.tools
|
|
||||||
|
|
||||||
if self.tools:
|
|
||||||
print(f"[{self.name}] Servidor iniciado con herramientas:")
|
|
||||||
for tool in self.tools:
|
|
||||||
nombre = getattr(tool, "name", "[sin nombre]")
|
|
||||||
descripcion = getattr(tool, "description", "[sin descripción]")
|
|
||||||
print(f" - {nombre} - {descripcion}")
|
|
||||||
else:
|
|
||||||
print(f"[{self.name}] Servidor iniciado, pero no se detectaron herramientas.")
|
|
||||||
|
|
||||||
async def call_tool(self, tool_name: str, arguments: Dict):
|
|
||||||
if not self.session:
|
|
||||||
raise RuntimeError("La sesión no está inicializada.")
|
|
||||||
result = await self.session.call_tool(tool_name, arguments)
|
|
||||||
return result.content
|
|
||||||
|
|
||||||
def get_tool_names(self) -> List[str]:
|
|
||||||
return [tool.name for tool in self.tools]
|
|
||||||
|
|
||||||
async def stop(self):
|
|
||||||
await self.exit_stack.aclose()
|
|
||||||
print(f"[{self.name}] Servidor detenido.")
|
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Optional, Union
|
||||||
|
from pydantic import AnyUrl
|
||||||
|
from fastmcp.client import Client
|
||||||
|
from fastmcp.client.transports import (
|
||||||
|
StreamableHttpTransport,
|
||||||
|
PythonStdioTransport,
|
||||||
|
ClientTransport,
|
||||||
|
)
|
||||||
|
from mcp.types import *
|
||||||
|
from fastmcp.exceptions import ClientError
|
||||||
|
|
||||||
|
|
||||||
|
class MCPClient:
|
||||||
|
def __init__(self, name: str, client: Client):
|
||||||
|
self.name = name
|
||||||
|
self.client = client
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"<ClientWrapper(name={self.name})>"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_http(cls, name: str, url: str | AnyUrl) -> "MCPClient":
|
||||||
|
transport = StreamableHttpTransport(url=str(url))
|
||||||
|
client = Client(transport=transport)
|
||||||
|
return cls(name=name, client=client)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_stdio(
|
||||||
|
cls,
|
||||||
|
name: str,
|
||||||
|
script_path: Union[str, Path],
|
||||||
|
args: Optional[list[str]] = None,
|
||||||
|
cwd: Optional[Union[str, Path]] = None,
|
||||||
|
env: Optional[dict[str, str]] = None,
|
||||||
|
) -> "MCPClient":
|
||||||
|
transport = PythonStdioTransport(
|
||||||
|
script_path=script_path, args=args, cwd=cwd, env=env
|
||||||
|
)
|
||||||
|
client = Client(transport=transport)
|
||||||
|
return cls(name=name, client=client)
|
||||||
|
|
||||||
|
def is_connected(self) -> bool:
|
||||||
|
return self.client.is_connected()
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
await self.client.__aenter__()
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
await self.client.__aexit__(exc_type, exc_val, exc_tb)
|
||||||
|
|
||||||
|
# Delegación MCP
|
||||||
|
|
||||||
|
async def call_tool(
|
||||||
|
self, name: str, arguments: dict[str, Any] | None = None
|
||||||
|
) -> list[TextContent | ImageContent | EmbeddedResource]:
|
||||||
|
return await self.client.call_tool(name, arguments)
|
||||||
|
|
||||||
|
async def get_prompt(
|
||||||
|
self, name: str, arguments: dict[str, str] | None = None
|
||||||
|
) -> GetPromptResult:
|
||||||
|
return await self.client.get_prompt(name, arguments)
|
||||||
|
|
||||||
|
async def list_tools(self) -> list[Tool]:
|
||||||
|
return await self.client.list_tools()
|
||||||
|
|
||||||
|
async def list_prompts(self) -> list[Prompt]:
|
||||||
|
return await self.client.list_prompts()
|
||||||
|
|
||||||
|
async def list_resources(self) -> list[Resource]:
|
||||||
|
return await self.client.list_resources()
|
||||||
|
|
||||||
|
async def list_resource_templates(self) -> list[ResourceTemplate]:
|
||||||
|
return await self.client.list_resource_templates()
|
||||||
|
|
||||||
|
async def read_resource(
|
||||||
|
self, uri: AnyUrl | str
|
||||||
|
) -> list[TextResourceContents | BlobResourceContents]:
|
||||||
|
return await self.client.read_resource(uri)
|
||||||
|
|
||||||
|
async def complete(
|
||||||
|
self,
|
||||||
|
ref: ResourceReference | PromptReference,
|
||||||
|
argument: dict[str, str],
|
||||||
|
) -> Completion:
|
||||||
|
return await self.client.complete(ref, argument)
|
||||||
|
|
||||||
|
async def ping(self) -> bool:
|
||||||
|
return await self.client.ping()
|
||||||
|
|
||||||
|
async def set_logging_level(self, level: LoggingLevel) -> None:
|
||||||
|
return await self.client.set_logging_level(level)
|
||||||
|
|
||||||
|
async def send_roots_list_changed(self) -> None:
|
||||||
|
return await self.client.send_roots_list_changed()
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
from src.Llms.MCPs.McpClient import MCPClient
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
class ClientRegistry:
|
||||||
|
def __init__(self):
|
||||||
|
self._clients: dict[str, MCPClient] = {}
|
||||||
|
|
||||||
|
def add(self, name: str, wrapper: MCPClient) -> None:
|
||||||
|
self._clients[name] = wrapper
|
||||||
|
|
||||||
|
def get(self, name: str) -> MCPClient:
|
||||||
|
if name not in self._clients:
|
||||||
|
raise KeyError(f"Cliente '{name}' no encontrado en el registro.")
|
||||||
|
return self._clients[name]
|
||||||
|
|
||||||
|
def all(self) -> dict[str, MCPClient]:
|
||||||
|
return self._clients
|
||||||
|
|
||||||
|
def list_names(self) -> list[str]:
|
||||||
|
return list(self._clients.keys())
|
||||||
|
|
||||||
|
def __contains__(self, name: str) -> bool:
|
||||||
|
return name in self._clients
|
||||||
|
|
||||||
|
async def listar_tools_por_cliente(self) -> dict[str, list[Any]]:
|
||||||
|
resultado = {}
|
||||||
|
for name, wrapper in self._clients.items():
|
||||||
|
try:
|
||||||
|
async with wrapper:
|
||||||
|
resultado[name] = await wrapper.list_tools()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[TOOLS] ❌ Error en '{name}': {e}")
|
||||||
|
resultado[name] = []
|
||||||
|
return resultado
|
||||||
|
|
||||||
|
async def listar_prompts_por_cliente(self) -> dict[str, list[Any]]:
|
||||||
|
resultado = {}
|
||||||
|
for name, wrapper in self._clients.items():
|
||||||
|
try:
|
||||||
|
async with wrapper:
|
||||||
|
resultado[name] = await wrapper.list_prompts()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[PROMPTS] ❌ Error en '{name}': {e}")
|
||||||
|
resultado[name] = []
|
||||||
|
return resultado
|
||||||
|
|
||||||
|
async def listar_resources_por_cliente(self) -> dict[str, list[Any]]:
|
||||||
|
resultado = {}
|
||||||
|
for name, wrapper in self._clients.items():
|
||||||
|
try:
|
||||||
|
async with wrapper:
|
||||||
|
resultado[name] = await wrapper.list_resources()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[RESOURCES] ❌ Error en '{name}': {e}")
|
||||||
|
resultado[name] = []
|
||||||
|
return resultado
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
from fastmcp import FastMCP
|
||||||
|
|
||||||
|
mcp = FastMCP()
|
||||||
|
|
||||||
|
@mcp.tool(description="Suma dos números enteros.")
|
||||||
|
def add(a: int, b: int) -> int:
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
@mcp.tool(description="Resta dos números enteros.")
|
||||||
|
def subtract(a: int, b: int) -> int:
|
||||||
|
return a - b
|
||||||
|
|
||||||
|
@mcp.tool(description="Multiplica dos números enteros.")
|
||||||
|
def multiply(a: int, b: int) -> int:
|
||||||
|
return a * b
|
||||||
|
|
||||||
|
@mcp.tool(description="Divide dos números y devuelve el resultado flotante.")
|
||||||
|
def divide(a: float, b: float) -> float:
|
||||||
|
if b == 0:
|
||||||
|
raise ValueError("No se puede dividir entre cero.")
|
||||||
|
return a / b
|
||||||
|
|
||||||
|
@mcp.tool(description="Calcula el módulo de dos números enteros.")
|
||||||
|
def modulo(a: int, b: int) -> int:
|
||||||
|
return a % b
|
||||||
|
|
||||||
|
@mcp.tool(description="Concatena dos cadenas de texto.")
|
||||||
|
def concat(a: str, b: str) -> str:
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
@mcp.tool(description="Devuelve la longitud de una cadena.")
|
||||||
|
def string_length(s: str) -> int:
|
||||||
|
return len(s)
|
||||||
|
|
||||||
|
@mcp.tool(description="Convierte una cadena a mayúsculas.")
|
||||||
|
def to_upper(s: str) -> str:
|
||||||
|
return s.upper()
|
||||||
|
|
||||||
|
@mcp.tool(description="Convierte una cadena a minúsculas.")
|
||||||
|
def to_lower(s: str) -> str:
|
||||||
|
return s.lower()
|
||||||
|
|
||||||
|
@mcp.tool(description="Devuelve la suma de todos los elementos en una lista de enteros.")
|
||||||
|
def sum_list(numbers: list[int]) -> int:
|
||||||
|
return sum(numbers)
|
||||||
|
|
||||||
|
@mcp.tool(description="Devuelve el valor máximo en una lista de enteros.")
|
||||||
|
def max_in_list(numbers: list[int]) -> int:
|
||||||
|
return max(numbers)
|
||||||
|
|
||||||
|
@mcp.tool(description="Verifica si un número es par.")
|
||||||
|
def is_even(n: int) -> bool:
|
||||||
|
return n % 2 == 0
|
||||||
|
|
||||||
|
@mcp.tool(description="Verifica si una cadena es un palíndromo.")
|
||||||
|
def is_palindrome(s: str) -> bool:
|
||||||
|
return s == s[::-1]
|
||||||
|
|
||||||
|
@mcp.tool(description="Calcula el factorial de un número entero positivo.")
|
||||||
|
def factorial(n: int) -> int:
|
||||||
|
if n < 0:
|
||||||
|
raise ValueError("El factorial no está definido para negativos.")
|
||||||
|
if n == 0:
|
||||||
|
return 1
|
||||||
|
result = 1
|
||||||
|
for i in range(1, n + 1):
|
||||||
|
result *= i
|
||||||
|
return result
|
||||||
|
|
||||||
|
@mcp.tool(description="Devuelve los primeros n números de Fibonacci.")
|
||||||
|
def fibonacci(n: int) -> list[int]:
|
||||||
|
if n <= 0:
|
||||||
|
return []
|
||||||
|
seq = [0, 1]
|
||||||
|
while len(seq) < n:
|
||||||
|
seq.append(seq[-1] + seq[-2])
|
||||||
|
return seq[:n]
|
||||||
|
|
||||||
|
@mcp.tool(description="Devuelve si un número es primo.")
|
||||||
|
def is_prime(n: int) -> bool:
|
||||||
|
if n <= 1:
|
||||||
|
return False
|
||||||
|
for i in range(2, int(n**0.5) + 1):
|
||||||
|
if n % i == 0:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# mcp.run(transport="streamable-http", host="127.0.0.1", port=4200, path="/math")
|
||||||
|
|
||||||
|
mcp.run(transport="stdio")
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
from fastmcp import FastMCP
|
||||||
|
import uuid
|
||||||
|
import datetime
|
||||||
|
import socket
|
||||||
|
import platform
|
||||||
|
import os
|
||||||
|
|
||||||
|
mcp = FastMCP()
|
||||||
|
|
||||||
|
@mcp.tool(description="Genera un UUID versión 4.")
|
||||||
|
def generate_uuid() -> str:
|
||||||
|
return str(uuid.uuid4())
|
||||||
|
|
||||||
|
@mcp.tool(description="Devuelve la fecha y hora actuales en formato ISO 8601.")
|
||||||
|
def current_datetime() -> str:
|
||||||
|
return datetime.datetime.now().isoformat()
|
||||||
|
|
||||||
|
@mcp.tool(description="Devuelve solo la fecha actual.")
|
||||||
|
def current_date() -> str:
|
||||||
|
return datetime.date.today().isoformat()
|
||||||
|
|
||||||
|
@mcp.tool(description="Devuelve el nombre del host actual.")
|
||||||
|
def get_hostname() -> str:
|
||||||
|
return socket.gethostname()
|
||||||
|
|
||||||
|
@mcp.tool(description="Devuelve el sistema operativo actual.")
|
||||||
|
def get_os() -> str:
|
||||||
|
return platform.system()
|
||||||
|
|
||||||
|
@mcp.tool(description="Devuelve el nombre del usuario actual del sistema.")
|
||||||
|
def get_current_user() -> str:
|
||||||
|
return os.getlogin()
|
||||||
|
|
||||||
|
@mcp.tool(description="Invierte un valor booleano.")
|
||||||
|
def invert_boolean(flag: bool) -> bool:
|
||||||
|
return not flag
|
||||||
|
|
||||||
|
# @mcp.tool(description="Devuelve los archivos y carpetas del directorio actual.")
|
||||||
|
# def list_current_directory() -> list[str]:
|
||||||
|
# return os.listdir()
|
||||||
|
|
||||||
|
# @mcp.tool(description="Crea un archivo con un nombre dado.")
|
||||||
|
# def create_file(filename: str) -> str:
|
||||||
|
# with open(filename, "w") as f:
|
||||||
|
# f.write("")
|
||||||
|
# return f"Archivo '{filename}' creado."
|
||||||
|
|
||||||
|
# @mcp.tool(description="Lee el contenido de un archivo de texto dado.")
|
||||||
|
# def read_file(filename: str) -> str:
|
||||||
|
# with open(filename, "r") as f:
|
||||||
|
# return f.read()
|
||||||
|
|
||||||
|
# @mcp.tool(description="Escribe contenido a un archivo, sobrescribiéndolo.")
|
||||||
|
# def write_file(filename: str, content: str) -> str:
|
||||||
|
# with open(filename, "w") as f:
|
||||||
|
# f.write(content)
|
||||||
|
# return f"Contenido escrito en '{filename}'."
|
||||||
|
|
||||||
|
@mcp.tool(description="Devuelve el número de CPUs disponibles en el sistema.")
|
||||||
|
def get_cpu_count() -> int:
|
||||||
|
return os.cpu_count()
|
||||||
|
|
||||||
|
@mcp.tool(description="Devuelve el timestamp actual (UNIX).")
|
||||||
|
def current_timestamp() -> float:
|
||||||
|
return datetime.datetime.now().timestamp()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
mcp.run(transport="streamable-http", host="127.0.0.1", port=4300, path="/tools")
|
||||||
@@ -27,7 +27,7 @@ class MemoryConvPostgres(MemoryConvABC):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Crea la tabla si no existe
|
# Crea la tabla si no existe
|
||||||
self.metadata.create_all(self.conexion.engine)
|
self.metadata.create_all(self.conexion._engine)
|
||||||
|
|
||||||
def guardar_turno(self, rol: Literal["user", "assistant"], contenido: str) -> None:
|
def guardar_turno(self, rol: Literal["user", "assistant"], contenido: str) -> None:
|
||||||
stmt = insert(self.tabla).values(rol=rol, contenido=contenido)
|
stmt = insert(self.tabla).values(rol=rol, contenido=contenido)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from src.Llms.Modelos.Base_model import ModeloABC
|
from src.Llms.Modelos.Base_model import ModeloABC
|
||||||
from src.ConexionApis.OpenAi_conexion import OpenAICliente
|
from src.ConexionApis.OpenAi_conexion import OpenAICliente
|
||||||
|
from src.Security.GenerarIDs import GeneradorIDUnico
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import AsyncGenerator, Union
|
from typing import AsyncGenerator, Union
|
||||||
|
|
||||||
@@ -8,6 +9,7 @@ class ModeloOpenAI(ModeloABC):
|
|||||||
self,
|
self,
|
||||||
cliente: OpenAICliente,
|
cliente: OpenAICliente,
|
||||||
model: str = "gpt-4o",
|
model: str = "gpt-4o",
|
||||||
|
id: str = None,
|
||||||
temperature: float = 0.7,
|
temperature: float = 0.7,
|
||||||
top_p: float = 1.0,
|
top_p: float = 1.0,
|
||||||
top_k: int = None,
|
top_k: int = None,
|
||||||
@@ -15,6 +17,10 @@ class ModeloOpenAI(ModeloABC):
|
|||||||
num_tokens_maximos: int = 512,
|
num_tokens_maximos: int = 512,
|
||||||
use_legacy: bool = False
|
use_legacy: bool = False
|
||||||
):
|
):
|
||||||
|
# Generar ID con prefijo MOPA si no fue proporcionado
|
||||||
|
self.id = id if id is not None else GeneradorIDUnico("MOPA").generar()
|
||||||
|
|
||||||
|
# Inicializar resto del modelo base
|
||||||
super().__init__(
|
super().__init__(
|
||||||
model=model,
|
model=model,
|
||||||
temperature=temperature,
|
temperature=temperature,
|
||||||
@@ -23,6 +29,8 @@ class ModeloOpenAI(ModeloABC):
|
|||||||
frecuencia_penalizacion=frecuencia_penalizacion,
|
frecuencia_penalizacion=frecuencia_penalizacion,
|
||||||
num_tokens_maximos=num_tokens_maximos
|
num_tokens_maximos=num_tokens_maximos
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Asignar cliente e indicadores adicionales
|
||||||
self.cliente = cliente
|
self.cliente = cliente
|
||||||
self.use_legacy = use_legacy
|
self.use_legacy = use_legacy
|
||||||
|
|
||||||
@@ -75,7 +83,7 @@ class ModeloOpenAI(ModeloABC):
|
|||||||
|
|
||||||
if stream:
|
if stream:
|
||||||
async def generador():
|
async def generador():
|
||||||
for token in resultado: # ya es un generador del cliente
|
for token in resultado:
|
||||||
yield token
|
yield token
|
||||||
return generador()
|
return generador()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -2,6 +2,12 @@ import os
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from sqlalchemy import Column, Integer, String, Float, Boolean
|
from sqlalchemy import Column, Integer, String, Float, Boolean
|
||||||
|
|
||||||
|
from src.ArquitectureLayer.Mapper import Mapper_base
|
||||||
|
from src.ArquitectureLayer.Model import Model_base
|
||||||
|
from src.ArquitectureLayer.Repo import Repo_base
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
from src.ConexionSql.Base_conexion import ConexionBase
|
from src.ConexionSql.Base_conexion import ConexionBase
|
||||||
from src.base import Base
|
from src.base import Base
|
||||||
from src.Llms.Modelos.Openai_model import ModeloOpenAI # Clase real de lógica
|
from src.Llms.Modelos.Openai_model import ModeloOpenAI # Clase real de lógica
|
||||||
@@ -19,38 +25,44 @@ if pssword is None:
|
|||||||
# MODELO (SQLAlchemy)
|
# MODELO (SQLAlchemy)
|
||||||
# ----------------------
|
# ----------------------
|
||||||
|
|
||||||
class ModeloOpenAIConfigModel(Base):
|
class ModeloOpenAIConfigModel(Base, Model_base):
|
||||||
__tablename__ = 'modelo_openai_configs'
|
__tablename__ = 'modelo_openai_configs'
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(String, primary_key=True)
|
||||||
|
|
||||||
model = Column(String, nullable=False)
|
model = Column(String, nullable=False)
|
||||||
temperature = Column(Float, default=0.7)
|
temperature = Column(Float, default=0.7, nullable=False)
|
||||||
top_p = Column(Float, default=1.0)
|
top_p = Column(Float, default=1.0, nullable=False)
|
||||||
top_k = Column(Integer, nullable=True)
|
top_k = Column(Integer, nullable=True)
|
||||||
frecuencia_penalizacion = Column(Float, default=0.0)
|
|
||||||
num_tokens_maximos = Column(Integer, default=512)
|
frecuencia_penalizacion = Column(Float, default=0.0, nullable=False)
|
||||||
use_legacy = Column(Boolean, default=False)
|
num_tokens_maximos = Column(Integer, default=512, nullable=False)
|
||||||
|
|
||||||
|
use_legacy = Column(Boolean, default=False, nullable=False)
|
||||||
|
|
||||||
# ----------------------
|
# ----------------------
|
||||||
# MAPPER
|
# MAPPER
|
||||||
# ----------------------
|
# ----------------------
|
||||||
|
|
||||||
class ModeloOpenAIConfigMapper:
|
class ModeloOpenAIConfigMapper(Mapper_base[ModeloOpenAI, ModeloOpenAIConfigModel]):
|
||||||
@staticmethod
|
|
||||||
def to_dict(obj: ModeloOpenAI) -> dict:
|
|
||||||
return {
|
|
||||||
"model": obj.model,
|
|
||||||
"temperature": obj.temperature,
|
|
||||||
"top_p": obj.top_p,
|
|
||||||
"top_k": obj.top_k,
|
|
||||||
"frecuencia_penalizacion": obj.frecuencia_penalizacion,
|
|
||||||
"num_tokens_maximos": obj.num_tokens_maximos,
|
|
||||||
"use_legacy": obj.use_legacy
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_model(model: ModeloOpenAIConfigModel, cliente: object) -> ModeloOpenAI:
|
def to_model(obj: ModeloOpenAI) -> ModeloOpenAIConfigModel:
|
||||||
|
return ModeloOpenAIConfigModel(
|
||||||
|
id=obj.id,
|
||||||
|
model=obj.model,
|
||||||
|
temperature=obj.temperature,
|
||||||
|
top_p=obj.top_p,
|
||||||
|
top_k=obj.top_k,
|
||||||
|
frecuencia_penalizacion=obj.frecuencia_penalizacion,
|
||||||
|
num_tokens_maximos=obj.num_tokens_maximos,
|
||||||
|
use_legacy=obj.use_legacy
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_model(model: ModeloOpenAIConfigModel, cliente: Optional[object] = None) -> ModeloOpenAI:
|
||||||
return ModeloOpenAI(
|
return ModeloOpenAI(
|
||||||
|
id=model.id,
|
||||||
cliente=cliente,
|
cliente=cliente,
|
||||||
model=model.model,
|
model=model.model,
|
||||||
temperature=model.temperature,
|
temperature=model.temperature,
|
||||||
@@ -61,26 +73,50 @@ class ModeloOpenAIConfigMapper:
|
|||||||
use_legacy=model.use_legacy
|
use_legacy=model.use_legacy
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_dict(obj: ModeloOpenAI) -> dict:
|
||||||
|
return {
|
||||||
|
"id": obj.id,
|
||||||
|
"model": obj.model,
|
||||||
|
"temperature": obj.temperature,
|
||||||
|
"top_p": obj.top_p,
|
||||||
|
"top_k": obj.top_k,
|
||||||
|
"frecuencia_penalizacion": obj.frecuencia_penalizacion,
|
||||||
|
"num_tokens_maximos": obj.num_tokens_maximos,
|
||||||
|
"use_legacy": obj.use_legacy
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(data: dict, cliente: Optional[object] = None) -> ModeloOpenAI:
|
||||||
|
return ModeloOpenAI(
|
||||||
|
id=data["id"],
|
||||||
|
cliente=cliente,
|
||||||
|
model=data["model"],
|
||||||
|
temperature=data["temperature"],
|
||||||
|
top_p=data["top_p"],
|
||||||
|
top_k=data["top_k"],
|
||||||
|
frecuencia_penalizacion=data["frecuencia_penalizacion"],
|
||||||
|
num_tokens_maximos=data["num_tokens_maximos"],
|
||||||
|
use_legacy=data["use_legacy"]
|
||||||
|
)
|
||||||
|
|
||||||
# ----------------------
|
# ----------------------
|
||||||
# REPO
|
# REPO
|
||||||
# ----------------------
|
# ----------------------
|
||||||
|
|
||||||
class ModeloOpenAIConfigRepo:
|
class ModeloOpenAIConfigRepo(Repo_base[ModeloOpenAIConfigModel, ModeloOpenAI]):
|
||||||
def __init__(self, conexion: ConexionBase, cliente: object):
|
def __init__(self, conexion: ConexionBase, cliente: object):
|
||||||
self.session = conexion.get_session()
|
super().__init__(
|
||||||
self.cliente = cliente # Necesario para crear ModeloOpenAI
|
session=conexion.get_session(),
|
||||||
|
modelo=ModeloOpenAIConfigModel,
|
||||||
|
mapper=ModeloOpenAIConfigMapper
|
||||||
|
)
|
||||||
|
self.cliente = cliente # Necesario para construir el dominio con lógica
|
||||||
|
|
||||||
def add(self, config: ModeloOpenAI) -> int:
|
def get_by_id(self, id_: str) -> ModeloOpenAI | None:
|
||||||
data = ModeloOpenAIConfigMapper.to_dict(config)
|
model = self.session.get(self.Modelo, id_)
|
||||||
model = ModeloOpenAIConfigModel(**data)
|
return self.Mapper.from_model(model, self.cliente) if model else None
|
||||||
self.session.add(model)
|
|
||||||
self.session.commit()
|
|
||||||
return model.id
|
|
||||||
|
|
||||||
def get_by_id(self, id_: int) -> ModeloOpenAI | None:
|
|
||||||
model = self.session.get(ModeloOpenAIConfigModel, id_)
|
|
||||||
return ModeloOpenAIConfigMapper.from_model(model, self.cliente) if model else None
|
|
||||||
|
|
||||||
def get_all(self) -> list[ModeloOpenAI]:
|
def get_all(self) -> list[ModeloOpenAI]:
|
||||||
models = self.session.query(ModeloOpenAIConfigModel).all()
|
models = self.session.query(self.Modelo).all()
|
||||||
return [ModeloOpenAIConfigMapper.from_model(m, self.cliente) for m in models]
|
return [self.Mapper.from_model(m, self.cliente) for m in models]
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import uuid
|
||||||
|
import datetime
|
||||||
|
import re
|
||||||
|
|
||||||
|
class GeneradorIDUnico:
|
||||||
|
def __init__(self, tipo_objeto: str):
|
||||||
|
if not re.match(r'^[A-Z]{4}$', tipo_objeto):
|
||||||
|
raise ValueError("El tipo de objeto debe tener 4 letras en mayúscula (ej: ABCD)")
|
||||||
|
self.tipo_objeto = tipo_objeto
|
||||||
|
|
||||||
|
def generar(self):
|
||||||
|
f = datetime.datetime.now().strftime('%Y%m%d')
|
||||||
|
u = uuid.uuid4().hex[:9]
|
||||||
|
n = ''.join(filter(str.isdigit, uuid.uuid4().hex))[:8]
|
||||||
|
n = n.ljust(8, '0')
|
||||||
|
t = sum(int(c) for c in f)
|
||||||
|
t += sum(int(c, 16) for c in u)
|
||||||
|
t += sum(int(c) for c in n)
|
||||||
|
c = str(t % 10)
|
||||||
|
l = t + int(c)
|
||||||
|
d = hex(l % 16)[-1]
|
||||||
|
|
||||||
|
return f"{self.tipo_objeto}{f}-{d}{u}{c}{n}"
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def verificar(id_: str) -> bool:
|
||||||
|
try:
|
||||||
|
f = id_[4:12]
|
||||||
|
cuerpo = id_[13:]
|
||||||
|
d = cuerpo[0]
|
||||||
|
u = cuerpo[1:10]
|
||||||
|
c = cuerpo[10]
|
||||||
|
n = cuerpo[11:19]
|
||||||
|
t = sum(int(c) for c in f)
|
||||||
|
t += sum(int(c, 16) for c in u)
|
||||||
|
t += sum(int(c) for c in n)
|
||||||
|
esd = str(t % 10)
|
||||||
|
l = t + int(esd)
|
||||||
|
esh = hex(l % 16)[-1]
|
||||||
|
return (
|
||||||
|
d.lower() == esh.lower() and
|
||||||
|
c == esd
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def tamaño_bytes_bits(id_: str):
|
||||||
|
"""Devuelve el tamaño del ID en bytes y bits (UTF-8)"""
|
||||||
|
bytes_ = len(id_.encode('utf-8'))
|
||||||
|
return bytes_, bytes_ * 8
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Ejemplo de uso
|
||||||
|
generador = GeneradorIDUnico("FACT")
|
||||||
|
nuevo_id = generador.generar()
|
||||||
|
print(f"Nuevo ID generado: {nuevo_id}")
|
||||||
|
|
||||||
|
# Verificación del ID
|
||||||
|
es_valido = GeneradorIDUnico.verificar(nuevo_id)
|
||||||
|
print(f"El ID es válido: {es_valido}")
|
||||||
|
|
||||||
|
# Tamaño del ID
|
||||||
|
bytes_, bits_ = GeneradorIDUnico.tamaño_bytes_bits(nuevo_id)
|
||||||
|
print(f"Tamaño del ID: {bytes_} bytes / {bits_} bits")
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
from src.Security.GenerarIDs import GeneradorIDUnico
|
||||||
|
from src.Llms.Embedders.Base_Embedder import EmbedderABC # Asegúrate de que esta ruta sea correcta
|
||||||
|
from typing import List, Optional
|
||||||
|
from src.ConexionSql.Base_conexion import ConexionBase
|
||||||
|
from sqlalchemy import MetaData # Asegúrate de importar esto
|
||||||
|
from src.TextManager.notas_mmr import generar_tabla_nota_para_biblioteca # Ajusta si es necesario
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
from src.base import Base
|
||||||
|
|
||||||
|
|
||||||
|
class Biblioteca:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
nombre: str,
|
||||||
|
descripcion: str = "",
|
||||||
|
id: Optional[str] = None,
|
||||||
|
embedder: Optional[EmbedderABC] = None,
|
||||||
|
vector_dim: Optional[int] = None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Clase que representa una biblioteca de notas de texto.
|
||||||
|
|
||||||
|
:param nombre: Nombre de la biblioteca.
|
||||||
|
:param descripcion: Breve descripción de la biblioteca.
|
||||||
|
:param id: ID único opcional. Si no se proporciona, se genera automáticamente.
|
||||||
|
:param embedder: Objeto que implementa EmbedderABC para generar el vector del nombre.
|
||||||
|
:param vector_dim: Dimensión del vector si no se proporciona un embedder.
|
||||||
|
"""
|
||||||
|
self.id = id if id is not None else GeneradorIDUnico("BBLI").generar()
|
||||||
|
self.nombre = nombre if "biblio" in nombre else f"biblio_{nombre}"
|
||||||
|
self.descripcion = descripcion
|
||||||
|
self.embedder = embedder
|
||||||
|
|
||||||
|
if self.embedder is not None:
|
||||||
|
self.vector_dim = self.embedder.dimension_number()
|
||||||
|
elif vector_dim is not None:
|
||||||
|
self.vector_dim = vector_dim
|
||||||
|
else:
|
||||||
|
raise ValueError("Debes proporcionar un 'embedder' o un 'vector_dim' explícito.")
|
||||||
|
|
||||||
|
def generar_modelo_notas(self, conexion: ConexionBase):
|
||||||
|
nombre_tabla = f"{self.nombre}"
|
||||||
|
print(f"[Notas] Generando tabla: {nombre_tabla}")
|
||||||
|
|
||||||
|
engine = conexion.get_engine()
|
||||||
|
inspector = inspect(engine)
|
||||||
|
|
||||||
|
if inspector.has_table(nombre_tabla):
|
||||||
|
print(f"[Notas] ❌ Ya existe la tabla {nombre_tabla}")
|
||||||
|
raise ValueError(f"Ya existe una tabla con el nombre '{nombre_tabla}' en la base de datos.")
|
||||||
|
|
||||||
|
print("[Notas] Generando definición SQL...")
|
||||||
|
tabla, NotaModel = generar_tabla_nota_para_biblioteca(nombre_tabla, self.vector_dim, Base.metadata)
|
||||||
|
|
||||||
|
print("[Notas] Creando tabla en base de datos...")
|
||||||
|
Base.metadata.create_all(engine)
|
||||||
|
print("[Notas] ✔️ Tabla creada")
|
||||||
|
|
||||||
|
return NotaModel
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import os
|
||||||
|
import base64
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from sqlalchemy import Column, String, Integer
|
||||||
|
|
||||||
|
from src.ArquitectureLayer.Mapper import Mapper_base
|
||||||
|
from src.ArquitectureLayer.Model import Model_base
|
||||||
|
from src.ArquitectureLayer.Repo import Repo_base
|
||||||
|
|
||||||
|
from src.ConexionSql.Base_conexion import ConexionBase
|
||||||
|
from src.base import Base
|
||||||
|
from src.Security.Encriptar import Encriptar_fernet
|
||||||
|
from src.Security.GenerarIDs import GeneradorIDUnico
|
||||||
|
from src.Llms.Embedders.Base_Embedder import EmbedderABC
|
||||||
|
from src.TextManager.biblioteca import Biblioteca # Suponiendo que defines la clase lógica Biblioteca aquí
|
||||||
|
|
||||||
|
# ----------------------
|
||||||
|
# Cargar clave maestra
|
||||||
|
# ----------------------
|
||||||
|
from entrypoint import ENV_PATH
|
||||||
|
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 BibliotecaModel(Base, Model_base):
|
||||||
|
__tablename__ = "bibliotecas"
|
||||||
|
|
||||||
|
id = Column(String, primary_key=True, unique=True)
|
||||||
|
nombre = Column(String, nullable=False, unique=True)
|
||||||
|
|
||||||
|
descripcion = Column(String, nullable=False, default="")
|
||||||
|
|
||||||
|
vector_dim = Column(Integer, nullable=False)
|
||||||
|
|
||||||
|
embedder_info = Column(String, nullable=True) # Nombre de clase, ID de configuración, o info encriptada del embedder
|
||||||
|
|
||||||
|
# ----------------------
|
||||||
|
# MAPPER
|
||||||
|
# ----------------------
|
||||||
|
|
||||||
|
class BibliotecaMapper(Mapper_base[Biblioteca, BibliotecaModel]):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_model(obj: Biblioteca) -> BibliotecaModel:
|
||||||
|
return BibliotecaModel(
|
||||||
|
id=obj.id,
|
||||||
|
nombre=obj.nombre,
|
||||||
|
descripcion=obj.descripcion,
|
||||||
|
vector_dim=obj.vector_dim
|
||||||
|
# embedder no se serializa en el modelo, se maneja por separado
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_model(model: BibliotecaModel) -> Biblioteca:
|
||||||
|
return Biblioteca(
|
||||||
|
id=model.id,
|
||||||
|
nombre=model.nombre,
|
||||||
|
descripcion=model.descripcion,
|
||||||
|
vector_dim=model.vector_dim,
|
||||||
|
embedder=None # se deja para inyección posterior si es necesario
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_dict(obj: Biblioteca) -> dict:
|
||||||
|
embedder_info = type(obj.embedder).__name__ if obj.embedder else None
|
||||||
|
return {
|
||||||
|
"id": obj.id,
|
||||||
|
"nombre": obj.nombre,
|
||||||
|
"descripcion": obj.descripcion,
|
||||||
|
"vector_dim": obj.vector_dim,
|
||||||
|
"embedder_info": embedder_info
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(data: dict) -> Biblioteca:
|
||||||
|
return Biblioteca(
|
||||||
|
id=data["id"],
|
||||||
|
nombre=data["nombre"],
|
||||||
|
descripcion=data["descripcion"],
|
||||||
|
vector_dim=data["vector_dim"],
|
||||||
|
embedder=None # inyección manual si se desea
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------
|
||||||
|
# REPO
|
||||||
|
# ----------------------
|
||||||
|
|
||||||
|
class BibliotecaRepo(Repo_base[BibliotecaModel, Biblioteca]):
|
||||||
|
def __init__(self, conexion: ConexionBase):
|
||||||
|
super().__init__(
|
||||||
|
session=conexion.get_session(),
|
||||||
|
modelo=BibliotecaModel,
|
||||||
|
mapper=BibliotecaMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
def add(self, biblioteca: Biblioteca, created_by: str = None, notes: str = None) -> str:
|
||||||
|
# Lógica personalizada: prevenir duplicados por nombre
|
||||||
|
existente = self.session.query(self.Modelo).filter_by(nombre=biblioteca.nombre).first()
|
||||||
|
if existente:
|
||||||
|
raise ValueError(f"Ya existe una biblioteca con el nombre '{biblioteca.nombre}'")
|
||||||
|
|
||||||
|
return super().add(biblioteca, created_by=created_by, notes=notes)
|
||||||
|
|
||||||
|
def get_by_nombre(self, nombre: str) -> Biblioteca | None:
|
||||||
|
model = self.session.query(self.Modelo).filter_by(nombre=nombre, sys_deleted_at=None).first()
|
||||||
|
return self.Mapper.from_model(model) if model else None
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
from src.Security.GenerarIDs import GeneradorIDUnico
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
class Nota:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
titulo: str,
|
||||||
|
tags: List[str] = None,
|
||||||
|
conexiones: List[str] = None,
|
||||||
|
texto: str = "",
|
||||||
|
vector: List[float] = None,
|
||||||
|
resumen: str = "",
|
||||||
|
vector_resumen: List[float] = None,
|
||||||
|
id: str = None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Clase que representa una nota de texto con estructura semántica.
|
||||||
|
|
||||||
|
:param titulo: Título de la nota.
|
||||||
|
:param tags: Lista de etiquetas asociadas.
|
||||||
|
:param conexiones: Lista de identificadores relacionados.
|
||||||
|
:param vector: Embedding vectorial de la nota.
|
||||||
|
:param resumen: Texto resumen de la nota.
|
||||||
|
:param vector_resumen: Embedding del resumen.
|
||||||
|
:param id: Identificador único (si no se proporciona, se genera automáticamente).
|
||||||
|
"""
|
||||||
|
self.id = id if id is not None else GeneradorIDUnico("NOTA").generar()
|
||||||
|
self.titulo = titulo
|
||||||
|
self.tags = tags if tags is not None else []
|
||||||
|
self.conexiones = conexiones if conexiones is not None else []
|
||||||
|
self.texto = texto
|
||||||
|
self.vector = vector
|
||||||
|
self.resumen = resumen
|
||||||
|
self.vector_resumen = vector_resumen
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return (
|
||||||
|
f"<Nota id={self.id}, titulo='{self.titulo}', tags={len(self.tags)}, "
|
||||||
|
f"conexiones={len(self.conexiones)}, vector_dim={len(self.vector)}, "
|
||||||
|
f"resumen_len={len(self.resumen)}, vector_resumen_dim={len(self.vector_resumen)}>"
|
||||||
|
)
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from sqlalchemy import Table, Column, String, Text, MetaData
|
||||||
|
from pgvector.sqlalchemy import Vector
|
||||||
|
from sqlalchemy.orm import registry, Session
|
||||||
|
from src.TextManager.nota import Nota
|
||||||
|
from src.ConexionSql.Base_conexion import ConexionBase
|
||||||
|
from typing import Tuple
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
from src.ArquitectureLayer.Mapper import Mapper_base
|
||||||
|
from src.ArquitectureLayer.Model import Model_base
|
||||||
|
from src.ArquitectureLayer.Repo import Repo_base
|
||||||
|
|
||||||
|
|
||||||
|
from src.base import Base # Este es tu declarative_base()
|
||||||
|
|
||||||
|
# ----------------------
|
||||||
|
# Cargar .env
|
||||||
|
# ----------------------
|
||||||
|
from entrypoint import ENV_PATH
|
||||||
|
load_dotenv(ENV_PATH)
|
||||||
|
|
||||||
|
# ----------------------
|
||||||
|
# REGISTRO DINÁMICO PARA TABLAS
|
||||||
|
# ----------------------
|
||||||
|
mapper_registry = registry()
|
||||||
|
|
||||||
|
# ----------------------
|
||||||
|
# FUNCIONES AUXILIARES
|
||||||
|
# ----------------------
|
||||||
|
|
||||||
|
|
||||||
|
def generar_tabla_nota_para_biblioteca(biblioteca_nombre: str, vector_dim: int, metadata: MetaData) -> Tuple[Table, type]:
|
||||||
|
"""
|
||||||
|
Genera una tabla dinámica y modelo ORM para una biblioteca dada, con campos vectoriales y campos del sistema.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
print(f"[INFO] Generando tabla para biblioteca: '{biblioteca_nombre}' con dimensión de vector: {vector_dim}")
|
||||||
|
|
||||||
|
# Nombre SQL-safe
|
||||||
|
nombre_tabla = re.sub(r"[^a-zA-Z0-9_]", "_", biblioteca_nombre.strip().lower())
|
||||||
|
print(f"[DEBUG] Nombre de tabla SQL-safe: '{nombre_tabla}'")
|
||||||
|
|
||||||
|
# Modelo ORM dinámico
|
||||||
|
class NotaModel(Base, Model_base):
|
||||||
|
__tablename__ = nombre_tabla
|
||||||
|
__table_args__ = {"extend_existing": True}
|
||||||
|
|
||||||
|
id = Column(String, primary_key=True)
|
||||||
|
titulo = Column(String, nullable=False)
|
||||||
|
tags = Column(String)
|
||||||
|
conexiones = Column(String)
|
||||||
|
texto = Column(Text)
|
||||||
|
resumen = Column(Text)
|
||||||
|
vector = Column(Vector(vector_dim), nullable=True)
|
||||||
|
vector_resumen = Column(Vector(vector_dim), nullable=True)
|
||||||
|
|
||||||
|
print(f"[INFO] Modelo ORM 'NotaModel' creado para la tabla '{nombre_tabla}'")
|
||||||
|
print(f"[DEBUG] Columnas del modelo: {[c.name for c in NotaModel.__table__.columns]}")
|
||||||
|
print(f"[DEBUG] Tipos de columnas: {[str(c.type) for c in NotaModel.__table__.columns]}")
|
||||||
|
print(f"[DEBUG] Claves primarias: {[c.name for c in NotaModel.__table__.primary_key]}")
|
||||||
|
|
||||||
|
return NotaModel.__table__, NotaModel
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] Error al generar la tabla y modelo ORM para '{biblioteca_nombre}': {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
# ----------------------
|
||||||
|
# MAPPER
|
||||||
|
# ----------------------
|
||||||
|
|
||||||
|
class NotaMapper(Mapper_base[Nota, object]): # Usa `object` si el modelo es dinámico
|
||||||
|
@staticmethod
|
||||||
|
def to_model(nota: Nota):
|
||||||
|
return {
|
||||||
|
"id": nota.id,
|
||||||
|
"titulo": nota.titulo,
|
||||||
|
"tags": ",".join(nota.tags),
|
||||||
|
"conexiones": ",".join(nota.conexiones),
|
||||||
|
"texto": nota.texto,
|
||||||
|
"resumen": nota.resumen,
|
||||||
|
"vector": nota.vector,
|
||||||
|
"vector_resumen": nota.vector_resumen
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_model(model) -> Nota:
|
||||||
|
return Nota(
|
||||||
|
id=model.id,
|
||||||
|
titulo=model.titulo,
|
||||||
|
tags=model.tags.split(",") if model.tags else [],
|
||||||
|
conexiones=model.conexiones.split(",") if model.conexiones else [],
|
||||||
|
texto=model.texto,
|
||||||
|
resumen=model.resumen,
|
||||||
|
vector=list(model.vector) if model.vector is not None else None,
|
||||||
|
vector_resumen=list(model.vector_resumen) if model.vector_resumen is not None else None
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_dict(nota: Nota) -> dict:
|
||||||
|
return {
|
||||||
|
"id": nota.id,
|
||||||
|
"titulo": nota.titulo,
|
||||||
|
"tags": ",".join(nota.tags),
|
||||||
|
"conexiones": ",".join(nota.conexiones),
|
||||||
|
"texto": nota.texto,
|
||||||
|
"resumen": nota.resumen,
|
||||||
|
"vector": nota.vector,
|
||||||
|
"vector_resumen": nota.vector_resumen
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(data: dict) -> Nota:
|
||||||
|
return Nota(
|
||||||
|
id=data["id"],
|
||||||
|
titulo=data["titulo"],
|
||||||
|
tags=data["tags"].split(",") if data.get("tags") else [],
|
||||||
|
conexiones=data["conexiones"].split(",") if data.get("conexiones") else [],
|
||||||
|
texto=data["texto"],
|
||||||
|
resumen=data["resumen"],
|
||||||
|
vector=data.get("vector"),
|
||||||
|
vector_resumen=data.get("vector_resumen")
|
||||||
|
)
|
||||||
|
|
||||||
|
# ----------------------
|
||||||
|
# REPO
|
||||||
|
# ----------------------
|
||||||
|
|
||||||
|
class NotaRepo(Repo_base):
|
||||||
|
def __init__(self, session: Session, modelo_nota: type):
|
||||||
|
if modelo_nota is None:
|
||||||
|
raise ValueError("No se puede instanciar NotaRepo sin un modelo válido de nota.")
|
||||||
|
super().__init__(session=session, modelo=modelo_nota, mapper=NotaMapper)
|
||||||
|
|
||||||
|
# ------------------------
|
||||||
|
# Métodos personalizados
|
||||||
|
# ------------------------
|
||||||
|
|
||||||
|
def get_by_tag(self, tag: str) -> list[Nota]:
|
||||||
|
query = self.session.query(self.Modelo).filter(self.Modelo.tags.contains([tag]))
|
||||||
|
models = query.all()
|
||||||
|
return self.Mapper.from_model_list(models)
|
||||||
|
|
||||||
|
def search_by_text(self, texto: str) -> list[Nota]:
|
||||||
|
query = self.session.query(self.Modelo).filter(self.Modelo.texto.ilike(f'%{texto}%'))
|
||||||
|
models = query.all()
|
||||||
|
return self.Mapper.from_model_list(models)
|
||||||
|
|
||||||
|
def get_paginated(self, offset: int = 0, limit: int = 10) -> list[Nota]:
|
||||||
|
models = self.session.query(self.Modelo).offset(offset).limit(limit).all()
|
||||||
|
return self.Mapper.from_model_list(models)
|
||||||
|
|
||||||
|
def update(self, id_: str, nota_actualizada: Nota) -> bool:
|
||||||
|
model = self.session.get(self.Modelo, id_)
|
||||||
|
if not model:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Campos de dominio
|
||||||
|
model.titulo = nota_actualizada.titulo
|
||||||
|
model.texto = nota_actualizada.texto
|
||||||
|
model.tags = nota_actualizada.tags
|
||||||
|
model.conexiones = nota_actualizada.conexiones
|
||||||
|
model.resumen = nota_actualizada.resumen
|
||||||
|
|
||||||
|
# Actualización de campos de sistema
|
||||||
|
model.sys_version = (model.sys_version or 1) + 1
|
||||||
|
|
||||||
|
self.session.commit()
|
||||||
|
return True
|
||||||
Reference in New Issue
Block a user