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