Notas y bibliotecas funcionando
This commit is contained in:
@@ -3,6 +3,7 @@ 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
|
||||
@@ -10,21 +11,24 @@ from src.ConexionSql.Postgres_conexion import PostgresConexion
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/", summary="Crear una nueva biblioteca")
|
||||
def crear_biblioteca_endpoint(
|
||||
@router.post("/biblioteca", summary="Crear una nueva biblioteca")
|
||||
async def crear_biblioteca_endpoint(
|
||||
data: BibliotecaInput,
|
||||
conexion: PostgresConexion = Depends(get_conexion)
|
||||
):
|
||||
try:
|
||||
return crear_biblioteca(
|
||||
nombre_biblioteca=data.nombre_biblioteca,
|
||||
descripcion=data.descripcion,
|
||||
conexion=conexion
|
||||
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="Error interno al crear la biblioteca")
|
||||
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(
|
||||
|
||||
@@ -10,24 +10,45 @@ from backend.schemas.text_manager_schema import NotaInput
|
||||
|
||||
|
||||
|
||||
def crear_biblioteca(nombre_biblioteca: str, conexion: PostgresConexion, descripcion: str):
|
||||
cred_repo = OpenAICredencialRepo(conexion)
|
||||
credencial = cred_repo.get_by_id("OPAK20250510-ac2cea8af3110632314")
|
||||
def crear_biblioteca(nombre_biblioteca: str, conexion: PostgresConexion, descripcion: str = None):
|
||||
print("[INICIO] Creando biblioteca...")
|
||||
|
||||
embedder = OpenAIEmbedder(credencial, model="text-embedding-3-large")
|
||||
biblioteca = Biblioteca(nombre=nombre_biblioteca,
|
||||
embedder=embedder,
|
||||
descripcion=descripcion)
|
||||
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")
|
||||
|
||||
repo = BibliotecaRepo(conexion)
|
||||
repo.add(biblioteca=biblioteca)
|
||||
print("[Paso 2] Instanciando embedder...")
|
||||
embedder = OpenAIEmbedder(credencial, model="text-embedding-3-large")
|
||||
print("[OK] Embedder instanciado")
|
||||
|
||||
biblioteca.generar_modelo_notas(conexion)
|
||||
print("[Paso 3] Instanciando biblioteca...")
|
||||
biblioteca = Biblioteca(
|
||||
nombre=nombre_biblioteca,
|
||||
embedder=embedder,
|
||||
descripcion=descripcion
|
||||
)
|
||||
print(f"[OK] Biblioteca instanciada con ID: {biblioteca.id}")
|
||||
|
||||
return {
|
||||
"mensaje": f"Biblioteca '{nombre_biblioteca}' creada con éxito.",
|
||||
"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]:
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
Modal,
|
||||
Box,
|
||||
Loader,
|
||||
Textarea
|
||||
} from '@mantine/core';
|
||||
import { AppShellWithMenu } from '../components/Appshell/Appshell';
|
||||
import axios from 'axios';
|
||||
@@ -38,6 +39,10 @@ export function Biblioteca() {
|
||||
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 () => {
|
||||
@@ -63,6 +68,31 @@ export function Biblioteca() {
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
@@ -101,7 +131,14 @@ export function Biblioteca() {
|
||||
if (!bibliotecaSeleccionada) return;
|
||||
try {
|
||||
await axios.delete(`/api/v1/text_manager/nota/${bibliotecaSeleccionada.id}/${notaId}`);
|
||||
await fetchBibliotecas();
|
||||
|
||||
// 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);
|
||||
}
|
||||
@@ -140,9 +177,9 @@ export function Biblioteca() {
|
||||
<Box w={240} p="md">
|
||||
<ScrollArea h="100%">
|
||||
<Stack>
|
||||
<Button color="teal" onClick={fetchBibliotecas}>
|
||||
🔄 Recuperar bibliotecas
|
||||
</Button>
|
||||
<Button color="teal" onClick={fetchBibliotecas}>🔄 Recuperar bibliotecas</Button>
|
||||
<Button color="grape" variant="outline" onClick={() => setModalNuevaBiblio(true)}>➕ Nueva biblioteca</Button>
|
||||
|
||||
|
||||
{bibliotecas.map((biblio) => (
|
||||
<Button
|
||||
@@ -181,32 +218,29 @@ export function Biblioteca() {
|
||||
padding="lg"
|
||||
radius="md"
|
||||
withBorder
|
||||
style={{ width: 300, position: 'relative' }}
|
||||
style={{
|
||||
width: 300,
|
||||
height: 250, // Altura fija para asegurar la separación
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
{/* Botones en esquina superior derecha */}
|
||||
<Box style={{ position: 'absolute', top: 8, right: 8, display: 'flex', gap: 4, zIndex: 1 }}>
|
||||
<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"
|
||||
color="blue"
|
||||
variant="light"
|
||||
p={4}
|
||||
color="blue"
|
||||
onClick={() => abrirModalEditar(nota)}
|
||||
>
|
||||
✏️
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
color="red"
|
||||
variant="light"
|
||||
p={4}
|
||||
onClick={() => eliminarNota(nota.id)}
|
||||
>
|
||||
🗑️
|
||||
Editar
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Title order={4} style={{ marginBottom: 10 }}>{nota.titulo}</Title>
|
||||
<Text>{nota.texto}</Text>
|
||||
</Card>
|
||||
|
||||
// Fin de notas en cards
|
||||
@@ -218,9 +252,7 @@ export function Biblioteca() {
|
||||
) : (
|
||||
<Stack>
|
||||
<Text>Selecciona una biblioteca</Text>
|
||||
<Button color="teal" onClick={fetchBibliotecas}>
|
||||
🔄 Recuperar bibliotecas
|
||||
</Button>
|
||||
|
||||
</Stack>
|
||||
)}
|
||||
</Box>
|
||||
@@ -234,8 +266,10 @@ export function Biblioteca() {
|
||||
value={tituloNota}
|
||||
onChange={(event) => setTituloNota(event.currentTarget.value)}
|
||||
/>
|
||||
<TextInput
|
||||
<Textarea
|
||||
label="Contenido"
|
||||
minRows={6}
|
||||
autosize
|
||||
value={contenidoNota}
|
||||
onChange={(event) => setContenidoNota(event.currentTarget.value)}
|
||||
/>
|
||||
@@ -253,16 +287,60 @@ export function Biblioteca() {
|
||||
setNotaEnEdicion((prev) => (prev ? { ...prev, titulo: e.currentTarget.value } : null))
|
||||
}
|
||||
/>
|
||||
<TextInput
|
||||
<Textarea
|
||||
label="Contenido"
|
||||
minRows={6}
|
||||
autosize
|
||||
value={notaEnEdicion?.texto || ""}
|
||||
onChange={(e) =>
|
||||
setNotaEnEdicion((prev) => (prev ? { ...prev, texto: e.currentTarget.value } : null))
|
||||
}
|
||||
/>
|
||||
<Button onClick={guardarEdicionNota}>Guardar cambios</Button>
|
||||
<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>
|
||||
);
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'OPAK20250510-ac2cea8af3110632314'"
|
||||
"'OPAK20250513-61b29978b7604031014'"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
@@ -37,8 +37,11 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# apikey_gpt = OpenAICredencial(titulo=\"Credencial_enmanuel_gpt\")\n",
|
||||
"# repo.add(apikey_gpt)"
|
||||
"apikey_gpt = OpenAICredencial(titulo=\"Credencial_enmanuel_gpt\",\n",
|
||||
" api_key=\"\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"repo.add(apikey_gpt)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -56,7 +59,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"credencial_openai = repo.get_by_id('OPAK20250510-ac2cea8af3110632314')\n",
|
||||
"credencial_openai = repo.get_by_id('OPAK20250513-61b29978b7604031014')\n",
|
||||
"print(f\"✅ Credencial: {credencial_openai.titulo}\")"
|
||||
]
|
||||
},
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -48,10 +48,16 @@ class Model_base:
|
||||
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)
|
||||
val = getattr(self, attr.name, None)
|
||||
if isinstance(val, datetime):
|
||||
out[attr.name] = val.isoformat()
|
||||
else:
|
||||
out[attr.name] = val
|
||||
return out
|
||||
|
||||
return out
|
||||
@@ -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,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)
|
||||
|
||||
@@ -17,9 +17,11 @@ class ModeloOpenAI(ModeloABC):
|
||||
num_tokens_maximos: int = 512,
|
||||
use_legacy: bool = False
|
||||
):
|
||||
id = id if id is not None else GeneradorIDUnico("MOPA").generar()
|
||||
# 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__(
|
||||
id=id,
|
||||
model=model,
|
||||
temperature=temperature,
|
||||
top_p=top_p,
|
||||
@@ -27,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
|
||||
|
||||
@@ -79,8 +83,8 @@ 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:
|
||||
return resultado.choices[0].message.content
|
||||
return resultado.choices[0].message.content
|
||||
|
||||
@@ -5,6 +5,7 @@ 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:
|
||||
@@ -38,18 +39,21 @@ class Biblioteca:
|
||||
raise ValueError("Debes proporcionar un 'embedder' o un 'vector_dim' explícito.")
|
||||
|
||||
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.
|
||||
Previene la creación si la tabla ya existe.
|
||||
"""
|
||||
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.")
|
||||
|
||||
metadata = MetaData()
|
||||
tabla, NotaModel = generar_tabla_nota_para_biblioteca(nombre_tabla, self.vector_dim, metadata)
|
||||
metadata.create_all(engine)
|
||||
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
|
||||
@@ -1,6 +1,6 @@
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
from sqlalchemy import Column, String, Table, MetaData
|
||||
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
|
||||
@@ -13,6 +13,9 @@ 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
|
||||
# ----------------------
|
||||
@@ -28,47 +31,42 @@ 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.
|
||||
Genera una tabla dinámica y modelo ORM para una biblioteca dada, con campos vectoriales y campos del sistema.
|
||||
"""
|
||||
# Normalización robusta del nombre de la tabla
|
||||
nombre_tabla = re.sub(r"[^a-zA-Z0-9_]", "_", biblioteca_nombre.strip().lower())
|
||||
try:
|
||||
print(f"[INFO] Generando tabla para biblioteca: '{biblioteca_nombre}' con dimensión de vector: {vector_dim}")
|
||||
|
||||
tabla = Table(
|
||||
nombre_tabla,
|
||||
metadata,
|
||||
Column("id", String, primary_key=True),
|
||||
Column("titulo", String, nullable=False),
|
||||
Column("tags", String),
|
||||
Column("conexiones", String),
|
||||
Column("texto", String),
|
||||
Column("resumen", String),
|
||||
Column("vector", Vector(vector_dim), nullable=True),
|
||||
Column("vector_resumen", Vector(vector_dim), nullable=True),
|
||||
*Model_base.__table__.columns # Añade columnas del sistema si Model_base es declarativo
|
||||
)
|
||||
# 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}'")
|
||||
|
||||
class NotaModel(Model_base):
|
||||
"""
|
||||
Modelo ORM generado dinámicamente para notas de una biblioteca.
|
||||
"""
|
||||
def __init__(self, nota):
|
||||
self.id = nota.id
|
||||
self.titulo = nota.titulo
|
||||
self.tags = ",".join(nota.tags)
|
||||
self.conexiones = ",".join(nota.conexiones)
|
||||
self.texto = nota.texto
|
||||
self.resumen = nota.resumen
|
||||
self.vector = nota.vector
|
||||
self.vector_resumen = getattr(nota, "vector_resumen", None)
|
||||
# Modelo ORM dinámico
|
||||
class NotaModel(Base, Model_base):
|
||||
__tablename__ = nombre_tabla
|
||||
__table_args__ = {"extend_existing": True}
|
||||
|
||||
# Registrar solo si aún no se ha hecho
|
||||
if NotaModel.__name__ not in mapper_registry._class_registry:
|
||||
mapper_registry.map_imperatively(NotaModel, tabla)
|
||||
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)
|
||||
|
||||
return tabla, NotaModel
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user