feat(soporte): botón de borrador que redacta con la base de conocimiento
En cada ticket, "✨ Borrador" propone una respuesta usando los fragmentos más parecidos de la base de conocimiento del agente de uMind que se elija en la configuración de soporte, más el hilo de la conversación. El borrador cae en el cuadro de respuesta y no se manda: lo revisa una persona y aprieta enviar. Contestarle a un cliente con lo que dijo un modelo, sin que nadie lo lea, es la forma más rápida de perderlo. Sin agente configurado el borrador se arma igual, solo con la conversación — peor, pero mejor que un error. El prompt le prohíbe inventar precios, plazos o pasos que no estén en la documentación, y le pide decir qué falta preguntar cuando no alcanza para resolver. El cuadro de respuesta pasa a textarea: un borrador de varios renglones no entraba en un input de una línea. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
bfcc320b7c
commit
1b599606f7
@@ -204,31 +204,32 @@ func GetSoporteWebhookConfig(c *fiber.Ctx) error {
|
||||
|
||||
func SaveSoporteWebhookConfig(c *fiber.Ctx) error {
|
||||
type body struct {
|
||||
ID uint `json:"id"`
|
||||
Nombre string `json:"nombre"`
|
||||
Provider string `json:"provider"`
|
||||
ApiKey string `json:"api_key"`
|
||||
EmailDestino string `json:"email_destino"`
|
||||
ResponderAuto bool `json:"responder_auto"`
|
||||
MensajeAuto string `json:"mensaje_auto"`
|
||||
AsignarA *uint `json:"asignar_a"`
|
||||
SmtpHost string `json:"smtp_host"`
|
||||
SmtpPort int `json:"smtp_port"`
|
||||
SmtpUsername string `json:"smtp_username"`
|
||||
SmtpPassword string `json:"smtp_password"`
|
||||
SmtpEncryption string `json:"smtp_encryption"`
|
||||
SmtpFromAddr string `json:"smtp_from_addr"`
|
||||
SmtpFromName string `json:"smtp_from_name"`
|
||||
ImapActivo bool `json:"imap_activo"`
|
||||
ImapHost string `json:"imap_host"`
|
||||
ImapPort int `json:"imap_port"`
|
||||
ImapUsername string `json:"imap_username"`
|
||||
ImapPassword string `json:"imap_password"`
|
||||
ImapEncryption string `json:"imap_encryption"`
|
||||
ImapCarpeta string `json:"imap_carpeta"`
|
||||
ImapHorasAtras int `json:"imap_horas_atras"`
|
||||
ClasificarConIA bool `json:"clasificar_con_ia"`
|
||||
ContextoNegocio string `json:"contexto_negocio"`
|
||||
ID uint `json:"id"`
|
||||
Nombre string `json:"nombre"`
|
||||
Provider string `json:"provider"`
|
||||
ApiKey string `json:"api_key"`
|
||||
EmailDestino string `json:"email_destino"`
|
||||
ResponderAuto bool `json:"responder_auto"`
|
||||
MensajeAuto string `json:"mensaje_auto"`
|
||||
AsignarA *uint `json:"asignar_a"`
|
||||
SmtpHost string `json:"smtp_host"`
|
||||
SmtpPort int `json:"smtp_port"`
|
||||
SmtpUsername string `json:"smtp_username"`
|
||||
SmtpPassword string `json:"smtp_password"`
|
||||
SmtpEncryption string `json:"smtp_encryption"`
|
||||
SmtpFromAddr string `json:"smtp_from_addr"`
|
||||
SmtpFromName string `json:"smtp_from_name"`
|
||||
ImapActivo bool `json:"imap_activo"`
|
||||
ImapHost string `json:"imap_host"`
|
||||
ImapPort int `json:"imap_port"`
|
||||
ImapUsername string `json:"imap_username"`
|
||||
ImapPassword string `json:"imap_password"`
|
||||
ImapEncryption string `json:"imap_encryption"`
|
||||
ImapCarpeta string `json:"imap_carpeta"`
|
||||
ImapHorasAtras int `json:"imap_horas_atras"`
|
||||
AgenteBorradorID *uint `json:"agente_borrador_id"`
|
||||
ClasificarConIA bool `json:"clasificar_con_ia"`
|
||||
ContextoNegocio string `json:"contexto_negocio"`
|
||||
}
|
||||
var b body
|
||||
if err := c.BodyParser(&b); err != nil {
|
||||
@@ -270,31 +271,32 @@ func SaveSoporteWebhookConfig(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
cfg := &models.SoporteWebhookConfig{
|
||||
Nombre: b.Nombre,
|
||||
Provider: b.Provider,
|
||||
ApiKey: b.ApiKey,
|
||||
EmailDestino: b.EmailDestino,
|
||||
ResponderAuto: b.ResponderAuto,
|
||||
MensajeAuto: b.MensajeAuto,
|
||||
AsignarA: b.AsignarA,
|
||||
SmtpHost: b.SmtpHost,
|
||||
SmtpPort: port,
|
||||
SmtpUsername: b.SmtpUsername,
|
||||
SmtpPassword: b.SmtpPassword,
|
||||
SmtpEncryption: enc,
|
||||
SmtpFromAddr: b.SmtpFromAddr,
|
||||
SmtpFromName: b.SmtpFromName,
|
||||
ImapActivo: b.ImapActivo,
|
||||
ImapHost: b.ImapHost,
|
||||
ImapPort: imapPort,
|
||||
ImapUsername: b.ImapUsername,
|
||||
ImapPasswordEnc: passEnc,
|
||||
ImapEncryption: imapEnc,
|
||||
ImapCarpeta: carpeta,
|
||||
ImapHorasAtras: b.ImapHorasAtras,
|
||||
ClasificarConIA: b.ClasificarConIA,
|
||||
ContextoNegocio: b.ContextoNegocio,
|
||||
Activo: true,
|
||||
Nombre: b.Nombre,
|
||||
Provider: b.Provider,
|
||||
ApiKey: b.ApiKey,
|
||||
EmailDestino: b.EmailDestino,
|
||||
ResponderAuto: b.ResponderAuto,
|
||||
MensajeAuto: b.MensajeAuto,
|
||||
AsignarA: b.AsignarA,
|
||||
SmtpHost: b.SmtpHost,
|
||||
SmtpPort: port,
|
||||
SmtpUsername: b.SmtpUsername,
|
||||
SmtpPassword: b.SmtpPassword,
|
||||
SmtpEncryption: enc,
|
||||
SmtpFromAddr: b.SmtpFromAddr,
|
||||
SmtpFromName: b.SmtpFromName,
|
||||
ImapActivo: b.ImapActivo,
|
||||
ImapHost: b.ImapHost,
|
||||
ImapPort: imapPort,
|
||||
ImapUsername: b.ImapUsername,
|
||||
ImapPasswordEnc: passEnc,
|
||||
ImapEncryption: imapEnc,
|
||||
ImapCarpeta: carpeta,
|
||||
ImapHorasAtras: b.ImapHorasAtras,
|
||||
ClasificarConIA: b.ClasificarConIA,
|
||||
ContextoNegocio: b.ContextoNegocio,
|
||||
AgenteBorradorID: b.AgenteBorradorID,
|
||||
Activo: true,
|
||||
}
|
||||
cfg.ID = b.ID
|
||||
if err := models.SaveSoporteWebhookConfig(cfg); err != nil {
|
||||
@@ -337,3 +339,38 @@ func RevisarBuzonAhora(c *fiber.Ctx) error {
|
||||
return c.JSON(fiber.Map{"ok": true, "message": fmt.Sprintf(
|
||||
"%d correo(s) sin leer, %d convertido(s) en ticket. El resto ya estaba registrado.", encontrados, procesados)})
|
||||
}
|
||||
|
||||
// BorradorRespuestaTicket propone una respuesta usando la base de conocimiento.
|
||||
// No envía nada: el borrador vuelve al cuadro de respuesta para que lo revise
|
||||
// una persona.
|
||||
// POST /app/tickets/:ticketID/borrador
|
||||
func BorradorRespuestaTicket(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("ticketID"), 10, 32)
|
||||
if err != nil {
|
||||
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
||||
}
|
||||
ticket, err := models.GetTicketByID(uint(id))
|
||||
if err != nil {
|
||||
return c.Status(404).JSON(fiber.Map{"error": "Ticket no encontrado"})
|
||||
}
|
||||
borrador, err := services.RedactarBorradorTicket(ticket)
|
||||
if err != nil {
|
||||
return c.Status(422).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
return c.JSON(fiber.Map{"borrador": borrador})
|
||||
}
|
||||
|
||||
// GetAgentesParaBorrador lista los agentes de uMind para elegir de cuál sale la
|
||||
// documentación de los borradores.
|
||||
// GET /app/soporte/agentes
|
||||
func GetAgentesParaBorrador(c *fiber.Ctx) error {
|
||||
items, err := models.GetTodosLosUmindAgentes()
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
salida := make([]fiber.Map, 0, len(items))
|
||||
for _, a := range items {
|
||||
salida = append(salida, fiber.Map{"id": a.ID, "nombre": a.Nombre})
|
||||
}
|
||||
return c.JSON(salida)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user