Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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.get("/ping")
|
||||
@router.get("/")
|
||||
async def ping():
|
||||
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
|
||||
|
||||
from fastapi import APIRouter
|
||||
from backend.api.v1.endpoints import ping
|
||||
from backend.api.v1.endpoints import ping, text_manager_endpoint
|
||||
|
||||
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
|
||||
app.add_middleware(
|
||||
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_methods=["*"],
|
||||
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)
|
||||
-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
|
||||
+42
-1
@@ -41,6 +41,7 @@ E:\Fitz_Studio
|
||||
│ │ ├── 0e
|
||||
│ │ ├── 10
|
||||
│ │ ├── 11
|
||||
│ │ ├── 13
|
||||
│ │ ├── 15
|
||||
│ │ ├── 17
|
||||
│ │ ├── 18
|
||||
@@ -55,9 +56,11 @@ E:\Fitz_Studio
|
||||
│ │ ├── 22
|
||||
│ │ ├── 23
|
||||
│ │ ├── 24
|
||||
│ │ ├── 25
|
||||
│ │ ├── 26
|
||||
│ │ ├── 27
|
||||
│ │ ├── 28
|
||||
│ │ ├── 2b
|
||||
│ │ ├── 2c
|
||||
│ │ ├── 2d
|
||||
│ │ ├── 2f
|
||||
@@ -65,11 +68,14 @@ E:\Fitz_Studio
|
||||
│ │ ├── 32
|
||||
│ │ ├── 33
|
||||
│ │ ├── 34
|
||||
│ │ ├── 36
|
||||
│ │ ├── 39
|
||||
│ │ ├── 3a
|
||||
│ │ ├── 3c
|
||||
│ │ ├── 3d
|
||||
│ │ ├── 3e
|
||||
│ │ ├── 3f
|
||||
│ │ ├── 40
|
||||
│ │ ├── 41
|
||||
│ │ ├── 42
|
||||
│ │ ├── 43
|
||||
@@ -83,10 +89,14 @@ E:\Fitz_Studio
|
||||
│ │ ├── 4d
|
||||
│ │ ├── 4e
|
||||
│ │ ├── 4f
|
||||
│ │ ├── 50
|
||||
│ │ ├── 51
|
||||
│ │ ├── 52
|
||||
│ │ ├── 55
|
||||
│ │ ├── 56
|
||||
│ │ ├── 57
|
||||
│ │ ├── 58
|
||||
│ │ ├── 59
|
||||
│ │ ├── 5a
|
||||
│ │ ├── 5b
|
||||
│ │ ├── 5c
|
||||
@@ -100,6 +110,7 @@ E:\Fitz_Studio
|
||||
│ │ ├── 65
|
||||
│ │ ├── 67
|
||||
│ │ ├── 69
|
||||
│ │ ├── 6b
|
||||
│ │ ├── 6c
|
||||
│ │ ├── 6d
|
||||
│ │ ├── 6e
|
||||
@@ -109,27 +120,33 @@ E:\Fitz_Studio
|
||||
│ │ ├── 75
|
||||
│ │ ├── 76
|
||||
│ │ ├── 77
|
||||
│ │ ├── 79
|
||||
│ │ ├── 7b
|
||||
│ │ ├── 7c
|
||||
│ │ ├── 7d
|
||||
│ │ ├── 7f
|
||||
│ │ ├── 80
|
||||
│ │ ├── 81
|
||||
│ │ ├── 82
|
||||
│ │ ├── 83
|
||||
│ │ ├── 84
|
||||
│ │ ├── 85
|
||||
│ │ ├── 86
|
||||
│ │ ├── 87
|
||||
│ │ ├── 89
|
||||
│ │ ├── 8a
|
||||
│ │ ├── 8b
|
||||
│ │ ├── 8c
|
||||
│ │ ├── 8d
|
||||
│ │ ├── 90
|
||||
│ │ ├── 92
|
||||
│ │ ├── 94
|
||||
│ │ ├── 95
|
||||
│ │ ├── 97
|
||||
│ │ ├── 98
|
||||
│ │ ├── 99
|
||||
│ │ ├── 9a
|
||||
│ │ ├── 9b
|
||||
│ │ ├── 9c
|
||||
│ │ ├── 9d
|
||||
│ │ ├── a0
|
||||
@@ -148,7 +165,9 @@ E:\Fitz_Studio
|
||||
│ │ ├── ad
|
||||
│ │ ├── ae
|
||||
│ │ ├── af
|
||||
│ │ ├── b0
|
||||
│ │ ├── b1
|
||||
│ │ ├── b2
|
||||
│ │ ├── b3
|
||||
│ │ ├── b4
|
||||
│ │ ├── b5
|
||||
@@ -158,6 +177,7 @@ E:\Fitz_Studio
|
||||
│ │ ├── ba
|
||||
│ │ ├── bb
|
||||
│ │ ├── bc
|
||||
│ │ ├── bd
|
||||
│ │ ├── bf
|
||||
│ │ ├── c0
|
||||
│ │ ├── c3
|
||||
@@ -171,6 +191,7 @@ E:\Fitz_Studio
|
||||
│ │ ├── ce
|
||||
│ │ ├── cf
|
||||
│ │ ├── d1
|
||||
│ │ ├── d3
|
||||
│ │ ├── d4
|
||||
│ │ ├── d5
|
||||
│ │ ├── d6
|
||||
@@ -184,6 +205,8 @@ E:\Fitz_Studio
|
||||
│ │ ├── de
|
||||
│ │ ├── df
|
||||
│ │ ├── e0
|
||||
│ │ ├── e1
|
||||
│ │ ├── e2
|
||||
│ │ ├── e3
|
||||
│ │ ├── e4
|
||||
│ │ ├── e5
|
||||
@@ -202,6 +225,7 @@ E:\Fitz_Studio
|
||||
│ │ ├── f6
|
||||
│ │ ├── f7
|
||||
│ │ ├── f9
|
||||
│ │ ├── fa
|
||||
│ │ ├── fb
|
||||
│ │ ├── fc
|
||||
│ │ ├── fd
|
||||
@@ -298,6 +322,7 @@ E:\Fitz_Studio
|
||||
│ ├── jupyter
|
||||
│ └── man
|
||||
├── Apikeys.ipynb
|
||||
├── Apikeys_embedding.ipynb
|
||||
├── Credenciales.ipynb
|
||||
├── Encriptacion.ipynb
|
||||
├── README.md
|
||||
@@ -310,6 +335,9 @@ E:\Fitz_Studio
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── __pycache__
|
||||
│ │ └── v1
|
||||
│ ├── db
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── conexion.py
|
||||
│ ├── deps
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── auth.py
|
||||
@@ -721,16 +749,19 @@ E:\Fitz_Studio
|
||||
│ │ ├── queue-microtask
|
||||
│ │ ├── range-parser
|
||||
│ │ ├── raw-body
|
||||
│ │ ├── re-resizable
|
||||
│ │ ├── react
|
||||
│ │ ├── react-docgen
|
||||
│ │ ├── react-docgen-typescript
|
||||
│ │ ├── react-dom
|
||||
│ │ ├── react-draggable
|
||||
│ │ ├── react-is
|
||||
│ │ ├── react-number-format
|
||||
│ │ ├── react-reconciler
|
||||
│ │ ├── react-refresh
|
||||
│ │ ├── react-remove-scroll
|
||||
│ │ ├── react-remove-scroll-bar
|
||||
│ │ ├── react-rnd
|
||||
│ │ ├── react-router
|
||||
│ │ ├── react-router-dom
|
||||
│ │ ├── react-style-singleton
|
||||
@@ -892,9 +923,9 @@ E:\Fitz_Studio
|
||||
│ │ ├── Router.tsx
|
||||
│ │ ├── assets
|
||||
│ │ ├── components
|
||||
│ │ ├── data
|
||||
│ │ ├── main.tsx
|
||||
│ │ ├── pages
|
||||
│ │ ├── public
|
||||
│ │ ├── theme.ts
|
||||
│ │ ├── types
|
||||
│ │ └── vite-env.d.ts
|
||||
@@ -905,6 +936,7 @@ E:\Fitz_Studio
|
||||
│ ├── vite.config.js
|
||||
│ ├── vitest.setup.mjs
|
||||
│ └── yarn.lock
|
||||
├── github_tutorial.ipynb
|
||||
├── main.py
|
||||
├── notebooks
|
||||
│ └── hacer_script_nombres.ipynb
|
||||
@@ -958,6 +990,7 @@ E:\Fitz_Studio
|
||||
│ │ └── postgres_credencial_mmr.py
|
||||
│ ├── Llms
|
||||
│ │ ├── Agente.py
|
||||
│ │ ├── Embedders
|
||||
│ │ ├── MCPs
|
||||
│ │ ├── Memory
|
||||
│ │ ├── Modelos
|
||||
@@ -965,8 +998,16 @@ E:\Fitz_Studio
|
||||
│ │ └── __pycache__
|
||||
│ ├── Security
|
||||
│ │ ├── Encriptar.py
|
||||
│ │ ├── GenerarIDs.py
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── __pycache__
|
||||
│ ├── TextManager
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── __pycache__
|
||||
│ │ ├── biblioteca.py
|
||||
│ │ ├── biblioteca_mmr.py
|
||||
│ │ ├── nota.py
|
||||
│ │ └── notas_biblioteca_mmr.py
|
||||
│ ├── __init__.py
|
||||
│ ├── __pycache__
|
||||
│ │ ├── __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.ApiKeys.openai_apikey_mmr import OpenAICredencialModel
|
||||
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
|
||||
import os
|
||||
@@ -41,7 +44,7 @@ def init_db():
|
||||
|
||||
# Crear engine desde la clase de conexión PostgreSQL
|
||||
conexion = PostgresConexion(db_credencial)
|
||||
engine = conexion.engine # Recuperamos el engine directamente
|
||||
engine = conexion.get_engine() # Recuperamos el engine directamente
|
||||
|
||||
print("Creando tablas...")
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
Generated
+156
-19
@@ -13,8 +13,10 @@
|
||||
"@react-three/fiber": "^9.1.2",
|
||||
"@tabler/icons": "^3.31.0",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"axios": "^1.9.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-rnd": "^10.5.2",
|
||||
"react-router-dom": "^7.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -3303,6 +3305,12 @@
|
||||
"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": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||
@@ -3329,6 +3337,17 @@
|
||||
"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": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||
@@ -3551,7 +3570,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
@@ -3712,6 +3730,18 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -4052,6 +4082,15 @@
|
||||
"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": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
@@ -4126,7 +4165,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
@@ -4278,7 +4316,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -4288,7 +4325,6 @@
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -4333,7 +4369,6 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
@@ -4346,7 +4381,6 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
@@ -5060,6 +5094,26 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||
@@ -5093,6 +5147,42 @@
|
||||
"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": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@@ -5132,7 +5222,6 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -5183,7 +5272,6 @@
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
@@ -5217,7 +5305,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
@@ -5412,7 +5499,6 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -5491,7 +5577,6 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -5504,7 +5589,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
@@ -5520,7 +5604,6 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
@@ -6268,7 +6351,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
@@ -6513,7 +6595,6 @@
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
@@ -6580,7 +6661,6 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -6842,7 +6922,6 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -7531,7 +7610,6 @@
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
@@ -7543,7 +7621,6 @@
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
@@ -7560,6 +7637,12 @@
|
||||
"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": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
@@ -7633,6 +7716,16 @@
|
||||
"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": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||
@@ -7699,6 +7792,29 @@
|
||||
"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": {
|
||||
"version": "17.0.2",
|
||||
"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": {
|
||||
"version": "7.5.3",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.3.tgz",
|
||||
|
||||
@@ -25,8 +25,10 @@
|
||||
"@react-three/fiber": "^9.1.2",
|
||||
"@tabler/icons": "^3.31.0",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"axios": "^1.9.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-rnd": "^10.5.2",
|
||||
"react-router-dom": "^7.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
+10
-3
@@ -2,7 +2,8 @@ import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
||||
import { HomePage } from './pages/Home.page';
|
||||
import { Consulta_API } from './pages/Consulta_api';
|
||||
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([
|
||||
{
|
||||
@@ -14,9 +15,15 @@ const router = createBrowserRouter([
|
||||
element: <Consulta_API />,
|
||||
},
|
||||
{
|
||||
path: '/prueba_appshell',
|
||||
element: <Prueba_appshell />,
|
||||
path: '/Grid_Dashboard',
|
||||
element: <Grid_Dashboard />,
|
||||
},
|
||||
{
|
||||
path: '/Biblioteca',
|
||||
element: <Biblioteca />,
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
path: '*',
|
||||
element: <Error_404 />,
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
var(--mantine-font-family);
|
||||
margin-bottom: var(--mantine-spacing-sm);
|
||||
background-color: var(--mantine-color-body);
|
||||
padding: var(--mantine-spacing-md);
|
||||
padding-top: 18px;
|
||||
height: px;
|
||||
padding: var(--mantine-spacing-xs);
|
||||
padding-top: 15px;
|
||||
height: 50px;
|
||||
border-bottom: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-7));
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
&,
|
||||
&:hover {
|
||||
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,52 +2,99 @@ import {
|
||||
AppShell,
|
||||
Burger,
|
||||
Group,
|
||||
Skeleton,
|
||||
Tooltip,
|
||||
UnstyledButton,
|
||||
ActionIcon,
|
||||
Title,
|
||||
useMantineTheme,
|
||||
} from '@mantine/core';
|
||||
|
||||
import { default as LogoIcon } from '../../assets/icons/favicon'; // ruta relativa ajusta según tu estructura
|
||||
|
||||
import { useMantineTheme } from '@mantine/core';
|
||||
|
||||
import { mainLinksdata } from '../../data/navigationsLinks_1'; // ajusta la ruta
|
||||
import { submenuLinks } from '../../data/submenuLinks_1'; // ajusta la ruta según tu estructura
|
||||
|
||||
|
||||
import { useDisclosure, useMediaQuery } from '@mantine/hooks';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { default as LogoIcon } from '../../assets/icons/favicon';
|
||||
import { mainLinksdata } from '../../data/navigationsLinks_1';
|
||||
import { submenuLinks } from '../../data/submenuLinks_1';
|
||||
|
||||
import classes from './Appshell.module.css';
|
||||
|
||||
|
||||
|
||||
type AppShellWithMenuProps = {
|
||||
children?: React.ReactNode; // <- ahora es opcional
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
// Persistencia en localStorage
|
||||
const STORAGE_KEY = 'lastSubmenuRoutes';
|
||||
|
||||
function getLastSubmenuRoute(section: string): string | null {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
const parsed = raw ? JSON.parse(raw) : {};
|
||||
return parsed[section] ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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]);
|
||||
|
||||
const [manualActiveTab, setManualActiveTab] = useState<string | null>(null);
|
||||
|
||||
const matchedMain = Object.entries(submenuLinks).find(([mainKey, items]) =>
|
||||
items.some((item) => location.pathname.startsWith(item.to))
|
||||
const isCollapsed = useMemo(
|
||||
() => (isMobile ? !mobileOpened : !desktopOpened),
|
||||
[isMobile, mobileOpened, desktopOpened]
|
||||
);
|
||||
|
||||
const routeBasedActive = matchedMain?.[0] ?? 'Home';
|
||||
const active = manualActiveTab ?? routeBasedActive;
|
||||
const activeLink = submenuLinks[active as keyof typeof submenuLinks]?.find((item) => location.pathname === item.to)?.label ?? '';
|
||||
// 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
|
||||
@@ -59,18 +106,25 @@ import {
|
||||
>
|
||||
<UnstyledButton
|
||||
onClick={() => {
|
||||
setManualActiveTab(link.label);
|
||||
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 === active || undefined}
|
||||
data-active={link.label === activeMain || undefined}
|
||||
>
|
||||
<link.icon />
|
||||
</UnstyledButton>
|
||||
</Tooltip>
|
||||
));
|
||||
|
||||
const links = (submenuLinks[active as keyof typeof submenuLinks] || []).map((item) => (
|
||||
const links = (submenuLinks[activeMain as keyof typeof submenuLinks] || []).map((item) => (
|
||||
<Link
|
||||
className={classes.link}
|
||||
data-active={activeLink === item.label || undefined}
|
||||
@@ -78,6 +132,7 @@ import {
|
||||
key={item.label}
|
||||
style={{ display: isCollapsed ? 'none' : 'block' }}
|
||||
onClick={() => {
|
||||
setLastSubmenuRoute(activeMain, item.to);
|
||||
if (isMobile) closeMobile();
|
||||
}}
|
||||
>
|
||||
@@ -85,10 +140,6 @@ import {
|
||||
</Link>
|
||||
));
|
||||
|
||||
useEffect(() => {
|
||||
setManualActiveTab(null);
|
||||
}, [location.pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isMobile) openDesktop();
|
||||
}, [isMobile, openDesktop]);
|
||||
@@ -103,9 +154,7 @@ import {
|
||||
}}
|
||||
padding="md"
|
||||
>
|
||||
|
||||
{/* Header */}
|
||||
|
||||
<AppShell.Header>
|
||||
<Group h="100%" px="sm">
|
||||
<Burger opened={mobileOpened} onClick={toggleMobile} hiddenFrom="sm" size="sm" />
|
||||
@@ -115,37 +164,28 @@ import {
|
||||
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 className={classes.topSection}>{mainLinks}</div>
|
||||
</div>
|
||||
<div className={classes.main}>
|
||||
{!isCollapsed && <Title order={4} className={classes.title}>{active}</Title>}
|
||||
{!isCollapsed && (
|
||||
<Title order={4} className={classes.title}>
|
||||
{activeMain}
|
||||
</Title>
|
||||
)}
|
||||
{links}
|
||||
</div>
|
||||
</div>
|
||||
</AppShell.Navbar>
|
||||
|
||||
{/* Main Content */}
|
||||
|
||||
<AppShell.Main>
|
||||
{children}
|
||||
</AppShell.Main>
|
||||
|
||||
|
||||
<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';
|
||||
|
||||
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 [contenido, setContenido] = useState('');
|
||||
const [respuesta, setRespuesta] = useState('');
|
||||
|
||||
@@ -11,7 +11,7 @@ export function Welcome() {
|
||||
<Title className={classes.title} ta="center" mt={100}>
|
||||
Hola! {' '}
|
||||
<Text inherit variant="gradient" component="span" gradient={{ from: theme.colors.brand[7], to: theme.colors.secondary[4], }} style={{ letterSpacing: '1px' }}>
|
||||
Egutierrez
|
||||
Holooooo
|
||||
</Text>
|
||||
</Title>
|
||||
<Text c="dimmed" ta="left" size="lg" maw={580} mx="auto" mt="xl">
|
||||
|
||||
@@ -4,10 +4,12 @@ export const submenuLinks = {
|
||||
Home: [
|
||||
{ label: 'Inicio', to: '/' },
|
||||
{ label: 'Consulta Api', to: '/Consulta_API' },
|
||||
{ label: 'Prueba_appshell', to: '/prueba_appshell' },
|
||||
{ label: 'Biblioteca', to: '/Biblioteca' },
|
||||
|
||||
],
|
||||
Dashboard: [
|
||||
{ label: 'Resumen', to: '/dashboard/resumen' },
|
||||
{ label: 'Grid_Dashboard', to: '/Grid_Dashboard' },
|
||||
{ label: 'Estadísticas', to: '/dashboard/estadisticas' },
|
||||
{ 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 { Welcome } from '@/components/Welcome/Welcome';
|
||||
import { ColorSchemeToggle } from '@/components/ColorSchemeToggle/ColorSchemeToggle';
|
||||
|
||||
|
||||
export function HomePage() {
|
||||
return (
|
||||
@@ -13,6 +15,8 @@ export function HomePage() {
|
||||
</div>
|
||||
|
||||
|
||||
<ColorSchemeToggle></ColorSchemeToggle>
|
||||
|
||||
</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({
|
||||
plugins: [react(), tsconfigPaths(), svgr()],
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8000',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
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"
|
||||
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:
|
||||
version "1.0.7"
|
||||
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"
|
||||
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:
|
||||
version "4.1.0"
|
||||
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"
|
||||
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:
|
||||
version "2.1.1"
|
||||
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"
|
||||
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:
|
||||
version "0.0.1"
|
||||
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"
|
||||
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:
|
||||
version "2.0.0"
|
||||
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"
|
||||
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:
|
||||
version "0.3.5"
|
||||
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"
|
||||
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:
|
||||
version "0.2.0"
|
||||
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"
|
||||
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:
|
||||
version "3.0.1"
|
||||
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"
|
||||
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:
|
||||
version "2.3.1"
|
||||
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"
|
||||
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:
|
||||
version "2.2.2"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz"
|
||||
integrity sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==
|
||||
dependencies:
|
||||
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:
|
||||
version "16.13.1"
|
||||
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-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:
|
||||
version "7.5.3"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.npmjs.org/react/-/react-19.1.0.tgz"
|
||||
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"
|
||||
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:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz"
|
||||
|
||||
@@ -5,15 +5,7 @@
|
||||
"execution_count": 1,
|
||||
"id": "26aa8e2b",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"✅ Credencial: Credencial_enmanuel\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from src.ApiKeys.openai_apikey import OpenAICredencial\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",
|
||||
"\n",
|
||||
"# 3. Guardar la credencial en la base de datos\n",
|
||||
"repo = OpenAICredencialRepo(conexion_admin)\n",
|
||||
"credencial_openai = repo.get_by_id(1)\n",
|
||||
"repo = OpenAICredencialRepo(conexion_admin)\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}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": null,
|
||||
"id": "7464fa65",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "e5b665a6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 1,
|
||||
"id": "5206b9c6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"execution_count": 2,
|
||||
"id": "63a0b954",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 3,
|
||||
"id": "0575f424",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -38,17 +38,17 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"execution_count": 4,
|
||||
"id": "a5266309",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"1"
|
||||
"'PGCR20250510-02f3cf9610127084237'"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
@@ -72,7 +72,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"modelo_cred = repo_cred.get_by_id(1)\n",
|
||||
"modelo_cred = repo_cred.get_by_id(\"PGCR20250510-02f3cf9610127084237\")\n",
|
||||
"\n",
|
||||
"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
|
||||
}
|
||||
@@ -13,7 +13,7 @@ from src.Llms.Memory.postgres_MemoryConv import MemoryConvPostgres
|
||||
conexion_admin = PostgresConexion(db_credencial)
|
||||
|
||||
repo = OpenAICredencialRepo(conexion_admin)
|
||||
credencial_openai = repo.get_by_id(1)
|
||||
credencial_openai = repo.get_by_id("OPAK20250513-61b29978b7604031014")
|
||||
|
||||
cliente = OpenAICliente(credencial_openai)
|
||||
|
||||
|
||||
@@ -28,3 +28,4 @@ def save_tree_to_file(start_path='.', max_depth=2, output_file='tree.txt'):
|
||||
# Ejemplo de uso:
|
||||
# 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')
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
from src.Security.GenerarIDs import GeneradorIDUnico
|
||||
|
||||
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 api_key: Clave secreta de la API de OpenAI.
|
||||
:param organizacion: (Opcional) ID de la organización asociada a la cuenta de OpenAI.
|
||||
"""
|
||||
self.id = id if id is not None else GeneradorIDUnico("OPAK").generar()
|
||||
self.titulo = titulo
|
||||
self.api_key = api_key
|
||||
self.organizacion = organizacion
|
||||
|
||||
@@ -8,6 +8,13 @@ from src.base import Base
|
||||
from src.ApiKeys.openai_apikey import OpenAICredencial
|
||||
from src.Security.Encriptar import Encriptar_fernet
|
||||
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
|
||||
@@ -21,10 +28,10 @@ if pssword is None:
|
||||
# MODELO (SQLAlchemy)
|
||||
# ----------------------
|
||||
|
||||
class OpenAICredencialModel(Base):
|
||||
class OpenAICredencialModel(Base, Model_base):
|
||||
__tablename__ = 'openai_credenciales'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
id = Column(String, primary_key=True)
|
||||
titulo = Column(String, nullable=False)
|
||||
api_key = Column(String, nullable=False) # Encriptada como base64 string
|
||||
organizacion = Column(String, nullable=True)
|
||||
@@ -33,30 +40,23 @@ class OpenAICredencialModel(Base):
|
||||
# MAPPER
|
||||
# ----------------------
|
||||
|
||||
class OpenAICredencialMapper:
|
||||
@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
|
||||
}
|
||||
class OpenAICredencialMapper(Mapper_base[OpenAICredencial, OpenAICredencialModel]):
|
||||
|
||||
@staticmethod
|
||||
def from_dict(data: dict) -> OpenAICredencial:
|
||||
return OpenAICredencial(
|
||||
titulo=data["titulo"],
|
||||
api_key=Encriptar_fernet.desencriptar(
|
||||
base64.b64decode(data["api_key"]), pssword
|
||||
),
|
||||
organizacion=data.get("organizacion")
|
||||
def to_model(obj: OpenAICredencial) -> OpenAICredencialModel:
|
||||
return OpenAICredencialModel(
|
||||
id=obj.id,
|
||||
titulo=obj.titulo,
|
||||
api_key=base64.b64encode(
|
||||
Encriptar_fernet.encriptar(obj.api_key, pssword)
|
||||
).decode("utf-8"),
|
||||
organizacion=obj.organizacion
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def from_model(model: OpenAICredencialModel) -> OpenAICredencial:
|
||||
return OpenAICredencial(
|
||||
id=model.id,
|
||||
titulo=model.titulo,
|
||||
api_key=Encriptar_fernet.desencriptar(
|
||||
base64.b64decode(model.api_key), pssword
|
||||
@@ -64,29 +64,44 @@ class OpenAICredencialMapper:
|
||||
organizacion=model.organizacion
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def to_dict(obj: OpenAICredencial) -> dict:
|
||||
return {
|
||||
"id": obj.id,
|
||||
"titulo": obj.titulo,
|
||||
"api_key": base64.b64encode(
|
||||
Encriptar_fernet.encriptar(obj.api_key, pssword)
|
||||
).decode("utf-8"),
|
||||
"organizacion": obj.organizacion
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def from_dict(data: dict) -> OpenAICredencial:
|
||||
return OpenAICredencial(
|
||||
id=data["id"],
|
||||
titulo=data["titulo"],
|
||||
api_key=Encriptar_fernet.desencriptar(
|
||||
base64.b64decode(data["api_key"]), pssword
|
||||
),
|
||||
organizacion=data.get("organizacion")
|
||||
)
|
||||
|
||||
# ----------------------
|
||||
# REPO
|
||||
# ----------------------
|
||||
|
||||
class OpenAICredencialRepo:
|
||||
class OpenAICredencialRepo(Repo_base[OpenAICredencialModel, OpenAICredencial]):
|
||||
def __init__(self, conexion: ConexionBase):
|
||||
self.session = conexion.get_session()
|
||||
|
||||
def add(self, credencial: OpenAICredencial) -> int:
|
||||
data = OpenAICredencialMapper.to_dict(credencial)
|
||||
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]
|
||||
super().__init__(
|
||||
session=conexion.get_session(),
|
||||
modelo=OpenAICredencialModel,
|
||||
mapper=OpenAICredencialMapper
|
||||
)
|
||||
|
||||
def get_by_titulo(self, titulo: str) -> OpenAICredencial | None:
|
||||
model = self.session.query(OpenAICredencialModel).filter_by(titulo=titulo).first()
|
||||
return OpenAICredencialMapper.from_model(model) if model else None
|
||||
|
||||
def get_by_id(self, id_: int) -> OpenAICredencial | None:
|
||||
model = self.session.get(OpenAICredencialModel, id_)
|
||||
return OpenAICredencialMapper.from_model(model) if model else None
|
||||
model = (
|
||||
self.session.query(self.Modelo)
|
||||
.filter_by(titulo=titulo, sys_deleted_at=None)
|
||||
.first()
|
||||
)
|
||||
return self.Mapper.from_model(model) if model else None
|
||||
@@ -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 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):
|
||||
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
|
||||
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 sqlalchemy import create_engine, text
|
||||
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.Credenciales.postgres_credencial import PostgresCredencial
|
||||
|
||||
|
||||
class PostgresConexion(ConexionBase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.estado = "pendiente"
|
||||
@@ -18,28 +18,41 @@ class PostgresConexion(ConexionBase):
|
||||
self.port = credencial.port
|
||||
self.dbname = credencial.dbname
|
||||
self.user = credencial.user
|
||||
self.password = credencial.password # se guarda la contraseña
|
||||
self.password = credencial.password
|
||||
uri = credencial.get_uri()
|
||||
else:
|
||||
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.port = kwargs.get("port", 5432)
|
||||
self.dbname = kwargs.get("db") or kwargs.get("dbname")
|
||||
uri = f"postgresql://{self.user}:{self.password}@{self.host}:{self.port}/{self.dbname}"
|
||||
|
||||
self.engine = create_engine(uri)
|
||||
self.SessionLocal = sessionmaker(bind=self.engine)
|
||||
self._engine: Engine = create_engine(uri)
|
||||
self._Session = sessionmaker(bind=self._engine)
|
||||
|
||||
def get_session(self):
|
||||
return self.SessionLocal()
|
||||
# ✅ INICIALIZAR LA SESIÓN AQUÍ
|
||||
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:
|
||||
try:
|
||||
with self.engine.connect() as connection:
|
||||
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,5 +1,8 @@
|
||||
from src.Security.GenerarIDs import GeneradorIDUnico
|
||||
|
||||
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.host = host
|
||||
self.port = port
|
||||
@@ -7,6 +10,7 @@ class PostgresCredencial:
|
||||
self.user = user
|
||||
self.password = password
|
||||
|
||||
|
||||
def get_uri(self) -> str:
|
||||
"""
|
||||
Retorna una URI de conexión para PostgreSQL.
|
||||
|
||||
@@ -2,6 +2,13 @@ import os
|
||||
from dotenv import load_dotenv
|
||||
from sqlalchemy import Column, Integer, String
|
||||
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.base import Base
|
||||
@@ -21,10 +28,10 @@ if pssword is None:
|
||||
# MODELO (SQLAlchemy)
|
||||
# ----------------------
|
||||
|
||||
class PostgresCredencialModel(Base):
|
||||
class PostgresCredencialModel(Base, Model_base):
|
||||
__tablename__ = 'postgres_credenciales'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
id = Column(String, primary_key=True)
|
||||
titulo = Column(String, nullable=False)
|
||||
host = Column(String, nullable=False)
|
||||
port = Column(Integer, nullable=False)
|
||||
@@ -36,12 +43,40 @@ class PostgresCredencialModel(Base):
|
||||
# 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
|
||||
def to_dict(obj: PostgresCredencial) -> dict:
|
||||
return {
|
||||
"id": obj.id,
|
||||
"titulo": obj.titulo,
|
||||
"host": obj.host,
|
||||
"port": obj.port,
|
||||
@@ -55,6 +90,7 @@ class PostgresCredencialMapper:
|
||||
@staticmethod
|
||||
def from_dict(data: dict) -> PostgresCredencial:
|
||||
return PostgresCredencial(
|
||||
id=data["id"],
|
||||
titulo=data["titulo"],
|
||||
host=data["host"],
|
||||
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
|
||||
# ----------------------
|
||||
|
||||
class PostgresCredencialRepo:
|
||||
class PostgresCredencialRepo(Repo_base[PostgresCredencialModel, PostgresCredencial]):
|
||||
def __init__(self, conexion: ConexionBase):
|
||||
self.session = conexion.get_session()
|
||||
|
||||
def add(self, credencial: PostgresCredencial) -> int:
|
||||
data = PostgresCredencialMapper.to_dict(credencial)
|
||||
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]
|
||||
super().__init__(
|
||||
session=conexion.get_session(),
|
||||
modelo=PostgresCredencialModel,
|
||||
mapper=PostgresCredencialMapper
|
||||
)
|
||||
|
||||
def get_by_titulo(self, titulo: str) -> PostgresCredencial | None:
|
||||
model = self.session.query(PostgresCredencialModel).filter_by(titulo=titulo).first()
|
||||
return PostgresCredencialMapper.from_model(model) if model else None
|
||||
|
||||
def get_by_id(self, id_: int) -> PostgresCredencial | None:
|
||||
model = self.session.get(PostgresCredencialModel, id_)
|
||||
return PostgresCredencialMapper.from_model(model) if model else None
|
||||
model = (
|
||||
self.session.query(self.Modelo)
|
||||
.filter_by(titulo=titulo, sys_deleted_at=None)
|
||||
.first()
|
||||
)
|
||||
return self.Mapper.from_model(model) if model else None
|
||||
@@ -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,15 @@
|
||||
from fastmcp import Client
|
||||
import asyncio
|
||||
|
||||
async def main():
|
||||
|
||||
async with Client("http://127.0.0.1:8080/mcp") as client:
|
||||
tools = await client.list_tools()
|
||||
for tool in tools:
|
||||
print(f"🔧 {tool.name} - {tool.description or 'sin descripción'}")
|
||||
|
||||
|
||||
client.call_tool_mcp()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,17 @@
|
||||
# cliente_prueba_http.py
|
||||
|
||||
import asyncio
|
||||
from fastmcp import Client
|
||||
|
||||
async def main():
|
||||
async with Client("http://127.0.0.1:8080/mcp") as client:
|
||||
tools = await client.list_tools()
|
||||
for tool in tools:
|
||||
print(f"🔧 {tool.name} - {tool.description or 'sin descripción'}")
|
||||
|
||||
# ✅ llamar a la herramienta correctamente
|
||||
result = await client.call_tool_mcp(name="esperar", arguments={"segundos": 5})
|
||||
print(f"\nResultado de 'saludar': {result.content[0].text}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,30 @@
|
||||
# archivo: sse_server.py
|
||||
|
||||
from fastmcp.server import FastMCP
|
||||
import asyncio
|
||||
from fastmcp import Client
|
||||
|
||||
# Crear la instancia del servidor
|
||||
server = FastMCP(
|
||||
name="ServidorSSE",
|
||||
instructions="Este servidor expone herramientas de prueba.",
|
||||
)
|
||||
|
||||
# Herramienta 1: saludar
|
||||
@server.tool(name="saludar", description="Saluda a una persona por su nombre.")
|
||||
def saludar(nombre: str) -> str:
|
||||
return f"¡Hola, {nombre}!"
|
||||
|
||||
# Herramienta 2: espera asíncrona
|
||||
@server.tool(name="esperar", description="Espera N segundos y responde.")
|
||||
async def esperar(segundos: int) -> str:
|
||||
await asyncio.sleep(segundos)
|
||||
return f"Esperé {segundos} segundos como me pediste."
|
||||
|
||||
# Punto de entrada para ejecutarlo por SSE
|
||||
if __name__ == "__main__":
|
||||
server.run(
|
||||
transport="streamable-http", # <-- cambio aquí
|
||||
host="0.0.0.0",
|
||||
port=8080,
|
||||
)
|
||||
@@ -0,0 +1,30 @@
|
||||
# archivo: sse_server.py
|
||||
|
||||
from fastmcp.server import FastMCP
|
||||
import asyncio
|
||||
from fastmcp import Client
|
||||
|
||||
# Crear la instancia del servidor
|
||||
server = FastMCP(
|
||||
name="ServidorSSE",
|
||||
instructions="Este servidor expone herramientas de prueba.",
|
||||
)
|
||||
|
||||
# Herramienta 1: saludar
|
||||
@server.tool(name="saludar", description="Saluda a una persona por su nombre.")
|
||||
def saludar(nombre: str) -> str:
|
||||
return f"¡Hola, {nombre}!"
|
||||
|
||||
# Herramienta 2: espera asíncrona
|
||||
@server.tool(name="esperar", description="Espera N segundos y responde.")
|
||||
async def esperar(segundos: int) -> str:
|
||||
await asyncio.sleep(segundos)
|
||||
return f"Esperé {segundos} segundos como me pediste."
|
||||
|
||||
# Punto de entrada para ejecutarlo por SSE
|
||||
if __name__ == "__main__":
|
||||
server.run(
|
||||
transport="streamable-http", # <-- cambio aquí
|
||||
host="0.0.0.0",
|
||||
port=8080,
|
||||
)
|
||||
@@ -27,7 +27,7 @@ class MemoryConvPostgres(MemoryConvABC):
|
||||
)
|
||||
|
||||
# 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:
|
||||
stmt = insert(self.tabla).values(rol=rol, contenido=contenido)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from src.Llms.Modelos.Base_model import ModeloABC
|
||||
from src.ConexionApis.OpenAi_conexion import OpenAICliente
|
||||
from src.Security.GenerarIDs import GeneradorIDUnico
|
||||
import asyncio
|
||||
from typing import AsyncGenerator, Union
|
||||
|
||||
@@ -8,6 +9,7 @@ class ModeloOpenAI(ModeloABC):
|
||||
self,
|
||||
cliente: OpenAICliente,
|
||||
model: str = "gpt-4o",
|
||||
id: str = None,
|
||||
temperature: float = 0.7,
|
||||
top_p: float = 1.0,
|
||||
top_k: int = None,
|
||||
@@ -15,6 +17,10 @@ class ModeloOpenAI(ModeloABC):
|
||||
num_tokens_maximos: int = 512,
|
||||
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__(
|
||||
model=model,
|
||||
temperature=temperature,
|
||||
@@ -23,6 +29,8 @@ class ModeloOpenAI(ModeloABC):
|
||||
frecuencia_penalizacion=frecuencia_penalizacion,
|
||||
num_tokens_maximos=num_tokens_maximos
|
||||
)
|
||||
|
||||
# Asignar cliente e indicadores adicionales
|
||||
self.cliente = cliente
|
||||
self.use_legacy = use_legacy
|
||||
|
||||
@@ -75,7 +83,7 @@ class ModeloOpenAI(ModeloABC):
|
||||
|
||||
if stream:
|
||||
async def generador():
|
||||
for token in resultado: # ya es un generador del cliente
|
||||
for token in resultado:
|
||||
yield token
|
||||
return generador()
|
||||
else:
|
||||
|
||||
@@ -2,6 +2,12 @@ import os
|
||||
from dotenv import load_dotenv
|
||||
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.base import Base
|
||||
from src.Llms.Modelos.Openai_model import ModeloOpenAI # Clase real de lógica
|
||||
@@ -19,38 +25,44 @@ if pssword is None:
|
||||
# MODELO (SQLAlchemy)
|
||||
# ----------------------
|
||||
|
||||
class ModeloOpenAIConfigModel(Base):
|
||||
class ModeloOpenAIConfigModel(Base, Model_base):
|
||||
__tablename__ = 'modelo_openai_configs'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
id = Column(String, primary_key=True)
|
||||
|
||||
model = Column(String, nullable=False)
|
||||
temperature = Column(Float, default=0.7)
|
||||
top_p = Column(Float, default=1.0)
|
||||
temperature = Column(Float, default=0.7, nullable=False)
|
||||
top_p = Column(Float, default=1.0, nullable=False)
|
||||
top_k = Column(Integer, nullable=True)
|
||||
frecuencia_penalizacion = Column(Float, default=0.0)
|
||||
num_tokens_maximos = Column(Integer, default=512)
|
||||
use_legacy = Column(Boolean, default=False)
|
||||
|
||||
frecuencia_penalizacion = Column(Float, default=0.0, nullable=False)
|
||||
num_tokens_maximos = Column(Integer, default=512, nullable=False)
|
||||
|
||||
use_legacy = Column(Boolean, default=False, nullable=False)
|
||||
|
||||
# ----------------------
|
||||
# MAPPER
|
||||
# ----------------------
|
||||
|
||||
class ModeloOpenAIConfigMapper:
|
||||
@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
|
||||
}
|
||||
class ModeloOpenAIConfigMapper(Mapper_base[ModeloOpenAI, ModeloOpenAIConfigModel]):
|
||||
|
||||
@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(
|
||||
id=model.id,
|
||||
cliente=cliente,
|
||||
model=model.model,
|
||||
temperature=model.temperature,
|
||||
@@ -61,26 +73,50 @@ class ModeloOpenAIConfigMapper:
|
||||
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
|
||||
# ----------------------
|
||||
|
||||
class ModeloOpenAIConfigRepo:
|
||||
class ModeloOpenAIConfigRepo(Repo_base[ModeloOpenAIConfigModel, ModeloOpenAI]):
|
||||
def __init__(self, conexion: ConexionBase, cliente: object):
|
||||
self.session = conexion.get_session()
|
||||
self.cliente = cliente # Necesario para crear ModeloOpenAI
|
||||
super().__init__(
|
||||
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:
|
||||
data = ModeloOpenAIConfigMapper.to_dict(config)
|
||||
model = ModeloOpenAIConfigModel(**data)
|
||||
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_by_id(self, id_: str) -> ModeloOpenAI | None:
|
||||
model = self.session.get(self.Modelo, id_)
|
||||
return self.Mapper.from_model(model, self.cliente) if model else None
|
||||
|
||||
def get_all(self) -> list[ModeloOpenAI]:
|
||||
models = self.session.query(ModeloOpenAIConfigModel).all()
|
||||
return [ModeloOpenAIConfigMapper.from_model(m, self.cliente) for m in models]
|
||||
models = self.session.query(self.Modelo).all()
|
||||
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