up
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user