mirror of
https://github.com/2930134478/AI-CS.git
synced 2026-06-15 00:44:30 +08:00
212 lines
6.4 KiB
Go
212 lines
6.4 KiB
Go
package service
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"time"
|
||
|
||
"github.com/2930134478/AI-CS/backend/models"
|
||
"github.com/2930134478/AI-CS/backend/repository"
|
||
"github.com/2930134478/AI-CS/backend/utils"
|
||
)
|
||
|
||
// EmbeddingConfigService 知识库向量配置服务
|
||
type EmbeddingConfigService struct {
|
||
repo *repository.EmbeddingConfigRepository
|
||
userRepo *repository.UserRepository
|
||
}
|
||
|
||
// NewEmbeddingConfigService 创建服务实例
|
||
func NewEmbeddingConfigService(repo *repository.EmbeddingConfigRepository, userRepo *repository.UserRepository) *EmbeddingConfigService {
|
||
return &EmbeddingConfigService{repo: repo, userRepo: userRepo}
|
||
}
|
||
|
||
// GetForAPI 返回给前端的配置(API Key 脱敏,不返回明文)
|
||
func (s *EmbeddingConfigService) GetForAPI() (*EmbeddingConfigResult, error) {
|
||
c, err := s.repo.Get()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if c == nil {
|
||
return &EmbeddingConfigResult{
|
||
EmbeddingType: "openai",
|
||
APIURL: "",
|
||
APIKeyMasked: "",
|
||
Model: "text-embedding-3-small",
|
||
CustomerCanUseKB: true,
|
||
VisitorWebSearchEnabled: false,
|
||
WebSearchSource: "custom",
|
||
}, nil
|
||
}
|
||
masked := ""
|
||
if c.APIKey != "" {
|
||
masked = "sk-***"
|
||
}
|
||
return &EmbeddingConfigResult{
|
||
ID: c.ID,
|
||
EmbeddingType: c.EmbeddingType,
|
||
APIURL: c.APIURL,
|
||
APIKeyMasked: masked,
|
||
Model: c.Model,
|
||
CustomerCanUseKB: c.CustomerCanUseKB,
|
||
VisitorWebSearchEnabled: c.VisitorWebSearchEnabled,
|
||
WebSearchSource: normalizeWebSearchSource(c.WebSearchSource),
|
||
UpdatedAt: c.UpdatedAt,
|
||
}, nil
|
||
}
|
||
|
||
func normalizeWebSearchSource(v string) string {
|
||
if v == "vendor" || v == "custom" {
|
||
return v
|
||
}
|
||
return "custom"
|
||
}
|
||
|
||
// GetRaw 供 embedding 工厂使用,返回含解密后 API Key 的配置;若 DB 无有效配置返回 nil, nil
|
||
func (s *EmbeddingConfigService) GetRaw() (embeddingType, apiURL, apiKey, model string, err error) {
|
||
c, err := s.repo.Get()
|
||
if err != nil || c == nil || c.APIKey == "" {
|
||
return "", "", "", "", nil
|
||
}
|
||
decrypted, err := utils.DecryptAPIKey(c.APIKey)
|
||
if err != nil {
|
||
return "", "", "", "", fmt.Errorf("解密 API Key 失败: %w", err)
|
||
}
|
||
return c.EmbeddingType, c.APIURL, decrypted, c.Model, nil
|
||
}
|
||
|
||
// CustomerCanUseKB 是否开放知识库给客服使用
|
||
func (s *EmbeddingConfigService) CustomerCanUseKB() (bool, error) {
|
||
c, err := s.repo.Get()
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
if c == nil {
|
||
return true, nil // 默认开放
|
||
}
|
||
return c.CustomerCanUseKB, nil
|
||
}
|
||
|
||
// GetVisitorWebSearchConfig 返回访客端联网设置(供小窗拉取,无需登录)
|
||
func (s *EmbeddingConfigService) GetVisitorWebSearchConfig() (*VisitorWebSearchConfig, error) {
|
||
c, err := s.repo.Get()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if c == nil {
|
||
return &VisitorWebSearchConfig{WebSearchEnabled: false}, nil
|
||
}
|
||
return &VisitorWebSearchConfig{WebSearchEnabled: c.VisitorWebSearchEnabled}, nil
|
||
}
|
||
|
||
// GetWebSearchSource 返回联网方式:vendor(厂商内置)/ custom(自建 Serper)
|
||
func (s *EmbeddingConfigService) GetWebSearchSource() (string, error) {
|
||
c, err := s.repo.Get()
|
||
if err != nil {
|
||
return "custom", err
|
||
}
|
||
if c == nil {
|
||
return "custom", nil
|
||
}
|
||
return normalizeWebSearchSource(c.WebSearchSource), nil
|
||
}
|
||
|
||
// CheckKnowledgeBaseAccess 校验当前用户是否允许使用知识库(创建/上传/导入等)
|
||
// 若未开放且用户非 admin 则返回 error
|
||
func (s *EmbeddingConfigService) CheckKnowledgeBaseAccess(userID uint) error {
|
||
ok, err := s.CustomerCanUseKB()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if ok {
|
||
return nil
|
||
}
|
||
user, err := s.userRepo.GetByID(userID)
|
||
if err != nil || user == nil {
|
||
return errors.New("用户不存在")
|
||
}
|
||
if user.Role == "admin" {
|
||
return nil
|
||
}
|
||
return errors.New("当前未开放知识库功能,仅管理员可使用")
|
||
}
|
||
|
||
// Update 更新配置(仅管理员可调);若传入 api_key 为空则保留原密钥
|
||
func (s *EmbeddingConfigService) Update(userID uint, input UpdateEmbeddingConfigInput) (*EmbeddingConfigResult, error) {
|
||
user, err := s.userRepo.GetByID(userID)
|
||
if err != nil || user == nil {
|
||
return nil, errors.New("用户不存在")
|
||
}
|
||
if user.Role != "admin" {
|
||
return nil, errors.New("仅管理员可修改知识库向量配置")
|
||
}
|
||
|
||
c, err := s.repo.Get()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if c == nil {
|
||
c = &models.EmbeddingConfig{ID: 1}
|
||
}
|
||
|
||
if input.EmbeddingType != nil {
|
||
c.EmbeddingType = *input.EmbeddingType
|
||
}
|
||
if input.APIURL != nil {
|
||
c.APIURL = *input.APIURL
|
||
}
|
||
if input.APIKey != nil && *input.APIKey != "" {
|
||
encrypted, err := utils.EncryptAPIKey(*input.APIKey)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("加密 API Key 失败: %v", err)
|
||
}
|
||
c.APIKey = encrypted
|
||
}
|
||
if input.Model != nil {
|
||
c.Model = *input.Model
|
||
}
|
||
if input.CustomerCanUseKB != nil {
|
||
c.CustomerCanUseKB = *input.CustomerCanUseKB
|
||
}
|
||
if input.VisitorWebSearchEnabled != nil {
|
||
c.VisitorWebSearchEnabled = *input.VisitorWebSearchEnabled
|
||
}
|
||
if input.WebSearchSource != nil {
|
||
c.WebSearchSource = normalizeWebSearchSource(*input.WebSearchSource)
|
||
}
|
||
|
||
if err := s.repo.Save(c); err != nil {
|
||
return nil, err
|
||
}
|
||
return s.GetForAPI()
|
||
}
|
||
|
||
// EmbeddingConfigResult 返回给前端的结构(不含明文 API Key)
|
||
type EmbeddingConfigResult struct {
|
||
ID uint `json:"id"`
|
||
EmbeddingType string `json:"embedding_type"`
|
||
APIURL string `json:"api_url"`
|
||
APIKeyMasked string `json:"api_key_masked"`
|
||
Model string `json:"model"`
|
||
CustomerCanUseKB bool `json:"customer_can_use_kb"`
|
||
VisitorWebSearchEnabled bool `json:"visitor_web_search_enabled"`
|
||
WebSearchSource string `json:"web_search_source"`
|
||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||
}
|
||
|
||
// VisitorWebSearchConfig 访客端联网设置(供小窗拉取,无需登录)
|
||
type VisitorWebSearchConfig struct {
|
||
WebSearchEnabled bool `json:"web_search_enabled"`
|
||
}
|
||
|
||
// UpdateEmbeddingConfigInput 更新入参
|
||
type UpdateEmbeddingConfigInput struct {
|
||
EmbeddingType *string `json:"embedding_type"`
|
||
APIURL *string `json:"api_url"`
|
||
APIKey *string `json:"api_key"`
|
||
Model *string `json:"model"`
|
||
CustomerCanUseKB *bool `json:"customer_can_use_kb"`
|
||
VisitorWebSearchEnabled *bool `json:"visitor_web_search_enabled"`
|
||
WebSearchSource *string `json:"web_search_source"`
|
||
}
|