Notas en frontend funcionando y pudiendo subir mas por sus endpoints

This commit is contained in:
2025-05-11 02:30:55 +02:00
parent b34d52036e
commit 712bd877b8
14 changed files with 795 additions and 41 deletions
+231
View File
@@ -0,0 +1,231 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 8,
"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": 9,
"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": 11,
"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: sajdhasjdhasjdh\n",
"[DEBUG] Nota creada: titulo='sajdhasjdhasjdh', 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-04dbb203a9126228444\n",
"[SUCCESS] Nota 'sajdhasjdhasjdh' agregada con éxito a la biblioteca 'biblio_Pruebas_1'.\n"
]
},
{
"data": {
"text/plain": [
"{'mensaje': \"Nota 'sajdhasjdhasjdh' agregada con éxito a la biblioteca 'biblio_Pruebas_1'.\",\n",
" 'nota_id': 'NOTA20250511-04dbb203a9126228444'}"
]
},
"execution_count": 11,
"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=\"sajdhasjdhasjdh\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 13,
"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": 14,
"id": "ae4f2994",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"E:\\Fitz_Studio\\src\\TextManager\\notas_biblioteca_mmr.py:51: SAWarning: This declarative base already contains a class with the same class name and module name as src.TextManager.notas_biblioteca_mmr.NotaModel, and will be replaced in the string-lookup table.\n",
" mapper_registry.map_imperatively(NotaModel, tabla)\n"
]
},
{
"ename": "TypeError",
"evalue": "'NoneType' object is not iterable",
"output_type": "error",
"traceback": [
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
"\u001b[31mTypeError\u001b[39m Traceback (most recent call last)",
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[14]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mlistar_notas_de_biblioteca\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 2\u001b[39m \u001b[43m \u001b[49m\u001b[43mconexion\u001b[49m\u001b[43m=\u001b[49m\u001b[43mconexion_admin\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 3\u001b[39m \u001b[43m \u001b[49m\u001b[43mbiblioteca_id\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mBBLI20250511-a91dbb2168172979414\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\n\u001b[32m 4\u001b[39m \u001b[43m)\u001b[49m\n",
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[13]\u001b[39m\u001b[32m, line 12\u001b[39m, in \u001b[36mlistar_notas_de_biblioteca\u001b[39m\u001b[34m(conexion, biblioteca_id)\u001b[39m\n\u001b[32m 9\u001b[39m metadata.create_all(conexion.get_engine())\n\u001b[32m 11\u001b[39m repo_nota = NotaRepo(conexion.get_session(), ModeloNota)\n\u001b[32m---> \u001b[39m\u001b[32m12\u001b[39m notas = \u001b[43mrepo_nota\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget_all\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 13\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m [\n\u001b[32m 14\u001b[39m {\n\u001b[32m 15\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mid\u001b[39m\u001b[33m\"\u001b[39m: n.id,\n\u001b[32m (...)\u001b[39m\u001b[32m 22\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m n \u001b[38;5;129;01min\u001b[39;00m notas\n\u001b[32m 23\u001b[39m ]\n",
"\u001b[36mFile \u001b[39m\u001b[32mE:\\Fitz_Studio\\src\\TextManager\\notas_biblioteca_mmr.py:109\u001b[39m, in \u001b[36mget_all\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 0\u001b[39m <Error retrieving source code with stack_data see ipython/ipython#13598>\n",
"\u001b[36mFile \u001b[39m\u001b[32mE:\\Fitz_Studio\\src\\TextManager\\notas_biblioteca_mmr.py:109\u001b[39m, in \u001b[36m<listcomp>\u001b[39m\u001b[34m(.0)\u001b[39m\n\u001b[32m 0\u001b[39m <Error retrieving source code with stack_data see ipython/ipython#13598>\n",
"\u001b[36mFile \u001b[39m\u001b[32mE:\\Fitz_Studio\\src\\TextManager\\notas_biblioteca_mmr.py:82\u001b[39m, in \u001b[36mfrom_model\u001b[39m\u001b[34m(model)\u001b[39m\n\u001b[32m 76\u001b[39m \u001b[38;5;129m@staticmethod\u001b[39m\n\u001b[32m 77\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mfrom_model\u001b[39m(model) -> Nota:\n\u001b[32m 78\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m Nota(\n\u001b[32m 79\u001b[39m \u001b[38;5;28mid\u001b[39m=model.id,\n\u001b[32m 80\u001b[39m titulo=model.titulo,\n\u001b[32m 81\u001b[39m tags=model.tags.split(\u001b[33m\"\u001b[39m\u001b[33m,\u001b[39m\u001b[33m\"\u001b[39m) \u001b[38;5;28;01mif\u001b[39;00m model.tags \u001b[38;5;28;01melse\u001b[39;00m [],\n\u001b[32m---> \u001b[39m\u001b[32m82\u001b[39m conexiones=model.conexiones.split(\u001b[33m\"\u001b[39m\u001b[33m,\u001b[39m\u001b[33m\"\u001b[39m) \u001b[38;5;28;01mif\u001b[39;00m model.conexiones \u001b[38;5;28;01melse\u001b[39;00m [],\n\u001b[32m 83\u001b[39m texto=model.texto,\n\u001b[32m 84\u001b[39m resumen=model.resumen,\n\u001b[32m 85\u001b[39m vector=\u001b[38;5;28mlist\u001b[39m(model.vector),\n\u001b[32m 86\u001b[39m vector_resumen=\u001b[38;5;28mlist\u001b[39m(model.vector_resumen) \u001b[38;5;28;01mif\u001b[39;00m model.vector_resumen \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 87\u001b[39m )\n",
"\u001b[31mTypeError\u001b[39m: 'NoneType' object is not iterable"
]
}
],
"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
}
@@ -1,21 +1,99 @@
# backend/api/v1/biblioteca.py from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends
from pydantic import BaseModel from pydantic import BaseModel
from typing import List, Optional
from fastapi import Path
from backend.db.conexion import get_conexion from backend.db.conexion import get_conexion
from backend.services.text_manager import crear_biblioteca from backend.services.text_manager import (
crear_biblioteca,
listar_bibliotecas,
agregar_nota_a_biblioteca,
listar_notas_de_biblioteca
)
from src.ConexionSql.Postgres_conexion import PostgresConexion from src.ConexionSql.Postgres_conexion import PostgresConexion
router = APIRouter() router = APIRouter()
# ---------------------------
# MODELOS PARA BIBLIOTECAS
# ---------------------------
class BibliotecaInput(BaseModel): class BibliotecaInput(BaseModel):
nombre_biblioteca: str nombre_biblioteca: str
descripcion: str descripcion: str
@router.post("/") @router.post("/", summary="Crear una nueva biblioteca")
def crear_biblioteca_endpoint( def crear_biblioteca_endpoint(
data: BibliotecaInput, data: BibliotecaInput,
conexion: PostgresConexion = Depends(get_conexion) conexion: PostgresConexion = Depends(get_conexion)
): ):
return crear_biblioteca(nombre_biblioteca=data.nombre_biblioteca, try:
return crear_biblioteca(
nombre_biblioteca=data.nombre_biblioteca,
descripcion=data.descripcion, descripcion=data.descripcion,
conexion=conexion) 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 crear la biblioteca")
@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")
# ---------------------------
# MODELOS PARA NOTAS
# ---------------------------
class NotaInput(BaseModel):
titulo: str
texto: str = ""
tags: Optional[List[str]] = []
conexiones: Optional[List[str]] = []
resumen: Optional[str] = ""
@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")
+106 -4
View File
@@ -3,8 +3,9 @@ from src.TextManager.biblioteca_mmr import BibliotecaRepo
from src.Llms.Embedders.Openai_embedder import OpenAIEmbedder from src.Llms.Embedders.Openai_embedder import OpenAIEmbedder
from src.ApiKeys.openai_apikey_mmr import OpenAICredencialRepo from src.ApiKeys.openai_apikey_mmr import OpenAICredencialRepo
from src.ConexionSql.Postgres_conexion import PostgresConexion from src.ConexionSql.Postgres_conexion import PostgresConexion
from backend.db.conexion import get_conexion from src.TextManager.nota import Nota
from src.TextManager.notas_biblioteca_mmr import generar_tabla_nota_para_biblioteca, NotaRepo
from sqlalchemy import MetaData
def crear_biblioteca(nombre_biblioteca: str, conexion: PostgresConexion, descripcion: str): def crear_biblioteca(nombre_biblioteca: str, conexion: PostgresConexion, descripcion: str):
cred_repo = OpenAICredencialRepo(conexion) cred_repo = OpenAICredencialRepo(conexion)
@@ -26,5 +27,106 @@ def crear_biblioteca(nombre_biblioteca: str, conexion: PostgresConexion, descrip
} }
if __name__ == "__main__": def listar_bibliotecas(conexion: PostgresConexion) -> list[dict]:
crear_biblioteca("hola_intento5") 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 = ""
):
print("[INFO] Iniciando el proceso de agregar nota a la biblioteca...")
# Obtener la biblioteca
print(f"[INFO] Buscando biblioteca con ID: {biblioteca_id}")
repo_biblioteca = BibliotecaRepo(conexion)
biblioteca = repo_biblioteca.get_by_id(biblioteca_id)
if biblioteca is None:
print(f"[ERROR] No se encontró la biblioteca con ID {biblioteca_id}")
raise ValueError(f"No se encontró la biblioteca con ID {biblioteca_id}")
print(f"[INFO] Biblioteca encontrada: {biblioteca.nombre} (vector_dim={biblioteca.vector_dim})")
# Crear objeto Nota
print(f"[INFO] Creando objeto Nota con título: {titulo}")
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
print(f"[INFO] Generando tabla y modelo de Nota para la biblioteca: {biblioteca.nombre}")
metadata = MetaData()
tabla, ModeloNota = generar_tabla_nota_para_biblioteca(
biblioteca.nombre,
biblioteca.vector_dim,
metadata
)
print(f"[INFO] Creando tabla en la base de datos si no existe...")
metadata.create_all(conexion.get_engine())
print(f"[INFO] Tabla '{tabla.name}' verificada/creada.")
# Guardar la nota
print(f"[INFO] Guardando nota en la base de datos...")
repo_nota = NotaRepo(conexion.get_session(), ModeloNota)
nota_id = repo_nota.add(nota)
print(f"[INFO] Nota guardada con ID: {nota_id}")
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
]
+101 -14
View File
@@ -13,6 +13,7 @@
"@react-three/fiber": "^9.1.2", "@react-three/fiber": "^9.1.2",
"@tabler/icons": "^3.31.0", "@tabler/icons": "^3.31.0",
"@tabler/icons-react": "^3.31.0", "@tabler/icons-react": "^3.31.0",
"axios": "^1.9.0",
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
"react-rnd": "^10.5.2", "react-rnd": "^10.5.2",
@@ -3304,6 +3305,12 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/available-typed-arrays": { "node_modules/available-typed-arrays": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@@ -3330,6 +3337,17 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/axios": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axobject-query": { "node_modules/axobject-query": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -3552,7 +3570,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@@ -3713,6 +3730,18 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -4053,6 +4082,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/depd": { "node_modules/depd": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -4127,7 +4165,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.1", "call-bind-apply-helpers": "^1.0.1",
@@ -4279,7 +4316,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -4289,7 +4325,6 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -4334,7 +4369,6 @@
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0" "es-errors": "^1.3.0"
@@ -4347,7 +4381,6 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@@ -5061,6 +5094,26 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/for-each": { "node_modules/for-each": {
"version": "0.3.5", "version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
@@ -5094,6 +5147,42 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/form-data": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/form-data/node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/form-data/node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/forwarded": { "node_modules/forwarded": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -5133,7 +5222,6 @@
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@@ -5184,7 +5272,6 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.2", "call-bind-apply-helpers": "^1.0.2",
@@ -5218,7 +5305,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"dunder-proto": "^1.0.1", "dunder-proto": "^1.0.1",
@@ -5413,7 +5499,6 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -5492,7 +5577,6 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -5505,7 +5589,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"has-symbols": "^1.0.3" "has-symbols": "^1.0.3"
@@ -5521,7 +5604,6 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"function-bind": "^1.1.2" "function-bind": "^1.1.2"
@@ -6579,7 +6661,6 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -7556,6 +7637,12 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+1
View File
@@ -25,6 +25,7 @@
"@react-three/fiber": "^9.1.2", "@react-three/fiber": "^9.1.2",
"@tabler/icons": "^3.31.0", "@tabler/icons": "^3.31.0",
"@tabler/icons-react": "^3.31.0", "@tabler/icons-react": "^3.31.0",
"axios": "^1.9.0",
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
"react-rnd": "^10.5.2", "react-rnd": "^10.5.2",
+5 -1
View File
@@ -3,6 +3,7 @@ import { HomePage } from './pages/Home.page';
import { Consulta_API } from './pages/Consulta_api'; import { Consulta_API } from './pages/Consulta_api';
import { Error_404 } from './pages/404'; // Ajusta si está en otra carpeta import { Error_404 } from './pages/404'; // Ajusta si está en otra carpeta
import { Grid_Dashboard } from './pages/Grid_dashboard'; // Ajusta si está en otra carpeta import { Grid_Dashboard } from './pages/Grid_dashboard'; // Ajusta si está en otra carpeta
import { Biblioteca } from './pages/Biblioteca';
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
@@ -17,7 +18,10 @@ const router = createBrowserRouter([
path: '/Grid_Dashboard', path: '/Grid_Dashboard',
element: <Grid_Dashboard />, element: <Grid_Dashboard />,
}, },
{
path: '/Biblioteca',
element: <Biblioteca />,
},
{ {
+1
View File
@@ -4,6 +4,7 @@ export const submenuLinks = {
Home: [ Home: [
{ label: 'Inicio', to: '/' }, { label: 'Inicio', to: '/' },
{ label: 'Consulta Api', to: '/Consulta_API' }, { label: 'Consulta Api', to: '/Consulta_API' },
{ label: 'Biblioteca', to: '/Biblioteca' },
], ],
Dashboard: [ Dashboard: [
+169
View File
@@ -0,0 +1,169 @@
import { useEffect, useState } from 'react';
import {
AppShell,
Stack,
Card,
Text,
Title,
ScrollArea,
Group,
Button,
TextInput,
Modal,
Box,
Loader,
} 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 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 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();
}, []);
return (
<AppShellWithMenu>
<Box display="flex" h="100%">
<Box w={240} p="md">
<ScrollArea h="100%">
<Stack>
{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 color="teal" onClick={fetchBibliotecas}>
🔄 Recuperar bibliotecas
</Button>
<Button onClick={() => setModalAbierto(true)}>Agregar nota</Button>
</Group>
<Group>
{loadingNotas ? (
<Loader />
) : (
bibliotecaSeleccionada.notas.map((nota) => (
<Card key={nota.id} shadow="sm" padding="lg" radius="md" withBorder style={{ width: 300 }}>
<Title order={4}>{nota.titulo}</Title>
<Text>{nota.texto}</Text>
</Card>
))
)}
</Group>
</Stack>
) : (
<Stack>
<Text>Selecciona una biblioteca</Text>
<Button color="teal" onClick={fetchBibliotecas}>
🔄 Recuperar bibliotecas
</Button>
</Stack>
)}
</Box>
</Box>
<Modal opened={modalAbierto} onClose={() => setModalAbierto(false)} title="Agregar nueva nota">
<Stack>
<TextInput
label="Título"
value={tituloNota}
onChange={(event) => setTituloNota(event.currentTarget.value)}
/>
<TextInput
label="Contenido"
value={contenidoNota}
onChange={(event) => setContenidoNota(event.currentTarget.value)}
/>
<Button onClick={agregarNota}>Guardar</Button>
</Stack>
</Modal>
</AppShellWithMenu>
);
}
+9
View File
@@ -5,6 +5,15 @@ import svgr from 'vite-plugin-svgr';
export default defineConfig({ export default defineConfig({
plugins: [react(), tsconfigPaths(), svgr()], plugins: [react(), tsconfigPaths(), svgr()],
server: {
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
secure: false,
},
},
},
test: { test: {
globals: true, globals: true,
environment: 'jsdom', environment: 'jsdom',
+58
View File
@@ -1224,6 +1224,11 @@ async-function@^1.0.0:
resolved "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz" resolved "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz"
integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
available-typed-arrays@^1.0.7: available-typed-arrays@^1.0.7:
version "1.0.7" version "1.0.7"
resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz" resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz"
@@ -1236,6 +1241,15 @@ axe-core@^4.10.0:
resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz" resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz"
integrity sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg== integrity sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==
axios@^1.9.0:
version "1.9.0"
resolved "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz"
integrity sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==
dependencies:
follow-redirects "^1.15.6"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
axobject-query@^4.1.0: axobject-query@^4.1.0:
version "4.1.0" version "4.1.0"
resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz" resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz"
@@ -1446,6 +1460,13 @@ colord@^2.9.3:
resolved "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz" resolved "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz"
integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
concat-map@0.0.1: concat-map@0.0.1:
version "0.0.1" version "0.0.1"
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
@@ -1641,6 +1662,11 @@ define-properties@^1.1.3, define-properties@^1.2.1:
has-property-descriptors "^1.0.0" has-property-descriptors "^1.0.0"
object-keys "^1.1.1" object-keys "^1.1.1"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
depd@^2.0.0, depd@2.0.0: depd@^2.0.0, depd@2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz"
@@ -2273,6 +2299,11 @@ flatted@^3.2.9, flatted@^3.3.3:
resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz" resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz"
integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==
follow-redirects@^1.15.6:
version "1.15.9"
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz"
integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
for-each@^0.3.3, for-each@^0.3.5: for-each@^0.3.3, for-each@^0.3.5:
version "0.3.5" version "0.3.5"
resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz" resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz"
@@ -2288,6 +2319,16 @@ foreground-child@^3.1.0:
cross-spawn "^7.0.6" cross-spawn "^7.0.6"
signal-exit "^4.0.1" signal-exit "^4.0.1"
form-data@^4.0.0:
version "4.0.2"
resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz"
integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
es-set-tostringtag "^2.1.0"
mime-types "^2.1.12"
forwarded@0.2.0: forwarded@0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz"
@@ -3162,6 +3203,18 @@ mime-db@^1.54.0:
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz"
integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.1.12:
version "2.1.35"
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
mime-types@^3.0.0, mime-types@^3.0.1: mime-types@^3.0.0, mime-types@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz" resolved "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz"
@@ -3586,6 +3639,11 @@ proxy-addr@^2.0.7:
forwarded "0.2.0" forwarded "0.2.0"
ipaddr.js "1.9.1" ipaddr.js "1.9.1"
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
punycode@^2.1.0, punycode@^2.3.1: punycode@^2.1.0, punycode@^2.3.1:
version "2.3.1" version "2.3.1"
resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz"
+10 -6
View File
@@ -4,6 +4,8 @@ from typing import List, Optional
from src.ConexionSql.Base_conexion import ConexionBase from src.ConexionSql.Base_conexion import ConexionBase
from sqlalchemy import MetaData # Asegúrate de importar esto from sqlalchemy import MetaData # Asegúrate de importar esto
from src.TextManager.notas_biblioteca_mmr import generar_tabla_nota_para_biblioteca # Ajusta si es necesario from src.TextManager.notas_biblioteca_mmr import generar_tabla_nota_para_biblioteca # Ajusta si es necesario
from sqlalchemy import inspect
class Biblioteca: class Biblioteca:
def __init__( def __init__(
@@ -24,7 +26,7 @@ class Biblioteca:
:param vector_dim: Dimensión del vector si no se proporciona un embedder. :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.id = id if id is not None else GeneradorIDUnico("BBLI").generar()
self.nombre = nombre self.nombre = nombre if "biblio" in nombre else f"biblio_{nombre}"
self.descripcion = descripcion self.descripcion = descripcion
self.embedder = embedder self.embedder = embedder
@@ -38,14 +40,16 @@ class Biblioteca:
def generar_modelo_notas(self, conexion: ConexionBase): def generar_modelo_notas(self, conexion: ConexionBase):
""" """
Genera dinámicamente un modelo de notas asociado a esta biblioteca y lo crea en la base de datos. Genera dinámicamente un modelo de notas asociado a esta biblioteca y lo crea en la base de datos.
Previene la creación si la tabla ya existe.
:param conexion: Objeto de conexión a la base de datos.
:return: Clase del modelo SQLAlchemy correspondiente a las notas.
""" """
nombre_tabla = f"biblio_{self.nombre}" nombre_tabla = f"{self.nombre}"
engine = conexion.get_engine()
inspector = inspect(engine)
if inspector.has_table(nombre_tabla):
raise ValueError(f"Ya existe una tabla con el nombre '{nombre_tabla}' en la base de datos.")
metadata = MetaData() metadata = MetaData()
engine = conexion.get_engine()
tabla, NotaModel = generar_tabla_nota_para_biblioteca(nombre_tabla, self.vector_dim, metadata) tabla, NotaModel = generar_tabla_nota_para_biblioteca(nombre_tabla, self.vector_dim, metadata)
metadata.create_all(engine) metadata.create_all(engine)
return NotaModel return NotaModel
+7 -2
View File
@@ -26,8 +26,8 @@ if pssword is None:
class BibliotecaModel(Base): class BibliotecaModel(Base):
__tablename__ = "bibliotecas" __tablename__ = "bibliotecas"
id = Column(String, primary_key=True) id = Column(String, primary_key=True, unique=True)
nombre = Column(String, nullable=False) nombre = Column(String, nullable=False, unique=True)
descripcion = Column(String, default="") descripcion = Column(String, default="")
vector_dim = Column(Integer, nullable=False) vector_dim = Column(Integer, nullable=False)
embedder_info = Column(String, nullable=True) # Se puede guardar nombre de clase o config encriptada embedder_info = Column(String, nullable=True) # Se puede guardar nombre de clase o config encriptada
@@ -77,6 +77,11 @@ class BibliotecaRepo:
self.session = conexion.get_session() self.session = conexion.get_session()
def add(self, biblioteca: Biblioteca) -> str: def add(self, biblioteca: Biblioteca) -> str:
# Verificar si ya existe una biblioteca con el mismo nombre
existente = self.session.query(BibliotecaModel).filter_by(nombre=biblioteca.nombre).first()
if existente:
raise ValueError(f"Ya existe una biblioteca con el nombre '{biblioteca.nombre}'")
data = BibliotecaMapper.to_dict(biblioteca) data = BibliotecaMapper.to_dict(biblioteca)
model = BibliotecaModel(**data) model = BibliotecaModel(**data)
self.session.add(model) self.session.add(model)
+9 -4
View File
@@ -33,8 +33,8 @@ def generar_tabla_nota_para_biblioteca(biblioteca_nombre: str, vector_dim: int,
Column("conexiones", String), Column("conexiones", String),
Column("texto", String), Column("texto", String),
Column("resumen", String), Column("resumen", String),
Column("vector", Vector(vector_dim), nullable=False), Column("vector", Vector(vector_dim), nullable=True),
Column("vector_resumen", Vector(vector_dim), nullable=True), # AHORA ES OPCIONAL Column("vector_resumen", Vector(vector_dim), nullable=True),
) )
class NotaModel: class NotaModel:
@@ -46,9 +46,12 @@ def generar_tabla_nota_para_biblioteca(biblioteca_nombre: str, vector_dim: int,
self.texto = nota.texto self.texto = nota.texto
self.resumen = nota.resumen self.resumen = nota.resumen
self.vector = nota.vector self.vector = nota.vector
self.vector_resumen = getattr(nota, "vector_resumen", None) # AHORA ES OPCIONAL self.vector_resumen = getattr(nota, "vector_resumen", None)
# Evitar mapear dos veces la misma clase
if NotaModel not in mapper_registry._class_registry.values():
mapper_registry.map_imperatively(NotaModel, tabla) mapper_registry.map_imperatively(NotaModel, tabla)
return tabla, NotaModel return tabla, NotaModel
@@ -79,7 +82,7 @@ class NotaMapper:
conexiones=model.conexiones.split(",") if model.conexiones else [], conexiones=model.conexiones.split(",") if model.conexiones else [],
texto=model.texto, texto=model.texto,
resumen=model.resumen, resumen=model.resumen,
vector=list(model.vector), 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 vector_resumen=list(model.vector_resumen) if model.vector_resumen is not None else None
) )
@@ -89,6 +92,8 @@ class NotaMapper:
class NotaRepo: class NotaRepo:
def __init__(self, session: Session, modelo_nota): def __init__(self, session: Session, modelo_nota):
if modelo_nota is None:
raise ValueError("No se puede instanciar NotaRepo sin un modelo válido de nota.")
self.session = session self.session = session
self.ModeloNota = modelo_nota self.ModeloNota = modelo_nota