package controllers import ( "crypto/rand" "encoding/hex" "strings" "github.com/gofiber/fiber/v2" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" "github.com/sujit-baniya/fiber-boilerplate/pkg/services" ) func agenteDeContexto(c *fiber.Ctx) (*models.UmindAgente, error) { agente, ok := c.Locals("umind_agente").(*models.UmindAgente) if !ok || agente == nil { return nil, fiber.NewError(fiber.StatusUnauthorized, "no autenticado") } return agente, nil } func tenantDeContexto(c *fiber.Ctx) (*models.UmindTenant, error) { tenant, ok := c.Locals("umind_tenant").(*models.UmindTenant) if !ok || tenant == nil { return nil, fiber.NewError(fiber.StatusUnauthorized, "no autenticado") } return tenant, nil } func nuevaSessionID() string { b := make([]byte, 12) _, _ = rand.Read(b) return hex.EncodeToString(b) } // UmindWidgetInit devuelve una sesión nueva y el mensaje de bienvenida del // agente, sin gastar una llamada al LLM solo para saludar. El nombre que se // muestra es el del negocio (tenant dueño del agente), no la etiqueta // interna del agente. // Ruta: GET /widget/:site_key/init func UmindWidgetInit(c *fiber.Ctx) error { agente, err := agenteDeContexto(c) if err != nil { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": true, "message": err.Error()}) } tenant, err := tenantDeContexto(c) if err != nil { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": true, "message": err.Error()}) } bienvenida := strings.TrimSpace(agente.MensajeBienvenida) if bienvenida == "" { bienvenida = "¡Hola! ¿En qué puedo ayudarte?" } color := strings.TrimSpace(agente.Color) if color == "" { color = "#8eb02f" } return c.JSON(fiber.Map{ "session_id": nuevaSessionID(), "nombre": tenant.Nombre, "mensaje_bienvenida": bienvenida, "color": color, }) } // UmindWidgetMensaje procesa un mensaje del visitante y devuelve la respuesta del agente. // Ruta: POST /widget/:site_key/mensaje func UmindWidgetMensaje(c *fiber.Ctx) error { agente, err := agenteDeContexto(c) if err != nil { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": true, "message": err.Error()}) } var req struct { SessionID string `json:"session_id"` Mensaje string `json:"mensaje"` } if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": true, "message": "body inválido"}) } if strings.TrimSpace(req.Mensaje) == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": true, "message": "mensaje requerido"}) } sessionID := strings.TrimSpace(req.SessionID) if sessionID == "" { sessionID = nuevaSessionID() } // Límite generoso pero real: evita que alguien mande un mensaje gigante al LLM. if len(req.Mensaje) > 4000 { req.Mensaje = req.Mensaje[:4000] } respuesta, err := services.ProcessWidgetMessage(agente, sessionID, strings.TrimSpace(req.Mensaje)) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": true, "message": err.Error()}) } return c.JSON(fiber.Map{ "session_id": sessionID, "respuesta": respuesta, }) } // UmindWidgetScript sirve el script embebible que renderiza la burbuja de // chat. Se instala con: // Ruta: GET /widget/umind.js func UmindWidgetScript(c *fiber.Ctx) error { c.Set("Content-Type", "application/javascript; charset=utf-8") c.Set("Cache-Control", "public, max-age=300") return c.SendString(umindWidgetJS) } const umindWidgetJS = `(function () { var script = document.currentScript; var siteKey = script.getAttribute('data-site'); if (!siteKey) { console.error('[uMind] falta data-site en el