1072 lines
38 KiB
Go
1072 lines
38 KiB
Go
package usecase
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
"sync"
|
|
"time"
|
|
|
|
v1 "github.com/chaitin/panda-wiki/api/share/v1"
|
|
"github.com/chaitin/panda-wiki/config"
|
|
"github.com/chaitin/panda-wiki/consts"
|
|
"github.com/chaitin/panda-wiki/domain"
|
|
"github.com/chaitin/panda-wiki/log"
|
|
"github.com/chaitin/panda-wiki/pkg/bot"
|
|
"github.com/chaitin/panda-wiki/pkg/bot/dingtalk"
|
|
"github.com/chaitin/panda-wiki/pkg/bot/discord"
|
|
"github.com/chaitin/panda-wiki/pkg/bot/feishu"
|
|
"github.com/chaitin/panda-wiki/pkg/bot/lark"
|
|
"github.com/chaitin/panda-wiki/repo/pg"
|
|
"github.com/chaitin/panda-wiki/store/cache"
|
|
)
|
|
|
|
type AppUsecase struct {
|
|
repo *pg.AppRepository
|
|
authRepo *pg.AuthRepo
|
|
nodeRepo *pg.NodeRepository
|
|
navRepo *pg.NavRepository
|
|
kbRepo *pg.KnowledgeBaseRepository
|
|
nodeUsecase *NodeUsecase
|
|
chatUsecase *ChatUsecase
|
|
logger *log.Logger
|
|
config *config.Config
|
|
cache *cache.Cache
|
|
dingTalkBots map[string]*dingtalk.DingTalkClient
|
|
dingTalkMutex sync.RWMutex
|
|
feishuBots map[string]*feishu.FeishuClient
|
|
feishuMutex sync.RWMutex
|
|
larkBots map[string]*lark.LarkClient
|
|
larkMutex sync.RWMutex
|
|
discordBots map[string]*discord.DiscordClient
|
|
discordMutex sync.RWMutex
|
|
}
|
|
|
|
func NewAppUsecase(
|
|
repo *pg.AppRepository,
|
|
authRepo *pg.AuthRepo,
|
|
navRepo *pg.NavRepository,
|
|
nodeRepo *pg.NodeRepository,
|
|
kbRepo *pg.KnowledgeBaseRepository,
|
|
nodeUsecase *NodeUsecase,
|
|
logger *log.Logger,
|
|
config *config.Config,
|
|
chatUsecase *ChatUsecase,
|
|
cache *cache.Cache,
|
|
) *AppUsecase {
|
|
u := &AppUsecase{
|
|
repo: repo,
|
|
nodeUsecase: nodeUsecase,
|
|
chatUsecase: chatUsecase,
|
|
authRepo: authRepo,
|
|
navRepo: navRepo,
|
|
nodeRepo: nodeRepo,
|
|
kbRepo: kbRepo,
|
|
logger: logger.WithModule("usecase.app"),
|
|
config: config,
|
|
cache: cache,
|
|
dingTalkBots: make(map[string]*dingtalk.DingTalkClient),
|
|
feishuBots: make(map[string]*feishu.FeishuClient),
|
|
larkBots: make(map[string]*lark.LarkClient),
|
|
discordBots: make(map[string]*discord.DiscordClient),
|
|
}
|
|
|
|
// Initialize all valid DingTalkBot, FeishuBot, LarkBot and DiscordBot instances
|
|
apps, err := u.repo.GetAppsByTypes(context.Background(), []domain.AppType{domain.AppTypeDingTalkBot, domain.AppTypeFeishuBot, domain.AppTypeLarkBot, domain.AppTypeDisCordBot})
|
|
if err != nil {
|
|
u.logger.Error("failed to get dingtalk bot apps", log.Error(err))
|
|
return u
|
|
}
|
|
|
|
for _, app := range apps {
|
|
switch app.Type {
|
|
case domain.AppTypeDingTalkBot:
|
|
u.updateDingTalkBot(app)
|
|
case domain.AppTypeFeishuBot:
|
|
u.updateFeishuBot(app)
|
|
case domain.AppTypeLarkBot:
|
|
u.updateLarkBot(app)
|
|
case domain.AppTypeDisCordBot:
|
|
u.updateDisCordBot(app)
|
|
}
|
|
}
|
|
|
|
return u
|
|
}
|
|
|
|
func (u *AppUsecase) ValidateUpdateApp(ctx context.Context, id string, req *domain.UpdateAppReq) error {
|
|
app, err := u.repo.GetAppDetail(ctx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
limitation := domain.GetBaseEditionLimitation(ctx)
|
|
if !limitation.AllowCopyProtection && app.Settings.CopySetting != req.Settings.CopySetting {
|
|
return domain.ErrPermissionDenied
|
|
}
|
|
|
|
if !limitation.AllowWatermark {
|
|
if app.Settings.WatermarkSetting != req.Settings.WatermarkSetting || app.Settings.WatermarkContent != req.Settings.WatermarkContent {
|
|
return domain.ErrPermissionDenied
|
|
}
|
|
}
|
|
|
|
if !limitation.AllowAdvancedBot {
|
|
if !slices.Equal(app.Settings.WechatServiceContainKeywords, req.Settings.WechatServiceContainKeywords) ||
|
|
!slices.Equal(app.Settings.WechatServiceEqualKeywords, req.Settings.WechatServiceEqualKeywords) ||
|
|
app.Settings.WechatServiceLogo != req.Settings.WechatServiceLogo {
|
|
return domain.ErrPermissionDenied
|
|
}
|
|
|
|
if app.Settings.WeChatAppAdvancedSetting.FeedbackEnable != req.Settings.WeChatAppAdvancedSetting.FeedbackEnable ||
|
|
app.Settings.WeChatAppAdvancedSetting.TextResponseEnable != req.Settings.WeChatAppAdvancedSetting.TextResponseEnable ||
|
|
app.Settings.WeChatAppAdvancedSetting.Prompt != req.Settings.WeChatAppAdvancedSetting.Prompt ||
|
|
!slices.Equal(app.Settings.WeChatAppAdvancedSetting.FeedbackType, req.Settings.WeChatAppAdvancedSetting.FeedbackType) ||
|
|
app.Settings.WeChatAppAdvancedSetting.DisclaimerContent != req.Settings.WeChatAppAdvancedSetting.DisclaimerContent {
|
|
return domain.ErrPermissionDenied
|
|
}
|
|
} else {
|
|
if req.Settings.WeChatAppAdvancedSetting.Prompt == "" {
|
|
req.Settings.WeChatAppAdvancedSetting.Prompt = domain.SystemDefaultPrompt
|
|
}
|
|
}
|
|
|
|
if !limitation.AllowCommentAudit && app.Settings.WebAppCommentSettings.ModerationEnable != req.Settings.WebAppCommentSettings.ModerationEnable {
|
|
return domain.ErrPermissionDenied
|
|
}
|
|
|
|
if !limitation.AllowOpenAIBotSettings {
|
|
if app.Settings.OpenAIAPIBotSettings.IsEnabled != req.Settings.OpenAIAPIBotSettings.IsEnabled || app.Settings.OpenAIAPIBotSettings.SecretKey != req.Settings.OpenAIAPIBotSettings.SecretKey {
|
|
return domain.ErrPermissionDenied
|
|
}
|
|
}
|
|
|
|
if !limitation.AllowCustomCopyright {
|
|
if app.Settings.WidgetBotSettings.CopyrightHideEnabled != req.Settings.WidgetBotSettings.CopyrightHideEnabled || app.Settings.WidgetBotSettings.CopyrightInfo != req.Settings.WidgetBotSettings.CopyrightInfo {
|
|
return domain.ErrPermissionDenied
|
|
}
|
|
if app.Settings.ConversationSetting.CopyrightHideEnabled != req.Settings.ConversationSetting.CopyrightHideEnabled {
|
|
return domain.ErrPermissionDenied
|
|
}
|
|
if req.Settings.ConversationSetting.CopyrightInfo != domain.SettingCopyrightInfo && app.Settings.ConversationSetting.CopyrightInfo != req.Settings.ConversationSetting.CopyrightInfo {
|
|
req.Settings.ConversationSetting.CopyrightInfo = domain.SettingCopyrightInfo
|
|
}
|
|
}
|
|
|
|
if !limitation.AllowMCPServer {
|
|
if app.Settings.MCPServerSettings.IsEnabled != req.Settings.MCPServerSettings.IsEnabled {
|
|
return domain.ErrPermissionDenied
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (u *AppUsecase) UpdateApp(ctx context.Context, id string, appRequest *domain.UpdateAppReq) error {
|
|
if err := u.handleBotAuths(ctx, id, appRequest.Settings); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := u.repo.UpdateApp(ctx, id, appRequest.KbID, appRequest); err != nil {
|
|
return err
|
|
}
|
|
|
|
if appRequest.Settings != nil {
|
|
app, err := u.repo.GetAppDetail(ctx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch app.Type {
|
|
case domain.AppTypeDingTalkBot:
|
|
u.updateDingTalkBot(app)
|
|
case domain.AppTypeFeishuBot:
|
|
u.updateFeishuBot(app)
|
|
case domain.AppTypeLarkBot:
|
|
u.updateLarkBot(app)
|
|
case domain.AppTypeDisCordBot:
|
|
u.updateDisCordBot(app)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (u *AppUsecase) getQAFunc(kbID string, appType domain.AppType) bot.GetQAFun {
|
|
return func(ctx context.Context, msg string, info domain.ConversationInfo, ConversationID string) (chan string, error) {
|
|
auth, err := u.authRepo.GetAuthByKBIDAndSourceType(ctx, kbID, appType.ToSourceType())
|
|
if err != nil {
|
|
u.logger.Error("get auth failed", log.Error(err))
|
|
return nil, err
|
|
}
|
|
info.UserInfo.AuthUserID = auth.ID
|
|
|
|
eventCh, err := u.chatUsecase.Chat(ctx, &domain.ChatRequest{
|
|
Message: msg,
|
|
KBID: kbID,
|
|
AppType: appType,
|
|
RemoteIP: "",
|
|
ConversationID: ConversationID,
|
|
Info: info,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// check ai feedback. --> default is open
|
|
appinfo, err := u.GetAppDetailByKBIDAndAppType(ctx, kbID, domain.AppTypeWeb)
|
|
if err != nil {
|
|
u.logger.Error("wechat GetAppDetailByKBIDAndAppType failed", log.Error(err))
|
|
}
|
|
|
|
var feedback = "\n\n--- \n\n本回答由 PandaWiki 基于 AI 生成,仅供参考。\n[👍 满意](%s) | [👎 不满意](%s)"
|
|
var likeUrl = "%s/feedback?score=1&message_id=%s"
|
|
var dislikeUrl = "%s/feedback?score=-1&message_id=%s"
|
|
var messageId string
|
|
var kb *domain.KnowledgeBase
|
|
|
|
if appinfo.Settings.AIFeedbackSettings.AIFeedbackIsEnabled == nil || *appinfo.Settings.AIFeedbackSettings.AIFeedbackIsEnabled { // open
|
|
kb, err = u.chatUsecase.llmUsecase.kbRepo.GetKnowledgeBaseByID(ctx, kbID)
|
|
if err != nil {
|
|
u.logger.Error("wechat GetKnowledgeBaseByID failed", log.Error(err))
|
|
}
|
|
|
|
}
|
|
|
|
contentCh := make(chan string, 10)
|
|
go func() {
|
|
defer close(contentCh)
|
|
for event := range eventCh {
|
|
if event.Type == "done" || event.Type == "error" {
|
|
break
|
|
}
|
|
if event.Type == "data" {
|
|
contentCh <- event.Content
|
|
}
|
|
if event.Type == "message_id" {
|
|
messageId = event.Content
|
|
}
|
|
}
|
|
// check again
|
|
// contact --> send
|
|
if kb != nil && (appinfo.Settings.AIFeedbackSettings.AIFeedbackIsEnabled == nil || *appinfo.Settings.AIFeedbackSettings.AIFeedbackIsEnabled) { // open
|
|
like := fmt.Sprintf(likeUrl, kb.AccessSettings.BaseURL, messageId)
|
|
dislike := fmt.Sprintf(dislikeUrl, kb.AccessSettings.BaseURL, messageId)
|
|
feedback_data := fmt.Sprintf(feedback, like, dislike)
|
|
contentCh <- feedback_data
|
|
}
|
|
}()
|
|
return contentCh, nil
|
|
}
|
|
}
|
|
|
|
func (u *AppUsecase) updateFeishuBot(app *domain.App) {
|
|
u.feishuMutex.Lock()
|
|
defer u.feishuMutex.Unlock()
|
|
|
|
if bot, exists := u.feishuBots[app.ID]; exists {
|
|
if bot != nil {
|
|
bot.Stop()
|
|
delete(u.feishuBots, app.ID)
|
|
}
|
|
}
|
|
|
|
if (app.Settings.FeishuBotIsEnabled != nil && !*app.Settings.FeishuBotIsEnabled) || app.Settings.FeishuBotAppID == "" || app.Settings.FeishuBotAppSecret == "" {
|
|
return
|
|
}
|
|
|
|
getQA := u.getQAFunc(app.KBID, app.Type)
|
|
|
|
botCtx, cancel := context.WithCancel(context.Background())
|
|
feishuClient := feishu.NewFeishuClient(
|
|
botCtx,
|
|
cancel,
|
|
app.Settings.FeishuBotAppID,
|
|
app.Settings.FeishuBotAppSecret,
|
|
u.logger,
|
|
getQA,
|
|
)
|
|
|
|
go func() {
|
|
u.logger.Info("feishu bot is starting", log.String("app_id", app.Settings.FeishuBotAppID))
|
|
err := feishuClient.Start()
|
|
if err != nil {
|
|
u.logger.Error("failed to start feishu client", log.Error(err))
|
|
cancel()
|
|
return
|
|
}
|
|
}()
|
|
|
|
u.feishuBots[app.ID] = feishuClient
|
|
}
|
|
|
|
func (u *AppUsecase) updateLarkBot(app *domain.App) {
|
|
u.larkMutex.Lock()
|
|
defer u.larkMutex.Unlock()
|
|
|
|
if bot, exists := u.larkBots[app.ID]; exists {
|
|
if bot != nil {
|
|
bot.Stop()
|
|
delete(u.larkBots, app.ID)
|
|
}
|
|
}
|
|
|
|
if (app.Settings.LarkBotSettings.IsEnabled != nil && !*app.Settings.LarkBotSettings.IsEnabled) || app.Settings.LarkBotSettings.AppID == "" || app.Settings.LarkBotSettings.AppSecret == "" {
|
|
return
|
|
}
|
|
|
|
getQA := u.getQAFunc(app.KBID, app.Type)
|
|
|
|
botCtx, cancel := context.WithCancel(context.Background())
|
|
larkClient, err := lark.NewLarkClient(
|
|
botCtx,
|
|
cancel,
|
|
app.Settings.LarkBotSettings.AppID,
|
|
app.Settings.LarkBotSettings.AppSecret,
|
|
app.Settings.LarkBotSettings.VerifyToken,
|
|
app.Settings.LarkBotSettings.EncryptKey,
|
|
u.logger,
|
|
getQA,
|
|
)
|
|
if err != nil {
|
|
u.logger.Error("failed to create lark client", log.Error(err))
|
|
return
|
|
}
|
|
|
|
go func() {
|
|
u.logger.Info("lark bot is starting", log.String("app_id", app.Settings.LarkBotSettings.AppID))
|
|
err := larkClient.Start()
|
|
if err != nil {
|
|
u.logger.Error("failed to start lark client", log.Error(err))
|
|
cancel()
|
|
return
|
|
}
|
|
}()
|
|
|
|
u.larkBots[app.ID] = larkClient
|
|
}
|
|
|
|
func (u *AppUsecase) updateDingTalkBot(app *domain.App) {
|
|
u.dingTalkMutex.Lock()
|
|
defer u.dingTalkMutex.Unlock()
|
|
|
|
if bot, exists := u.dingTalkBots[app.ID]; exists {
|
|
if bot != nil {
|
|
bot.Stop()
|
|
delete(u.dingTalkBots, app.ID)
|
|
}
|
|
}
|
|
|
|
if (app.Settings.DingTalkBotIsEnabled != nil && !*app.Settings.DingTalkBotIsEnabled) || app.Settings.DingTalkBotClientID == "" || app.Settings.DingTalkBotClientSecret == "" {
|
|
return
|
|
}
|
|
|
|
getQA := u.getQAFunc(app.KBID, app.Type)
|
|
|
|
botCtx, cancel := context.WithCancel(context.Background())
|
|
dingTalkClient, err := dingtalk.NewDingTalkClient(
|
|
botCtx,
|
|
cancel,
|
|
app.Settings.DingTalkBotClientID,
|
|
app.Settings.DingTalkBotClientSecret,
|
|
app.Settings.DingTalkBotTemplateID,
|
|
u.logger,
|
|
getQA,
|
|
)
|
|
if err != nil {
|
|
u.logger.Error("failed to create dingtalk client", log.Error(err))
|
|
return
|
|
}
|
|
|
|
go func() {
|
|
u.logger.Info("dingtalk bot is starting", log.String("client_id", app.Settings.DingTalkBotClientID))
|
|
err := dingTalkClient.Start()
|
|
if err != nil {
|
|
u.logger.Error("failed to start dingtalk bot", log.Error(err))
|
|
cancel()
|
|
return
|
|
}
|
|
}()
|
|
|
|
u.dingTalkBots[app.ID] = dingTalkClient
|
|
}
|
|
|
|
func (u *AppUsecase) updateDisCordBot(app *domain.App) {
|
|
u.discordMutex.Lock()
|
|
defer u.discordMutex.Unlock()
|
|
|
|
if bot, exists := u.discordBots[app.ID]; exists {
|
|
if bot != nil {
|
|
if err := bot.Stop(); err != nil {
|
|
u.logger.Error("failed to stop discord bot", log.Error(err))
|
|
}
|
|
delete(u.discordBots, app.ID)
|
|
}
|
|
}
|
|
token := app.Settings.DiscordBotToken
|
|
if (app.Settings.DiscordBotIsEnabled != nil && !*app.Settings.DiscordBotIsEnabled) || token == "" {
|
|
return
|
|
}
|
|
|
|
getQA := u.getQAFunc(app.KBID, app.Type)
|
|
|
|
discordBots, err := discord.NewDiscordClient(
|
|
u.logger, token, getQA,
|
|
)
|
|
if err != nil {
|
|
u.logger.Error("failed to create discord client", log.Error(err))
|
|
return
|
|
}
|
|
|
|
if err := discordBots.Start(); err != nil {
|
|
u.logger.Error("failed to start discord bot", log.Error(err))
|
|
return
|
|
}
|
|
|
|
u.logger.Info("discord bot is starting", log.String("token", token))
|
|
u.discordBots[app.ID] = discordBots
|
|
}
|
|
|
|
func (u *AppUsecase) DeleteApp(ctx context.Context, id, kbID string) error {
|
|
return u.repo.DeleteApp(ctx, id, kbID)
|
|
}
|
|
|
|
// GetLarkBotClient returns the Lark bot client for a given app ID
|
|
// This is used to access the event handler for HTTP callbacks
|
|
func (u *AppUsecase) GetLarkBotClient(appID string) (*lark.LarkClient, bool) {
|
|
u.larkMutex.RLock()
|
|
defer u.larkMutex.RUnlock()
|
|
client, ok := u.larkBots[appID]
|
|
return client, ok
|
|
}
|
|
|
|
func (u *AppUsecase) GetAppDetailByKBIDAndAppType(ctx context.Context, kbID string, appType domain.AppType) (*domain.AppDetailResp, error) {
|
|
app, err := u.repo.GetOrCreateAppByKBIDAndType(ctx, kbID, appType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
appDetailResp := &domain.AppDetailResp{
|
|
ID: app.ID,
|
|
KBID: app.KBID,
|
|
Name: app.Name,
|
|
Type: app.Type,
|
|
}
|
|
var webAppLandingConfigs []domain.WebAppLandingConfigResp
|
|
for i := range app.Settings.WebAppLandingConfigs {
|
|
webAppLandingConfigResp := domain.WebAppLandingConfigResp{
|
|
Type: app.Settings.WebAppLandingConfigs[i].Type,
|
|
BannerConfig: app.Settings.WebAppLandingConfigs[i].BannerConfig,
|
|
BasicDocConfig: app.Settings.WebAppLandingConfigs[i].BasicDocConfig,
|
|
DirDocConfig: app.Settings.WebAppLandingConfigs[i].DirDocConfig,
|
|
NavDocConfig: app.Settings.WebAppLandingConfigs[i].NavDocConfig,
|
|
SimpleDocConfig: app.Settings.WebAppLandingConfigs[i].SimpleDocConfig,
|
|
CarouselConfig: app.Settings.WebAppLandingConfigs[i].CarouselConfig,
|
|
FaqConfig: app.Settings.WebAppLandingConfigs[i].FaqConfig,
|
|
TextConfig: app.Settings.WebAppLandingConfigs[i].TextConfig,
|
|
CaseConfig: app.Settings.WebAppLandingConfigs[i].CaseConfig,
|
|
MetricsConfig: app.Settings.WebAppLandingConfigs[i].MetricsConfig,
|
|
CommentConfig: app.Settings.WebAppLandingConfigs[i].CommentConfig,
|
|
FeatureConfig: app.Settings.WebAppLandingConfigs[i].FeatureConfig,
|
|
ImgTextConfig: app.Settings.WebAppLandingConfigs[i].ImgTextConfig,
|
|
TextImgConfig: app.Settings.WebAppLandingConfigs[i].TextImgConfig,
|
|
QuestionConfig: app.Settings.WebAppLandingConfigs[i].QuestionConfig,
|
|
BlockGridConfig: app.Settings.WebAppLandingConfigs[i].BlockGridConfig,
|
|
ComConfigOrder: app.Settings.WebAppLandingConfigs[i].ComConfigOrder,
|
|
NodeIds: app.Settings.WebAppLandingConfigs[i].NodeIds,
|
|
}
|
|
webAppLandingConfigs = append(webAppLandingConfigs, webAppLandingConfigResp)
|
|
}
|
|
appDetailResp.Settings = domain.AppSettingsResp{
|
|
Title: app.Settings.Title,
|
|
Icon: app.Settings.Icon,
|
|
Btns: app.Settings.Btns,
|
|
WelcomeStr: app.Settings.WelcomeStr,
|
|
SearchPlaceholder: app.Settings.SearchPlaceholder,
|
|
RecommendQuestions: app.Settings.RecommendQuestions,
|
|
RecommendNodeIDs: app.Settings.RecommendNodeIDs,
|
|
Desc: app.Settings.Desc,
|
|
Keyword: app.Settings.Keyword,
|
|
HeadCode: app.Settings.HeadCode,
|
|
BodyCode: app.Settings.BodyCode,
|
|
// DingTalkBot
|
|
DingTalkBotIsEnabled: app.Settings.DingTalkBotIsEnabled,
|
|
DingTalkBotClientID: app.Settings.DingTalkBotClientID,
|
|
DingTalkBotClientSecret: app.Settings.DingTalkBotClientSecret,
|
|
DingTalkBotTemplateID: app.Settings.DingTalkBotTemplateID,
|
|
// FeishuBot
|
|
FeishuBotIsEnabled: app.Settings.FeishuBotIsEnabled,
|
|
FeishuBotAppID: app.Settings.FeishuBotAppID,
|
|
FeishuBotAppSecret: app.Settings.FeishuBotAppSecret,
|
|
// LarkBot
|
|
LarkBotSettings: app.Settings.LarkBotSettings,
|
|
// WechatBot
|
|
WeChatAppIsEnabled: app.Settings.WeChatAppIsEnabled,
|
|
WeChatAppToken: app.Settings.WeChatAppToken,
|
|
WeChatAppCorpID: app.Settings.WeChatAppCorpID,
|
|
WeChatAppEncodingAESKey: app.Settings.WeChatAppEncodingAESKey,
|
|
WeChatAppSecret: app.Settings.WeChatAppSecret,
|
|
WeChatAppAgentID: app.Settings.WeChatAppAgentID,
|
|
WeChatAppAdvancedSetting: app.Settings.WeChatAppAdvancedSetting,
|
|
// WechatServiceBot
|
|
WeChatServiceIsEnabled: app.Settings.WeChatServiceIsEnabled,
|
|
WeChatServiceToken: app.Settings.WeChatServiceToken,
|
|
WeChatServiceEncodingAESKey: app.Settings.WeChatServiceEncodingAESKey,
|
|
WeChatServiceCorpID: app.Settings.WeChatServiceCorpID,
|
|
WeChatServiceSecret: app.Settings.WeChatServiceSecret,
|
|
WechatServiceContainKeywords: app.Settings.WechatServiceContainKeywords,
|
|
WechatServiceEqualKeywords: app.Settings.WechatServiceEqualKeywords,
|
|
WechatServiceLogo: app.Settings.WechatServiceLogo,
|
|
// Discord
|
|
DiscordBotIsEnabled: app.Settings.DiscordBotIsEnabled,
|
|
DiscordBotToken: app.Settings.DiscordBotToken,
|
|
// WechatOfficialAccount
|
|
WechatOfficialAccountIsEnabled: app.Settings.WechatOfficialAccountIsEnabled,
|
|
WechatOfficialAccountAppID: app.Settings.WechatOfficialAccountAppID,
|
|
WechatOfficialAccountAppSecret: app.Settings.WechatOfficialAccountAppSecret,
|
|
WechatOfficialAccountToken: app.Settings.WechatOfficialAccountToken,
|
|
WechatOfficialAccountEncodingAESKey: app.Settings.WechatOfficialAccountEncodingAESKey,
|
|
// theme
|
|
ThemeMode: app.Settings.ThemeMode,
|
|
ThemeAndStyle: app.Settings.ThemeAndStyle,
|
|
// catalog settings
|
|
CatalogSettings: app.Settings.CatalogSettings,
|
|
// footer settings
|
|
FooterSettings: app.Settings.FooterSettings,
|
|
// widget bot settings
|
|
WidgetBotSettings: app.Settings.WidgetBotSettings,
|
|
// webapp comment settings
|
|
WebAppCommentSettings: app.Settings.WebAppCommentSettings,
|
|
// document feedback
|
|
DocumentFeedBackIsEnabled: app.Settings.DocumentFeedBackIsEnabled,
|
|
// AI Feedback
|
|
AIFeedbackSettings: app.Settings.AIFeedbackSettings,
|
|
// WebApp Custom Settings
|
|
WebAppCustomSettings: app.Settings.WebAppCustomSettings,
|
|
// openai api settings
|
|
OpenAIAPIBotSettings: app.Settings.OpenAIAPIBotSettings,
|
|
// disclaimer settings
|
|
DisclaimerSettings: app.Settings.DisclaimerSettings,
|
|
// webapp landing settings
|
|
WebAppLandingConfigs: webAppLandingConfigs,
|
|
WebAppLandingTheme: app.Settings.WebAppLandingTheme,
|
|
|
|
WatermarkContent: app.Settings.WatermarkContent,
|
|
WatermarkSetting: app.Settings.WatermarkSetting,
|
|
CopySetting: app.Settings.CopySetting,
|
|
ContributeSettings: app.Settings.ContributeSettings,
|
|
HomePageSetting: app.Settings.HomePageSetting,
|
|
ConversationSetting: app.Settings.ConversationSetting,
|
|
|
|
WecomAIBotSettings: app.Settings.WecomAIBotSettings,
|
|
|
|
MCPServerSettings: app.Settings.MCPServerSettings,
|
|
StatsSetting: app.Settings.StatsSetting,
|
|
}
|
|
|
|
if !domain.GetBaseEditionLimitation(ctx).AllowCustomCopyright {
|
|
appDetailResp.Settings.ConversationSetting.CopyrightHideEnabled = false
|
|
appDetailResp.Settings.ConversationSetting.CopyrightInfo = domain.SettingCopyrightInfo
|
|
}
|
|
|
|
// init ai feedback string
|
|
if app.Settings.AIFeedbackSettings.AIFeedbackType == nil {
|
|
appDetailResp.Settings.AIFeedbackSettings.AIFeedbackType = []string{"内容不准确", "没有帮助", "其他"}
|
|
}
|
|
if appDetailResp.Settings.HomePageSetting == "" {
|
|
appDetailResp.Settings.HomePageSetting = consts.HomePageSettingDoc
|
|
}
|
|
|
|
// get recommend nodes
|
|
if len(app.Settings.RecommendNodeIDs) > 0 {
|
|
nodes, err := u.nodeUsecase.GetRecommendNodeList(ctx, &domain.GetRecommendNodeListReq{
|
|
KBID: kbID,
|
|
NodeIDs: app.Settings.RecommendNodeIDs,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
appDetailResp.RecommendNodes = nodes
|
|
}
|
|
return appDetailResp, nil
|
|
}
|
|
|
|
func (u *AppUsecase) SanitizeAppDetailForDocManage(app *domain.AppDetailResp) *domain.AppDetailResp {
|
|
if app == nil {
|
|
return nil
|
|
}
|
|
|
|
sanitized := &domain.AppDetailResp{
|
|
ID: app.ID,
|
|
KBID: app.KBID,
|
|
Name: app.Name,
|
|
Type: app.Type,
|
|
}
|
|
|
|
if app.Type != domain.AppTypeWeb {
|
|
return sanitized
|
|
}
|
|
|
|
sanitized.Settings = domain.AppSettingsResp{
|
|
ThemeMode: app.Settings.ThemeMode,
|
|
ThemeAndStyle: app.Settings.ThemeAndStyle,
|
|
CatalogSettings: app.Settings.CatalogSettings,
|
|
WatermarkContent: app.Settings.WatermarkContent,
|
|
WatermarkSetting: app.Settings.WatermarkSetting,
|
|
CopySetting: app.Settings.CopySetting,
|
|
ContributeSettings: app.Settings.ContributeSettings,
|
|
ConversationSetting: app.Settings.ConversationSetting,
|
|
HomePageSetting: app.Settings.HomePageSetting,
|
|
}
|
|
|
|
return sanitized
|
|
}
|
|
|
|
func (u *AppUsecase) GetMCPServerAppInfo(ctx context.Context, kbID string) (*domain.AppInfoResp, error) {
|
|
apiApp, err := u.repo.GetOrCreateAppByKBIDAndType(ctx, kbID, domain.AppTypeMcpServer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
appInfo := &domain.AppInfoResp{
|
|
Settings: domain.AppSettingsResp{
|
|
MCPServerSettings: apiApp.Settings.MCPServerSettings,
|
|
},
|
|
}
|
|
return appInfo, nil
|
|
}
|
|
|
|
func (u *AppUsecase) ShareGetWebAppInfo(ctx context.Context, kbID string, authId uint) (*domain.AppInfoResp, error) {
|
|
kb, err := u.kbRepo.GetKnowledgeBaseByID(ctx, kbID)
|
|
if err != nil {
|
|
u.logger.Error("get kb failed", log.Error(err), log.String("kb_id", kbID))
|
|
return nil, err
|
|
}
|
|
|
|
app, err := u.repo.GetOrCreateAppByKBIDAndType(ctx, kbID, domain.AppTypeWeb)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var webAppLandingConfigs []domain.WebAppLandingConfigResp
|
|
for i := range app.Settings.WebAppLandingConfigs {
|
|
webAppLandingConfigResp := domain.WebAppLandingConfigResp{
|
|
Type: app.Settings.WebAppLandingConfigs[i].Type,
|
|
BannerConfig: app.Settings.WebAppLandingConfigs[i].BannerConfig,
|
|
BasicDocConfig: app.Settings.WebAppLandingConfigs[i].BasicDocConfig,
|
|
DirDocConfig: app.Settings.WebAppLandingConfigs[i].DirDocConfig,
|
|
NavDocConfig: app.Settings.WebAppLandingConfigs[i].NavDocConfig,
|
|
SimpleDocConfig: app.Settings.WebAppLandingConfigs[i].SimpleDocConfig,
|
|
CarouselConfig: app.Settings.WebAppLandingConfigs[i].CarouselConfig,
|
|
FaqConfig: app.Settings.WebAppLandingConfigs[i].FaqConfig,
|
|
TextConfig: app.Settings.WebAppLandingConfigs[i].TextConfig,
|
|
CaseConfig: app.Settings.WebAppLandingConfigs[i].CaseConfig,
|
|
CommentConfig: app.Settings.WebAppLandingConfigs[i].CommentConfig,
|
|
FeatureConfig: app.Settings.WebAppLandingConfigs[i].FeatureConfig,
|
|
ImgTextConfig: app.Settings.WebAppLandingConfigs[i].ImgTextConfig,
|
|
TextImgConfig: app.Settings.WebAppLandingConfigs[i].TextImgConfig,
|
|
MetricsConfig: app.Settings.WebAppLandingConfigs[i].MetricsConfig,
|
|
QuestionConfig: app.Settings.WebAppLandingConfigs[i].QuestionConfig,
|
|
BlockGridConfig: app.Settings.WebAppLandingConfigs[i].BlockGridConfig,
|
|
ComConfigOrder: app.Settings.WebAppLandingConfigs[i].ComConfigOrder,
|
|
NodeIds: app.Settings.WebAppLandingConfigs[i].NodeIds,
|
|
}
|
|
if app.Settings.WebAppLandingConfigs[i].NavDocConfig != nil {
|
|
navNodes, err := u.GetRecommendNodesByNavIds(ctx, kbID, app.Settings.WebAppLandingConfigs[i].NavDocConfig.NavIds, authId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
webAppLandingConfigResp.Nodes = navNodes
|
|
} else {
|
|
nodes, err := u.GetRecommendNodesByIds(ctx, kbID, app.Settings.WebAppLandingConfigs[i].NodeIds, authId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
webAppLandingConfigResp.Nodes = nodes
|
|
}
|
|
webAppLandingConfigs = append(webAppLandingConfigs, webAppLandingConfigResp)
|
|
}
|
|
appInfo := &domain.AppInfoResp{
|
|
Name: app.Name,
|
|
BaseUrl: kb.AccessSettings.BaseURL,
|
|
Settings: domain.AppSettingsResp{
|
|
Title: app.Settings.Title,
|
|
Icon: app.Settings.Icon,
|
|
Btns: app.Settings.Btns,
|
|
WelcomeStr: app.Settings.WelcomeStr,
|
|
SearchPlaceholder: app.Settings.SearchPlaceholder,
|
|
RecommendQuestions: app.Settings.RecommendQuestions,
|
|
RecommendNodeIDs: app.Settings.RecommendNodeIDs,
|
|
Desc: app.Settings.Desc,
|
|
Keyword: app.Settings.Keyword,
|
|
HeadCode: app.Settings.HeadCode,
|
|
BodyCode: app.Settings.BodyCode,
|
|
// theme
|
|
ThemeMode: app.Settings.ThemeMode,
|
|
ThemeAndStyle: app.Settings.ThemeAndStyle,
|
|
// catalog settings
|
|
CatalogSettings: app.Settings.CatalogSettings,
|
|
// footer settings
|
|
FooterSettings: app.Settings.FooterSettings,
|
|
// widget bot settings
|
|
WebAppCommentSettings: app.Settings.WebAppCommentSettings,
|
|
// document feedback
|
|
DocumentFeedBackIsEnabled: app.Settings.DocumentFeedBackIsEnabled,
|
|
// AI Feedback
|
|
AIFeedbackSettings: app.Settings.AIFeedbackSettings,
|
|
// WebApp Custom Settings
|
|
WebAppCustomSettings: app.Settings.WebAppCustomSettings,
|
|
// Disclaimer Settings
|
|
DisclaimerSettings: app.Settings.DisclaimerSettings,
|
|
// WebApp Landing Settings
|
|
WebAppLandingConfigs: webAppLandingConfigs,
|
|
WebAppLandingTheme: app.Settings.WebAppLandingTheme,
|
|
|
|
WatermarkContent: app.Settings.WatermarkContent,
|
|
WatermarkSetting: app.Settings.WatermarkSetting,
|
|
CopySetting: app.Settings.CopySetting,
|
|
ContributeSettings: app.Settings.ContributeSettings,
|
|
HomePageSetting: app.Settings.HomePageSetting,
|
|
ConversationSetting: app.Settings.ConversationSetting,
|
|
StatsSetting: app.Settings.StatsSetting,
|
|
},
|
|
}
|
|
// init ai feedback string
|
|
if app.Settings.AIFeedbackSettings.AIFeedbackType == nil {
|
|
appInfo.Settings.AIFeedbackSettings.AIFeedbackType = []string{"内容不准确", "没有帮助", "其他"}
|
|
}
|
|
if app.Settings.HomePageSetting == "" {
|
|
appInfo.Settings.HomePageSetting = consts.HomePageSettingDoc
|
|
}
|
|
showBrand := true
|
|
defaultDisclaimer := "本回答由 PandaWiki 基于 AI 生成,仅供参考。"
|
|
|
|
if !domain.GetBaseEditionLimitation(ctx).AllowCustomCopyright {
|
|
appInfo.Settings.WebAppCustomSettings.ShowBrandInfo = &showBrand
|
|
appInfo.Settings.DisclaimerSettings.Content = &defaultDisclaimer
|
|
appInfo.Settings.ConversationSetting.CopyrightHideEnabled = false
|
|
appInfo.Settings.ConversationSetting.CopyrightInfo = domain.SettingCopyrightInfo
|
|
} else {
|
|
if appInfo.Settings.DisclaimerSettings.Content == nil {
|
|
appInfo.Settings.DisclaimerSettings.Content = &defaultDisclaimer
|
|
}
|
|
}
|
|
|
|
return appInfo, nil
|
|
}
|
|
|
|
func (u *AppUsecase) GetWidgetAppInfo(ctx context.Context, kbID string) (*domain.AppInfoResp, error) {
|
|
webApp, err := u.repo.GetOrCreateAppByKBIDAndType(ctx, kbID, domain.AppTypeWeb)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
widgetApp, err := u.repo.GetOrCreateAppByKBIDAndType(ctx, kbID, domain.AppTypeWidget)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
appInfo := &domain.AppInfoResp{
|
|
Settings: domain.AppSettingsResp{
|
|
Title: webApp.Settings.Title,
|
|
Icon: webApp.Settings.Icon,
|
|
WelcomeStr: webApp.Settings.WelcomeStr,
|
|
SearchPlaceholder: webApp.Settings.SearchPlaceholder,
|
|
RecommendQuestions: widgetApp.Settings.WidgetBotSettings.RecommendQuestions,
|
|
WidgetBotSettings: widgetApp.Settings.WidgetBotSettings,
|
|
},
|
|
}
|
|
if len(widgetApp.Settings.WidgetBotSettings.RecommendNodeIDs) > 0 {
|
|
nodes, err := u.nodeUsecase.GetRecommendNodeList(ctx, &domain.GetRecommendNodeListReq{
|
|
KBID: kbID,
|
|
NodeIDs: widgetApp.Settings.WidgetBotSettings.RecommendNodeIDs,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
appInfo.RecommendNodes = nodes
|
|
}
|
|
|
|
if !domain.GetBaseEditionLimitation(ctx).AllowCustomCopyright {
|
|
appInfo.Settings.WidgetBotSettings.CopyrightHideEnabled = false
|
|
appInfo.Settings.WidgetBotSettings.CopyrightInfo = domain.SettingCopyrightInfo
|
|
}
|
|
|
|
return appInfo, nil
|
|
}
|
|
|
|
func (u *AppUsecase) GetWechatAppInfo(ctx context.Context, kbID string) (*v1.WechatAppInfoResp, error) {
|
|
wechatApp, err := u.repo.GetOrCreateAppByKBIDAndType(ctx, kbID, domain.AppTypeWechatBot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp := &v1.WechatAppInfoResp{}
|
|
|
|
if wechatApp.Settings.WeChatAppIsEnabled != nil {
|
|
resp.WeChatAppIsEnabled = *wechatApp.Settings.WeChatAppIsEnabled
|
|
}
|
|
|
|
if domain.GetBaseEditionLimitation(ctx).AllowAdvancedBot {
|
|
resp.FeedbackEnable = wechatApp.Settings.WeChatAppAdvancedSetting.FeedbackEnable
|
|
resp.FeedbackType = wechatApp.Settings.WeChatAppAdvancedSetting.FeedbackType
|
|
resp.DisclaimerContent = wechatApp.Settings.WeChatAppAdvancedSetting.DisclaimerContent
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (u *AppUsecase) handleBotAuths(ctx context.Context, id string, newSettings *domain.AppSettings) error {
|
|
|
|
currentApp, err := u.repo.GetAppDetail(ctx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch currentApp.Type {
|
|
|
|
}
|
|
// Handle Widget Bot
|
|
if currentApp.Settings.WidgetBotSettings.IsOpen != newSettings.WidgetBotSettings.IsOpen {
|
|
if err := u.handleBotAuth(ctx, currentApp.KBID, currentApp.ID, ¤tApp.Settings.WidgetBotSettings.IsOpen,
|
|
&newSettings.WidgetBotSettings.IsOpen, consts.SourceTypeWidget); err != nil {
|
|
u.logger.Error("failed to handle widget auth", log.Error(err))
|
|
}
|
|
}
|
|
|
|
// Handle DingTalk Bot
|
|
if currentApp.Settings.DingTalkBotIsEnabled != newSettings.DingTalkBotIsEnabled {
|
|
if err := u.handleBotAuth(ctx, currentApp.KBID, currentApp.ID, currentApp.Settings.DingTalkBotIsEnabled,
|
|
newSettings.DingTalkBotIsEnabled, consts.SourceTypeDingtalkBot); err != nil {
|
|
u.logger.Error("failed to handle dingtalk bot auth", log.Error(err))
|
|
}
|
|
}
|
|
|
|
// Handle Feishu Bot
|
|
if currentApp.Settings.FeishuBotIsEnabled != newSettings.FeishuBotIsEnabled {
|
|
if err := u.handleBotAuth(ctx, currentApp.KBID, currentApp.ID, currentApp.Settings.FeishuBotIsEnabled,
|
|
newSettings.FeishuBotIsEnabled, consts.SourceTypeFeishuBot); err != nil {
|
|
u.logger.Error("failed to handle feishu bot auth", log.Error(err))
|
|
}
|
|
}
|
|
|
|
// Handle Lark Bot
|
|
if currentApp.Settings.LarkBotSettings.IsEnabled != newSettings.LarkBotSettings.IsEnabled {
|
|
if err := u.handleBotAuth(ctx, currentApp.KBID, currentApp.ID, currentApp.Settings.LarkBotSettings.IsEnabled,
|
|
newSettings.LarkBotSettings.IsEnabled, consts.SourceTypeLarkBot); err != nil {
|
|
u.logger.Error("failed to handle lark bot auth", log.Error(err))
|
|
}
|
|
}
|
|
|
|
// Handle WeChat Bot
|
|
if currentApp.Settings.WeChatAppIsEnabled != newSettings.WeChatAppIsEnabled {
|
|
if err := u.handleBotAuth(ctx, currentApp.KBID, currentApp.ID, currentApp.Settings.WeChatAppIsEnabled,
|
|
newSettings.WeChatAppIsEnabled, consts.SourceTypeWechatBot); err != nil {
|
|
u.logger.Error("failed to handle wechat bot auth", log.Error(err))
|
|
}
|
|
}
|
|
|
|
// Handle WeChat Service Bot
|
|
if currentApp.Settings.WeChatServiceIsEnabled != newSettings.WeChatServiceIsEnabled {
|
|
if err := u.handleBotAuth(ctx, currentApp.KBID, currentApp.ID, currentApp.Settings.WeChatServiceIsEnabled,
|
|
newSettings.WeChatServiceIsEnabled, consts.SourceTypeWechatServiceBot); err != nil {
|
|
u.logger.Error("failed to handle wechat service bot auth", log.Error(err))
|
|
}
|
|
}
|
|
|
|
// Handle Discord Bot
|
|
if currentApp.Settings.DiscordBotIsEnabled != newSettings.DiscordBotIsEnabled {
|
|
if err := u.handleBotAuth(ctx, currentApp.KBID, currentApp.ID, currentApp.Settings.DiscordBotIsEnabled,
|
|
newSettings.DiscordBotIsEnabled, consts.SourceTypeDiscordBot); err != nil {
|
|
u.logger.Error("failed to handle discord bot auth", log.Error(err))
|
|
}
|
|
}
|
|
|
|
// Handle WeChat Official Account
|
|
if currentApp.Settings.WechatOfficialAccountIsEnabled != newSettings.WechatOfficialAccountIsEnabled {
|
|
if err := u.handleBotAuth(ctx, currentApp.KBID, currentApp.ID, currentApp.Settings.WechatOfficialAccountIsEnabled,
|
|
newSettings.WechatOfficialAccountIsEnabled, consts.SourceTypeWechatOfficialAccount); err != nil {
|
|
u.logger.Error("failed to handle wechat official account auth", log.Error(err))
|
|
}
|
|
}
|
|
|
|
// Handle OpenAI API BOT Account
|
|
if currentApp.Settings.OpenAIAPIBotSettings.IsEnabled != newSettings.OpenAIAPIBotSettings.IsEnabled {
|
|
if err := u.handleBotAuth(ctx, currentApp.KBID, currentApp.ID, ¤tApp.Settings.OpenAIAPIBotSettings.IsEnabled,
|
|
&newSettings.OpenAIAPIBotSettings.IsEnabled, consts.SourceTypeOpenAIAPI); err != nil {
|
|
u.logger.Error("failed to handle openai api bot auth", log.Error(err))
|
|
}
|
|
}
|
|
|
|
// Handle Wecom AI Bot
|
|
if currentApp.Settings.WecomAIBotSettings.IsEnabled != newSettings.WecomAIBotSettings.IsEnabled {
|
|
if err := u.handleBotAuth(ctx, currentApp.KBID, currentApp.ID, ¤tApp.Settings.WecomAIBotSettings.IsEnabled,
|
|
&newSettings.WecomAIBotSettings.IsEnabled, consts.SourceTypeWecomAIBot); err != nil {
|
|
u.logger.Error("failed to handle wecom ai bot account auth", log.Error(err))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (u *AppUsecase) handleBotAuth(ctx context.Context, kbID, appId string, currentEnabled, newEnabled *bool, sourceType consts.SourceType) error {
|
|
wasEnabled := currentEnabled != nil && *currentEnabled
|
|
isEnabled := newEnabled != nil && *newEnabled
|
|
|
|
if !wasEnabled && isEnabled {
|
|
rdsKey := fmt.Sprintf("handleBotAuth:%s:%s", kbID, sourceType)
|
|
if !u.cache.AcquireLock(ctx, rdsKey) {
|
|
return fmt.Errorf("bot auth creation is in progress, please try again later")
|
|
}
|
|
defer u.cache.ReleaseLock(ctx, rdsKey)
|
|
|
|
existingAuth, _ := u.authRepo.GetAuthByKBIDAndSourceType(ctx, kbID, sourceType)
|
|
if existingAuth != nil {
|
|
return nil
|
|
}
|
|
|
|
auth := &domain.Auth{
|
|
KBID: kbID,
|
|
UnionID: fmt.Sprintf("bot_%s_%s", appId, sourceType),
|
|
SourceType: sourceType,
|
|
LastLoginTime: time.Now(),
|
|
UserInfo: domain.AuthUserInfo{
|
|
Username: sourceType.Name(),
|
|
},
|
|
}
|
|
|
|
if err := u.authRepo.CreateAuth(ctx, auth); err != nil {
|
|
return fmt.Errorf("failed to create auth for %s: %w", sourceType, err)
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (u *AppUsecase) GetOpenAIAPIAppInfo(ctx context.Context, kbID string) (*domain.AppInfoResp, error) {
|
|
apiApp, err := u.repo.GetOrCreateAppByKBIDAndType(ctx, kbID, domain.AppTypeOpenAIAPI)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
appInfo := &domain.AppInfoResp{
|
|
Settings: domain.AppSettingsResp{
|
|
OpenAIAPIBotSettings: apiApp.Settings.OpenAIAPIBotSettings,
|
|
},
|
|
}
|
|
return appInfo, nil
|
|
}
|
|
|
|
// filterNodesByPermissions 对节点列表进行权限过滤
|
|
func (u *AppUsecase) filterNodesByPermissions(nodes []*domain.RecommendNodeListResp, nodeVisibleGroupIds, nodeVisitableGroupIds []string) []*domain.RecommendNodeListResp {
|
|
filteredNodes := make([]*domain.RecommendNodeListResp, 0)
|
|
|
|
for i, node := range nodes {
|
|
// 处理 Visitable 权限
|
|
switch node.Permissions.Visitable {
|
|
case consts.NodeAccessPermClosed:
|
|
nodes[i].Summary = ""
|
|
case consts.NodeAccessPermPartial:
|
|
if !slices.Contains(nodeVisitableGroupIds, node.ID) {
|
|
nodes[i].Summary = ""
|
|
}
|
|
}
|
|
|
|
// 处理 Visible 权限
|
|
switch node.Permissions.Visible {
|
|
case consts.NodeAccessPermOpen:
|
|
filteredNodes = append(filteredNodes, nodes[i])
|
|
case consts.NodeAccessPermPartial:
|
|
if slices.Contains(nodeVisibleGroupIds, node.ID) {
|
|
filteredNodes = append(filteredNodes, nodes[i])
|
|
}
|
|
}
|
|
|
|
// 如果是文件夹类型,处理其子节点的权限
|
|
if node.Type == domain.NodeTypeFolder {
|
|
newFileNodes := make([]*domain.RecommendNodeListResp, 0)
|
|
|
|
for i2, recommendNode := range node.RecommendNodes {
|
|
node.RecommendNodes[i2].Summary = ""
|
|
switch recommendNode.Permissions.Visible {
|
|
case consts.NodeAccessPermOpen:
|
|
newFileNodes = append(newFileNodes, node.RecommendNodes[i2])
|
|
case consts.NodeAccessPermPartial:
|
|
if slices.Contains(nodeVisibleGroupIds, node.RecommendNodes[i2].ID) {
|
|
newFileNodes = append(newFileNodes, node.RecommendNodes[i2])
|
|
}
|
|
}
|
|
}
|
|
node.RecommendNodes = newFileNodes
|
|
}
|
|
}
|
|
|
|
return filteredNodes
|
|
}
|
|
|
|
func (u *AppUsecase) GetRecommendNodesByIds(ctx context.Context, kbId string, nodeIds []string, authId uint) ([]*domain.RecommendNodeListResp, error) {
|
|
nodes, err := u.nodeUsecase.GetRecommendNodeList(ctx, &domain.GetRecommendNodeListReq{
|
|
KBID: kbId,
|
|
NodeIDs: nodeIds,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nodeVisibleGroupIds, err := u.nodeUsecase.GetNodeIdsByAuthId(ctx, authId, consts.NodePermNameVisible)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nodeVisitableGroupIds, err := u.nodeUsecase.GetNodeIdsByAuthId(ctx, authId, consts.NodePermNameVisitable)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 使用抽象的权限过滤方法
|
|
return u.filterNodesByPermissions(nodes, nodeVisibleGroupIds, nodeVisitableGroupIds), nil
|
|
}
|
|
|
|
func (u *AppUsecase) GetRecommendNodesByNavIds(ctx context.Context, kbId string, navIds []string, authId uint) ([]*domain.RecommendNodeListResp, error) {
|
|
// 获取用户的可见和可访问权限节点列表
|
|
nodeVisibleGroupIds, err := u.nodeUsecase.GetNodeIdsByAuthId(ctx, authId, consts.NodePermNameVisible)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nodeVisitableGroupIds, err := u.nodeUsecase.GetNodeIdsByAuthId(ctx, authId, consts.NodePermNameVisitable)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
navList, err := u.navRepo.GetListByIds(ctx, kbId, navIds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 构建 navId -> navName 的 map
|
|
navMap := make(map[string]string)
|
|
for _, nav := range navList {
|
|
navMap[nav.ID] = nav.Name
|
|
}
|
|
|
|
allNodes, err := u.nodeUsecase.GetRecommendNodeList(ctx, &domain.GetRecommendNodeListReq{
|
|
KBID: kbId,
|
|
NavIds: navIds,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
filteredAll := u.filterNodesByPermissions(allNodes, nodeVisibleGroupIds, nodeVisitableGroupIds)
|
|
|
|
// 按 navId 分组,保持 navIds 的原始顺序
|
|
nodesByNav := make(map[string][]*domain.RecommendNodeListResp, len(navIds))
|
|
for _, node := range filteredAll {
|
|
nodesByNav[node.NavId] = append(nodesByNav[node.NavId], node)
|
|
}
|
|
|
|
recommendNodes := make([]*domain.RecommendNodeListResp, 0, len(navIds))
|
|
for _, navId := range navIds {
|
|
recommendNodes = append(recommendNodes, &domain.RecommendNodeListResp{
|
|
NavId: navId,
|
|
NavName: navMap[navId],
|
|
RecommendNodes: nodesByNav[navId],
|
|
})
|
|
}
|
|
|
|
return recommendNodes, nil
|
|
}
|