/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package siteinfo import ( "context" "encoding/json" errpkg "errors" "fmt" "strings" "github.com/apache/answer/internal/base/constant" "github.com/apache/answer/internal/base/handler" "github.com/apache/answer/internal/base/reason" "github.com/apache/answer/internal/base/translator" "github.com/apache/answer/internal/entity" "github.com/apache/answer/internal/schema" "github.com/apache/answer/internal/service/config" "github.com/apache/answer/internal/service/export" "github.com/apache/answer/internal/service/file_record" questioncommon "github.com/apache/answer/internal/service/question_common" "github.com/apache/answer/internal/service/siteinfo_common" tagcommon "github.com/apache/answer/internal/service/tag_common" "github.com/apache/answer/plugin" "github.com/go-resty/resty/v2" "github.com/jinzhu/copier" "github.com/segmentfault/pacman/errors" "github.com/segmentfault/pacman/log" ) type SiteInfoService struct { siteInfoRepo siteinfo_common.SiteInfoRepo siteInfoCommonService siteinfo_common.SiteInfoCommonService emailService *export.EmailService tagCommonService *tagcommon.TagCommonService configService *config.ConfigService questioncommon *questioncommon.QuestionCommon fileRecordService *file_record.FileRecordService } func NewSiteInfoService( siteInfoRepo siteinfo_common.SiteInfoRepo, siteInfoCommonService siteinfo_common.SiteInfoCommonService, emailService *export.EmailService, tagCommonService *tagcommon.TagCommonService, configService *config.ConfigService, questioncommon *questioncommon.QuestionCommon, fileRecordService *file_record.FileRecordService, ) *SiteInfoService { plugin.RegisterGetSiteURLFunc(func() string { generalSiteInfo, err := siteInfoCommonService.GetSiteGeneral(context.Background()) if err != nil { log.Error(err) return "" } return generalSiteInfo.SiteUrl }) return &SiteInfoService{ siteInfoRepo: siteInfoRepo, siteInfoCommonService: siteInfoCommonService, emailService: emailService, tagCommonService: tagCommonService, configService: configService, questioncommon: questioncommon, fileRecordService: fileRecordService, } } // GetSiteGeneral get site info general func (s *SiteInfoService) GetSiteGeneral(ctx context.Context) (resp *schema.SiteGeneralResp, err error) { return s.siteInfoCommonService.GetSiteGeneral(ctx) } // GetSiteInterface get site info interface func (s *SiteInfoService) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceSettingsResp, err error) { return s.siteInfoCommonService.GetSiteInterface(ctx) } // GetSiteUsersSettings get site info users settings func (s *SiteInfoService) GetSiteUsersSettings(ctx context.Context) (resp *schema.SiteUsersSettingsResp, err error) { return s.siteInfoCommonService.GetSiteUsersSettings(ctx) } // GetSiteBranding get site info branding func (s *SiteInfoService) GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingResp, err error) { return s.siteInfoCommonService.GetSiteBranding(ctx) } // GetSiteUsers get site info about users func (s *SiteInfoService) GetSiteUsers(ctx context.Context) (resp *schema.SiteUsersResp, err error) { return s.siteInfoCommonService.GetSiteUsers(ctx) } // GetSiteTag get site info write func (s *SiteInfoService) GetSiteTag(ctx context.Context) (resp *schema.SiteTagsResp, err error) { resp, err = s.siteInfoCommonService.GetSiteTag(ctx) if err != nil { log.Error(err) return resp, nil } resp.RecommendTags, err = s.tagCommonService.GetSiteWriteRecommendTag(ctx) if err != nil { log.Error(err) } resp.ReservedTags, err = s.tagCommonService.GetSiteWriteReservedTag(ctx) if err != nil { log.Error(err) } return resp, nil } // GetSiteQuestion get site questions settings func (s *SiteInfoService) GetSiteQuestion(ctx context.Context) (resp *schema.SiteQuestionsResp, err error) { return s.siteInfoCommonService.GetSiteQuestion(ctx) } // GetSiteAdvanced get site advanced settings func (s *SiteInfoService) GetSiteAdvanced(ctx context.Context) (resp *schema.SiteAdvancedResp, err error) { return s.siteInfoCommonService.GetSiteAdvanced(ctx) } // GetSitePolicies get site legal info func (s *SiteInfoService) GetSitePolicies(ctx context.Context) (resp *schema.SitePoliciesResp, err error) { return s.siteInfoCommonService.GetSitePolicies(ctx) } // GetSiteSecurity get site security info func (s *SiteInfoService) GetSiteSecurity(ctx context.Context) (resp *schema.SiteSecurityResp, err error) { return s.siteInfoCommonService.GetSiteSecurity(ctx) } // GetSiteLogin get site login info func (s *SiteInfoService) GetSiteLogin(ctx context.Context) (resp *schema.SiteLoginResp, err error) { return s.siteInfoCommonService.GetSiteLogin(ctx) } // GetSiteCustomCssHTML get site custom css html config func (s *SiteInfoService) GetSiteCustomCssHTML(ctx context.Context) (resp *schema.SiteCustomCssHTMLResp, err error) { return s.siteInfoCommonService.GetSiteCustomCssHTML(ctx) } // GetSiteTheme get site theme config func (s *SiteInfoService) GetSiteTheme(ctx context.Context) (resp *schema.SiteThemeResp, err error) { return s.siteInfoCommonService.GetSiteTheme(ctx) } func (s *SiteInfoService) SaveSiteGeneral(ctx context.Context, req schema.SiteGeneralReq) (err error) { req.FormatSiteUrl() content, _ := json.Marshal(req) data := &entity.SiteInfo{ Type: constant.SiteTypeGeneral, Content: string(content), Status: 1, } return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeGeneral, data) } func (s *SiteInfoService) SaveSiteInterface(ctx context.Context, req schema.SiteInterfaceReq) (err error) { // If the language is invalid, set it to the default language "en_US" if !translator.CheckLanguageIsValid(req.Language) { req.Language = "en_US" } content, _ := json.Marshal(req) data := entity.SiteInfo{ Type: constant.SiteTypeInterfaceSettings, Content: string(content), } return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeInterfaceSettings, &data) } // SaveSiteUsersSettings save site users settings func (s *SiteInfoService) SaveSiteUsersSettings(ctx context.Context, req schema.SiteUsersSettingsReq) (err error) { content, _ := json.Marshal(req) data := entity.SiteInfo{ Type: constant.SiteTypeInterfaceSettings, Content: string(content), } return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeUsersSettings, &data) } // SaveSiteBranding save site branding information func (s *SiteInfoService) SaveSiteBranding(ctx context.Context, req *schema.SiteBrandingReq) (err error) { content, _ := json.Marshal(req) data := &entity.SiteInfo{ Type: constant.SiteTypeBranding, Content: string(content), Status: 1, } return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeBranding, data) } // SaveSiteAdvanced save site advanced configuration func (s *SiteInfoService) SaveSiteAdvanced(ctx context.Context, req *schema.SiteAdvancedReq) (resp any, err error) { content, _ := json.Marshal(req) data := &entity.SiteInfo{ Type: constant.SiteTypeAdvanced, Content: string(content), Status: 1, } return nil, s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeAdvanced, data) } // SaveSiteQuestions save site questions configuration func (s *SiteInfoService) SaveSiteQuestions(ctx context.Context, req *schema.SiteQuestionsReq) (resp any, err error) { content, _ := json.Marshal(req) data := &entity.SiteInfo{ Type: constant.SiteTypeQuestions, Content: string(content), Status: 1, } return nil, s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeQuestions, data) } // SaveSiteTags save site tags configuration func (s *SiteInfoService) SaveSiteTags(ctx context.Context, req *schema.SiteTagsReq) (resp any, err error) { recommendTags, reservedTags := make([]string, 0), make([]string, 0) recommendTagMapping, reservedTagMapping := make(map[string]bool), make(map[string]bool) for _, tag := range req.ReservedTags { if !recommendTagMapping[tag.SlugName] { reservedTagMapping[tag.SlugName] = true reservedTags = append(reservedTags, tag.SlugName) } } // recommend tags can't contain reserved tag for _, tag := range req.RecommendTags { if reservedTagMapping[tag.SlugName] { continue } if !recommendTagMapping[tag.SlugName] { recommendTagMapping[tag.SlugName] = true recommendTags = append(recommendTags, tag.SlugName) } } errData, err := s.tagCommonService.SetSiteWriteTag(ctx, recommendTags, reservedTags, req.UserID) if err != nil { return errData, err } content, _ := json.Marshal(req) data := &entity.SiteInfo{ Type: constant.SiteTypeTags, Content: string(content), Status: 1, } return nil, s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeTags, data) } // SaveSitePolicies save site policies configuration func (s *SiteInfoService) SaveSitePolicies(ctx context.Context, req *schema.SitePoliciesReq) (err error) { content, _ := json.Marshal(req) data := &entity.SiteInfo{ Type: constant.SiteTypePolicies, Content: string(content), Status: 1, } return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypePolicies, data) } // SaveSiteSecurity save site security configuration func (s *SiteInfoService) SaveSiteSecurity(ctx context.Context, req *schema.SiteSecurityReq) (err error) { content, _ := json.Marshal(req) data := &entity.SiteInfo{ Type: constant.SiteTypeSecurity, Content: string(content), Status: 1, } return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeSecurity, data) } // SaveSiteLogin save site legal configuration func (s *SiteInfoService) SaveSiteLogin(ctx context.Context, req *schema.SiteLoginReq) (err error) { if req.RequireEmailVerification == nil { return errors.BadRequest(reason.RequestFormatError) } loginConfig := &schema.SiteLoginResp{ AllowNewRegistrations: req.AllowNewRegistrations, AllowEmailRegistrations: req.AllowEmailRegistrations, AllowPasswordLogin: req.AllowPasswordLogin, AllowEmailDomains: req.AllowEmailDomains, RequireEmailVerification: *req.RequireEmailVerification, } content, _ := json.Marshal(loginConfig) data := &entity.SiteInfo{ Type: constant.SiteTypeLogin, Content: string(content), Status: 1, } return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeLogin, data) } // SaveSiteCustomCssHTML save site custom html configuration func (s *SiteInfoService) SaveSiteCustomCssHTML(ctx context.Context, req *schema.SiteCustomCssHTMLReq) (err error) { content, _ := json.Marshal(req) data := &entity.SiteInfo{ Type: constant.SiteTypeCustomCssHTML, Content: string(content), Status: 1, } return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeCustomCssHTML, data) } // SaveSiteTheme save site custom html configuration func (s *SiteInfoService) SaveSiteTheme(ctx context.Context, req *schema.SiteThemeReq) (err error) { if len(req.Layout) == 0 { req.Layout = constant.ThemeLayoutFullWidth } content, _ := json.Marshal(req) data := &entity.SiteInfo{ Type: constant.SiteTypeTheme, Content: string(content), Status: 1, } return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeTheme, data) } // SaveSiteUsers save site users func (s *SiteInfoService) SaveSiteUsers(ctx context.Context, req *schema.SiteUsersReq) (err error) { content, _ := json.Marshal(req) data := &entity.SiteInfo{ Type: constant.SiteTypeUsers, Content: string(content), Status: 1, } return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeUsers, data) } // GetSiteAI get site AI configuration func (s *SiteInfoService) GetSiteAI(ctx context.Context) (resp *schema.SiteAIResp, err error) { resp, err = s.siteInfoCommonService.GetSiteAI(ctx) if err != nil { return nil, err } aiProvider, err := s.GetAIProvider(ctx) if err != nil { return nil, err } providerMapping := make(map[string]*schema.SiteAIProvider) for _, provider := range resp.SiteAIProviders { providerMapping[provider.Provider] = provider } providers := make([]*schema.SiteAIProvider, 0) for _, p := range aiProvider { if provider, ok := providerMapping[p.Name]; ok { providers = append(providers, provider) } else { providers = append(providers, &schema.SiteAIProvider{ Provider: p.Name, }) } } resp.SiteAIProviders = providers s.maskAIKeys(resp) return resp, nil } // SaveSiteAI save site AI configuration func (s *SiteInfoService) SaveSiteAI(ctx context.Context, req *schema.SiteAIReq) (err error) { if err := s.restoreMaskedAIKeys(ctx, req); err != nil { return err } if req.PromptConfig == nil { req.PromptConfig = &schema.AIPromptConfig{ ZhCN: constant.DefaultAIPromptConfigZhCN, EnUS: constant.DefaultAIPromptConfigEnUS, } } aiProvider, err := s.GetAIProvider(ctx) if err != nil { return err } providerMapping := make(map[string]*schema.SiteAIProvider) for _, provider := range req.SiteAIProviders { providerMapping[provider.Provider] = provider } providers := make([]*schema.SiteAIProvider, 0) for _, p := range aiProvider { if provider, ok := providerMapping[p.Name]; ok { if len(provider.APIHost) == 0 && provider.Provider == req.ChosenProvider { provider.APIHost = p.DefaultAPIHost } providers = append(providers, provider) } else { providers = append(providers, &schema.SiteAIProvider{ Provider: p.Name, APIHost: p.DefaultAPIHost, }) } } req.SiteAIProviders = providers content, _ := json.Marshal(req) siteInfo := &entity.SiteInfo{ Type: constant.SiteTypeAI, Content: string(content), Status: 1, } return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeAI, siteInfo) } func (s *SiteInfoService) maskAIKeys(resp *schema.SiteAIResp) { for _, provider := range resp.SiteAIProviders { if provider.APIKey == "" { continue } provider.APIKey = strings.Repeat("*", len(provider.APIKey)) } } func (s *SiteInfoService) restoreMaskedAIKeys(ctx context.Context, req *schema.SiteAIReq) error { hasMasked := false for _, provider := range req.SiteAIProviders { if provider.APIKey != "" && isAllMask(provider.APIKey) { hasMasked = true break } } if !hasMasked { return nil } current, err := s.siteInfoCommonService.GetSiteAI(ctx) if err != nil { return err } currentMapping := make(map[string]*schema.SiteAIProvider) for _, provider := range current.SiteAIProviders { currentMapping[provider.Provider] = provider } for _, provider := range req.SiteAIProviders { if provider.APIKey == "" || !isAllMask(provider.APIKey) { continue } if stored, ok := currentMapping[provider.Provider]; ok { provider.APIKey = stored.APIKey } } return nil } func isAllMask(value string) bool { return strings.Trim(value, "*") == "" } // GetSiteMCP get site MCP configuration func (s *SiteInfoService) GetSiteMCP(ctx context.Context) (resp *schema.SiteMCPResp, err error) { resp, err = s.siteInfoCommonService.GetSiteMCP(ctx) if err != nil { return nil, err } siteInfo, err := s.GetSiteGeneral(ctx) if err != nil { return nil, err } resp.Type = "Server-Sent Event (SSE)" resp.URL = fmt.Sprintf("%s/answer/api/v1/mcp/sse", siteInfo.SiteUrl) resp.HTTPHeader = "Authorization={key}" return } // SaveSiteMCP save site MCP configuration func (s *SiteInfoService) SaveSiteMCP(ctx context.Context, req *schema.SiteMCPReq) (err error) { content, _ := json.Marshal(req) siteInfo := &entity.SiteInfo{ Type: constant.SiteTypeMCP, Content: string(content), Status: 1, } return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeMCP, siteInfo) } // GetSMTPConfig get smtp config func (s *SiteInfoService) GetSMTPConfig(ctx context.Context) (resp *schema.GetSMTPConfigResp, err error) { emailConfig, err := s.emailService.GetEmailConfig(ctx) if err != nil { return nil, err } resp = &schema.GetSMTPConfigResp{} _ = copier.Copy(resp, emailConfig) resp.SMTPPassword = strings.Repeat("*", len(resp.SMTPPassword)) return resp, nil } // UpdateSMTPConfig get smtp config func (s *SiteInfoService) UpdateSMTPConfig(ctx context.Context, req *schema.UpdateSMTPConfigReq) (err error) { emailConfig, err := s.emailService.GetEmailConfig(ctx) if err != nil { return err } ec := &export.EmailConfig{} _ = copier.Copy(ec, req) if len(ec.SMTPPassword) > 0 && ec.SMTPPassword == strings.Repeat("*", len(ec.SMTPPassword)) { ec.SMTPPassword = emailConfig.SMTPPassword } err = s.emailService.SetEmailConfig(ctx, ec) if err != nil { return err } if len(req.TestEmailRecipient) > 0 { title, body, err := s.emailService.TestTemplate(ctx) if err != nil { return err } go s.emailService.Send(ctx, req.TestEmailRecipient, title, body) } return nil } func (s *SiteInfoService) GetSeo(ctx context.Context) (resp *schema.SiteSeoReq, err error) { resp = &schema.SiteSeoReq{} if err = s.siteInfoCommonService.GetSiteInfoByType(ctx, constant.SiteTypeSeo, resp); err != nil { return resp, err } siteSecurity, err := s.GetSiteSecurity(ctx) if err != nil { log.Error(err) return resp, nil } // If the site is set to privacy mode, prohibit crawling any page. if siteSecurity.LoginRequired { resp.Robots = "User-agent: *\nDisallow: /" return resp, nil } return resp, nil } func (s *SiteInfoService) SaveSeo(ctx context.Context, req schema.SiteSeoReq) (err error) { content, _ := json.Marshal(req) data := entity.SiteInfo{ Type: constant.SiteTypeSeo, Content: string(content), } return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeSeo, &data) } func (s *SiteInfoService) GetPrivilegesConfig(ctx context.Context) (resp *schema.GetPrivilegesConfigResp, err error) { privilege := &schema.UpdatePrivilegesConfigReq{} if err = s.siteInfoCommonService.GetSiteInfoByType(ctx, constant.SiteTypePrivileges, privilege); err != nil { return nil, err } privilegeOptions := schema.DefaultPrivilegeOptions if len(privilege.CustomPrivileges) > 0 { privilegeOptions = append(privilegeOptions, &schema.PrivilegeOption{ Level: schema.PrivilegeLevelCustom, LevelDesc: reason.PrivilegeLevelCustomDesc, Privileges: privilege.CustomPrivileges, }) } else { privilegeOptions = append(privilegeOptions, schema.DefaultCustomPrivilegeOption) } resp = &schema.GetPrivilegesConfigResp{ Options: s.translatePrivilegeOptions(ctx, privilegeOptions), SelectedLevel: schema.PrivilegeLevel3, } if privilege.Level > 0 { resp.SelectedLevel = privilege.Level } return resp, nil } func (s *SiteInfoService) translatePrivilegeOptions(ctx context.Context, privilegeOptions []*schema.PrivilegeOption) (options []*schema.PrivilegeOption) { la := handler.GetLangByCtx(ctx) for _, option := range privilegeOptions { op := &schema.PrivilegeOption{ Level: option.Level, LevelDesc: translator.Tr(la, option.LevelDesc), } for _, privilege := range option.Privileges { op.Privileges = append(op.Privileges, &constant.Privilege{ Key: privilege.Key, Label: translator.Tr(la, privilege.Label), Value: privilege.Value, }) } options = append(options, op) } return } func (s *SiteInfoService) UpdatePrivilegesConfig(ctx context.Context, req *schema.UpdatePrivilegesConfigReq) (err error) { var choosePrivileges []*constant.Privilege if req.Level == schema.PrivilegeLevelCustom { choosePrivileges = req.CustomPrivileges } else { chooseOption := schema.DefaultPrivilegeOptions.Choose(req.Level) if chooseOption == nil { return nil } choosePrivileges = chooseOption.Privileges } if choosePrivileges == nil { return nil } // update site info that user choose which privilege level if req.Level == schema.PrivilegeLevelCustom { privilegeMap := make(map[string]int) for _, privilege := range req.CustomPrivileges { privilegeMap[privilege.Key] = privilege.Value } var privileges []*constant.Privilege for _, privilege := range constant.RankAllPrivileges { privileges = append(privileges, &constant.Privilege{ Key: privilege.Key, Label: privilege.Label, Value: privilegeMap[privilege.Key], }) } req.CustomPrivileges = privileges } else { privilege := &schema.UpdatePrivilegesConfigReq{} if err = s.siteInfoCommonService.GetSiteInfoByType(ctx, constant.SiteTypePrivileges, privilege); err != nil { return err } req.CustomPrivileges = privilege.CustomPrivileges } content, _ := json.Marshal(req) data := &entity.SiteInfo{ Type: constant.SiteTypePrivileges, Content: string(content), Status: 1, } err = s.siteInfoRepo.SaveByType(ctx, constant.SiteTypePrivileges, data) if err != nil { return err } // update privilege in config for _, privilege := range choosePrivileges { err = s.configService.UpdateConfig(ctx, privilege.Key, fmt.Sprintf("%d", privilege.Value)) if err != nil { return err } } return } func (s *SiteInfoService) CleanUpRemovedBrandingFiles( ctx context.Context, newBranding *schema.SiteBrandingReq, currentBranding *schema.SiteBrandingResp, ) error { var allErrors []error currentFiles := map[string]string{ "logo": currentBranding.Logo, "mobile_logo": currentBranding.MobileLogo, "square_icon": currentBranding.SquareIcon, "favicon": currentBranding.Favicon, } newFiles := map[string]string{ "logo": newBranding.Logo, "mobile_logo": newBranding.MobileLogo, "square_icon": newBranding.SquareIcon, "favicon": newBranding.Favicon, } for key, currentFile := range currentFiles { newFile := newFiles[key] if currentFile != "" && currentFile != newFile { fileRecord, err := s.fileRecordService.GetFileRecordByURL(ctx, currentFile) if err != nil { allErrors = append(allErrors, err) continue } if fileRecord == nil { err := errpkg.New("file record is nil for key " + key) allErrors = append(allErrors, err) continue } if err := s.fileRecordService.DeleteAndMoveFileRecord(ctx, fileRecord); err != nil { allErrors = append(allErrors, err) } } } if len(allErrors) > 0 { return errpkg.Join(allErrors...) } return nil } func (s *SiteInfoService) GetAIProvider(ctx context.Context) (resp []*schema.GetAIProviderResp, err error) { resp = make([]*schema.GetAIProviderResp, 0) aiProviderConfig, err := s.configService.GetStringValue(context.TODO(), constant.AIConfigProvider) if err != nil { log.Error(err) return resp, nil } _ = json.Unmarshal([]byte(aiProviderConfig), &resp) return resp, nil } func (s *SiteInfoService) GetAIModels(ctx context.Context, req *schema.GetAIModelsReq) (resp []*schema.GetAIModelResp, err error) { resp = make([]*schema.GetAIModelResp, 0) if req.APIKey != "" && isAllMask(req.APIKey) { storedKey, err := s.getStoredAIKey(ctx, req.APIHost) if err != nil { return resp, err } if storedKey == "" { return resp, errors.BadRequest("api_key is required") } req.APIKey = storedKey } r := resty.New() r.SetHeader("Authorization", fmt.Sprintf("Bearer %s", req.APIKey)) r.SetHeader("Content-Type", "application/json") respBody, err := r.R().Get(req.APIHost + "/v1/models") if err != nil { log.Error(err) return resp, errors.BadRequest(fmt.Sprintf("failed to get AI models %s", err.Error())) } if !respBody.IsSuccess() { log.Error(fmt.Sprintf("failed to get AI models, status code: %d, body: %s", respBody.StatusCode(), respBody.String())) return resp, errors.BadRequest(fmt.Sprintf("failed to get AI models, response: %s", respBody.String())) } data := schema.GetAIModelsResp{} _ = json.Unmarshal(respBody.Body(), &data) for _, model := range data.Data { resp = append(resp, &schema.GetAIModelResp{ Id: model.Id, Object: model.Object, Created: model.Created, OwnedBy: model.OwnedBy, }) } return resp, nil } func (s *SiteInfoService) getStoredAIKey(ctx context.Context, apiHost string) (string, error) { current, err := s.siteInfoCommonService.GetSiteAI(ctx) if err != nil { return "", err } apiHost = strings.TrimRight(apiHost, "/") for _, provider := range current.SiteAIProviders { if strings.TrimRight(provider.APIHost, "/") == apiHost && provider.APIKey != "" { return provider.APIKey, nil } } if current.ChosenProvider != "" { for _, provider := range current.SiteAIProviders { if provider.Provider == current.ChosenProvider { return provider.APIKey, nil } } } return "", nil }