2026-04-09 10:01:23 +08:00
|
|
|
package services
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
|
2026-08-21 00:41:07 +08:00
|
|
|
"code.tczkiot.com/wlw/ai-agent/internal/models"
|
|
|
|
|
"code.tczkiot.com/wlw/ai-agent/internal/pkg/dto"
|
|
|
|
|
"code.tczkiot.com/wlw/ai-agent/internal/pkg/dto/request"
|
|
|
|
|
"code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
|
|
|
|
|
"code.tczkiot.com/wlw/ai-agent/internal/pkg/errorsx"
|
|
|
|
|
"code.tczkiot.com/wlw/ai-agent/internal/pkg/utils"
|
|
|
|
|
"code.tczkiot.com/wlw/ai-agent/internal/repositories"
|
|
|
|
|
|
|
|
|
|
"code.tczkiot.com/wlw/ai-agent/internal/pkg/httpx/params"
|
2026-05-31 18:43:48 +08:00
|
|
|
|
2026-04-09 10:01:23 +08:00
|
|
|
"github.com/mlogclub/simple/common/strs"
|
|
|
|
|
"github.com/mlogclub/simple/sqls"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var AIConfigService = newAIConfigService()
|
|
|
|
|
|
|
|
|
|
func newAIConfigService() *aIConfigService {
|
|
|
|
|
return &aIConfigService{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type aIConfigService struct {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) Get(id int64) *models.AIConfig {
|
|
|
|
|
return repositories.AIConfigRepository.Get(sqls.DB(), id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) Take(where ...interface{}) *models.AIConfig {
|
|
|
|
|
return repositories.AIConfigRepository.Take(sqls.DB(), where...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) Find(cnd *sqls.Cnd) []models.AIConfig {
|
|
|
|
|
return repositories.AIConfigRepository.Find(sqls.DB(), cnd)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) FindOne(cnd *sqls.Cnd) *models.AIConfig {
|
|
|
|
|
return repositories.AIConfigRepository.FindOne(sqls.DB(), cnd)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) FindPageByParams(params *params.QueryParams) (list []models.AIConfig, paging *sqls.Paging) {
|
|
|
|
|
return repositories.AIConfigRepository.FindPageByParams(sqls.DB(), params)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) FindPageByCnd(cnd *sqls.Cnd) (list []models.AIConfig, paging *sqls.Paging) {
|
|
|
|
|
return repositories.AIConfigRepository.FindPageByCnd(sqls.DB(), cnd)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) Count(cnd *sqls.Cnd) int64 {
|
|
|
|
|
return repositories.AIConfigRepository.Count(sqls.DB(), cnd)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) Create(t *models.AIConfig) error {
|
|
|
|
|
return repositories.AIConfigRepository.Create(sqls.DB(), t)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) Update(t *models.AIConfig) error {
|
|
|
|
|
return repositories.AIConfigRepository.Update(sqls.DB(), t)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) Updates(id int64, columns map[string]interface{}) error {
|
|
|
|
|
return repositories.AIConfigRepository.Updates(sqls.DB(), id, columns)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) UpdateColumn(id int64, name string, value interface{}) error {
|
|
|
|
|
return repositories.AIConfigRepository.UpdateColumn(sqls.DB(), id, name, value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) Delete(id int64) {
|
|
|
|
|
repositories.AIConfigRepository.Delete(sqls.DB(), id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) CreateAIConfig(req request.CreateAIConfigRequest, operator *dto.AuthPrincipal) (*models.AIConfig, error) {
|
|
|
|
|
if operator == nil {
|
2026-06-02 20:51:13 +08:00
|
|
|
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
item, err := s.buildAIConfigModel(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
item.Status = enums.StatusDisabled
|
|
|
|
|
item.SortNo = s.nextSortNo()
|
|
|
|
|
item.AuditFields = utils.BuildAuditFields(operator)
|
|
|
|
|
if err := sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
|
|
|
|
if err := ctx.Tx.Create(item).Error; err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return item, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) UpdateAIConfig(req request.UpdateAIConfigRequest, operator *dto.AuthPrincipal) error {
|
|
|
|
|
if operator == nil {
|
2026-06-02 20:51:13 +08:00
|
|
|
return errorsx.UnauthorizedI18n("error.auth.expired")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
current := s.Get(req.ID)
|
|
|
|
|
if current == nil {
|
2026-06-02 20:51:13 +08:00
|
|
|
return errorsx.InvalidParamI18n("error.e0012")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
item, err := s.buildAIConfigModel(req.CreateAIConfigRequest)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-30 22:41:21 +08:00
|
|
|
columns := map[string]any{
|
2026-04-09 10:01:23 +08:00
|
|
|
"name": item.Name,
|
|
|
|
|
"provider": item.Provider,
|
|
|
|
|
"base_url": item.BaseURL,
|
|
|
|
|
"model_type": item.ModelType,
|
|
|
|
|
"model_name": item.ModelName,
|
|
|
|
|
"dimension": item.Dimension,
|
|
|
|
|
"max_context_tokens": item.MaxContextTokens,
|
|
|
|
|
"max_output_tokens": item.MaxOutputTokens,
|
|
|
|
|
"timeout_ms": item.TimeoutMS,
|
|
|
|
|
"max_retry_count": item.MaxRetryCount,
|
|
|
|
|
"rpm_limit": item.RPMLimit,
|
|
|
|
|
"tpm_limit": item.TPMLimit,
|
|
|
|
|
"remark": item.Remark,
|
|
|
|
|
"update_user_id": operator.UserID,
|
|
|
|
|
"update_user_name": operator.Username,
|
|
|
|
|
"updated_at": time.Now(),
|
2026-05-30 22:41:21 +08:00
|
|
|
}
|
|
|
|
|
if item.APIKey != "" {
|
|
|
|
|
columns["api_key"] = item.APIKey
|
|
|
|
|
}
|
|
|
|
|
return repositories.AIConfigRepository.Updates(sqls.DB(), req.ID, columns)
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) DeleteAIConfig(id int64, operator *dto.AuthPrincipal) error {
|
|
|
|
|
current := s.Get(id)
|
|
|
|
|
if current == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if current.Status == enums.StatusOk {
|
2026-06-02 20:51:13 +08:00
|
|
|
return errorsx.ForbiddenI18n("error.e0143")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
return repositories.AIConfigRepository.Updates(sqls.DB(), id, map[string]any{
|
|
|
|
|
"status": enums.StatusDeleted,
|
|
|
|
|
"update_user_id": operator.UserID,
|
|
|
|
|
"update_user_name": operator.Username,
|
|
|
|
|
"updated_at": time.Now(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) UpdateStatus(id int64, status enums.Status, operator *dto.AuthPrincipal) error {
|
|
|
|
|
if operator == nil {
|
2026-06-02 20:51:13 +08:00
|
|
|
return errorsx.UnauthorizedI18n("error.auth.expired")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
current := s.Get(id)
|
|
|
|
|
if current == nil {
|
2026-06-02 20:51:13 +08:00
|
|
|
return errorsx.InvalidParamI18n("error.e0012")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
if status != enums.StatusOk && status != enums.StatusDisabled {
|
2026-06-02 20:51:13 +08:00
|
|
|
return errorsx.InvalidParamI18n("error.e0254")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
|
|
|
|
if status == enums.StatusOk {
|
|
|
|
|
if err := s.disableOthersByModelType(ctx, current.ModelType, id); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return repositories.AIConfigRepository.Updates(ctx.Tx, id, map[string]any{
|
|
|
|
|
"status": status,
|
|
|
|
|
"update_user_id": operator.UserID,
|
|
|
|
|
"update_user_name": operator.Username,
|
|
|
|
|
"updated_at": time.Now(),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) disableOthersByModelType(ctx *sqls.TxContext, modelType enums.AIModelType, excludeID int64) error {
|
|
|
|
|
query := ctx.Tx.Model(&models.AIConfig{}).Where("model_type = ?", modelType)
|
|
|
|
|
if excludeID > 0 {
|
|
|
|
|
query = query.Where("id <> ?", excludeID)
|
|
|
|
|
}
|
|
|
|
|
return query.Updates(map[string]any{
|
|
|
|
|
"status": int(enums.StatusDisabled),
|
|
|
|
|
"updated_at": time.Now(),
|
|
|
|
|
}).Error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) UpdateSort(ids []int64) error {
|
|
|
|
|
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
|
|
|
|
for i, id := range ids {
|
|
|
|
|
if err := repositories.AIConfigRepository.UpdateColumn(ctx.Tx, id, "sort_no", i); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) buildAIConfigModel(req request.CreateAIConfigRequest) (*models.AIConfig, error) {
|
|
|
|
|
name := strings.TrimSpace(req.Name)
|
|
|
|
|
baseURL := strings.TrimSpace(req.BaseURL)
|
|
|
|
|
modelName := strings.TrimSpace(req.ModelName)
|
|
|
|
|
|
|
|
|
|
if name == "" {
|
2026-06-02 20:51:13 +08:00
|
|
|
return nil, errorsx.InvalidParamI18n("error.e0339")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
if strs.IsBlank(string(req.Provider)) {
|
2026-06-02 20:51:13 +08:00
|
|
|
return nil, errorsx.InvalidParamI18n("error.e0122")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
if baseURL == "" {
|
2026-06-02 20:51:13 +08:00
|
|
|
return nil, errorsx.InvalidParamI18n("error.e0147")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
if strs.IsBlank(string(req.ModelType)) {
|
2026-06-02 20:51:13 +08:00
|
|
|
return nil, errorsx.InvalidParamI18n("error.e0243")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
if modelName == "" {
|
2026-06-02 20:51:13 +08:00
|
|
|
return nil, errorsx.InvalidParamI18n("error.e0242")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
if req.Dimension < 0 {
|
|
|
|
|
req.Dimension = 0
|
|
|
|
|
}
|
|
|
|
|
if req.MaxContextTokens < 0 {
|
|
|
|
|
req.MaxContextTokens = 0
|
|
|
|
|
}
|
|
|
|
|
if req.MaxOutputTokens < 0 {
|
|
|
|
|
req.MaxOutputTokens = 0
|
|
|
|
|
}
|
|
|
|
|
if req.TimeoutMS <= 0 {
|
|
|
|
|
req.TimeoutMS = 30000
|
|
|
|
|
}
|
|
|
|
|
if req.MaxRetryCount < 0 {
|
|
|
|
|
req.MaxRetryCount = 0
|
|
|
|
|
}
|
|
|
|
|
if req.RPMLimit < 0 {
|
|
|
|
|
req.RPMLimit = 0
|
|
|
|
|
}
|
|
|
|
|
if req.TPMLimit < 0 {
|
|
|
|
|
req.TPMLimit = 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &models.AIConfig{
|
|
|
|
|
Name: name,
|
|
|
|
|
Provider: req.Provider,
|
|
|
|
|
BaseURL: baseURL,
|
|
|
|
|
APIKey: strings.TrimSpace(req.APIKey),
|
|
|
|
|
ModelType: req.ModelType,
|
|
|
|
|
ModelName: modelName,
|
|
|
|
|
Dimension: req.Dimension,
|
|
|
|
|
MaxContextTokens: req.MaxContextTokens,
|
|
|
|
|
MaxOutputTokens: req.MaxOutputTokens,
|
|
|
|
|
TimeoutMS: req.TimeoutMS,
|
|
|
|
|
MaxRetryCount: req.MaxRetryCount,
|
|
|
|
|
RPMLimit: req.RPMLimit,
|
|
|
|
|
TPMLimit: req.TPMLimit,
|
|
|
|
|
Remark: strings.TrimSpace(req.Remark),
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *aIConfigService) nextSortNo() int {
|
|
|
|
|
if latest := s.FindOne(sqls.NewCnd().Desc("sort_no").Desc("id")); latest != nil {
|
|
|
|
|
return latest.SortNo + 1
|
|
|
|
|
}
|
|
|
|
|
return 1
|
|
|
|
|
}
|