Files
soft_usite/pkg/models/umind_canal.go
T
Lizandro GuarnizoandClaude Sonnet 5 84506a98e2 feat: OCR y Whisper como herramientas opcionales por canal en uMind
Cada canal (Telegram/WhatsApp) de un agente ahora puede activar, de forma
independiente, que los audios entrantes se transcriban con el Whisper ASR
propio y que a las imágenes entrantes se les extraiga texto con el
servicio OCR propio, antes de pasarle el mensaje al agente. Antes esos
mensajes se ignoraban en silencio.

- UmindCanal gana usar_whisper_audio/usar_ocr_imagenes (default off).
- WhatsApp: se descarga el media vía Graph API (resolución de URL + fetch
  con el mismo access_token del canal) y se enruta a Whisper/OCR según type.
- Telegram: se descarga el archivo vía getFile + CDN de archivos del bot,
  mismo enrutamiento para voice/audio/photo.
- Panel: checkboxes en el alta de canal y toggles inline por canal ya
  creado, en la tab Canales del agente.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 10:35:48 -05:00

91 lines
3.3 KiB
Go

package models
import (
"crypto/rand"
"encoding/hex"
"fmt"
"github.com/sujit-baniya/fiber-boilerplate/app"
"gorm.io/gorm"
)
// UmindCanal es un canal de mensajería adicional (Telegram, WhatsApp) que
// alimenta al mismo agente del tenant que ya atiende el widget web. Los
// secretos reales (bot token, access token de WhatsApp, etc.) viven cifrados
// en CredencialesEnc (ver pkg/services/umind_secrets.go) — el modelo solo
// persiste el string ya cifrado, no conoce la clave.
//
// WebhookSecret es un identificador público generado por nosotros, distinto
// del secreto real del proveedor, usado SOLO para enrutar el webhook
// entrante al canal correcto (va en la URL que se registra en
// Telegram/Meta). Evita que el token real del proveedor termine en logs de
// acceso o de un proxy intermedio.
type UmindCanal struct {
gorm.Model
AgenteID uint `json:"agente_id" gorm:"column:agente_id;index"`
Tipo string `json:"tipo" gorm:"column:tipo;size:20;not null"` // telegram | whatsapp
Activo bool `json:"activo" gorm:"column:activo;default:true"`
WebhookSecret string `json:"webhook_secret" gorm:"column:webhook_secret;uniqueIndex;size:40;not null"`
CredencialesEnc string `json:"-" gorm:"column:credenciales_enc;type:text"`
UltimoError string `json:"ultimo_error" gorm:"column:ultimo_error;type:text"`
// Si están activos, los mensajes de voz/audio e imágenes que llegan por
// este canal se transcriben (Whisper) o se les extrae el texto (OCR)
// antes de pasarlos al agente, en vez de ignorarse.
UsarWhisperAudio bool `json:"usar_whisper_audio" gorm:"column:usar_whisper_audio;default:false"`
UsarOcrImagenes bool `json:"usar_ocr_imagenes" gorm:"column:usar_ocr_imagenes;default:false"`
}
func (UmindCanal) TableName() string { return "umind_canales" }
func GenerarWebhookSecret() (string, error) {
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
return "", fmt.Errorf("no se pudo generar el webhook_secret: %w", err)
}
return "umc_" + hex.EncodeToString(b), nil
}
func CreateUmindCanal(c *UmindCanal) error {
if c.WebhookSecret == "" {
secret, err := GenerarWebhookSecret()
if err != nil {
return err
}
c.WebhookSecret = secret
}
return app.Http.Database.DB.Create(c).Error
}
func GetUmindCanalesByAgente(agenteID uint) ([]UmindCanal, error) {
var items []UmindCanal
err := app.Http.Database.DB.Where("agente_id = ?", agenteID).Order("id DESC").Find(&items).Error
return items, err
}
func GetUmindCanalByID(id uint) (*UmindCanal, error) {
var c UmindCanal
if err := app.Http.Database.DB.First(&c, id).Error; err != nil {
return nil, err
}
return &c, nil
}
// GetUmindCanalByWebhookSecret resuelve el canal a partir del identificador
// público que viene en la URL del webhook. Solo matchea si está activo.
func GetUmindCanalByWebhookSecret(tipo, webhookSecret string) (*UmindCanal, error) {
var c UmindCanal
err := app.Http.Database.DB.Where("tipo = ? AND webhook_secret = ? AND activo = ?", tipo, webhookSecret, true).First(&c).Error
if err != nil {
return nil, err
}
return &c, nil
}
func UpdateUmindCanal(id uint, updates map[string]interface{}) error {
return app.Http.Database.DB.Model(&UmindCanal{}).Where("id = ?", id).Updates(updates).Error
}
func DeleteUmindCanal(id uint) error {
return app.Http.Database.DB.Delete(&UmindCanal{}, id).Error
}