embedding_config_service.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. package service
  2. import (
  3. "errors"
  4. "fmt"
  5. "time"
  6. "github.com/2930134478/AI-CS/backend/models"
  7. "github.com/2930134478/AI-CS/backend/repository"
  8. "github.com/2930134478/AI-CS/backend/utils"
  9. )
  10. // EmbeddingConfigService 知识库向量配置服务
  11. type EmbeddingConfigService struct {
  12. repo *repository.EmbeddingConfigRepository
  13. userRepo *repository.UserRepository
  14. }
  15. // NewEmbeddingConfigService 创建服务实例
  16. func NewEmbeddingConfigService(repo *repository.EmbeddingConfigRepository, userRepo *repository.UserRepository) *EmbeddingConfigService {
  17. return &EmbeddingConfigService{repo: repo, userRepo: userRepo}
  18. }
  19. // GetForAPI 返回给前端的配置(API Key 脱敏,不返回明文)
  20. func (s *EmbeddingConfigService) GetForAPI() (*EmbeddingConfigResult, error) {
  21. c, err := s.repo.Get()
  22. if err != nil {
  23. return nil, err
  24. }
  25. if c == nil {
  26. return &EmbeddingConfigResult{
  27. EmbeddingType: "openai",
  28. APIURL: "",
  29. APIKeyMasked: "",
  30. Model: "text-embedding-3-small",
  31. CustomerCanUseKB: true,
  32. VisitorWebSearchEnabled: false,
  33. WebSearchSource: "custom",
  34. }, nil
  35. }
  36. masked := ""
  37. if c.APIKey != "" {
  38. masked = "sk-***"
  39. }
  40. return &EmbeddingConfigResult{
  41. ID: c.ID,
  42. EmbeddingType: c.EmbeddingType,
  43. APIURL: c.APIURL,
  44. APIKeyMasked: masked,
  45. Model: c.Model,
  46. CustomerCanUseKB: c.CustomerCanUseKB,
  47. VisitorWebSearchEnabled: c.VisitorWebSearchEnabled,
  48. WebSearchSource: normalizeWebSearchSource(c.WebSearchSource),
  49. UpdatedAt: c.UpdatedAt,
  50. }, nil
  51. }
  52. func normalizeWebSearchSource(v string) string {
  53. if v == "vendor" || v == "custom" {
  54. return v
  55. }
  56. return "custom"
  57. }
  58. // GetRaw 供 embedding 工厂使用,返回含解密后 API Key 的配置;若 DB 无有效配置返回 nil, nil
  59. func (s *EmbeddingConfigService) GetRaw() (embeddingType, apiURL, apiKey, model string, err error) {
  60. c, err := s.repo.Get()
  61. if err != nil || c == nil || c.APIKey == "" {
  62. return "", "", "", "", nil
  63. }
  64. decrypted, err := utils.DecryptAPIKey(c.APIKey)
  65. if err != nil {
  66. return "", "", "", "", fmt.Errorf("解密 API Key 失败: %w", err)
  67. }
  68. return c.EmbeddingType, c.APIURL, decrypted, c.Model, nil
  69. }
  70. // CustomerCanUseKB 是否开放知识库给客服使用
  71. func (s *EmbeddingConfigService) CustomerCanUseKB() (bool, error) {
  72. c, err := s.repo.Get()
  73. if err != nil {
  74. return false, err
  75. }
  76. if c == nil {
  77. return true, nil // 默认开放
  78. }
  79. return c.CustomerCanUseKB, nil
  80. }
  81. // GetVisitorWebSearchConfig 返回访客端联网设置(供小窗拉取,无需登录)
  82. func (s *EmbeddingConfigService) GetVisitorWebSearchConfig() (*VisitorWebSearchConfig, error) {
  83. c, err := s.repo.Get()
  84. if err != nil {
  85. return nil, err
  86. }
  87. if c == nil {
  88. return &VisitorWebSearchConfig{WebSearchEnabled: false}, nil
  89. }
  90. return &VisitorWebSearchConfig{WebSearchEnabled: c.VisitorWebSearchEnabled}, nil
  91. }
  92. // GetWebSearchSource 返回联网方式:vendor(厂商内置)/ custom(自建 Serper)
  93. func (s *EmbeddingConfigService) GetWebSearchSource() (string, error) {
  94. c, err := s.repo.Get()
  95. if err != nil {
  96. return "custom", err
  97. }
  98. if c == nil {
  99. return "custom", nil
  100. }
  101. return normalizeWebSearchSource(c.WebSearchSource), nil
  102. }
  103. // CheckKnowledgeBaseAccess 校验当前用户是否允许使用知识库(创建/上传/导入等)
  104. // 若未开放且用户非 admin 则返回 error
  105. func (s *EmbeddingConfigService) CheckKnowledgeBaseAccess(userID uint) error {
  106. ok, err := s.CustomerCanUseKB()
  107. if err != nil {
  108. return err
  109. }
  110. if ok {
  111. return nil
  112. }
  113. user, err := s.userRepo.GetByID(userID)
  114. if err != nil || user == nil {
  115. return errors.New("用户不存在")
  116. }
  117. if user.Role == "admin" {
  118. return nil
  119. }
  120. return errors.New("当前未开放知识库功能,仅管理员可使用")
  121. }
  122. // Update 更新配置(仅管理员可调);若传入 api_key 为空则保留原密钥
  123. func (s *EmbeddingConfigService) Update(userID uint, input UpdateEmbeddingConfigInput) (*EmbeddingConfigResult, error) {
  124. user, err := s.userRepo.GetByID(userID)
  125. if err != nil || user == nil {
  126. return nil, errors.New("用户不存在")
  127. }
  128. if user.Role != "admin" {
  129. return nil, errors.New("仅管理员可修改知识库向量配置")
  130. }
  131. c, err := s.repo.Get()
  132. if err != nil {
  133. return nil, err
  134. }
  135. if c == nil {
  136. c = &models.EmbeddingConfig{ID: 1}
  137. }
  138. if input.EmbeddingType != nil {
  139. c.EmbeddingType = *input.EmbeddingType
  140. }
  141. if input.APIURL != nil {
  142. c.APIURL = *input.APIURL
  143. }
  144. if input.APIKey != nil && *input.APIKey != "" {
  145. encrypted, err := utils.EncryptAPIKey(*input.APIKey)
  146. if err != nil {
  147. return nil, fmt.Errorf("加密 API Key 失败: %v", err)
  148. }
  149. c.APIKey = encrypted
  150. }
  151. if input.Model != nil {
  152. c.Model = *input.Model
  153. }
  154. if input.CustomerCanUseKB != nil {
  155. c.CustomerCanUseKB = *input.CustomerCanUseKB
  156. }
  157. if input.VisitorWebSearchEnabled != nil {
  158. c.VisitorWebSearchEnabled = *input.VisitorWebSearchEnabled
  159. }
  160. if input.WebSearchSource != nil {
  161. c.WebSearchSource = normalizeWebSearchSource(*input.WebSearchSource)
  162. }
  163. if err := s.repo.Save(c); err != nil {
  164. return nil, err
  165. }
  166. return s.GetForAPI()
  167. }
  168. // EmbeddingConfigResult 返回给前端的结构(不含明文 API Key)
  169. type EmbeddingConfigResult struct {
  170. ID uint `json:"id"`
  171. EmbeddingType string `json:"embedding_type"`
  172. APIURL string `json:"api_url"`
  173. APIKeyMasked string `json:"api_key_masked"`
  174. Model string `json:"model"`
  175. CustomerCanUseKB bool `json:"customer_can_use_kb"`
  176. VisitorWebSearchEnabled bool `json:"visitor_web_search_enabled"`
  177. WebSearchSource string `json:"web_search_source"`
  178. UpdatedAt time.Time `json:"updated_at,omitempty"`
  179. }
  180. // VisitorWebSearchConfig 访客端联网设置(供小窗拉取,无需登录)
  181. type VisitorWebSearchConfig struct {
  182. WebSearchEnabled bool `json:"web_search_enabled"`
  183. }
  184. // UpdateEmbeddingConfigInput 更新入参
  185. type UpdateEmbeddingConfigInput struct {
  186. EmbeddingType *string `json:"embedding_type"`
  187. APIURL *string `json:"api_url"`
  188. APIKey *string `json:"api_key"`
  189. Model *string `json:"model"`
  190. CustomerCanUseKB *bool `json:"customer_can_use_kb"`
  191. VisitorWebSearchEnabled *bool `json:"visitor_web_search_enabled"`
  192. WebSearchSource *string `json:"web_search_source"`
  193. }