Un tenant (negocio/sitio, dueño de los dominios permitidos) puede tener varios UmindAgente independientes (ej. "Ventas", "Soporte"), cada uno con su propia config de IA, tono, base de conocimiento, tools, canales y conexión de correo. El site_key también pasa a ser por agente, así cada uno tiene su propio <script> de widget embebible y su propio color. Backend: - Nuevo modelo UmindAgente (pkg/models/umind_agente.go), con SiteKey, AiConfigID, Tono, MensajeBienvenida y Color — campos que antes vivían en UmindTenant y se sacan de ahí (las columnas viejas quedan huérfanas sin usar, no se hace DROP COLUMN). - UmindDocumento, UmindChunk, UmindHerramienta, UmindCanal, UmindConexion y UmindMensaje pasan de TenantID a AgenteID. El campo se agrega sin "not null" para no romper el ALTER TABLE en Postgres sobre tablas que ya tienen filas (ej. emetropolitana). - migrations.MigrarUmindAgentes(): idempotente, crea un agente "Principal" por cada tenant existente heredando lo que ya tenía configurado, y mueve sus datos de tenant_id a agente_id. Corre en cada arranque normal, mismo criterio que los Seed* — nada se rompe para los tenants ya en producción. - Motor del agente, widget, canales (Telegram/WhatsApp) y OAuth de correo ahora operan sobre UmindAgente; el tenant solo se consulta para el chequeo de dominio permitido y el nombre del negocio que ve el visitante. Frontend: nueva jerarquía de navegación tenant → lista de agentes (TenantAgentes.vue) → detalle de un agente (AgenteDetail.vue, antes TenantDetail.vue) con las mismas 6 tabs de siempre, ahora por agente. El modal de tenant en el sidebar se achica a nombre/dominios/activo. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
73 lines
2.6 KiB
Go
73 lines
2.6 KiB
Go
package models
|
|
|
|
import (
|
|
"github.com/sujit-baniya/fiber-boilerplate/app"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// UmindAgente es un agente de IA independiente dentro de un tenant — un
|
|
// mismo negocio puede tener varios (ej. "Ventas", "Soporte"), cada uno con
|
|
// su propia base de conocimiento, tools, canales y conexión de correo. Lo
|
|
// único que sigue siendo del tenant (no del agente) son los dominios
|
|
// permitidos y el nombre del negocio que se muestra al visitante — eso es
|
|
// del sitio, no de un agente puntual.
|
|
type UmindAgente struct {
|
|
gorm.Model
|
|
TenantID uint `json:"tenant_id" gorm:"column:tenant_id;index;not null"`
|
|
Nombre string `json:"nombre" gorm:"column:nombre;size:150;not null"` // etiqueta interna, ej: "Ventas"
|
|
SiteKey string `json:"site_key" gorm:"column:site_key;uniqueIndex;size:40;not null"`
|
|
AiConfigID *uint `json:"ai_config_id" gorm:"column:ai_config_id"`
|
|
Tono string `json:"tono" gorm:"column:tono;type:text"`
|
|
MensajeBienvenida string `json:"mensaje_bienvenida" gorm:"column:mensaje_bienvenida;type:text"`
|
|
Color string `json:"color" gorm:"column:color;size:7;default:'#8eb02f'"`
|
|
Activo bool `json:"activo" gorm:"column:activo;default:true"`
|
|
}
|
|
|
|
func (UmindAgente) TableName() string { return "umind_agentes" }
|
|
|
|
func CreateUmindAgente(a *UmindAgente) error {
|
|
if a.SiteKey == "" {
|
|
key, err := GenerarSiteKey()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.SiteKey = key
|
|
}
|
|
if a.Color == "" {
|
|
a.Color = "#8eb02f"
|
|
}
|
|
return app.Http.Database.DB.Create(a).Error
|
|
}
|
|
|
|
func GetUmindAgentesByTenant(tenantID uint) ([]UmindAgente, error) {
|
|
var items []UmindAgente
|
|
err := app.Http.Database.DB.Where("tenant_id = ?", tenantID).Order("id ASC").Find(&items).Error
|
|
return items, err
|
|
}
|
|
|
|
func GetUmindAgenteByID(id uint) (*UmindAgente, error) {
|
|
var a UmindAgente
|
|
if err := app.Http.Database.DB.First(&a, id).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &a, nil
|
|
}
|
|
|
|
// GetUmindAgenteBySiteKey resuelve el agente a partir de la site_key pública
|
|
// que manda el widget. Solo hace match si el agente está activo.
|
|
func GetUmindAgenteBySiteKey(siteKey string) (*UmindAgente, error) {
|
|
var a UmindAgente
|
|
if err := app.Http.Database.DB.Where("site_key = ? AND activo = ?", siteKey, true).First(&a).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &a, nil
|
|
}
|
|
|
|
func UpdateUmindAgente(id uint, updates map[string]interface{}) error {
|
|
return app.Http.Database.DB.Model(&UmindAgente{}).Where("id = ?", id).Updates(updates).Error
|
|
}
|
|
|
|
func DeleteUmindAgente(id uint) error {
|
|
return app.Http.Database.DB.Delete(&UmindAgente{}, id).Error
|
|
}
|