This commit is contained in:
Lizandro Guarnizo
2026-06-02 13:13:29 -05:00
parent ba76754331
commit 28d0eae715
7 changed files with 434 additions and 77 deletions
+13 -7
View File
@@ -35,14 +35,15 @@ func GetAiConfigs(c *fiber.Ctx) error {
// Ocultar la API key en el listado (mostrar solo últimos 4 chars)
type safe struct {
ID uint `json:"ID"`
Nombre string `json:"nombre"`
Provider string `json:"provider"`
ID uint `json:"ID"`
Nombre string `json:"nombre"`
Provider string `json:"provider"`
ApiKeyHint string `json:"api_key_hint"`
BaseURL string `json:"base_url"`
ModelName string `json:"model_name"`
IsActive bool `json:"is_active"`
Notes string `json:"notes"`
BaseURL string `json:"base_url"`
ModelName string `json:"model_name"`
IsActive bool `json:"is_active"`
Notes string `json:"notes"`
Modulo string `json:"modulo"`
}
safeItems := make([]safe, len(items))
for i, it := range items {
@@ -59,6 +60,7 @@ func GetAiConfigs(c *fiber.Ctx) error {
ModelName: it.ModelName,
IsActive: it.IsActive,
Notes: it.Notes,
Modulo: it.Modulo,
}
}
@@ -80,6 +82,7 @@ func CreateAiConfigHandler(c *fiber.Ctx) error {
ModelName string `json:"model_name"`
IsActive bool `json:"is_active"`
Notes string `json:"notes"`
Modulo string `json:"modulo"`
}
var req Req
if err := c.BodyParser(&req); err != nil {
@@ -103,6 +106,7 @@ func CreateAiConfigHandler(c *fiber.Ctx) error {
ModelName: strings.TrimSpace(req.ModelName),
IsActive: req.IsActive,
Notes: req.Notes,
Modulo: strings.TrimSpace(req.Modulo),
}
if err := models.CreateAiConfig(&item); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
@@ -125,6 +129,7 @@ func UpdateAiConfigHandler(c *fiber.Ctx) error {
ModelName string `json:"model_name"`
IsActive bool `json:"is_active"`
Notes string `json:"notes"`
Modulo string `json:"modulo"`
}
var req Req
if err := c.BodyParser(&req); err != nil {
@@ -138,6 +143,7 @@ func UpdateAiConfigHandler(c *fiber.Ctx) error {
"model_name": strings.TrimSpace(req.ModelName),
"is_active": req.IsActive,
"notes": req.Notes,
"modulo": strings.TrimSpace(req.Modulo),
}
if strings.TrimSpace(req.ApiKey) != "" {
updates["api_key"] = strings.TrimSpace(req.ApiKey)
+2 -6
View File
@@ -373,13 +373,9 @@ func LandingGetAiConfig(c *fiber.Ctx) error {
if !landingSecret(c) {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "no autorizado"})
}
config, err := models.GetActiveAiConfig("qwen")
config, err := models.GetAiConfigForService("landing")
if err != nil {
// Intentar cualquier provider activo como fallback
config, err = models.GetActiveAiConfig("")
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "no hay configuración de IA activa"})
}
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "no hay configuración de IA activa"})
}
return c.JSON(fiber.Map{
"provider": config.Provider,
+73 -2
View File
@@ -320,6 +320,77 @@ func quoteIdent(name, driver string) string {
return "`" + strings.ReplaceAll(name, "`", "``") + "`"
}
// NL2SQLHandler convierte lenguaje natural a SQL usando IA.
// POST /app/query-runner/nl2sql
// Body: { conx_db_id, database, text, schema }
// schema es un array de { table, columns: [{name, type}] } para dar contexto a la IA.
func NL2SQLHandler(c *fiber.Ctx) error {
var body struct {
ConxDbID uint `json:"conx_db_id"`
Database string `json:"database"`
Text string `json:"text"`
Schema []struct {
Table string `json:"table"`
Columns []struct {
Name string `json:"name"`
Type string `json:"type"`
} `json:"columns"`
} `json:"schema"`
}
if err := c.BodyParser(&body); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cuerpo inválido"})
}
if strings.TrimSpace(body.Text) == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "El texto está vacío"})
}
// Construir representación del esquema para el prompt
var schemaLines []string
for _, t := range body.Schema {
var cols []string
for _, col := range t.Columns {
cols = append(cols, col.Name+" ("+col.Type+")")
}
if len(cols) > 0 {
schemaLines = append(schemaLines, fmt.Sprintf(" %s: %s", t.Table, strings.Join(cols, ", ")))
} else {
schemaLines = append(schemaLines, " "+t.Table)
}
}
schemaStr := ""
if len(schemaLines) > 0 {
schemaStr = "Esquema disponible:\n" + strings.Join(schemaLines, "\n")
}
aiConfig, err := models.GetAiConfigForService("query_runner")
if err != nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
"error": "No hay configuración de IA activa. Configura una en /app/ai-config",
})
}
prompt := fmt.Sprintf(`Eres un experto en SQL. Convierte la siguiente descripción en lenguaje natural a una consulta SQL válida.
%s
Descripción: %s
Reglas:
- Devuelve SOLO el SQL, sin explicaciones ni bloques de código markdown.
- Usa los nombres de tablas y columnas exactamente como aparecen en el esquema.
- Si la base de datos es PostgreSQL, usa comillas dobles para identificadores.
- Termina siempre con punto y coma.
- Si no hay suficiente información para generar el SQL, devuelve el SQL más razonable posible.
SQL:`, schemaStr, body.Text)
result := callAISQLSuggestion(aiConfig, prompt, "nl2sql", body.ConxDbID, body.Database)
if result == nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "La IA no pudo generar el SQL"})
}
return c.JSON(result)
}
// CorrectQueryHandler usa IA o heurística para corregir/sugerir queries.
// POST /app/query-runner/suggest
// Body: { conx_db_id, database, sql, action: "correct"|"complete"|"optimize" }
@@ -340,8 +411,8 @@ func CorrectQueryHandler(c *fiber.Ctx) error {
body.Action = "correct"
}
// 1. Si hay una configuración de IA activa, usarla
aiConfig, aiErr := models.GetActiveAiConfig("")
// 1. Si hay una configuración de IA activa para este servicio, usarla
aiConfig, aiErr := models.GetAiConfigForService("query_runner")
if aiErr == nil && aiConfig != nil {
aiResult := callAISQLSuggestion(aiConfig, body.SQL, body.Action, body.ConxDbID, body.Database)
if aiResult != nil {
+1
View File
@@ -123,6 +123,7 @@ func UserRoutes(app fiber.Router) {
protected.Get("/query-runner/columns", controllers.GetTableColumnsHandler)
protected.Post("/query-runner/update-cell", controllers.UpdateCellHandler)
protected.Post("/query-runner/suggest", controllers.CorrectQueryHandler)
protected.Post("/query-runner/nl2sql", controllers.NL2SQLHandler)
// ─── Hostinger API ────────────────────────────────────────────────
protected.Get("/hostinger", middlewares.MenuMiddleware, controllers.HostingerConfigPage)
protected.Post("/hostinger/config", controllers.SaveHostingerConfig)