mirror of https://github.com/chaitin/PandaWiki.git
Compare commits
7 Commits
ca323de9b2
...
63a4a964b1
| Author | SHA1 | Date |
|---|---|---|
|
|
63a4a964b1 | |
|
|
62f2b2eaf5 | |
|
|
5c1c6368b8 | |
|
|
7282503acf | |
|
|
60a4177229 | |
|
|
2f706a6100 | |
|
|
febcb06654 |
|
|
@ -1,8 +1,6 @@
|
||||||
package consts
|
package consts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -14,27 +12,12 @@ type LicenseEdition int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LicenseEditionFree LicenseEdition = 0 // 开源版
|
LicenseEditionFree LicenseEdition = 0 // 开源版
|
||||||
LicenseEditionContributor LicenseEdition = 1 // 联创版
|
LicenseEditionProfession LicenseEdition = 1 // 专业版
|
||||||
LicenseEditionEnterprise LicenseEdition = 2 // 企业版
|
LicenseEditionEnterprise LicenseEdition = 2 // 企业版
|
||||||
|
LicenseEditionBusiness LicenseEdition = 3 // 商业版
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetLicenseEdition(c echo.Context) LicenseEdition {
|
func GetLicenseEdition(c echo.Context) LicenseEdition {
|
||||||
edition, _ := c.Get("edition").(LicenseEdition)
|
edition, _ := c.Get("edition").(LicenseEdition)
|
||||||
return edition
|
return edition
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e LicenseEdition) GetMaxAuth(sourceType SourceType) int {
|
|
||||||
switch e {
|
|
||||||
case LicenseEditionFree:
|
|
||||||
if sourceType == SourceTypeGitHub {
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
case LicenseEditionContributor:
|
|
||||||
return 10
|
|
||||||
case LicenseEditionEnterprise:
|
|
||||||
return math.MaxInt
|
|
||||||
default:
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4067,22 +4067,26 @@ const docTemplate = `{
|
||||||
"enum": [
|
"enum": [
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
2
|
2,
|
||||||
|
3
|
||||||
],
|
],
|
||||||
"x-enum-comments": {
|
"x-enum-comments": {
|
||||||
"LicenseEditionContributor": "联创版",
|
"LicenseEditionBusiness": "商业版",
|
||||||
"LicenseEditionEnterprise": "企业版",
|
"LicenseEditionEnterprise": "企业版",
|
||||||
"LicenseEditionFree": "开源版"
|
"LicenseEditionFree": "开源版",
|
||||||
|
"LicenseEditionProfession": "专业版"
|
||||||
},
|
},
|
||||||
"x-enum-descriptions": [
|
"x-enum-descriptions": [
|
||||||
"开源版",
|
"开源版",
|
||||||
"联创版",
|
"专业版",
|
||||||
"企业版"
|
"企业版",
|
||||||
|
"商业版"
|
||||||
],
|
],
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
"LicenseEditionFree",
|
"LicenseEditionFree",
|
||||||
"LicenseEditionContributor",
|
"LicenseEditionProfession",
|
||||||
"LicenseEditionEnterprise"
|
"LicenseEditionEnterprise",
|
||||||
|
"LicenseEditionBusiness"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"consts.ModelSettingMode": {
|
"consts.ModelSettingMode": {
|
||||||
|
|
|
||||||
|
|
@ -4060,22 +4060,26 @@
|
||||||
"enum": [
|
"enum": [
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
2
|
2,
|
||||||
|
3
|
||||||
],
|
],
|
||||||
"x-enum-comments": {
|
"x-enum-comments": {
|
||||||
"LicenseEditionContributor": "联创版",
|
"LicenseEditionBusiness": "商业版",
|
||||||
"LicenseEditionEnterprise": "企业版",
|
"LicenseEditionEnterprise": "企业版",
|
||||||
"LicenseEditionFree": "开源版"
|
"LicenseEditionFree": "开源版",
|
||||||
|
"LicenseEditionProfession": "专业版"
|
||||||
},
|
},
|
||||||
"x-enum-descriptions": [
|
"x-enum-descriptions": [
|
||||||
"开源版",
|
"开源版",
|
||||||
"联创版",
|
"专业版",
|
||||||
"企业版"
|
"企业版",
|
||||||
|
"商业版"
|
||||||
],
|
],
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
"LicenseEditionFree",
|
"LicenseEditionFree",
|
||||||
"LicenseEditionContributor",
|
"LicenseEditionProfession",
|
||||||
"LicenseEditionEnterprise"
|
"LicenseEditionEnterprise",
|
||||||
|
"LicenseEditionBusiness"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"consts.ModelSettingMode": {
|
"consts.ModelSettingMode": {
|
||||||
|
|
|
||||||
|
|
@ -117,20 +117,24 @@ definitions:
|
||||||
- 0
|
- 0
|
||||||
- 1
|
- 1
|
||||||
- 2
|
- 2
|
||||||
|
- 3
|
||||||
format: int32
|
format: int32
|
||||||
type: integer
|
type: integer
|
||||||
x-enum-comments:
|
x-enum-comments:
|
||||||
LicenseEditionContributor: 联创版
|
LicenseEditionBusiness: 商业版
|
||||||
LicenseEditionEnterprise: 企业版
|
LicenseEditionEnterprise: 企业版
|
||||||
LicenseEditionFree: 开源版
|
LicenseEditionFree: 开源版
|
||||||
|
LicenseEditionProfession: 专业版
|
||||||
x-enum-descriptions:
|
x-enum-descriptions:
|
||||||
- 开源版
|
- 开源版
|
||||||
- 联创版
|
- 专业版
|
||||||
- 企业版
|
- 企业版
|
||||||
|
- 商业版
|
||||||
x-enum-varnames:
|
x-enum-varnames:
|
||||||
- LicenseEditionFree
|
- LicenseEditionFree
|
||||||
- LicenseEditionContributor
|
- LicenseEditionProfession
|
||||||
- LicenseEditionEnterprise
|
- LicenseEditionEnterprise
|
||||||
|
- LicenseEditionBusiness
|
||||||
consts.ModelSettingMode:
|
consts.ModelSettingMode:
|
||||||
enum:
|
enum:
|
||||||
- manual
|
- manual
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ContextKeyEditionLimitation contextKey = "edition_limitation"
|
||||||
|
|
||||||
|
type BaseEditionLimitation struct {
|
||||||
|
MaxKb int `json:"max_kb"` // 知识库站点数量
|
||||||
|
MaxNode int `json:"max_node"` // 单个知识库下文档数量
|
||||||
|
MaxSSOUser int `json:"max_sso_users"` // SSO认证用户数量
|
||||||
|
MaxAdmin int64 `json:"max_admin"` // 后台管理员数量
|
||||||
|
AllowAdminPerm bool `json:"allow_admin_perm"` // 支持管理员分权控制
|
||||||
|
AllowCustomCopyright bool `json:"allow_custom_copyright"` // 支持自定义版权信息
|
||||||
|
AllowCommentAudit bool `json:"allow_comment_audit"` // 支持评论审核
|
||||||
|
AllowAdvancedBot bool `json:"allow_advanced_bot"` // 支持高级机器人配置
|
||||||
|
AllowWatermark bool `json:"allow_watermark"` // 支持水印
|
||||||
|
AllowCopyProtection bool `json:"allow_copy_protection"` // 支持内容复制保护
|
||||||
|
AllowOpenAIBotSettings bool `json:"allow_open_ai_bot_settings"` // 支持问答机器人
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseEditionLimitationDefault = BaseEditionLimitation{
|
||||||
|
MaxKb: 1,
|
||||||
|
MaxAdmin: 1,
|
||||||
|
MaxNode: 300,
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBaseEditionLimitation(c context.Context) BaseEditionLimitation {
|
||||||
|
|
||||||
|
edition, ok := c.Value(ContextKeyEditionLimitation).([]byte)
|
||||||
|
if !ok {
|
||||||
|
return baseEditionLimitationDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
var editionLimitation BaseEditionLimitation
|
||||||
|
if err := json.Unmarshal(edition, &editionLimitation); err != nil {
|
||||||
|
return baseEditionLimitationDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
return editionLimitation
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,6 @@ import (
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
|
||||||
"github.com/chaitin/panda-wiki/consts"
|
|
||||||
"github.com/chaitin/panda-wiki/domain"
|
"github.com/chaitin/panda-wiki/domain"
|
||||||
"github.com/chaitin/panda-wiki/handler"
|
"github.com/chaitin/panda-wiki/handler"
|
||||||
"github.com/chaitin/panda-wiki/log"
|
"github.com/chaitin/panda-wiki/log"
|
||||||
|
|
@ -157,7 +156,7 @@ func (h *ShareCommentHandler) GetCommentList(c echo.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询数据库获取所有评论-->0 所有, 1,2 为需要审核的评论
|
// 查询数据库获取所有评论-->0 所有, 1,2 为需要审核的评论
|
||||||
commentsList, err := h.usecase.GetCommentListByNodeID(ctx, nodeID, consts.GetLicenseEdition(c))
|
commentsList, err := h.usecase.GetCommentListByNodeID(ctx, nodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return h.NewResponseWithError(c, "failed to get comment list", err)
|
return h.NewResponseWithError(c, "failed to get comment list", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
v1 "github.com/chaitin/panda-wiki/api/kb/v1"
|
v1 "github.com/chaitin/panda-wiki/api/kb/v1"
|
||||||
"github.com/chaitin/panda-wiki/consts"
|
"github.com/chaitin/panda-wiki/consts"
|
||||||
|
"github.com/chaitin/panda-wiki/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
// KBUserList
|
// KBUserList
|
||||||
|
|
@ -55,8 +56,8 @@ func (h *KnowledgeBaseHandler) KBUserInvite(c echo.Context) error {
|
||||||
return h.NewResponseWithError(c, "validate request failed", err)
|
return h.NewResponseWithError(c, "validate request failed", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if consts.GetLicenseEdition(c) != consts.LicenseEditionEnterprise && req.Perm != consts.UserKBPermissionFullControl {
|
if !domain.GetBaseEditionLimitation(c.Request().Context()).AllowAdminPerm && req.Perm != consts.UserKBPermissionFullControl {
|
||||||
return h.NewResponseWithError(c, "非企业版本只能使用完全控制权限", nil)
|
return h.NewResponseWithError(c, "当前版本不支持管理员分权控制", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.usecase.KBUserInvite(c.Request().Context(), req)
|
err := h.usecase.KBUserInvite(c.Request().Context(), req)
|
||||||
|
|
@ -87,8 +88,8 @@ func (h *KnowledgeBaseHandler) KBUserUpdate(c echo.Context) error {
|
||||||
return h.NewResponseWithError(c, "validate request failed", err)
|
return h.NewResponseWithError(c, "validate request failed", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if consts.GetLicenseEdition(c) != consts.LicenseEditionEnterprise && req.Perm != consts.UserKBPermissionFullControl {
|
if !domain.GetBaseEditionLimitation(c.Request().Context()).AllowAdminPerm && req.Perm != consts.UserKBPermissionFullControl {
|
||||||
return h.NewResponseWithError(c, "非企业版本只能使用完全控制权限", nil)
|
return h.NewResponseWithError(c, "当前版本不支持管理员分权控制", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.usecase.UpdateUserKB(c.Request().Context(), req)
|
err := h.usecase.UpdateUserKB(c.Request().Context(), req)
|
||||||
|
|
|
||||||
|
|
@ -91,11 +91,7 @@ func (h *KnowledgeBaseHandler) CreateKnowledgeBase(c echo.Context) error {
|
||||||
return h.NewResponseWithError(c, "ports is required", nil)
|
return h.NewResponseWithError(c, "ports is required", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.MaxKB = 1
|
req.MaxKB = domain.GetBaseEditionLimitation(c.Request().Context()).MaxKb
|
||||||
maxKB := c.Get("max_kb")
|
|
||||||
if maxKB != nil {
|
|
||||||
req.MaxKB = maxKB.(int)
|
|
||||||
}
|
|
||||||
|
|
||||||
did, err := h.usecase.CreateKnowledgeBase(c.Request().Context(), &req)
|
did, err := h.usecase.CreateKnowledgeBase(c.Request().Context(), &req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ func NewModelHandler(echo *echo.Echo, baseHandler *handler.BaseHandler, logger *
|
||||||
return handler
|
return handler
|
||||||
}
|
}
|
||||||
|
|
||||||
// get model list
|
// GetModelList
|
||||||
//
|
//
|
||||||
// @Summary get model list
|
// @Summary get model list
|
||||||
// @Description get model list
|
// @Description get model list
|
||||||
|
|
@ -66,7 +66,7 @@ func (h *ModelHandler) GetModelList(c echo.Context) error {
|
||||||
return h.NewResponseWithData(c, models)
|
return h.NewResponseWithData(c, models)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create model
|
// CreateModel
|
||||||
//
|
//
|
||||||
// @Summary create model
|
// @Summary create model
|
||||||
// @Description create model
|
// @Description create model
|
||||||
|
|
@ -85,9 +85,6 @@ func (h *ModelHandler) CreateModel(c echo.Context) error {
|
||||||
return h.NewResponseWithError(c, "invalid request", err)
|
return h.NewResponseWithError(c, "invalid request", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if consts.GetLicenseEdition(c) == consts.LicenseEditionContributor && req.Provider != domain.ModelProviderBrandBaiZhiCloud {
|
|
||||||
return h.NewResponseWithError(c, "联创版只能使用百智云模型哦~", nil)
|
|
||||||
}
|
|
||||||
ctx := c.Request().Context()
|
ctx := c.Request().Context()
|
||||||
|
|
||||||
param := domain.ModelParam{}
|
param := domain.ModelParam{}
|
||||||
|
|
@ -112,7 +109,7 @@ func (h *ModelHandler) CreateModel(c echo.Context) error {
|
||||||
return h.NewResponseWithData(c, model)
|
return h.NewResponseWithData(c, model)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update model
|
// UpdateModel
|
||||||
//
|
//
|
||||||
// @Description update model
|
// @Description update model
|
||||||
// @Tags model
|
// @Tags model
|
||||||
|
|
@ -130,9 +127,6 @@ func (h *ModelHandler) UpdateModel(c echo.Context) error {
|
||||||
return h.NewResponseWithError(c, "invalid request", err)
|
return h.NewResponseWithError(c, "invalid request", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if consts.GetLicenseEdition(c) == consts.LicenseEditionContributor && req.Provider != domain.ModelProviderBrandBaiZhiCloud {
|
|
||||||
return h.NewResponseWithError(c, "联创版只能使用百智云模型哦~", nil)
|
|
||||||
}
|
|
||||||
ctx := c.Request().Context()
|
ctx := c.Request().Context()
|
||||||
if err := h.usecase.Update(ctx, &req); err != nil {
|
if err := h.usecase.Update(ctx, &req); err != nil {
|
||||||
return h.NewResponseWithError(c, "update model failed", err)
|
return h.NewResponseWithError(c, "update model failed", err)
|
||||||
|
|
@ -140,7 +134,7 @@ func (h *ModelHandler) UpdateModel(c echo.Context) error {
|
||||||
return h.NewResponseWithData(c, nil)
|
return h.NewResponseWithData(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check model
|
// CheckModel
|
||||||
//
|
//
|
||||||
// @Summary check model
|
// @Summary check model
|
||||||
// @Description check model
|
// @Description check model
|
||||||
|
|
|
||||||
|
|
@ -81,15 +81,13 @@ func (h *NodeHandler) CreateNode(c echo.Context) error {
|
||||||
if err := c.Validate(req); err != nil {
|
if err := c.Validate(req); err != nil {
|
||||||
return h.NewResponseWithError(c, "validate request body failed", err)
|
return h.NewResponseWithError(c, "validate request body failed", err)
|
||||||
}
|
}
|
||||||
req.MaxNode = 300
|
|
||||||
if maxNode := c.Get("max_node"); maxNode != nil {
|
req.MaxNode = domain.GetBaseEditionLimitation(ctx).MaxNode
|
||||||
req.MaxNode = maxNode.(int)
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := h.usecase.Create(c.Request().Context(), req, authInfo.UserId)
|
id, err := h.usecase.Create(c.Request().Context(), req, authInfo.UserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, domain.ErrMaxNodeLimitReached) {
|
if errors.Is(err, domain.ErrMaxNodeLimitReached) {
|
||||||
return h.NewResponseWithError(c, "已达到最大文档数量限制,请升级到联创版或企业版", nil)
|
return h.NewResponseWithError(c, "已达到最大文档数量限制,请升级到更高版本", nil)
|
||||||
}
|
}
|
||||||
return h.NewResponseWithError(c, "create node failed", err)
|
return h.NewResponseWithError(c, "create node failed", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ type AuthMiddleware interface {
|
||||||
Authorize(next echo.HandlerFunc) echo.HandlerFunc
|
Authorize(next echo.HandlerFunc) echo.HandlerFunc
|
||||||
ValidateUserRole(role consts.UserRole) echo.MiddlewareFunc
|
ValidateUserRole(role consts.UserRole) echo.MiddlewareFunc
|
||||||
ValidateKBUserPerm(role consts.UserKBPermission) echo.MiddlewareFunc
|
ValidateKBUserPerm(role consts.UserKBPermission) echo.MiddlewareFunc
|
||||||
ValidateLicenseEdition(edition consts.LicenseEdition) echo.MiddlewareFunc
|
ValidateLicenseEdition(edition ...consts.LicenseEdition) echo.MiddlewareFunc
|
||||||
MustGetUserID(c echo.Context) (string, bool)
|
MustGetUserID(c echo.Context) (string, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
|
@ -194,7 +195,7 @@ func (m *JWTMiddleware) ValidateKBUserPerm(perm consts.UserKBPermission) echo.Mi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *JWTMiddleware) ValidateLicenseEdition(needEdition consts.LicenseEdition) echo.MiddlewareFunc {
|
func (m *JWTMiddleware) ValidateLicenseEdition(needEditions ...consts.LicenseEdition) echo.MiddlewareFunc {
|
||||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
|
|
||||||
|
|
@ -206,7 +207,7 @@ func (m *JWTMiddleware) ValidateLicenseEdition(needEdition consts.LicenseEdition
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if edition < needEdition {
|
if !slices.Contains(needEditions, edition) {
|
||||||
return c.JSON(http.StatusForbidden, domain.PWResponse{
|
return c.JSON(http.StatusForbidden, domain.PWResponse{
|
||||||
Success: false,
|
Success: false,
|
||||||
Message: "Unauthorized ValidateLicenseEdition",
|
Message: "Unauthorized ValidateLicenseEdition",
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit c4dc498df094cb617d31c95580db8239a445d652
|
Subproject commit bcf7e0f0bedb18f43cf36463ddb45ace6c1dbab9
|
||||||
|
|
@ -300,8 +300,8 @@ func (r *AuthRepo) GetOrCreateAuth(ctx context.Context, auth *domain.Auth, sourc
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if int(count) >= licenseEdition.GetMaxAuth(sourceType) {
|
if int(count) >= domain.GetBaseEditionLimitation(ctx).MaxSSOUser {
|
||||||
return fmt.Errorf("exceed max auth limit for kb %s, current count: %d, max limit: %d", auth.KBID, count, licenseEdition.GetMaxAuth(sourceType))
|
return fmt.Errorf("exceed max auth limit for kb %s, current count: %d, max limit: %d", auth.KBID, count, domain.GetBaseEditionLimitation(ctx).MaxSSOUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
auth.LastLoginTime = time.Now()
|
auth.LastLoginTime = time.Now()
|
||||||
|
|
|
||||||
|
|
@ -26,12 +26,12 @@ func (r *CommentRepository) CreateComment(ctx context.Context, comment *domain.C
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *CommentRepository) GetCommentList(ctx context.Context, nodeID string, edition consts.LicenseEdition) ([]*domain.ShareCommentListItem, int64, error) {
|
func (r *CommentRepository) GetCommentList(ctx context.Context, nodeID string) ([]*domain.ShareCommentListItem, int64, error) {
|
||||||
// 按照时间排序来查询node_id的comments
|
// 按照时间排序来查询node_id的comments
|
||||||
var comments []*domain.ShareCommentListItem
|
var comments []*domain.ShareCommentListItem
|
||||||
query := r.db.WithContext(ctx).Model(&domain.Comment{}).Where("node_id = ?", nodeID)
|
query := r.db.WithContext(ctx).Model(&domain.Comment{}).Where("node_id = ?", nodeID)
|
||||||
|
|
||||||
if edition == consts.LicenseEditionContributor || edition == consts.LicenseEditionEnterprise {
|
if domain.GetBaseEditionLimitation(ctx).AllowCommentAudit {
|
||||||
query = query.Where("status = ?", domain.CommentStatusAccepted) //accepted
|
query = query.Where("status = ?", domain.CommentStatusAccepted) //accepted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,14 +50,14 @@ func (r *CommentRepository) GetCommentList(ctx context.Context, nodeID string, e
|
||||||
|
|
||||||
func (r *CommentRepository) GetCommentListByKbID(ctx context.Context, req *domain.CommentListReq, edition consts.LicenseEdition) ([]*domain.CommentListItem, int64, error) {
|
func (r *CommentRepository) GetCommentListByKbID(ctx context.Context, req *domain.CommentListReq, edition consts.LicenseEdition) ([]*domain.CommentListItem, int64, error) {
|
||||||
comments := []*domain.CommentListItem{}
|
comments := []*domain.CommentListItem{}
|
||||||
query := r.db.Model(&domain.Comment{}).Where("comments.kb_id = ?", req.KbID)
|
query := r.db.WithContext(ctx).Model(&domain.Comment{}).Where("comments.kb_id = ?", req.KbID)
|
||||||
var count int64
|
var count int64
|
||||||
if req.Status == nil {
|
if req.Status == nil {
|
||||||
if err := query.Count(&count).Error; err != nil {
|
if err := query.Count(&count).Error; err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if edition == consts.LicenseEditionContributor || edition == consts.LicenseEditionEnterprise {
|
if domain.GetBaseEditionLimitation(ctx).AllowCommentAudit {
|
||||||
query = query.Where("comments.status = ?", *req.Status)
|
query = query.Where("comments.status = ?", *req.Status)
|
||||||
}
|
}
|
||||||
// 按照时间排序来查询kb_id的comments ->reject pending accepted
|
// 按照时间排序来查询kb_id的comments ->reject pending accepted
|
||||||
|
|
@ -84,7 +84,7 @@ func (r *CommentRepository) GetCommentListByKbID(ctx context.Context, req *domai
|
||||||
|
|
||||||
func (r *CommentRepository) DeleteCommentList(ctx context.Context, commentID []string) error {
|
func (r *CommentRepository) DeleteCommentList(ctx context.Context, commentID []string) error {
|
||||||
// 批量删除指定id的comment,获取删除的总的数量、
|
// 批量删除指定id的comment,获取删除的总的数量、
|
||||||
query := r.db.Model(&domain.Comment{}).Where("id IN (?)", commentID)
|
query := r.db.WithContext(ctx).Model(&domain.Comment{}).Where("id IN (?)", commentID)
|
||||||
|
|
||||||
if err := query.Delete(&domain.Comment{}).Error; err != nil {
|
if err := query.Delete(&domain.Comment{}).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -344,6 +344,7 @@ func (r *KnowledgeBaseRepository) CreateKnowledgeBase(ctx context.Context, maxKB
|
||||||
Title: kb.Name,
|
Title: kb.Name,
|
||||||
Desc: kb.Name,
|
Desc: kb.Name,
|
||||||
Keyword: kb.Name,
|
Keyword: kb.Name,
|
||||||
|
AutoSitemap: true,
|
||||||
Icon: domain.DefaultPandaWikiIconB64,
|
Icon: domain.DefaultPandaWikiIconB64,
|
||||||
WelcomeStr: fmt.Sprintf("欢迎使用%s", kb.Name),
|
WelcomeStr: fmt.Sprintf("欢迎使用%s", kb.Name),
|
||||||
Btns: []any{
|
Btns: []any{
|
||||||
|
|
|
||||||
|
|
@ -683,7 +683,7 @@ func (r *NodeRepository) GetNodeReleaseListByKBID(ctx context.Context, kbID stri
|
||||||
Where("kb_release_node_releases.kb_id = ?", kbID).
|
Where("kb_release_node_releases.kb_id = ?", kbID).
|
||||||
Where("kb_release_node_releases.release_id = ?", kbRelease.ID).
|
Where("kb_release_node_releases.release_id = ?", kbRelease.ID).
|
||||||
Where("nodes.permissions->>'visible' != ?", consts.NodeAccessPermClosed).
|
Where("nodes.permissions->>'visible' != ?", consts.NodeAccessPermClosed).
|
||||||
Select("node_releases.node_id as id, node_releases.name, node_releases.type, node_releases.parent_id, node_releases.position, node_releases.meta->>'emoji' as emoji, node_releases.updated_at, nodes.permissions").
|
Select("node_releases.node_id as id, node_releases.name, node_releases.type, node_releases.parent_id, nodes.position, node_releases.meta->>'emoji' as emoji, node_releases.updated_at, nodes.permissions").
|
||||||
Find(&nodes).Error; err != nil {
|
Find(&nodes).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,18 +60,14 @@ func (r *UserRepository) CreateUser(ctx context.Context, user *domain.User, edit
|
||||||
}
|
}
|
||||||
user.Password = string(hashedPassword)
|
user.Password = string(hashedPassword)
|
||||||
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
if edition == consts.LicenseEditionContributor || edition == consts.LicenseEditionFree {
|
|
||||||
var count int64
|
var count int64
|
||||||
if err := tx.Model(&domain.User{}).Count(&count).Error; err != nil {
|
if err := tx.Model(&domain.User{}).Count(&count).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if edition == consts.LicenseEditionFree && count >= 1 {
|
if count >= domain.GetBaseEditionLimitation(ctx).MaxAdmin {
|
||||||
return errors.New("free edition only allows 1 user")
|
return fmt.Errorf("exceed max admin limit, current count: %d, max limit: %d", count, domain.GetBaseEditionLimitation(ctx).MaxAdmin)
|
||||||
}
|
|
||||||
if edition == consts.LicenseEditionContributor && count >= 5 {
|
|
||||||
return errors.New("contributor edition only allows 5 user")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Create(user).Error; err != nil {
|
if err := tx.Create(user).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,34 +88,38 @@ func NewAppUsecase(
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *AppUsecase) ValidateUpdateApp(ctx context.Context, id string, req *domain.UpdateAppReq, edition consts.LicenseEdition) error {
|
func (u *AppUsecase) ValidateUpdateApp(ctx context.Context, id string, req *domain.UpdateAppReq, edition consts.LicenseEdition) error {
|
||||||
switch edition {
|
|
||||||
case consts.LicenseEditionFree:
|
|
||||||
app, err := u.repo.GetAppDetail(ctx, id)
|
app, err := u.repo.GetAppDetail(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if app.Settings.WatermarkContent != req.Settings.WatermarkContent ||
|
|
||||||
app.Settings.WatermarkSetting != req.Settings.WatermarkSetting ||
|
|
||||||
app.Settings.ContributeSettings != req.Settings.ContributeSettings ||
|
|
||||||
app.Settings.CopySetting != req.Settings.CopySetting {
|
|
||||||
return domain.ErrPermissionDenied
|
|
||||||
}
|
|
||||||
case consts.LicenseEditionContributor:
|
|
||||||
app, err := u.repo.GetAppDetail(ctx, id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if app.Settings.WatermarkContent != req.Settings.WatermarkContent ||
|
|
||||||
app.Settings.WatermarkSetting != req.Settings.WatermarkSetting ||
|
|
||||||
app.Settings.CopySetting != req.Settings.CopySetting {
|
|
||||||
return domain.ErrPermissionDenied
|
|
||||||
}
|
|
||||||
case consts.LicenseEditionEnterprise:
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported license type: %d", edition)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return domain.ErrPermissionDenied
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -618,8 +622,8 @@ func (u *AppUsecase) ShareGetWebAppInfo(ctx context.Context, kbID string, authId
|
||||||
}
|
}
|
||||||
showBrand := true
|
showBrand := true
|
||||||
defaultDisclaimer := "本回答由 PandaWiki 基于 AI 生成,仅供参考。"
|
defaultDisclaimer := "本回答由 PandaWiki 基于 AI 生成,仅供参考。"
|
||||||
licenseEdition, _ := ctx.Value(consts.ContextKeyEdition).(consts.LicenseEdition)
|
|
||||||
if licenseEdition < consts.LicenseEditionEnterprise {
|
if !domain.GetBaseEditionLimitation(ctx).AllowCustomCopyright {
|
||||||
appInfo.Settings.WebAppCustomSettings.ShowBrandInfo = &showBrand
|
appInfo.Settings.WebAppCustomSettings.ShowBrandInfo = &showBrand
|
||||||
appInfo.Settings.DisclaimerSettings.Content = &defaultDisclaimer
|
appInfo.Settings.DisclaimerSettings.Content = &defaultDisclaimer
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -72,8 +72,8 @@ func (u *CommentUsecase) CreateComment(ctx context.Context, commentReq *domain.C
|
||||||
return CommentStr, nil
|
return CommentStr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *CommentUsecase) GetCommentListByNodeID(ctx context.Context, nodeID string, edition consts.LicenseEdition) (*domain.PaginatedResult[[]*domain.ShareCommentListItem], error) {
|
func (u *CommentUsecase) GetCommentListByNodeID(ctx context.Context, nodeID string) (*domain.PaginatedResult[[]*domain.ShareCommentListItem], error) {
|
||||||
comments, total, err := u.CommentRepo.GetCommentList(ctx, nodeID, edition)
|
comments, total, err := u.CommentRepo.GetCommentList(ctx, nodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -407,7 +407,7 @@ func (u *NodeUsecase) GetNodePermissionsByID(ctx context.Context, id, kbID strin
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *NodeUsecase) ValidateNodePermissionsEdit(req v1.NodePermissionEditReq, edition consts.LicenseEdition) error {
|
func (u *NodeUsecase) ValidateNodePermissionsEdit(req v1.NodePermissionEditReq, edition consts.LicenseEdition) error {
|
||||||
if edition != consts.LicenseEditionEnterprise {
|
if !slices.Contains([]consts.LicenseEdition{consts.LicenseEditionBusiness, consts.LicenseEditionEnterprise}, edition) {
|
||||||
if req.Permissions.Answerable == consts.NodeAccessPermPartial || req.Permissions.Visitable == consts.NodeAccessPermPartial || req.Permissions.Visible == consts.NodeAccessPermPartial {
|
if req.Permissions.Answerable == consts.NodeAccessPermPartial || req.Permissions.Visitable == consts.NodeAccessPermPartial || req.Permissions.Visible == consts.NodeAccessPermPartial {
|
||||||
return domain.ErrPermissionDenied
|
return domain.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
|
@ -67,12 +68,12 @@ func (u *StatUseCase) ValidateStatDay(statDay consts.StatDay, edition consts.Lic
|
||||||
case consts.StatDay1:
|
case consts.StatDay1:
|
||||||
return nil
|
return nil
|
||||||
case consts.StatDay7:
|
case consts.StatDay7:
|
||||||
if edition < consts.LicenseEditionContributor {
|
if edition == consts.LicenseEditionFree {
|
||||||
return domain.ErrPermissionDenied
|
return domain.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case consts.StatDay30, consts.StatDay90:
|
case consts.StatDay30, consts.StatDay90:
|
||||||
if edition < consts.LicenseEditionEnterprise {
|
if !slices.Contains([]consts.LicenseEdition{consts.LicenseEditionBusiness, consts.LicenseEditionEnterprise}, edition) {
|
||||||
return domain.ErrPermissionDenied
|
return domain.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
|
|
@ -144,7 +144,7 @@ const Config: React.FC<ConfigProps> = ({ setIsEdit, id }) => {
|
||||||
callback();
|
callback();
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [subscribe]);
|
}, [subscribe, appPreviewData, id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledCommonWrapper>
|
<StyledCommonWrapper>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ import { setAppPreviewData } from '@/store/slices/config';
|
||||||
import { DomainSocialMediaAccount } from '@/request/types';
|
import { DomainSocialMediaAccount } from '@/request/types';
|
||||||
import Switch from '../basicComponents/Switch';
|
import Switch from '../basicComponents/Switch';
|
||||||
import DragSocialInfo from '../basicComponents/DragSocialInfo';
|
import DragSocialInfo from '../basicComponents/DragSocialInfo';
|
||||||
|
import VersionMask from '@/components/VersionMask';
|
||||||
|
import { PROFESSION_VERSION_PERMISSION } from '@/constant/version';
|
||||||
|
|
||||||
interface FooterConfigProps {
|
interface FooterConfigProps {
|
||||||
data?: AppDetail | null;
|
data?: AppDetail | null;
|
||||||
|
|
@ -75,9 +77,6 @@ const FooterConfig = ({ data, setIsEdit, isEdit }: FooterConfigProps) => {
|
||||||
);
|
);
|
||||||
const footer_show_intro = watch('footer_show_intro');
|
const footer_show_intro = watch('footer_show_intro');
|
||||||
|
|
||||||
const isEnterprise = useMemo(() => {
|
|
||||||
return license.edition === 2;
|
|
||||||
}, [license]);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isEdit && appPreviewData) {
|
if (isEdit && appPreviewData) {
|
||||||
setValue(
|
setValue(
|
||||||
|
|
@ -506,7 +505,7 @@ const FooterConfig = ({ data, setIsEdit, isEdit }: FooterConfigProps) => {
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
{isEnterprise && (
|
|
||||||
<Stack direction={'column'} gap={2}>
|
<Stack direction={'column'} gap={2}>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
|
@ -529,6 +528,10 @@ const FooterConfig = ({ data, setIsEdit, isEdit }: FooterConfigProps) => {
|
||||||
>
|
>
|
||||||
PandaWiki 版权信息
|
PandaWiki 版权信息
|
||||||
</Box>
|
</Box>
|
||||||
|
<VersionMask
|
||||||
|
permission={PROFESSION_VERSION_PERMISSION}
|
||||||
|
sx={{ inset: '-8px 0' }}
|
||||||
|
>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name='show_brand_info'
|
name='show_brand_info'
|
||||||
|
|
@ -548,7 +551,6 @@ const FooterConfig = ({ data, setIsEdit, isEdit }: FooterConfigProps) => {
|
||||||
<Switch
|
<Switch
|
||||||
sx={{ marginLeft: 'auto' }}
|
sx={{ marginLeft: 'auto' }}
|
||||||
{...field}
|
{...field}
|
||||||
disabled={!isEnterprise}
|
|
||||||
checked={field?.value === false ? false : true}
|
checked={field?.value === false ? false : true}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
field.onChange(e.target.checked);
|
field.onChange(e.target.checked);
|
||||||
|
|
@ -558,8 +560,8 @@ const FooterConfig = ({ data, setIsEdit, isEdit }: FooterConfigProps) => {
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</VersionMask>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { KnowledgeBaseListItem } from '@/api';
|
import { KnowledgeBaseListItem } from '@/api';
|
||||||
import { useURLSearchParams } from '@/hooks';
|
import { useURLSearchParams } from '@/hooks';
|
||||||
|
import { useFeatureValue } from '@/hooks';
|
||||||
import { ConstsUserRole } from '@/request/types';
|
import { ConstsUserRole } from '@/request/types';
|
||||||
import { useAppDispatch, useAppSelector } from '@/store';
|
import { useAppDispatch, useAppSelector } from '@/store';
|
||||||
import { setKbC, setKbId } from '@/store/slices/config';
|
import { setKbC, setKbId } from '@/store/slices/config';
|
||||||
|
|
@ -23,14 +24,14 @@ const KBSelect = () => {
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [_, setSearchParams] = useURLSearchParams();
|
const [_, setSearchParams] = useURLSearchParams();
|
||||||
const { kb_id, kbList, license, user } = useAppSelector(
|
const { kb_id, kbList, user } = useAppSelector(state => state.config);
|
||||||
state => state.config,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [modifyOpen, setModifyOpen] = useState(false);
|
const [modifyOpen, setModifyOpen] = useState(false);
|
||||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||||
const [opraData, setOpraData] = useState<KnowledgeBaseListItem | null>(null);
|
const [opraData, setOpraData] = useState<KnowledgeBaseListItem | null>(null);
|
||||||
|
|
||||||
|
const wikiCount = useFeatureValue('wikiCount');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{(kbList || []).length > 0 && (
|
{(kbList || []).length > 0 && (
|
||||||
|
|
@ -121,8 +122,7 @@ const KBSelect = () => {
|
||||||
}}
|
}}
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={
|
disabled={
|
||||||
(license.edition === 0 && (kbList || []).length >= 1) ||
|
(kbList || []).length >= wikiCount ||
|
||||||
(license.edition === 1 && (kbList || []).length >= 3) ||
|
|
||||||
user.role === ConstsUserRole.UserRoleUser
|
user.role === ConstsUserRole.UserRoleUser
|
||||||
}
|
}
|
||||||
onClick={event => {
|
onClick={event => {
|
||||||
|
|
|
||||||
|
|
@ -3,27 +3,20 @@ import {
|
||||||
getApiV1License,
|
getApiV1License,
|
||||||
deleteApiV1License,
|
deleteApiV1License,
|
||||||
} from '@/request/pro/License';
|
} from '@/request/pro/License';
|
||||||
import { PostApiV1LicensePayload } from '@/request/pro/types';
|
|
||||||
import HelpCenter from '@/assets/json/help-center.json';
|
import HelpCenter from '@/assets/json/help-center.json';
|
||||||
import Takeoff from '@/assets/json/takeoff.json';
|
import Takeoff from '@/assets/json/takeoff.json';
|
||||||
import error from '@/assets/json/error.json';
|
import error from '@/assets/json/error.json';
|
||||||
import IconUpgrade from '@/assets/json/upgrade.json';
|
import IconUpgrade from '@/assets/json/upgrade.json';
|
||||||
import Upload from '@/components/UploadFile/Drag';
|
import Upload from '@/components/UploadFile/Drag';
|
||||||
import { EditionType } from '@/constant/enums';
|
import { useVersionInfo } from '@/hooks';
|
||||||
import { useAppDispatch, useAppSelector } from '@/store';
|
import { useAppDispatch, useAppSelector } from '@/store';
|
||||||
import { setLicense } from '@/store/slices/config';
|
import { setLicense } from '@/store/slices/config';
|
||||||
import {
|
import { Box, Button, IconButton, Stack, TextField } from '@mui/material';
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
IconButton,
|
|
||||||
MenuItem,
|
|
||||||
Stack,
|
|
||||||
TextField,
|
|
||||||
} from '@mui/material';
|
|
||||||
import { CusTabs, Icon, message, Modal } from '@ctzhian/ui';
|
import { CusTabs, Icon, message, Modal } from '@ctzhian/ui';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import LottieIcon from '../LottieIcon';
|
import LottieIcon from '../LottieIcon';
|
||||||
|
import { ConstsLicenseEdition } from '@/request/types';
|
||||||
|
|
||||||
interface AuthTypeModalProps {
|
interface AuthTypeModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
|
@ -42,10 +35,9 @@ const AuthTypeModal = ({
|
||||||
const { license } = useAppSelector(state => state.config);
|
const { license } = useAppSelector(state => state.config);
|
||||||
|
|
||||||
const [selected, setSelected] = useState<'file' | 'code'>(
|
const [selected, setSelected] = useState<'file' | 'code'>(
|
||||||
license.edition === 2 ? 'file' : 'code',
|
license.edition === ConstsLicenseEdition.LicenseEditionEnterprise
|
||||||
);
|
? 'file'
|
||||||
const [authVersion, setAuthVersion] = useState<'contributor' | 'enterprise'>(
|
: 'code',
|
||||||
license.edition === 2 ? 'enterprise' : 'contributor',
|
|
||||||
);
|
);
|
||||||
const [updateOpen, setUpdateOpen] = useState(false);
|
const [updateOpen, setUpdateOpen] = useState(false);
|
||||||
const [code, setCode] = useState('');
|
const [code, setCode] = useState('');
|
||||||
|
|
@ -53,16 +45,15 @@ const AuthTypeModal = ({
|
||||||
const [file, setFile] = useState<File | undefined>(undefined);
|
const [file, setFile] = useState<File | undefined>(undefined);
|
||||||
const [unbindLoading, setUnbindLoading] = useState(false);
|
const [unbindLoading, setUnbindLoading] = useState(false);
|
||||||
|
|
||||||
|
const versionInfo = useVersionInfo();
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
const params: PostApiV1LicensePayload = {
|
setLoading(true);
|
||||||
license_edition: authVersion,
|
postApiV1License({
|
||||||
license_type: selected,
|
license_type: selected,
|
||||||
license_code: code,
|
license_code: code,
|
||||||
license_file: file,
|
license_file: file,
|
||||||
};
|
})
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
postApiV1License(params)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
message.success('激活成功');
|
message.success('激活成功');
|
||||||
setUpdateOpen(false);
|
setUpdateOpen(false);
|
||||||
|
|
@ -148,10 +139,8 @@ const AuthTypeModal = ({
|
||||||
<Stack direction={'row'} alignItems={'center'}>
|
<Stack direction={'row'} alignItems={'center'}>
|
||||||
<Box sx={{ width: 120, flexShrink: 0 }}>产品型号</Box>
|
<Box sx={{ width: 120, flexShrink: 0 }}>产品型号</Box>
|
||||||
<Stack direction={'row'} alignItems={'center'} gap={2}>
|
<Stack direction={'row'} alignItems={'center'} gap={2}>
|
||||||
<Box sx={{ minWidth: 50 }}>
|
<Box sx={{ minWidth: 50 }}>{versionInfo.label}</Box>
|
||||||
{EditionType[license.edition as keyof typeof EditionType].text}
|
{license.edition === ConstsLicenseEdition.LicenseEditionFree ? (
|
||||||
</Box>
|
|
||||||
{license.edition === 0 ? (
|
|
||||||
<Stack direction={'row'} gap={2}>
|
<Stack direction={'row'} gap={2}>
|
||||||
<Button
|
<Button
|
||||||
size='small'
|
size='small'
|
||||||
|
|
@ -240,7 +229,7 @@ const AuthTypeModal = ({
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
{license.edition! > 0 && (
|
{license.edition! !== ConstsLicenseEdition.LicenseEditionFree && (
|
||||||
<Box>
|
<Box>
|
||||||
<Stack direction={'row'} alignItems={'center'}>
|
<Stack direction={'row'} alignItems={'center'}>
|
||||||
<Box sx={{ width: 120, flexShrink: 0 }}>授权时间</Box>
|
<Box sx={{ width: 120, flexShrink: 0 }}>授权时间</Box>
|
||||||
|
|
@ -288,18 +277,6 @@ const AuthTypeModal = ({
|
||||||
value={selected}
|
value={selected}
|
||||||
change={(v: string) => setSelected(v as 'file' | 'code')}
|
change={(v: string) => setSelected(v as 'file' | 'code')}
|
||||||
/>
|
/>
|
||||||
<TextField
|
|
||||||
select
|
|
||||||
fullWidth
|
|
||||||
sx={{ mt: 2 }}
|
|
||||||
value={authVersion}
|
|
||||||
onChange={e =>
|
|
||||||
setAuthVersion(e.target.value as 'contributor' | 'enterprise')
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<MenuItem value='contributor'>联创版</MenuItem>
|
|
||||||
<MenuItem value='enterprise'>企业版</MenuItem>
|
|
||||||
</TextField>
|
|
||||||
{selected === 'code' && (
|
{selected === 'code' && (
|
||||||
<TextField
|
<TextField
|
||||||
sx={{ mt: 2 }}
|
sx={{ mt: 2 }}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,14 @@
|
||||||
import HelpCenter from '@/assets/json/help-center.json';
|
import HelpCenter from '@/assets/json/help-center.json';
|
||||||
import IconUpgrade from '@/assets/json/upgrade.json';
|
import IconUpgrade from '@/assets/json/upgrade.json';
|
||||||
import LottieIcon from '@/components/LottieIcon';
|
import LottieIcon from '@/components/LottieIcon';
|
||||||
import { EditionType } from '@/constant/enums';
|
|
||||||
import { useAppSelector } from '@/store';
|
|
||||||
import { Box, Stack, Tooltip } from '@mui/material';
|
import { Box, Stack, Tooltip } from '@mui/material';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import packageJson from '../../../package.json';
|
import packageJson from '../../../package.json';
|
||||||
import AuthTypeModal from './AuthTypeModal';
|
import AuthTypeModal from './AuthTypeModal';
|
||||||
import freeVersion from '@/assets/images/free-version.png';
|
import { useVersionInfo } from '@/hooks';
|
||||||
import enterpriseVersion from '@/assets/images/enterprise-version.png';
|
|
||||||
import contributorVersion from '@/assets/images/contributor-version.png';
|
|
||||||
|
|
||||||
const versionMap = {
|
|
||||||
0: freeVersion,
|
|
||||||
1: contributorVersion,
|
|
||||||
2: enterpriseVersion,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Version = () => {
|
const Version = () => {
|
||||||
const { license } = useAppSelector(state => state.config);
|
const versionInfo = useVersionInfo();
|
||||||
const curVersion = import.meta.env.VITE_APP_VERSION || packageJson.version;
|
const curVersion = import.meta.env.VITE_APP_VERSION || packageJson.version;
|
||||||
const [latestVersion, setLatestVersion] = useState<string | undefined>(
|
const [latestVersion, setLatestVersion] = useState<string | undefined>(
|
||||||
undefined,
|
undefined,
|
||||||
|
|
@ -57,11 +47,8 @@ const Version = () => {
|
||||||
>
|
>
|
||||||
<Stack direction={'row'} alignItems='center' gap={0.5}>
|
<Stack direction={'row'} alignItems='center' gap={0.5}>
|
||||||
<Box sx={{ width: 30, color: 'text.tertiary' }}>型号</Box>
|
<Box sx={{ width: 30, color: 'text.tertiary' }}>型号</Box>
|
||||||
<img
|
<img src={versionInfo.image} style={{ height: 13, marginTop: -1 }} />
|
||||||
src={versionMap[license.edition!]}
|
{versionInfo.label}
|
||||||
style={{ height: 13, marginTop: -1 }}
|
|
||||||
/>
|
|
||||||
{EditionType[license.edition as keyof typeof EditionType].text}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack direction={'row'} gap={0.5}>
|
<Stack direction={'row'} gap={0.5}>
|
||||||
<Box sx={{ width: 30, color: 'text.tertiary' }}>版本</Box>
|
<Box sx={{ width: 30, color: 'text.tertiary' }}>版本</Box>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ import { Modal, message } from '@ctzhian/ui';
|
||||||
import { useState, useMemo, useEffect } from 'react';
|
import { useState, useMemo, useEffect } from 'react';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { useAppSelector } from '@/store';
|
import { useAppSelector } from '@/store';
|
||||||
|
import { PROFESSION_VERSION_PERMISSION } from '@/constant/version';
|
||||||
|
import { VersionCanUse } from '@/components/VersionMask';
|
||||||
import { ConstsUserKBPermission, V1KBUserInviteReq } from '@/request/types';
|
import { ConstsUserKBPermission, V1KBUserInviteReq } from '@/request/types';
|
||||||
import { ConstsLicenseEdition } from '@/request/pro/types';
|
import { ConstsLicenseEdition } from '@/request/pro/types';
|
||||||
|
|
||||||
|
|
@ -26,9 +27,13 @@ const VERSION_MAP = {
|
||||||
message: '开源版只支持 1 个管理员',
|
message: '开源版只支持 1 个管理员',
|
||||||
max: 1,
|
max: 1,
|
||||||
},
|
},
|
||||||
[ConstsLicenseEdition.LicenseEditionContributor]: {
|
[ConstsLicenseEdition.LicenseEditionProfession]: {
|
||||||
message: '联创版最多支持 3 个管理员',
|
message: '专业版最多支持 20 个管理员',
|
||||||
max: 3,
|
max: 20,
|
||||||
|
},
|
||||||
|
[ConstsLicenseEdition.LicenseEditionBusiness]: {
|
||||||
|
message: '商业版最多支持 50 个管理员',
|
||||||
|
max: 50,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -45,9 +50,6 @@ const MemberAdd = ({
|
||||||
const { kbList, license, refreshAdminRequest } = useAppSelector(
|
const { kbList, license, refreshAdminRequest } = useAppSelector(
|
||||||
state => state.config,
|
state => state.config,
|
||||||
);
|
);
|
||||||
const isEnterprise = useMemo(() => {
|
|
||||||
return license.edition === 2;
|
|
||||||
}, [license]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
|
|
@ -118,6 +120,10 @@ const MemberAdd = ({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isPro = useMemo(() => {
|
||||||
|
return PROFESSION_VERSION_PERMISSION.includes(license.edition!);
|
||||||
|
}, [license.edition]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -253,6 +259,14 @@ const MemberAdd = ({
|
||||||
fullWidth
|
fullWidth
|
||||||
displayEmpty
|
displayEmpty
|
||||||
sx={{ height: 52 }}
|
sx={{ height: 52 }}
|
||||||
|
MenuProps={{
|
||||||
|
sx: {
|
||||||
|
'.Mui-disabled': {
|
||||||
|
opacity: 1,
|
||||||
|
color: 'text.disabled',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
renderValue={(value: V1KBUserInviteReq['perm']) => {
|
renderValue={(value: V1KBUserInviteReq['perm']) => {
|
||||||
return value ? (
|
return value ? (
|
||||||
PERM_MAP[value]
|
PERM_MAP[value]
|
||||||
|
|
@ -266,17 +280,25 @@ const MemberAdd = ({
|
||||||
>
|
>
|
||||||
完全控制
|
完全控制
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
disabled={!isEnterprise}
|
|
||||||
value={ConstsUserKBPermission.UserKBPermissionDocManage}
|
value={ConstsUserKBPermission.UserKBPermissionDocManage}
|
||||||
|
disabled={!isPro}
|
||||||
>
|
>
|
||||||
文档管理 {isEnterprise ? '' : '(企业版可用)'}
|
文档管理{' '}
|
||||||
|
<VersionCanUse
|
||||||
|
permission={PROFESSION_VERSION_PERMISSION}
|
||||||
|
/>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
disabled={!isEnterprise}
|
|
||||||
value={ConstsUserKBPermission.UserKBPermissionDataOperate}
|
value={ConstsUserKBPermission.UserKBPermissionDataOperate}
|
||||||
|
disabled={!isPro}
|
||||||
>
|
>
|
||||||
数据运营 {isEnterprise ? '' : '(企业版可用)'}
|
数据运营{' '}
|
||||||
|
<VersionCanUse
|
||||||
|
permission={PROFESSION_VERSION_PERMISSION}
|
||||||
|
/>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
import { styled } from '@mui/material';
|
||||||
|
import { useVersionInfo } from '@/hooks';
|
||||||
|
import { VersionInfoMap } from '@/constant/version';
|
||||||
|
import { ConstsLicenseEdition } from '@/request/types';
|
||||||
|
import { SxProps } from '@mui/material';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const StyledMaskWrapper = styled('div')(({ theme }) => ({
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledMask = styled('div')(({ theme }) => ({
|
||||||
|
position: 'absolute',
|
||||||
|
inset: -8,
|
||||||
|
zIndex: 99,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
flex: 1,
|
||||||
|
borderRadius: '10px',
|
||||||
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
|
background: 'rgba(241,242,248,0.8)',
|
||||||
|
backdropFilter: 'blur(0.5px)',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledMaskContent = styled('div')(({ theme }) => ({
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledMaskVersion = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: theme.spacing(0.5),
|
||||||
|
padding: theme.spacing(0.5, 1),
|
||||||
|
backgroundColor: theme.palette.background.paper3,
|
||||||
|
borderRadius: '10px',
|
||||||
|
fontSize: 12,
|
||||||
|
lineHeight: 1,
|
||||||
|
color: theme.palette.light.main,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const VersionMask = ({
|
||||||
|
permission = [
|
||||||
|
ConstsLicenseEdition.LicenseEditionFree,
|
||||||
|
ConstsLicenseEdition.LicenseEditionProfession,
|
||||||
|
ConstsLicenseEdition.LicenseEditionBusiness,
|
||||||
|
ConstsLicenseEdition.LicenseEditionEnterprise,
|
||||||
|
],
|
||||||
|
children,
|
||||||
|
sx,
|
||||||
|
}: {
|
||||||
|
permission?: ConstsLicenseEdition[];
|
||||||
|
children?: React.ReactNode;
|
||||||
|
sx?: SxProps;
|
||||||
|
}) => {
|
||||||
|
const versionInfo = useVersionInfo();
|
||||||
|
const hasPermission = permission.includes(versionInfo.permission);
|
||||||
|
if (hasPermission) return children;
|
||||||
|
const nextVersionInfo = VersionInfoMap[permission[0]];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledMaskWrapper>
|
||||||
|
{children}
|
||||||
|
<StyledMask sx={sx}>
|
||||||
|
<StyledMaskContent>
|
||||||
|
<StyledMaskVersion sx={{ backgroundColor: nextVersionInfo.bgColor }}>
|
||||||
|
<img
|
||||||
|
src={nextVersionInfo.image}
|
||||||
|
style={{ width: 12, objectFit: 'contain', marginTop: 1 }}
|
||||||
|
alt={nextVersionInfo.label}
|
||||||
|
/>
|
||||||
|
{nextVersionInfo?.label}可用
|
||||||
|
</StyledMaskVersion>
|
||||||
|
</StyledMaskContent>
|
||||||
|
</StyledMask>
|
||||||
|
</StyledMaskWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const VersionCanUse = ({
|
||||||
|
permission = [
|
||||||
|
ConstsLicenseEdition.LicenseEditionFree,
|
||||||
|
ConstsLicenseEdition.LicenseEditionProfession,
|
||||||
|
ConstsLicenseEdition.LicenseEditionBusiness,
|
||||||
|
ConstsLicenseEdition.LicenseEditionEnterprise,
|
||||||
|
],
|
||||||
|
sx,
|
||||||
|
}: {
|
||||||
|
permission?: ConstsLicenseEdition[];
|
||||||
|
sx?: SxProps;
|
||||||
|
}) => {
|
||||||
|
const versionInfo = useVersionInfo();
|
||||||
|
const hasPermission = permission.includes(versionInfo.permission);
|
||||||
|
if (hasPermission) return null;
|
||||||
|
const nextVersionInfo = VersionInfoMap[permission[0]];
|
||||||
|
return (
|
||||||
|
<StyledMaskContent sx={{ width: 'auto', ml: 1, ...sx }}>
|
||||||
|
<StyledMaskVersion sx={{ backgroundColor: nextVersionInfo.bgColor }}>
|
||||||
|
<img
|
||||||
|
src={nextVersionInfo.image}
|
||||||
|
style={{ width: 12, objectFit: 'contain', marginTop: 1 }}
|
||||||
|
alt={nextVersionInfo.label}
|
||||||
|
/>
|
||||||
|
{nextVersionInfo?.label}可用
|
||||||
|
</StyledMaskVersion>
|
||||||
|
</StyledMaskContent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VersionMask;
|
||||||
|
|
@ -797,21 +797,6 @@ export const FeedbackType = {
|
||||||
3: '其他',
|
3: '其他',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Free = 0;
|
|
||||||
export const Contributor = 1;
|
|
||||||
export const Enterprise = 2;
|
|
||||||
export const EditionType = {
|
|
||||||
[Free]: {
|
|
||||||
text: '开源版',
|
|
||||||
},
|
|
||||||
[Contributor]: {
|
|
||||||
text: '联创版',
|
|
||||||
},
|
|
||||||
[Enterprise]: {
|
|
||||||
text: '企业版',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DocWidth = {
|
export const DocWidth = {
|
||||||
full: {
|
full: {
|
||||||
label: '全屏',
|
label: '全屏',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,293 @@
|
||||||
|
import { ConstsLicenseEdition } from '@/request/types';
|
||||||
|
|
||||||
|
import freeVersion from '@/assets/images/free-version.png';
|
||||||
|
import proVersion from '@/assets/images/pro-version.png';
|
||||||
|
import businessVersion from '@/assets/images/business-version.png';
|
||||||
|
import enterpriseVersion from '@/assets/images/enterprise-version.png';
|
||||||
|
|
||||||
|
export const PROFESSION_VERSION_PERMISSION = [
|
||||||
|
ConstsLicenseEdition.LicenseEditionProfession,
|
||||||
|
ConstsLicenseEdition.LicenseEditionBusiness,
|
||||||
|
ConstsLicenseEdition.LicenseEditionEnterprise,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const BUSINESS_VERSION_PERMISSION = [
|
||||||
|
ConstsLicenseEdition.LicenseEditionBusiness,
|
||||||
|
ConstsLicenseEdition.LicenseEditionEnterprise,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ENTERPRISE_VERSION_PERMISSION = [
|
||||||
|
ConstsLicenseEdition.LicenseEditionEnterprise,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const VersionInfoMap = {
|
||||||
|
[ConstsLicenseEdition.LicenseEditionFree]: {
|
||||||
|
permission: ConstsLicenseEdition.LicenseEditionFree,
|
||||||
|
label: '开源版',
|
||||||
|
image: freeVersion,
|
||||||
|
bgColor: '#8E9DAC',
|
||||||
|
nextVersion: ConstsLicenseEdition.LicenseEditionProfession,
|
||||||
|
},
|
||||||
|
[ConstsLicenseEdition.LicenseEditionProfession]: {
|
||||||
|
permission: ConstsLicenseEdition.LicenseEditionProfession,
|
||||||
|
label: '专业版',
|
||||||
|
image: proVersion,
|
||||||
|
bgColor: '#0933BA',
|
||||||
|
nextVersion: ConstsLicenseEdition.LicenseEditionBusiness,
|
||||||
|
},
|
||||||
|
[ConstsLicenseEdition.LicenseEditionBusiness]: {
|
||||||
|
permission: ConstsLicenseEdition.LicenseEditionBusiness,
|
||||||
|
label: '商业版',
|
||||||
|
image: businessVersion,
|
||||||
|
bgColor: '#382A79',
|
||||||
|
nextVersion: ConstsLicenseEdition.LicenseEditionEnterprise,
|
||||||
|
},
|
||||||
|
[ConstsLicenseEdition.LicenseEditionEnterprise]: {
|
||||||
|
permission: ConstsLicenseEdition.LicenseEditionEnterprise,
|
||||||
|
label: '企业版',
|
||||||
|
image: enterpriseVersion,
|
||||||
|
bgColor: '#21222D',
|
||||||
|
nextVersion: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能支持状态
|
||||||
|
*/
|
||||||
|
export enum FeatureStatus {
|
||||||
|
/** 不支持 */
|
||||||
|
NOT_SUPPORTED = 'not_supported',
|
||||||
|
/** 支持 */
|
||||||
|
SUPPORTED = 'supported',
|
||||||
|
/** 基础配置 */
|
||||||
|
BASIC = 'basic',
|
||||||
|
/** 高级配置 */
|
||||||
|
ADVANCED = 'advanced',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本信息配置
|
||||||
|
*/
|
||||||
|
export interface VersionInfo {
|
||||||
|
/** 版本名称 */
|
||||||
|
label: string;
|
||||||
|
/** 功能特性 */
|
||||||
|
features: {
|
||||||
|
/** Wiki 站点数量 */
|
||||||
|
wikiCount: number;
|
||||||
|
/** 每个 Wiki 的文档数量 */
|
||||||
|
docCountPerWiki: number;
|
||||||
|
/** 管理员数量 */
|
||||||
|
adminCount: number;
|
||||||
|
/** 管理员分权控制 */
|
||||||
|
adminPermissionControl: FeatureStatus;
|
||||||
|
/** SEO 配置 */
|
||||||
|
seoConfig: FeatureStatus;
|
||||||
|
/** 多语言支持 */
|
||||||
|
multiLanguage: FeatureStatus;
|
||||||
|
/** 自定义版权信息 */
|
||||||
|
customCopyright: FeatureStatus;
|
||||||
|
/** 访问流量分析 */
|
||||||
|
trafficAnalysis: FeatureStatus;
|
||||||
|
/** 自定义 AI 提示词 */
|
||||||
|
customAIPrompt: FeatureStatus;
|
||||||
|
/** SSO 登录 */
|
||||||
|
ssoLogin: number;
|
||||||
|
/** 访客权限控制 */
|
||||||
|
visitorPermissionControl: FeatureStatus;
|
||||||
|
/** 页面水印 */
|
||||||
|
pageWatermark: FeatureStatus;
|
||||||
|
/** 内容不可复制 */
|
||||||
|
contentNoCopy: FeatureStatus;
|
||||||
|
/** 敏感内容过滤 */
|
||||||
|
sensitiveContentFilter: FeatureStatus;
|
||||||
|
/** 网页挂件机器人 */
|
||||||
|
webWidgetRobot: FeatureStatus;
|
||||||
|
/** 飞书问答机器人 */
|
||||||
|
feishuQARobot: FeatureStatus;
|
||||||
|
/** 钉钉问答机器人 */
|
||||||
|
dingtalkQARobot: FeatureStatus;
|
||||||
|
/** 企业微信问答机器人 */
|
||||||
|
wecomQARobot: FeatureStatus;
|
||||||
|
/** 企业微信客服机器人 */
|
||||||
|
wecomServiceRobot: FeatureStatus;
|
||||||
|
/** Discord 问答机器人 */
|
||||||
|
discordQARobot: FeatureStatus;
|
||||||
|
/** 文档历史版本管理 */
|
||||||
|
docVersionHistory: FeatureStatus;
|
||||||
|
/** API 调用 */
|
||||||
|
apiCall: FeatureStatus;
|
||||||
|
/** 项目源码 */
|
||||||
|
sourceCode: FeatureStatus;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本信息映射
|
||||||
|
*/
|
||||||
|
export const VERSION_INFO: Record<ConstsLicenseEdition, VersionInfo> = {
|
||||||
|
[ConstsLicenseEdition.LicenseEditionFree]: {
|
||||||
|
label: '开源版',
|
||||||
|
features: {
|
||||||
|
wikiCount: 1,
|
||||||
|
docCountPerWiki: 300,
|
||||||
|
adminCount: 1,
|
||||||
|
adminPermissionControl: FeatureStatus.NOT_SUPPORTED,
|
||||||
|
seoConfig: FeatureStatus.BASIC,
|
||||||
|
multiLanguage: FeatureStatus.NOT_SUPPORTED,
|
||||||
|
customCopyright: FeatureStatus.NOT_SUPPORTED,
|
||||||
|
trafficAnalysis: FeatureStatus.BASIC,
|
||||||
|
customAIPrompt: FeatureStatus.NOT_SUPPORTED,
|
||||||
|
ssoLogin: 0,
|
||||||
|
visitorPermissionControl: FeatureStatus.NOT_SUPPORTED,
|
||||||
|
pageWatermark: FeatureStatus.NOT_SUPPORTED,
|
||||||
|
contentNoCopy: FeatureStatus.NOT_SUPPORTED,
|
||||||
|
sensitiveContentFilter: FeatureStatus.NOT_SUPPORTED,
|
||||||
|
webWidgetRobot: FeatureStatus.BASIC,
|
||||||
|
feishuQARobot: FeatureStatus.BASIC,
|
||||||
|
dingtalkQARobot: FeatureStatus.BASIC,
|
||||||
|
wecomQARobot: FeatureStatus.BASIC,
|
||||||
|
wecomServiceRobot: FeatureStatus.BASIC,
|
||||||
|
discordQARobot: FeatureStatus.BASIC,
|
||||||
|
docVersionHistory: FeatureStatus.NOT_SUPPORTED,
|
||||||
|
apiCall: FeatureStatus.NOT_SUPPORTED,
|
||||||
|
sourceCode: FeatureStatus.SUPPORTED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[ConstsLicenseEdition.LicenseEditionProfession]: {
|
||||||
|
label: '专业版',
|
||||||
|
features: {
|
||||||
|
wikiCount: 10,
|
||||||
|
docCountPerWiki: 10000,
|
||||||
|
adminCount: 20,
|
||||||
|
adminPermissionControl: FeatureStatus.SUPPORTED,
|
||||||
|
seoConfig: FeatureStatus.ADVANCED,
|
||||||
|
multiLanguage: FeatureStatus.SUPPORTED,
|
||||||
|
customCopyright: FeatureStatus.SUPPORTED,
|
||||||
|
trafficAnalysis: FeatureStatus.ADVANCED,
|
||||||
|
customAIPrompt: FeatureStatus.SUPPORTED,
|
||||||
|
ssoLogin: 0,
|
||||||
|
visitorPermissionControl: FeatureStatus.NOT_SUPPORTED,
|
||||||
|
pageWatermark: FeatureStatus.NOT_SUPPORTED,
|
||||||
|
contentNoCopy: FeatureStatus.NOT_SUPPORTED,
|
||||||
|
sensitiveContentFilter: FeatureStatus.NOT_SUPPORTED,
|
||||||
|
webWidgetRobot: FeatureStatus.ADVANCED,
|
||||||
|
feishuQARobot: FeatureStatus.ADVANCED,
|
||||||
|
dingtalkQARobot: FeatureStatus.ADVANCED,
|
||||||
|
wecomQARobot: FeatureStatus.ADVANCED,
|
||||||
|
wecomServiceRobot: FeatureStatus.ADVANCED,
|
||||||
|
discordQARobot: FeatureStatus.ADVANCED,
|
||||||
|
docVersionHistory: FeatureStatus.NOT_SUPPORTED,
|
||||||
|
apiCall: FeatureStatus.NOT_SUPPORTED,
|
||||||
|
sourceCode: FeatureStatus.NOT_SUPPORTED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[ConstsLicenseEdition.LicenseEditionBusiness]: {
|
||||||
|
label: '商业版',
|
||||||
|
features: {
|
||||||
|
wikiCount: 20,
|
||||||
|
docCountPerWiki: 10000,
|
||||||
|
adminCount: 50,
|
||||||
|
adminPermissionControl: FeatureStatus.SUPPORTED,
|
||||||
|
seoConfig: FeatureStatus.ADVANCED,
|
||||||
|
multiLanguage: FeatureStatus.SUPPORTED,
|
||||||
|
customCopyright: FeatureStatus.SUPPORTED,
|
||||||
|
trafficAnalysis: FeatureStatus.ADVANCED,
|
||||||
|
customAIPrompt: FeatureStatus.SUPPORTED,
|
||||||
|
ssoLogin: 2000,
|
||||||
|
visitorPermissionControl: FeatureStatus.SUPPORTED,
|
||||||
|
pageWatermark: FeatureStatus.SUPPORTED,
|
||||||
|
contentNoCopy: FeatureStatus.SUPPORTED,
|
||||||
|
sensitiveContentFilter: FeatureStatus.SUPPORTED,
|
||||||
|
webWidgetRobot: FeatureStatus.ADVANCED,
|
||||||
|
feishuQARobot: FeatureStatus.ADVANCED,
|
||||||
|
dingtalkQARobot: FeatureStatus.ADVANCED,
|
||||||
|
wecomQARobot: FeatureStatus.ADVANCED,
|
||||||
|
wecomServiceRobot: FeatureStatus.ADVANCED,
|
||||||
|
discordQARobot: FeatureStatus.ADVANCED,
|
||||||
|
docVersionHistory: FeatureStatus.SUPPORTED,
|
||||||
|
apiCall: FeatureStatus.SUPPORTED,
|
||||||
|
sourceCode: FeatureStatus.NOT_SUPPORTED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[ConstsLicenseEdition.LicenseEditionEnterprise]: {
|
||||||
|
label: '企业版',
|
||||||
|
features: {
|
||||||
|
wikiCount: Infinity,
|
||||||
|
docCountPerWiki: Infinity,
|
||||||
|
adminCount: Infinity,
|
||||||
|
adminPermissionControl: FeatureStatus.SUPPORTED,
|
||||||
|
seoConfig: FeatureStatus.ADVANCED,
|
||||||
|
multiLanguage: FeatureStatus.SUPPORTED,
|
||||||
|
customCopyright: FeatureStatus.SUPPORTED,
|
||||||
|
trafficAnalysis: FeatureStatus.ADVANCED,
|
||||||
|
customAIPrompt: FeatureStatus.SUPPORTED,
|
||||||
|
ssoLogin: Infinity,
|
||||||
|
visitorPermissionControl: FeatureStatus.SUPPORTED,
|
||||||
|
pageWatermark: FeatureStatus.SUPPORTED,
|
||||||
|
contentNoCopy: FeatureStatus.SUPPORTED,
|
||||||
|
sensitiveContentFilter: FeatureStatus.SUPPORTED,
|
||||||
|
webWidgetRobot: FeatureStatus.ADVANCED,
|
||||||
|
feishuQARobot: FeatureStatus.ADVANCED,
|
||||||
|
dingtalkQARobot: FeatureStatus.ADVANCED,
|
||||||
|
wecomQARobot: FeatureStatus.ADVANCED,
|
||||||
|
wecomServiceRobot: FeatureStatus.ADVANCED,
|
||||||
|
discordQARobot: FeatureStatus.ADVANCED,
|
||||||
|
docVersionHistory: FeatureStatus.SUPPORTED,
|
||||||
|
apiCall: FeatureStatus.SUPPORTED,
|
||||||
|
sourceCode: FeatureStatus.SUPPORTED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能特性标签映射
|
||||||
|
*/
|
||||||
|
export const FEATURE_LABELS: Record<string, string> = {
|
||||||
|
wikiCount: 'Wiki 站点数量',
|
||||||
|
docCountPerWiki: '每个 Wiki 的文档数量',
|
||||||
|
adminCount: '管理员数量',
|
||||||
|
adminPermissionControl: '管理员分权控制',
|
||||||
|
seoConfig: 'SEO 配置',
|
||||||
|
multiLanguage: '多语言支持',
|
||||||
|
customCopyright: '自定义版权信息',
|
||||||
|
trafficAnalysis: '访问流量分析',
|
||||||
|
customAIPrompt: '自定义 AI 提示词',
|
||||||
|
ssoLogin: 'SSO 登录',
|
||||||
|
visitorPermissionControl: '访客权限控制',
|
||||||
|
pageWatermark: '页面水印',
|
||||||
|
contentNoCopy: '内容不可复制',
|
||||||
|
sensitiveContentFilter: '敏感内容过滤',
|
||||||
|
webWidgetRobot: '网页挂件机器人',
|
||||||
|
feishuQARobot: '飞书问答机器人',
|
||||||
|
dingtalkQARobot: '钉钉问答机器人',
|
||||||
|
wecomQARobot: '企业微信问答机器人',
|
||||||
|
wecomServiceRobot: '企业微信客服机器人',
|
||||||
|
discordQARobot: 'Discord 问答机器人',
|
||||||
|
docVersionHistory: '文档历史版本管理',
|
||||||
|
apiCall: 'API 调用',
|
||||||
|
sourceCode: '项目源码',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能状态显示文本映射
|
||||||
|
*/
|
||||||
|
export const FEATURE_STATUS_LABELS: Record<FeatureStatus, string> = {
|
||||||
|
[FeatureStatus.NOT_SUPPORTED]: '不支持',
|
||||||
|
[FeatureStatus.SUPPORTED]: '支持',
|
||||||
|
[FeatureStatus.BASIC]: '基础配置',
|
||||||
|
[FeatureStatus.ADVANCED]: '高级配置',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取功能特性值
|
||||||
|
*/
|
||||||
|
export function getFeatureValue<K extends keyof VersionInfo['features']>(
|
||||||
|
edition: ConstsLicenseEdition,
|
||||||
|
key: K,
|
||||||
|
): VersionInfo['features'][K] {
|
||||||
|
return (
|
||||||
|
VERSION_INFO[edition] ||
|
||||||
|
VERSION_INFO[ConstsLicenseEdition.LicenseEditionFree]
|
||||||
|
).features[key];
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
export { useBindCaptcha } from './useBindCaptcha';
|
export { useBindCaptcha } from './useBindCaptcha';
|
||||||
export { useCommitPendingInput } from './useCommitPendingInput';
|
export { useCommitPendingInput } from './useCommitPendingInput';
|
||||||
export { useURLSearchParams } from './useURLSearchParams';
|
export { useURLSearchParams } from './useURLSearchParams';
|
||||||
|
export {
|
||||||
|
useFeatureValue,
|
||||||
|
useFeatureValueSupported,
|
||||||
|
useVersionInfo,
|
||||||
|
} from './useVersionFeature';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import {
|
||||||
|
FeatureStatus,
|
||||||
|
VersionInfoMap,
|
||||||
|
VersionInfo,
|
||||||
|
getFeatureValue,
|
||||||
|
} from '@/constant/version';
|
||||||
|
import { ConstsLicenseEdition } from '@/request/types';
|
||||||
|
import { useAppSelector } from '@/store';
|
||||||
|
|
||||||
|
export const useFeatureValue = <K extends keyof VersionInfo['features']>(
|
||||||
|
key: K,
|
||||||
|
): VersionInfo['features'][K] => {
|
||||||
|
const { license } = useAppSelector(state => state.config);
|
||||||
|
return getFeatureValue(license.edition!, key);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFeatureValueSupported = (
|
||||||
|
key: keyof VersionInfo['features'],
|
||||||
|
) => {
|
||||||
|
const { license } = useAppSelector(state => state.config);
|
||||||
|
return (
|
||||||
|
getFeatureValue(license.edition!, key) === FeatureStatus.SUPPORTED ||
|
||||||
|
getFeatureValue(license.edition!, key) === FeatureStatus.ADVANCED
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useVersionInfo = () => {
|
||||||
|
const { license } = useAppSelector(state => state.config);
|
||||||
|
return (
|
||||||
|
VersionInfoMap[
|
||||||
|
license.edition ?? ConstsLicenseEdition.LicenseEditionFree
|
||||||
|
] || VersionInfoMap[ConstsLicenseEdition.LicenseEditionFree]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -8,6 +8,8 @@ import { styled } from '@mui/material/styles';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import DocModal from './DocModal';
|
import DocModal from './DocModal';
|
||||||
|
import VersionMask from '@/components/VersionMask';
|
||||||
|
import { PROFESSION_VERSION_PERMISSION } from '@/constant/version';
|
||||||
|
|
||||||
import { useURLSearchParams } from '@/hooks';
|
import { useURLSearchParams } from '@/hooks';
|
||||||
import {
|
import {
|
||||||
|
|
@ -46,7 +48,7 @@ const statusColorMap = {
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default function ContributionPage() {
|
export default function ContributionPage() {
|
||||||
const { kb_id = '', kbDetail } = useAppSelector(state => state.config);
|
const { kb_id = '', license } = useAppSelector(state => state.config);
|
||||||
const [searchParams, setSearchParams] = useURLSearchParams();
|
const [searchParams, setSearchParams] = useURLSearchParams();
|
||||||
const page = Number(searchParams.get('page') || '1');
|
const page = Number(searchParams.get('page') || '1');
|
||||||
const pageSize = Number(searchParams.get('page_size') || '20');
|
const pageSize = Number(searchParams.get('page_size') || '20');
|
||||||
|
|
@ -283,12 +285,14 @@ export default function ContributionPage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (kb_id) getData();
|
if (kb_id && PROFESSION_VERSION_PERMISSION.includes(license.edition!))
|
||||||
|
getData();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [page, pageSize, nodeNameParam, authNameParam, kb_id]);
|
}, [page, pageSize, nodeNameParam, authNameParam, kb_id, license.edition]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
|
<VersionMask permission={PROFESSION_VERSION_PERMISSION}>
|
||||||
<Stack
|
<Stack
|
||||||
direction='row'
|
direction='row'
|
||||||
alignItems={'center'}
|
alignItems={'center'}
|
||||||
|
|
@ -388,6 +392,7 @@ export default function ContributionPage() {
|
||||||
onClose={() => setDocModalOpen(false)}
|
onClose={() => setDocModalOpen(false)}
|
||||||
onOk={handleDocModalOk}
|
onOk={handleDocModalOk}
|
||||||
/>
|
/>
|
||||||
|
</VersionMask>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ import {
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
|
import { BUSINESS_VERSION_PERMISSION } from '@/constant/version';
|
||||||
|
import { VersionCanUse } from '@/components/VersionMask';
|
||||||
|
|
||||||
interface DocPropertiesModalProps {
|
interface DocPropertiesModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
|
@ -40,8 +42,6 @@ interface DocPropertiesModalProps {
|
||||||
data: DomainNodeListItemResp[];
|
data: DomainNodeListItemResp[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const tips = '(企业版可用)';
|
|
||||||
|
|
||||||
const StyledText = styled('div')(({ theme }) => ({
|
const StyledText = styled('div')(({ theme }) => ({
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
|
@ -53,7 +53,12 @@ const PER_OPTIONS = [
|
||||||
value: ConstsNodeAccessPerm.NodeAccessPermOpen,
|
value: ConstsNodeAccessPerm.NodeAccessPermOpen,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '部分开放',
|
label: (
|
||||||
|
<Stack direction={'row'} alignItems={'center'}>
|
||||||
|
<span>部分开放</span>
|
||||||
|
<VersionCanUse permission={BUSINESS_VERSION_PERMISSION} />
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
value: ConstsNodeAccessPerm.NodeAccessPermPartial,
|
value: ConstsNodeAccessPerm.NodeAccessPermPartial,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -128,13 +133,13 @@ const DocPropertiesModal = ({
|
||||||
visitable: values.visitable as ConstsNodeAccessPerm,
|
visitable: values.visitable as ConstsNodeAccessPerm,
|
||||||
visible: values.visible as ConstsNodeAccessPerm,
|
visible: values.visible as ConstsNodeAccessPerm,
|
||||||
},
|
},
|
||||||
answerable_groups: isEnterprise
|
answerable_groups: isBusiness
|
||||||
? values.answerable_groups.map(item => item.id!)
|
? values.answerable_groups.map(item => item.id!)
|
||||||
: undefined,
|
: undefined,
|
||||||
visitable_groups: isEnterprise
|
visitable_groups: isBusiness
|
||||||
? values.visitable_groups.map(item => item.id!)
|
? values.visitable_groups.map(item => item.id!)
|
||||||
: undefined,
|
: undefined,
|
||||||
visible_groups: isEnterprise
|
visible_groups: isBusiness
|
||||||
? values.visible_groups.map(item => item.id!)
|
? values.visible_groups.map(item => item.id!)
|
||||||
: undefined,
|
: undefined,
|
||||||
}),
|
}),
|
||||||
|
|
@ -153,15 +158,15 @@ const DocPropertiesModal = ({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const isEnterprise = useMemo(() => {
|
const isBusiness = useMemo(() => {
|
||||||
return license.edition === 2;
|
return BUSINESS_VERSION_PERMISSION.includes(license.edition!);
|
||||||
}, [license]);
|
}, [license]);
|
||||||
|
|
||||||
const tree = filterEmptyFolders(convertToTree(data));
|
const tree = filterEmptyFolders(convertToTree(data));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open && data) {
|
if (open && data) {
|
||||||
if (isEnterprise) {
|
if (isBusiness) {
|
||||||
getApiProV1AuthGroupList({
|
getApiProV1AuthGroupList({
|
||||||
kb_id: kb_id!,
|
kb_id: kb_id!,
|
||||||
page: 1,
|
page: 1,
|
||||||
|
|
@ -206,7 +211,7 @@ const DocPropertiesModal = ({
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [open, data, isEnterprise]);
|
}, [open, data, isBusiness]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
|
|
@ -302,22 +307,15 @@ const DocPropertiesModal = ({
|
||||||
name='answerable'
|
name='answerable'
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<RadioGroup row {...field}>
|
<RadioGroup row {...field} sx={{ gap: 2 }}>
|
||||||
{PER_OPTIONS.map(option => (
|
{PER_OPTIONS.map(option => (
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
key={option.value}
|
key={option.value}
|
||||||
value={option.value}
|
value={option.value}
|
||||||
control={<Radio size='small' />}
|
control={<Radio size='small' />}
|
||||||
label={
|
label={option.label}
|
||||||
option.label +
|
|
||||||
(!isEnterprise &&
|
|
||||||
option.value ===
|
|
||||||
ConstsNodeAccessPerm.NodeAccessPermPartial
|
|
||||||
? tips
|
|
||||||
: '')
|
|
||||||
}
|
|
||||||
disabled={
|
disabled={
|
||||||
!isEnterprise &&
|
!isBusiness &&
|
||||||
option.value ===
|
option.value ===
|
||||||
ConstsNodeAccessPerm.NodeAccessPermPartial
|
ConstsNodeAccessPerm.NodeAccessPermPartial
|
||||||
}
|
}
|
||||||
|
|
@ -359,22 +357,15 @@ const DocPropertiesModal = ({
|
||||||
name='visitable'
|
name='visitable'
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<RadioGroup row {...field}>
|
<RadioGroup row {...field} sx={{ gap: 2 }}>
|
||||||
{PER_OPTIONS.map(option => (
|
{PER_OPTIONS.map(option => (
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
key={option.value}
|
key={option.value}
|
||||||
value={option.value}
|
value={option.value}
|
||||||
control={<Radio size='small' />}
|
control={<Radio size='small' />}
|
||||||
label={
|
label={option.label}
|
||||||
option.label +
|
|
||||||
(!isEnterprise &&
|
|
||||||
option.value ===
|
|
||||||
ConstsNodeAccessPerm.NodeAccessPermPartial
|
|
||||||
? tips
|
|
||||||
: '')
|
|
||||||
}
|
|
||||||
disabled={
|
disabled={
|
||||||
!isEnterprise &&
|
!isBusiness &&
|
||||||
option.value ===
|
option.value ===
|
||||||
ConstsNodeAccessPerm.NodeAccessPermPartial
|
ConstsNodeAccessPerm.NodeAccessPermPartial
|
||||||
}
|
}
|
||||||
|
|
@ -416,22 +407,15 @@ const DocPropertiesModal = ({
|
||||||
name='visible'
|
name='visible'
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<RadioGroup row {...field}>
|
<RadioGroup row {...field} sx={{ gap: 2 }}>
|
||||||
{PER_OPTIONS.map(option => (
|
{PER_OPTIONS.map(option => (
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
key={option.value}
|
key={option.value}
|
||||||
value={option.value}
|
value={option.value}
|
||||||
control={<Radio size='small' />}
|
control={<Radio size='small' />}
|
||||||
label={
|
label={option.label}
|
||||||
option.label +
|
|
||||||
(!isEnterprise &&
|
|
||||||
option.value ===
|
|
||||||
ConstsNodeAccessPerm.NodeAccessPermPartial
|
|
||||||
? tips
|
|
||||||
: '')
|
|
||||||
}
|
|
||||||
disabled={
|
disabled={
|
||||||
!isEnterprise &&
|
!isBusiness &&
|
||||||
option.value ===
|
option.value ===
|
||||||
ConstsNodeAccessPerm.NodeAccessPermPartial
|
ConstsNodeAccessPerm.NodeAccessPermPartial
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ import { useNavigate, useOutletContext } from 'react-router-dom';
|
||||||
import { WrapContext } from '..';
|
import { WrapContext } from '..';
|
||||||
import DocAddByCustomText from '../../component/DocAddByCustomText';
|
import DocAddByCustomText from '../../component/DocAddByCustomText';
|
||||||
import DocDelete from '../../component/DocDelete';
|
import DocDelete from '../../component/DocDelete';
|
||||||
|
import { BUSINESS_VERSION_PERMISSION } from '@/constant/version';
|
||||||
|
import { VersionCanUse } from '@/components/VersionMask';
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
edit: boolean;
|
edit: boolean;
|
||||||
|
|
@ -52,8 +54,8 @@ const Header = ({
|
||||||
|
|
||||||
const [showSaveTip, setShowSaveTip] = useState(false);
|
const [showSaveTip, setShowSaveTip] = useState(false);
|
||||||
|
|
||||||
const isEnterprise = useMemo(() => {
|
const isBusiness = useMemo(() => {
|
||||||
return license.edition === 2;
|
return BUSINESS_VERSION_PERMISSION.includes(license.edition!);
|
||||||
}, [license]);
|
}, [license]);
|
||||||
|
|
||||||
const handlePublish = useCallback(() => {
|
const handlePublish = useCallback(() => {
|
||||||
|
|
@ -309,6 +311,7 @@ const Header = ({
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
key: 'copy',
|
key: 'copy',
|
||||||
|
textSx: { flex: 1 },
|
||||||
label: <StyledMenuSelect>复制</StyledMenuSelect>,
|
label: <StyledMenuSelect>复制</StyledMenuSelect>,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (kb_id) {
|
if (kb_id) {
|
||||||
|
|
@ -328,26 +331,22 @@ const Header = ({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'version',
|
key: 'version',
|
||||||
|
textSx: { flex: 1 },
|
||||||
label: (
|
label: (
|
||||||
<StyledMenuSelect disabled={!isEnterprise}>
|
<StyledMenuSelect disabled={!isBusiness}>
|
||||||
历史版本{' '}
|
历史版本
|
||||||
{!isEnterprise && (
|
<VersionCanUse permission={BUSINESS_VERSION_PERMISSION} />
|
||||||
<Tooltip title='企业版可用' placement='top' arrow>
|
|
||||||
<InfoIcon
|
|
||||||
sx={{ color: 'text.secondary', fontSize: 14 }}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</StyledMenuSelect>
|
</StyledMenuSelect>
|
||||||
),
|
),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (isEnterprise) {
|
if (isBusiness) {
|
||||||
navigate(`/doc/editor/history/${detail.id}`);
|
navigate(`/doc/editor/history/${detail.id}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'rename',
|
key: 'rename',
|
||||||
|
textSx: { flex: 1 },
|
||||||
label: <StyledMenuSelect>重命名</StyledMenuSelect>,
|
label: <StyledMenuSelect>重命名</StyledMenuSelect>,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setRenameOpen(true);
|
setRenameOpen(true);
|
||||||
|
|
@ -355,6 +354,7 @@ const Header = ({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
|
textSx: { flex: 1 },
|
||||||
label: <StyledMenuSelect>删除</StyledMenuSelect>,
|
label: <StyledMenuSelect>删除</StyledMenuSelect>,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setDelOpen(true);
|
setDelOpen(true);
|
||||||
|
|
@ -566,7 +566,7 @@ const StyledMenuSelect = styled('div')<{ disabled?: boolean }>(
|
||||||
padding: theme.spacing(0, 2),
|
padding: theme.spacing(0, 2),
|
||||||
lineHeight: '40px',
|
lineHeight: '40px',
|
||||||
height: 40,
|
height: 40,
|
||||||
width: 106,
|
minWidth: 106,
|
||||||
borderRadius: '5px',
|
borderRadius: '5px',
|
||||||
color: disabled ? theme.palette.text.secondary : theme.palette.text.primary,
|
color: disabled ? theme.palette.text.secondary : theme.palette.text.primary,
|
||||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import Header from './Header';
|
||||||
import Summary from './Summary';
|
import Summary from './Summary';
|
||||||
import Toc from './Toc';
|
import Toc from './Toc';
|
||||||
import Toolbar from './Toolbar';
|
import Toolbar from './Toolbar';
|
||||||
|
import { BUSINESS_VERSION_PERMISSION } from '@/constant/version';
|
||||||
|
|
||||||
interface WrapProps {
|
interface WrapProps {
|
||||||
detail: V1NodeDetailResp;
|
detail: V1NodeDetailResp;
|
||||||
|
|
@ -72,8 +73,8 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
||||||
emoji: defaultDetail.meta?.emoji || '',
|
emoji: defaultDetail.meta?.emoji || '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const isEnterprise = useMemo(() => {
|
const isBusiness = useMemo(() => {
|
||||||
return license.edition === 2;
|
return BUSINESS_VERSION_PERMISSION.includes(license.edition!);
|
||||||
}, [license]);
|
}, [license]);
|
||||||
|
|
||||||
const debouncedUpdateSummary = useCallback(
|
const debouncedUpdateSummary = useCallback(
|
||||||
|
|
@ -383,7 +384,7 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
||||||
</Stack>
|
</Stack>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<Tooltip arrow title={isEnterprise ? '查看历史版本' : ''}>
|
<Tooltip arrow title={isBusiness ? '查看历史版本' : ''}>
|
||||||
<Stack
|
<Stack
|
||||||
direction={'row'}
|
direction={'row'}
|
||||||
alignItems={'center'}
|
alignItems={'center'}
|
||||||
|
|
@ -391,13 +392,13 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: 'text.tertiary',
|
color: 'text.tertiary',
|
||||||
cursor: isEnterprise ? 'pointer' : 'text',
|
cursor: isBusiness ? 'pointer' : 'text',
|
||||||
':hover': {
|
':hover': {
|
||||||
color: isEnterprise ? 'primary.main' : 'text.tertiary',
|
color: isBusiness ? 'primary.main' : 'text.tertiary',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isEnterprise) {
|
if (isBusiness) {
|
||||||
navigate(`/doc/editor/history/${defaultDetail.id}`);
|
navigate(`/doc/editor/history/${defaultDetail.id}`);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import {
|
||||||
IconButton,
|
IconButton,
|
||||||
Stack,
|
Stack,
|
||||||
useTheme,
|
useTheme,
|
||||||
|
ButtonBase,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import VersionPublish from '../release/components/VersionPublish';
|
import VersionPublish from '../release/components/VersionPublish';
|
||||||
|
|
@ -418,15 +419,19 @@ const Content = () => {
|
||||||
>
|
>
|
||||||
{publish.unpublished} 个 文档/文件夹未发布,
|
{publish.unpublished} 个 文档/文件夹未发布,
|
||||||
</Box>
|
</Box>
|
||||||
<Button
|
<ButtonBase
|
||||||
size='small'
|
disableRipple
|
||||||
sx={{ minWidth: 0, p: 0, fontSize: 12 }}
|
sx={{
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 400,
|
||||||
|
color: 'primary.main',
|
||||||
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPublishOpen(true);
|
setPublishOpen(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
去发布
|
去发布
|
||||||
</Button>
|
</ButtonBase>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{ragReStartCount > 0 && (
|
{ragReStartCount > 0 && (
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import {
|
||||||
ButtonBase,
|
ButtonBase,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Ellipsis, Table, Modal, Icon, message } from '@ctzhian/ui';
|
import { Ellipsis, Table, Modal, Icon, message } from '@ctzhian/ui';
|
||||||
|
import { PROFESSION_VERSION_PERMISSION } from '@/constant/version';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useEffect, useState, useMemo } from 'react';
|
import { useEffect, useState, useMemo } from 'react';
|
||||||
|
|
||||||
|
|
@ -162,8 +163,8 @@ const Comments = ({
|
||||||
useState<DomainWebAppCommentSettings | null>(null);
|
useState<DomainWebAppCommentSettings | null>(null);
|
||||||
|
|
||||||
const isEnableReview = useMemo(() => {
|
const isEnableReview = useMemo(() => {
|
||||||
return !!(license.edition === 1 || license.edition === 2);
|
return PROFESSION_VERSION_PERMISSION.includes(license.edition!);
|
||||||
}, [license]);
|
}, [license.edition]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShowCommentsFilter(isEnableReview);
|
setShowCommentsFilter(isEnableReview);
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ import dayjs from 'dayjs';
|
||||||
import { ColumnType } from '@ctzhian/ui/dist/Table';
|
import { ColumnType } from '@ctzhian/ui/dist/Table';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { useAppSelector } from '@/store';
|
import { useAppSelector } from '@/store';
|
||||||
|
import { VersionCanUse } from '@/components/VersionMask';
|
||||||
|
import { PROFESSION_VERSION_PERMISSION } from '@/constant/version';
|
||||||
|
|
||||||
interface AddRoleProps {
|
interface AddRoleProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
|
@ -23,7 +25,8 @@ interface AddRoleProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddRole = ({ open, onCancel, onOk, selectedIds }: AddRoleProps) => {
|
const AddRole = ({ open, onCancel, onOk, selectedIds }: AddRoleProps) => {
|
||||||
const { kb_id, license } = useAppSelector(state => state.config);
|
const { kb_id } = useAppSelector(state => state.config);
|
||||||
|
const { license } = useAppSelector(state => state.config);
|
||||||
const [list, setList] = useState<V1UserListItemResp[]>([]);
|
const [list, setList] = useState<V1UserListItemResp[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState<string>('');
|
const [selectedRowKeys, setSelectedRowKeys] = useState<string>('');
|
||||||
|
|
@ -31,10 +34,6 @@ const AddRole = ({ open, onCancel, onOk, selectedIds }: AddRoleProps) => {
|
||||||
ConstsUserKBPermission.UserKBPermissionFullControl,
|
ConstsUserKBPermission.UserKBPermissionFullControl,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isEnterprise = useMemo(() => {
|
|
||||||
return license.edition === 2;
|
|
||||||
}, [license]);
|
|
||||||
|
|
||||||
const columns: ColumnType<V1UserListItemResp>[] = [
|
const columns: ColumnType<V1UserListItemResp>[] = [
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
|
|
@ -119,6 +118,10 @@ const AddRole = ({ open, onCancel, onOk, selectedIds }: AddRoleProps) => {
|
||||||
}
|
}
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
|
const isPro = useMemo(() => {
|
||||||
|
return PROFESSION_VERSION_PERMISSION.includes(license.edition!);
|
||||||
|
}, [license.edition]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title='添加 Wiki 站管理员'
|
title='添加 Wiki 站管理员'
|
||||||
|
|
@ -209,22 +212,33 @@ const AddRole = ({ open, onCancel, onOk, selectedIds }: AddRoleProps) => {
|
||||||
fullWidth
|
fullWidth
|
||||||
sx={{ height: 52 }}
|
sx={{ height: 52 }}
|
||||||
value={perm}
|
value={perm}
|
||||||
|
MenuProps={{
|
||||||
|
sx: {
|
||||||
|
'.Mui-disabled': {
|
||||||
|
opacity: 1,
|
||||||
|
color: 'text.disabled',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
onChange={e => setPerm(e.target.value as V1KBUserInviteReq['perm'])}
|
onChange={e => setPerm(e.target.value as V1KBUserInviteReq['perm'])}
|
||||||
>
|
>
|
||||||
<MenuItem value={ConstsUserKBPermission.UserKBPermissionFullControl}>
|
<MenuItem value={ConstsUserKBPermission.UserKBPermissionFullControl}>
|
||||||
完全控制
|
完全控制
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
disabled={!isEnterprise}
|
|
||||||
value={ConstsUserKBPermission.UserKBPermissionDocManage}
|
value={ConstsUserKBPermission.UserKBPermissionDocManage}
|
||||||
|
disabled={!isPro}
|
||||||
>
|
>
|
||||||
文档管理 {isEnterprise ? '' : '(企业版可用)'}
|
文档管理{' '}
|
||||||
|
<VersionCanUse permission={PROFESSION_VERSION_PERMISSION} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
disabled={!isEnterprise}
|
|
||||||
value={ConstsUserKBPermission.UserKBPermissionDataOperate}
|
value={ConstsUserKBPermission.UserKBPermissionDataOperate}
|
||||||
|
disabled={!isPro}
|
||||||
>
|
>
|
||||||
数据运营 {isEnterprise ? '' : '(企业版可用)'}
|
数据运营{' '}
|
||||||
|
<VersionCanUse permission={PROFESSION_VERSION_PERMISSION} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { getApiProV1Prompt, postApiProV1Prompt } from '@/request/pro/Prompt';
|
import { getApiProV1Prompt, postApiProV1Prompt } from '@/request/pro/Prompt';
|
||||||
import { DomainKnowledgeBaseDetail } from '@/request/types';
|
import { DomainKnowledgeBaseDetail } from '@/request/types';
|
||||||
|
import { PROFESSION_VERSION_PERMISSION } from '@/constant/version';
|
||||||
import { useAppSelector } from '@/store';
|
import { useAppSelector } from '@/store';
|
||||||
import { message } from '@ctzhian/ui';
|
import { message } from '@ctzhian/ui';
|
||||||
import { Box, Slider, TextField } from '@mui/material';
|
import { Box, Slider, TextField } from '@mui/material';
|
||||||
|
|
@ -33,11 +34,12 @@ const CardAI = ({ kb }: CardAIProps) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const isPro = useMemo(() => {
|
const isPro = useMemo(() => {
|
||||||
return license.edition === 1 || license.edition === 2;
|
return PROFESSION_VERSION_PERMISSION.includes(license.edition!);
|
||||||
}, [license]);
|
}, [license]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!kb.id || !isPro) return;
|
if (!kb.id || !PROFESSION_VERSION_PERMISSION.includes(license.edition!))
|
||||||
|
return;
|
||||||
getApiProV1Prompt({ kb_id: kb.id! }).then(res => {
|
getApiProV1Prompt({ kb_id: kb.id! }).then(res => {
|
||||||
setValue('content', res.content || '');
|
setValue('content', res.content || '');
|
||||||
});
|
});
|
||||||
|
|
@ -54,7 +56,7 @@ const CardAI = ({ kb }: CardAIProps) => {
|
||||||
<SettingCardItem title='智能问答' isEdit={isEdit} onSubmit={onSubmit}>
|
<SettingCardItem title='智能问答' isEdit={isEdit} onSubmit={onSubmit}>
|
||||||
<FormItem
|
<FormItem
|
||||||
vertical
|
vertical
|
||||||
tooltip={!isPro && '联创版和企业版可用'}
|
permission={PROFESSION_VERSION_PERMISSION}
|
||||||
extra={
|
extra={
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ import { ColumnType } from '@ctzhian/ui/dist/Table';
|
||||||
import { useEffect, useMemo, useState, useRef } from 'react';
|
import { useEffect, useMemo, useState, useRef } from 'react';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { useAppSelector } from '@/store';
|
import { useAppSelector } from '@/store';
|
||||||
|
import { BUSINESS_VERSION_PERMISSION } from '@/constant/version';
|
||||||
|
import { VersionCanUse } from '@/components/VersionMask';
|
||||||
import { SettingCardItem, FormItem } from './Common';
|
import { SettingCardItem, FormItem } from './Common';
|
||||||
|
|
||||||
interface CardAuthProps {
|
interface CardAuthProps {
|
||||||
|
|
@ -114,7 +116,7 @@ const CardAuth = ({ kb, refresh }: CardAuthProps) => {
|
||||||
}),
|
}),
|
||||||
value.enabled === '2' &&
|
value.enabled === '2' &&
|
||||||
source_type !== EXTEND_CONSTS_SOURCE_TYPE.SourceTypePassword
|
source_type !== EXTEND_CONSTS_SOURCE_TYPE.SourceTypePassword
|
||||||
? isPro
|
? isBusiness
|
||||||
? postApiProV1AuthSet({
|
? postApiProV1AuthSet({
|
||||||
kb_id,
|
kb_id,
|
||||||
source_type: value.source_type as ConstsSourceType,
|
source_type: value.source_type as ConstsSourceType,
|
||||||
|
|
@ -157,25 +159,18 @@ const CardAuth = ({ kb, refresh }: CardAuthProps) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const isPro = useMemo(() => {
|
const isBusiness = useMemo(() => {
|
||||||
return license.edition === 1 || license.edition === 2;
|
return BUSINESS_VERSION_PERMISSION.includes(license.edition!);
|
||||||
}, [license]);
|
|
||||||
|
|
||||||
const isEnterprise = useMemo(() => {
|
|
||||||
return license.edition === 2;
|
|
||||||
}, [license]);
|
}, [license]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const source_type = isPro
|
const source_type = isBusiness
|
||||||
? kb.access_settings?.source_type ||
|
? kb.access_settings?.source_type ||
|
||||||
EXTEND_CONSTS_SOURCE_TYPE.SourceTypePassword
|
EXTEND_CONSTS_SOURCE_TYPE.SourceTypePassword
|
||||||
: kb.access_settings?.source_type ===
|
|
||||||
EXTEND_CONSTS_SOURCE_TYPE.SourceTypeGitHub
|
|
||||||
? EXTEND_CONSTS_SOURCE_TYPE.SourceTypeGitHub
|
|
||||||
: EXTEND_CONSTS_SOURCE_TYPE.SourceTypePassword;
|
: EXTEND_CONSTS_SOURCE_TYPE.SourceTypePassword;
|
||||||
setValue('source_type', source_type);
|
setValue('source_type', source_type);
|
||||||
sourceTypeRef.current = source_type;
|
sourceTypeRef.current = source_type;
|
||||||
}, [kb, isPro]);
|
}, [kb, isBusiness]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (kb.access_settings?.simple_auth) {
|
if (kb.access_settings?.simple_auth) {
|
||||||
|
|
@ -191,7 +186,7 @@ const CardAuth = ({ kb, refresh }: CardAuthProps) => {
|
||||||
}, [kb]);
|
}, [kb]);
|
||||||
|
|
||||||
const getAuth = () => {
|
const getAuth = () => {
|
||||||
if (isPro) {
|
if (isBusiness) {
|
||||||
getApiProV1AuthGet({
|
getApiProV1AuthGet({
|
||||||
kb_id,
|
kb_id,
|
||||||
source_type: source_type as ConstsSourceType,
|
source_type: source_type as ConstsSourceType,
|
||||||
|
|
@ -236,7 +231,7 @@ const CardAuth = ({ kb, refresh }: CardAuthProps) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!kb_id || enabled !== '2') return;
|
if (!kb_id || enabled !== '2') return;
|
||||||
getAuth();
|
getAuth();
|
||||||
}, [kb_id, isPro, source_type, enabled]);
|
}, [kb_id, isBusiness, source_type, enabled]);
|
||||||
|
|
||||||
const columns: ColumnType<GithubComChaitinPandaWikiProApiAuthV1AuthItem>[] = [
|
const columns: ColumnType<GithubComChaitinPandaWikiProApiAuthV1AuthItem>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -875,8 +870,18 @@ const CardAuth = ({ kb, refresh }: CardAuthProps) => {
|
||||||
field.onChange(e.target.value);
|
field.onChange(e.target.value);
|
||||||
setIsEdit(true);
|
setIsEdit(true);
|
||||||
}}
|
}}
|
||||||
|
MenuProps={{
|
||||||
|
sx: {
|
||||||
|
'.Mui-disabled': {
|
||||||
|
opacity: 1,
|
||||||
|
color: 'text.disabled',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
fullWidth
|
fullWidth
|
||||||
sx={{ height: 52 }}
|
sx={{
|
||||||
|
height: 52,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypePassword}
|
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypePassword}
|
||||||
|
|
@ -885,44 +890,52 @@ const CardAuth = ({ kb, refresh }: CardAuthProps) => {
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeDingTalk}
|
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeDingTalk}
|
||||||
disabled={!isPro}
|
disabled={!isBusiness}
|
||||||
>
|
>
|
||||||
钉钉登录 {isPro ? '' : tips}
|
钉钉登录{' '}
|
||||||
|
<VersionCanUse permission={BUSINESS_VERSION_PERMISSION} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeFeishu}
|
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeFeishu}
|
||||||
disabled={!isPro}
|
disabled={!isBusiness}
|
||||||
>
|
>
|
||||||
飞书登录 {isPro ? '' : tips}
|
飞书登录{' '}
|
||||||
|
<VersionCanUse permission={BUSINESS_VERSION_PERMISSION} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeWeCom}
|
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeWeCom}
|
||||||
disabled={!isPro}
|
disabled={!isBusiness}
|
||||||
>
|
>
|
||||||
企业微信登录 {isPro ? '' : tips}
|
企业微信登录{' '}
|
||||||
|
<VersionCanUse permission={BUSINESS_VERSION_PERMISSION} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeOAuth}
|
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeOAuth}
|
||||||
disabled={!isPro}
|
disabled={!isBusiness}
|
||||||
>
|
>
|
||||||
OAuth 登录 {isPro ? '' : tips}
|
OAuth 登录{' '}
|
||||||
|
<VersionCanUse permission={BUSINESS_VERSION_PERMISSION} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeCAS}
|
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeCAS}
|
||||||
disabled={!isPro}
|
disabled={!isBusiness}
|
||||||
>
|
>
|
||||||
CAS 登录 {isPro ? '' : tips}
|
CAS 登录{' '}
|
||||||
|
<VersionCanUse permission={BUSINESS_VERSION_PERMISSION} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeLDAP}
|
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeLDAP}
|
||||||
disabled={!isPro}
|
disabled={!isBusiness}
|
||||||
>
|
>
|
||||||
LDAP 登录 {isPro ? '' : tips}
|
LDAP 登录{' '}
|
||||||
|
<VersionCanUse permission={BUSINESS_VERSION_PERMISSION} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeGitHub}
|
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeGitHub}
|
||||||
|
disabled={!isBusiness}
|
||||||
>
|
>
|
||||||
GitHub 登录
|
GitHub 登录{' '}
|
||||||
|
<VersionCanUse permission={BUSINESS_VERSION_PERMISSION} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import {
|
||||||
DomainKnowledgeBaseDetail,
|
DomainKnowledgeBaseDetail,
|
||||||
} from '@/request/types';
|
} from '@/request/types';
|
||||||
import { useAppSelector } from '@/store';
|
import { useAppSelector } from '@/store';
|
||||||
import InfoIcon from '@mui/icons-material/Info';
|
import { PROFESSION_VERSION_PERMISSION } from '@/constant/version';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Chip,
|
Chip,
|
||||||
|
|
@ -12,7 +12,6 @@ import {
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
styled,
|
styled,
|
||||||
TextField,
|
TextField,
|
||||||
Tooltip,
|
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
import { getApiV1AppDetail, putApiV1App } from '@/request/App';
|
import { getApiV1AppDetail, putApiV1App } from '@/request/App';
|
||||||
|
|
@ -37,7 +36,7 @@ const DocumentComments = ({
|
||||||
data: DomainAppDetailResp;
|
data: DomainAppDetailResp;
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { license, kb_id } = useAppSelector(state => state.config);
|
const { kb_id } = useAppSelector(state => state.config);
|
||||||
const [isEdit, setIsEdit] = useState(false);
|
const [isEdit, setIsEdit] = useState(false);
|
||||||
const { control, handleSubmit, setValue } = useForm({
|
const { control, handleSubmit, setValue } = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
|
@ -57,8 +56,6 @@ const DocumentComments = ({
|
||||||
);
|
);
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
const isPro = license.edition === 1 || license.edition === 2;
|
|
||||||
|
|
||||||
const onSubmit = handleSubmit(formData => {
|
const onSubmit = handleSubmit(formData => {
|
||||||
putApiV1App(
|
putApiV1App(
|
||||||
{ id: data.id! },
|
{ id: data.id! },
|
||||||
|
|
@ -108,7 +105,7 @@ const DocumentComments = ({
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem label='评论审核' tooltip={!isPro && '联创版和企业版可用'}>
|
<FormItem label='评论审核' permission={PROFESSION_VERSION_PERMISSION}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name='moderation_enable'
|
name='moderation_enable'
|
||||||
|
|
@ -116,7 +113,6 @@ const DocumentComments = ({
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
row
|
row
|
||||||
{...field}
|
{...field}
|
||||||
value={isPro ? field.value : undefined}
|
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
setIsEdit(true);
|
setIsEdit(true);
|
||||||
field.onChange(+e.target.value as 1 | 0);
|
field.onChange(+e.target.value as 1 | 0);
|
||||||
|
|
@ -124,12 +120,12 @@ const DocumentComments = ({
|
||||||
>
|
>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={1}
|
value={1}
|
||||||
control={<Radio size='small' disabled={!isPro} />}
|
control={<Radio size='small' />}
|
||||||
label={<StyledRadioLabel>启用</StyledRadioLabel>}
|
label={<StyledRadioLabel>启用</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={0}
|
value={0}
|
||||||
control={<Radio size='small' disabled={!isPro} />}
|
control={<Radio size='small' />}
|
||||||
label={<StyledRadioLabel>禁用</StyledRadioLabel>}
|
label={<StyledRadioLabel>禁用</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
@ -150,7 +146,7 @@ const AIQuestion = ({
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [isEdit, setIsEdit] = useState(false);
|
const [isEdit, setIsEdit] = useState(false);
|
||||||
const { kb_id, license } = useAppSelector(state => state.config);
|
const { kb_id } = useAppSelector(state => state.config);
|
||||||
const { control, handleSubmit, setValue } = useForm({
|
const { control, handleSubmit, setValue } = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
is_enabled: true,
|
is_enabled: true,
|
||||||
|
|
@ -159,7 +155,6 @@ const AIQuestion = ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const [inputValue, setInputValue] = useState('');
|
const [inputValue, setInputValue] = useState('');
|
||||||
const isEnterprise = license.edition === 2;
|
|
||||||
|
|
||||||
const onSubmit = handleSubmit(formData => {
|
const onSubmit = handleSubmit(formData => {
|
||||||
putApiV1App(
|
putApiV1App(
|
||||||
|
|
@ -273,7 +268,7 @@ const AIQuestion = ({
|
||||||
)}
|
)}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem label='免责声明' tooltip={!isEnterprise && '企业版可用'}>
|
<FormItem label='免责声明' permission={PROFESSION_VERSION_PERMISSION}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name='disclaimer'
|
name='disclaimer'
|
||||||
|
|
@ -282,7 +277,6 @@ const AIQuestion = ({
|
||||||
{...field}
|
{...field}
|
||||||
fullWidth
|
fullWidth
|
||||||
value={field.value || ''}
|
value={field.value || ''}
|
||||||
disabled={!isEnterprise}
|
|
||||||
placeholder='请输入免责声明'
|
placeholder='请输入免责声明'
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
setIsEdit(true);
|
setIsEdit(true);
|
||||||
|
|
@ -304,7 +298,7 @@ const DocumentContribution = ({
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [isEdit, setIsEdit] = useState(false);
|
const [isEdit, setIsEdit] = useState(false);
|
||||||
const { license, kb_id } = useAppSelector(state => state.config);
|
const { kb_id } = useAppSelector(state => state.config);
|
||||||
const { control, handleSubmit, setValue } = useForm({
|
const { control, handleSubmit, setValue } = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
is_enable: false,
|
is_enable: false,
|
||||||
|
|
@ -330,7 +324,6 @@ const DocumentContribution = ({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const isPro = license.edition === 1 || license.edition === 2;
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue(
|
setValue(
|
||||||
'is_enable',
|
'is_enable',
|
||||||
|
|
@ -340,21 +333,8 @@ const DocumentContribution = ({
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingCardItem
|
<SettingCardItem title='文档贡献' isEdit={isEdit} onSubmit={onSubmit}>
|
||||||
title={
|
<FormItem label='文档贡献' permission={PROFESSION_VERSION_PERMISSION}>
|
||||||
<>
|
|
||||||
文档贡献
|
|
||||||
{!isPro && (
|
|
||||||
<Tooltip title='联创版和企业版可用' placement='top' arrow>
|
|
||||||
<InfoIcon sx={{ color: 'text.secondary', fontSize: 14 }} />
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
isEdit={isEdit}
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
>
|
|
||||||
<FormItem label='文档贡献'>
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name='is_enable'
|
name='is_enable'
|
||||||
|
|
@ -362,7 +342,7 @@ const DocumentContribution = ({
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
row
|
row
|
||||||
{...field}
|
{...field}
|
||||||
value={isPro ? field.value : undefined}
|
value={field.value}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
setIsEdit(true);
|
setIsEdit(true);
|
||||||
field.onChange(e.target.value === 'true');
|
field.onChange(e.target.value === 'true');
|
||||||
|
|
@ -370,12 +350,12 @@ const DocumentContribution = ({
|
||||||
>
|
>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={true}
|
value={true}
|
||||||
control={<Radio size='small' disabled={!isPro} />}
|
control={<Radio size='small' />}
|
||||||
label={<StyledRadioLabel>启用</StyledRadioLabel>}
|
label={<StyledRadioLabel>启用</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={false}
|
value={false}
|
||||||
control={<Radio size='small' disabled={!isPro} />}
|
control={<Radio size='small' />}
|
||||||
label={<StyledRadioLabel>禁用</StyledRadioLabel>}
|
label={<StyledRadioLabel>禁用</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,10 @@ import { Controller, useForm } from 'react-hook-form';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import AddRole from './AddRole';
|
import AddRole from './AddRole';
|
||||||
import { Form, FormItem, SettingCardItem } from './Common';
|
import { Form, FormItem, SettingCardItem } from './Common';
|
||||||
|
import {
|
||||||
|
PROFESSION_VERSION_PERMISSION,
|
||||||
|
BUSINESS_VERSION_PERMISSION,
|
||||||
|
} from '@/constant/version';
|
||||||
|
|
||||||
type ApiTokenPermission =
|
type ApiTokenPermission =
|
||||||
GithubComChaitinPandaWikiProApiTokenV1CreateAPITokenReq['permission'];
|
GithubComChaitinPandaWikiProApiTokenV1CreateAPITokenReq['permission'];
|
||||||
|
|
@ -69,8 +73,8 @@ const ApiToken = () => {
|
||||||
perm: ConstsUserKBPermission.UserKBPermissionFullControl,
|
perm: ConstsUserKBPermission.UserKBPermissionFullControl,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const isEnterprise = useMemo(() => {
|
const isBusiness = useMemo(() => {
|
||||||
return license.edition === 2;
|
return BUSINESS_VERSION_PERMISSION.includes(license.edition!);
|
||||||
}, [license]);
|
}, [license]);
|
||||||
|
|
||||||
const onDeleteApiToken = (id: string, name: string) => {
|
const onDeleteApiToken = (id: string, name: string) => {
|
||||||
|
|
@ -131,9 +135,9 @@ const ApiToken = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!kb_id) return;
|
if (!kb_id || !isBusiness) return;
|
||||||
getApiTokenList();
|
getApiTokenList();
|
||||||
}, [kb_id]);
|
}, [kb_id, isBusiness]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!addOpen) reset();
|
if (!addOpen) reset();
|
||||||
|
|
@ -142,27 +146,17 @@ const ApiToken = () => {
|
||||||
return (
|
return (
|
||||||
<SettingCardItem
|
<SettingCardItem
|
||||||
title='API Token'
|
title='API Token'
|
||||||
|
permission={BUSINESS_VERSION_PERMISSION}
|
||||||
extra={
|
extra={
|
||||||
<Stack direction={'row'} alignItems={'center'}>
|
<Stack direction={'row'} alignItems={'center'}>
|
||||||
<Button
|
<Button
|
||||||
color='primary'
|
color='primary'
|
||||||
size='small'
|
size='small'
|
||||||
disabled={!isEnterprise}
|
|
||||||
onClick={() => setAddOpen(true)}
|
onClick={() => setAddOpen(true)}
|
||||||
sx={{ textTransform: 'none' }}
|
sx={{ textTransform: 'none' }}
|
||||||
>
|
>
|
||||||
创建 API Token
|
创建 API Token
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Tooltip title={'企业版可用'} placement='top' arrow>
|
|
||||||
<InfoIcon
|
|
||||||
sx={{
|
|
||||||
color: 'text.secondary',
|
|
||||||
fontSize: 14,
|
|
||||||
display: !isEnterprise ? 'block' : 'none',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -232,7 +226,7 @@ const ApiToken = () => {
|
||||||
size='small'
|
size='small'
|
||||||
sx={{ width: 120 }}
|
sx={{ width: 120 }}
|
||||||
value={it.permission}
|
value={it.permission}
|
||||||
disabled={!isEnterprise || user.role !== 'admin'}
|
disabled={!isBusiness || user.role !== 'admin'}
|
||||||
onChange={e =>
|
onChange={e =>
|
||||||
onUpdateApiToken(it.id!, e.target.value as ApiTokenPermission)
|
onUpdateApiToken(it.id!, e.target.value as ApiTokenPermission)
|
||||||
}
|
}
|
||||||
|
|
@ -259,7 +253,7 @@ const ApiToken = () => {
|
||||||
kbDetail?.perm !==
|
kbDetail?.perm !==
|
||||||
ConstsUserKBPermission.UserKBPermissionFullControl
|
ConstsUserKBPermission.UserKBPermissionFullControl
|
||||||
? '权限不足'
|
? '权限不足'
|
||||||
: '企业版可用'
|
: '商业版可用'
|
||||||
}
|
}
|
||||||
placement='top'
|
placement='top'
|
||||||
arrow
|
arrow
|
||||||
|
|
@ -270,7 +264,7 @@ const ApiToken = () => {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
ml: 1,
|
ml: 1,
|
||||||
visibility:
|
visibility:
|
||||||
!isEnterprise ||
|
!isBusiness ||
|
||||||
kbDetail?.perm !==
|
kbDetail?.perm !==
|
||||||
ConstsUserKBPermission.UserKBPermissionFullControl
|
ConstsUserKBPermission.UserKBPermissionFullControl
|
||||||
? 'visible'
|
? 'visible'
|
||||||
|
|
@ -285,13 +279,13 @@ const ApiToken = () => {
|
||||||
type='icon-icon_tool_close'
|
type='icon-icon_tool_close'
|
||||||
sx={{
|
sx={{
|
||||||
cursor:
|
cursor:
|
||||||
!isEnterprise ||
|
!isBusiness ||
|
||||||
kbDetail?.perm !==
|
kbDetail?.perm !==
|
||||||
ConstsUserKBPermission.UserKBPermissionFullControl
|
ConstsUserKBPermission.UserKBPermissionFullControl
|
||||||
? 'not-allowed'
|
? 'not-allowed'
|
||||||
: 'pointer',
|
: 'pointer',
|
||||||
color:
|
color:
|
||||||
!isEnterprise ||
|
!isBusiness ||
|
||||||
kbDetail?.perm !==
|
kbDetail?.perm !==
|
||||||
ConstsUserKBPermission.UserKBPermissionFullControl
|
ConstsUserKBPermission.UserKBPermissionFullControl
|
||||||
? 'text.disabled'
|
? 'text.disabled'
|
||||||
|
|
@ -299,7 +293,7 @@ const ApiToken = () => {
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (
|
if (
|
||||||
!isEnterprise ||
|
!isBusiness ||
|
||||||
kbDetail?.perm !==
|
kbDetail?.perm !==
|
||||||
ConstsUserKBPermission.UserKBPermissionFullControl
|
ConstsUserKBPermission.UserKBPermissionFullControl
|
||||||
)
|
)
|
||||||
|
|
@ -367,17 +361,16 @@ const ApiToken = () => {
|
||||||
>
|
>
|
||||||
完全控制
|
完全控制
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
disabled={!isEnterprise}
|
|
||||||
value={ConstsUserKBPermission.UserKBPermissionDocManage}
|
value={ConstsUserKBPermission.UserKBPermissionDocManage}
|
||||||
>
|
>
|
||||||
文档管理 {isEnterprise ? '' : '(企业版可用)'}
|
文档管理
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
disabled={!isEnterprise}
|
|
||||||
value={ConstsUserKBPermission.UserKBPermissionDataOperate}
|
value={ConstsUserKBPermission.UserKBPermissionDataOperate}
|
||||||
>
|
>
|
||||||
数据运营 {isEnterprise ? '' : '(企业版可用)'}
|
数据运营
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
|
|
@ -405,9 +398,9 @@ const CardKB = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const isEnterprise = useMemo(() => {
|
const isPro = useMemo(() => {
|
||||||
return license.edition === 2;
|
return PROFESSION_VERSION_PERMISSION.includes(license.edition!);
|
||||||
}, [license]);
|
}, [license.edition]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!kb_id) return;
|
if (!kb_id) return;
|
||||||
|
|
@ -513,7 +506,7 @@ const CardKB = () => {
|
||||||
size='small'
|
size='small'
|
||||||
sx={{ width: 180 }}
|
sx={{ width: 180 }}
|
||||||
value={it.perms}
|
value={it.perms}
|
||||||
disabled={!isEnterprise || it.role === 'admin'}
|
disabled={!isPro || it.role === 'admin'}
|
||||||
onChange={e =>
|
onChange={e =>
|
||||||
onUpdateUserPermission(
|
onUpdateUserPermission(
|
||||||
it.id!,
|
it.id!,
|
||||||
|
|
@ -542,7 +535,7 @@ const CardKB = () => {
|
||||||
title={
|
title={
|
||||||
it.role === 'admin'
|
it.role === 'admin'
|
||||||
? '超级管理员不可被修改权限'
|
? '超级管理员不可被修改权限'
|
||||||
: '企业版可用'
|
: '专业版可用'
|
||||||
}
|
}
|
||||||
placement='top'
|
placement='top'
|
||||||
arrow
|
arrow
|
||||||
|
|
@ -553,9 +546,7 @@ const CardKB = () => {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
ml: 1,
|
ml: 1,
|
||||||
visibility:
|
visibility:
|
||||||
!isEnterprise || it.role === 'admin'
|
!isPro || it.role === 'admin' ? 'visible' : 'hidden',
|
||||||
? 'visible'
|
|
||||||
: 'hidden',
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { DomainKnowledgeBaseDetail } from '@/request/types';
|
import { DomainKnowledgeBaseDetail } from '@/request/types';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
|
||||||
FormControl,
|
FormControl,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
Link,
|
Link,
|
||||||
|
|
@ -13,10 +12,11 @@ import {
|
||||||
import ShowText from '@/components/ShowText';
|
import ShowText from '@/components/ShowText';
|
||||||
import { getApiV1AppDetail, putApiV1App } from '@/request/App';
|
import { getApiV1AppDetail, putApiV1App } from '@/request/App';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { FormItem, SettingCardItem } from './Common';
|
import { FormItem, SettingCardItem } from './Common';
|
||||||
import { DomainAppDetailResp } from '@/request/types';
|
import { DomainAppDetailResp } from '@/request/types';
|
||||||
import { message } from '@ctzhian/ui';
|
import { message } from '@ctzhian/ui';
|
||||||
|
import { BUSINESS_VERSION_PERMISSION } from '@/constant/version';
|
||||||
import { useAppSelector } from '@/store';
|
import { useAppSelector } from '@/store';
|
||||||
|
|
||||||
const CardRobotApi = ({
|
const CardRobotApi = ({
|
||||||
|
|
@ -29,11 +29,6 @@ const CardRobotApi = ({
|
||||||
const [isEdit, setIsEdit] = useState(false);
|
const [isEdit, setIsEdit] = useState(false);
|
||||||
const [detail, setDetail] = useState<DomainAppDetailResp | null>(null);
|
const [detail, setDetail] = useState<DomainAppDetailResp | null>(null);
|
||||||
const { license } = useAppSelector(state => state.config);
|
const { license } = useAppSelector(state => state.config);
|
||||||
|
|
||||||
const isEnterprise = useMemo(() => {
|
|
||||||
return license.edition === 2;
|
|
||||||
}, [license]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
|
|
@ -114,10 +109,7 @@ const CardRobotApi = ({
|
||||||
}
|
}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
>
|
>
|
||||||
<FormItem
|
<FormItem label='问答机器人 API' permission={BUSINESS_VERSION_PERMISSION}>
|
||||||
label='问答机器人 API'
|
|
||||||
tooltip={!isEnterprise ? '企业版可用' : undefined}
|
|
||||||
>
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
|
|
@ -133,13 +125,11 @@ const CardRobotApi = ({
|
||||||
<Stack direction={'row'}>
|
<Stack direction={'row'}>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={true}
|
value={true}
|
||||||
disabled={!isEnterprise}
|
|
||||||
control={<Radio size='small' />}
|
control={<Radio size='small' />}
|
||||||
label={<Box sx={{ width: 100 }}>启用</Box>}
|
label={<Box sx={{ width: 100 }}>启用</Box>}
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={false}
|
value={false}
|
||||||
disabled={!isEnterprise}
|
|
||||||
control={<Radio size='small' />}
|
control={<Radio size='small' />}
|
||||||
label={<Box sx={{ width: 100 }}>禁用</Box>}
|
label={<Box sx={{ width: 100 }}>禁用</Box>}
|
||||||
/>
|
/>
|
||||||
|
|
@ -150,7 +140,7 @@ const CardRobotApi = ({
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
{isEnabled && (
|
{isEnabled && BUSINESS_VERSION_PERMISSION.includes(license.edition!) && (
|
||||||
<>
|
<>
|
||||||
<FormItem label='API Token' required>
|
<FormItem label='API Token' required>
|
||||||
<Controller
|
<Controller
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ import {
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { FormItem, SettingCardItem } from './Common';
|
import { FormItem, SettingCardItem } from './Common';
|
||||||
|
import VersionMask from '@/components/VersionMask';
|
||||||
|
import { PROFESSION_VERSION_PERMISSION } from '@/constant/version';
|
||||||
|
|
||||||
const CardRobotWecomService = ({
|
const CardRobotWecomService = ({
|
||||||
kb,
|
kb,
|
||||||
|
|
@ -262,6 +264,7 @@ const CardRobotWecomService = ({
|
||||||
<Icon type='icon-jinggao' sx={{ fontSize: 18 }} />
|
<Icon type='icon-jinggao' sx={{ fontSize: 18 }} />
|
||||||
人工客服转接配置:当用户触发以下场景时,会自动转接人工客服
|
人工客服转接配置:当用户触发以下场景时,会自动转接人工客服
|
||||||
</Stack>
|
</Stack>
|
||||||
|
<VersionMask permission={PROFESSION_VERSION_PERMISSION}>
|
||||||
<FormItem
|
<FormItem
|
||||||
label={
|
label={
|
||||||
<Box>
|
<Box>
|
||||||
|
|
@ -294,6 +297,7 @@ const CardRobotWecomService = ({
|
||||||
{...equalKeywordsField}
|
{...equalKeywordsField}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
</VersionMask>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</SettingCardItem>
|
</SettingCardItem>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import {
|
||||||
} from '@/request/types';
|
} from '@/request/types';
|
||||||
import { useAppSelector } from '@/store';
|
import { useAppSelector } from '@/store';
|
||||||
import { message } from '@ctzhian/ui';
|
import { message } from '@ctzhian/ui';
|
||||||
|
import { BUSINESS_VERSION_PERMISSION } from '@/constant/version';
|
||||||
import {
|
import {
|
||||||
Autocomplete,
|
Autocomplete,
|
||||||
Box,
|
Box,
|
||||||
|
|
@ -17,7 +18,7 @@ import {
|
||||||
TextField,
|
TextField,
|
||||||
styled,
|
styled,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { FormItem, SettingCardItem } from './Common';
|
import { FormItem, SettingCardItem } from './Common';
|
||||||
|
|
||||||
|
|
@ -32,15 +33,9 @@ const WatermarkForm = ({
|
||||||
data?: DomainAppDetailResp;
|
data?: DomainAppDetailResp;
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { license, kb_id } = useAppSelector(state => state.config);
|
const { kb_id } = useAppSelector(state => state.config);
|
||||||
const [watermarkIsEdit, setWatermarkIsEdit] = useState(false);
|
const [watermarkIsEdit, setWatermarkIsEdit] = useState(false);
|
||||||
const {
|
const { control, handleSubmit, setValue, watch } = useForm({
|
||||||
control,
|
|
||||||
handleSubmit,
|
|
||||||
setValue,
|
|
||||||
watch,
|
|
||||||
formState: { errors },
|
|
||||||
} = useForm({
|
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
watermark_setting: data?.settings?.watermark_setting ?? null,
|
watermark_setting: data?.settings?.watermark_setting ?? null,
|
||||||
watermark_content: data?.settings?.watermark_content ?? '',
|
watermark_content: data?.settings?.watermark_content ?? '',
|
||||||
|
|
@ -48,9 +43,6 @@ const WatermarkForm = ({
|
||||||
});
|
});
|
||||||
|
|
||||||
const watermarkSetting = watch('watermark_setting');
|
const watermarkSetting = watch('watermark_setting');
|
||||||
const isEnterprise = useMemo(() => {
|
|
||||||
return license.edition === 2;
|
|
||||||
}, [license]);
|
|
||||||
|
|
||||||
const handleSaveWatermark = handleSubmit(values => {
|
const handleSaveWatermark = handleSubmit(values => {
|
||||||
if (!data?.id || values.watermark_setting === null) return;
|
if (!data?.id || values.watermark_setting === null) return;
|
||||||
|
|
@ -82,8 +74,9 @@ const WatermarkForm = ({
|
||||||
title='水印'
|
title='水印'
|
||||||
isEdit={watermarkIsEdit}
|
isEdit={watermarkIsEdit}
|
||||||
onSubmit={handleSaveWatermark}
|
onSubmit={handleSaveWatermark}
|
||||||
|
permission={BUSINESS_VERSION_PERMISSION}
|
||||||
>
|
>
|
||||||
<FormItem label='水印开关' tooltip={!isEnterprise && '企业版可用'}>
|
<FormItem label='水印开关'>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name='watermark_setting'
|
name='watermark_setting'
|
||||||
|
|
@ -98,18 +91,18 @@ const WatermarkForm = ({
|
||||||
>
|
>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={ConstsWatermarkSetting.WatermarkVisible}
|
value={ConstsWatermarkSetting.WatermarkVisible}
|
||||||
control={<Radio size='small' disabled={!isEnterprise} />}
|
control={<Radio size='small' />}
|
||||||
label={<StyledRadioLabel>显性水印</StyledRadioLabel>}
|
label={<StyledRadioLabel>显性水印</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={ConstsWatermarkSetting.WatermarkHidden}
|
value={ConstsWatermarkSetting.WatermarkHidden}
|
||||||
control={<Radio size='small' disabled={!isEnterprise} />}
|
control={<Radio size='small' />}
|
||||||
label={<StyledRadioLabel>隐形水印</StyledRadioLabel>}
|
label={<StyledRadioLabel>隐形水印</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={ConstsWatermarkSetting.WatermarkDisabled}
|
value={ConstsWatermarkSetting.WatermarkDisabled}
|
||||||
control={<Radio size='small' disabled={!isEnterprise} />}
|
control={<Radio size='small' />}
|
||||||
label={<StyledRadioLabel>禁用</StyledRadioLabel>}
|
label={<StyledRadioLabel>禁用</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
@ -128,7 +121,6 @@ const WatermarkForm = ({
|
||||||
placeholder='请输入水印内容, 支持多行输入'
|
placeholder='请输入水印内容, 支持多行输入'
|
||||||
multiline
|
multiline
|
||||||
minRows={2}
|
minRows={2}
|
||||||
disabled={!isEnterprise}
|
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
setWatermarkIsEdit(true);
|
setWatermarkIsEdit(true);
|
||||||
field.onChange(e.target.value);
|
field.onChange(e.target.value);
|
||||||
|
|
@ -146,9 +138,6 @@ const KeywordsForm = ({ kb }: { kb: DomainKnowledgeBaseDetail }) => {
|
||||||
const { license } = useAppSelector(state => state.config);
|
const { license } = useAppSelector(state => state.config);
|
||||||
const [questionInputValue, setQuestionInputValue] = useState('');
|
const [questionInputValue, setQuestionInputValue] = useState('');
|
||||||
const [isEdit, setIsEdit] = useState(false);
|
const [isEdit, setIsEdit] = useState(false);
|
||||||
const isEnterprise = useMemo(() => {
|
|
||||||
return license.edition === 2;
|
|
||||||
}, [license]);
|
|
||||||
|
|
||||||
const { control, handleSubmit, setValue } = useForm({
|
const { control, handleSubmit, setValue } = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
|
@ -169,17 +158,18 @@ const KeywordsForm = ({ kb }: { kb: DomainKnowledgeBaseDetail }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!kb.id || !isEnterprise) return;
|
if (!kb.id || !BUSINESS_VERSION_PERMISSION.includes(license.edition!))
|
||||||
|
return;
|
||||||
getApiProV1Block({ kb_id: kb.id! }).then(res => {
|
getApiProV1Block({ kb_id: kb.id! }).then(res => {
|
||||||
setValue('block_words', res.words || []);
|
setValue('block_words', res.words || []);
|
||||||
});
|
});
|
||||||
}, [kb, isEnterprise]);
|
}, [kb, license.edition]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingCardItem title='内容合规' isEdit={isEdit} onSubmit={onSubmit}>
|
<SettingCardItem title='内容合规' isEdit={isEdit} onSubmit={onSubmit}>
|
||||||
<FormItem
|
<FormItem
|
||||||
vertical
|
vertical
|
||||||
tooltip={!isEnterprise && '企业版可用'}
|
permission={BUSINESS_VERSION_PERMISSION}
|
||||||
label='屏蔽 AI 问答中的关键字'
|
label='屏蔽 AI 问答中的关键字'
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
|
|
@ -193,7 +183,6 @@ const KeywordsForm = ({ kb }: { kb: DomainKnowledgeBaseDetail }) => {
|
||||||
inputValue={questionInputValue}
|
inputValue={questionInputValue}
|
||||||
options={[]}
|
options={[]}
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={!isEnterprise}
|
|
||||||
onInputChange={(_, value) => {
|
onInputChange={(_, value) => {
|
||||||
setQuestionInputValue(value);
|
setQuestionInputValue(value);
|
||||||
}}
|
}}
|
||||||
|
|
@ -234,23 +223,14 @@ const CopyForm = ({
|
||||||
data?: DomainAppDetailResp;
|
data?: DomainAppDetailResp;
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { license, kb_id } = useAppSelector(state => state.config);
|
const { kb_id } = useAppSelector(state => state.config);
|
||||||
const [isEdit, setIsEdit] = useState(false);
|
const [isEdit, setIsEdit] = useState(false);
|
||||||
const {
|
const { control, handleSubmit, setValue } = useForm({
|
||||||
control,
|
|
||||||
handleSubmit,
|
|
||||||
setValue,
|
|
||||||
formState: { errors },
|
|
||||||
} = useForm({
|
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
copy_setting: data?.settings?.copy_setting ?? null,
|
copy_setting: data?.settings?.copy_setting ?? null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const isEnterprise = useMemo(() => {
|
|
||||||
return license.edition === 2;
|
|
||||||
}, [license]);
|
|
||||||
|
|
||||||
const handleSaveWatermark = handleSubmit(values => {
|
const handleSaveWatermark = handleSubmit(values => {
|
||||||
if (!data?.id || values.copy_setting === null) return;
|
if (!data?.id || values.copy_setting === null) return;
|
||||||
putApiV1App(
|
putApiV1App(
|
||||||
|
|
@ -280,7 +260,7 @@ const CopyForm = ({
|
||||||
isEdit={isEdit}
|
isEdit={isEdit}
|
||||||
onSubmit={handleSaveWatermark}
|
onSubmit={handleSaveWatermark}
|
||||||
>
|
>
|
||||||
<FormItem label='限制复制' tooltip={!isEnterprise && '企业版可用'}>
|
<FormItem label='限制复制' permission={BUSINESS_VERSION_PERMISSION}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name='copy_setting'
|
name='copy_setting'
|
||||||
|
|
@ -295,18 +275,18 @@ const CopyForm = ({
|
||||||
>
|
>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={ConstsCopySetting.CopySettingNone}
|
value={ConstsCopySetting.CopySettingNone}
|
||||||
control={<Radio size='small' disabled={!isEnterprise} />}
|
control={<Radio size='small' />}
|
||||||
label={<StyledRadioLabel>不做限制</StyledRadioLabel>}
|
label={<StyledRadioLabel>不做限制</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={ConstsCopySetting.CopySettingAppend}
|
value={ConstsCopySetting.CopySettingAppend}
|
||||||
control={<Radio size='small' disabled={!isEnterprise} />}
|
control={<Radio size='small' />}
|
||||||
label={<StyledRadioLabel>增加内容尾巴</StyledRadioLabel>}
|
label={<StyledRadioLabel>增加内容尾巴</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={ConstsCopySetting.CopySettingDisabled}
|
value={ConstsCopySetting.CopySettingDisabled}
|
||||||
control={<Radio size='small' disabled={!isEnterprise} />}
|
control={<Radio size='small' />}
|
||||||
label={<StyledRadioLabel>禁止复制内容</StyledRadioLabel>}
|
label={<StyledRadioLabel>禁止复制内容</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ const CardWebSEO = ({ data, id, refresh }: CardWebSEOProps) => {
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
desc: '',
|
desc: '',
|
||||||
keyword: '',
|
keyword: '',
|
||||||
auto_sitemap: false,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -44,7 +43,6 @@ const CardWebSEO = ({ data, id, refresh }: CardWebSEOProps) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue('desc', data.settings?.desc || '');
|
setValue('desc', data.settings?.desc || '');
|
||||||
setValue('keyword', data.settings?.keyword || '');
|
setValue('keyword', data.settings?.keyword || '');
|
||||||
setValue('auto_sitemap', data.settings?.auto_sitemap ?? false);
|
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -88,25 +86,6 @@ const CardWebSEO = ({ data, id, refresh }: CardWebSEOProps) => {
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
<FormItem label='自动生成 Sitemap'>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name='auto_sitemap'
|
|
||||||
render={({ field }) => (
|
|
||||||
<Checkbox
|
|
||||||
{...field}
|
|
||||||
checked={field.value}
|
|
||||||
size='small'
|
|
||||||
sx={{ p: 0, m: 0 }}
|
|
||||||
onChange={event => {
|
|
||||||
setIsEdit(true);
|
|
||||||
field.onChange(event);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</FormItem>
|
|
||||||
</SettingCardItem>
|
</SettingCardItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
|
import { ConstsLicenseEdition } from '@/request/types';
|
||||||
import InfoIcon from '@mui/icons-material/Info';
|
import InfoIcon from '@mui/icons-material/Info';
|
||||||
import { Button, Stack, styled, SxProps, Tooltip } from '@mui/material';
|
import { Button, Stack, styled, SxProps, Tooltip } from '@mui/material';
|
||||||
import { createContext, useContext } from 'react';
|
import { createContext, useContext } from 'react';
|
||||||
|
import VersionMask from '@/components/VersionMask';
|
||||||
|
|
||||||
const StyledForm = styled('form')<{ gap?: number | string }>(
|
const StyledForm = styled('form')<{ gap?: number | string }>(
|
||||||
({ theme, gap = 2 }) => ({
|
({ theme, gap = 2 }) => ({
|
||||||
|
|
@ -40,6 +42,7 @@ const StyledFormLabel = styled('span')<{ required?: boolean }>(
|
||||||
|
|
||||||
export const StyledFormItem = styled('div')<{ vertical?: boolean }>(
|
export const StyledFormItem = styled('div')<{ vertical?: boolean }>(
|
||||||
({ theme, vertical }) => ({
|
({ theme, vertical }) => ({
|
||||||
|
position: 'relative',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: vertical ? 'flex-start' : 'center',
|
alignItems: vertical ? 'flex-start' : 'center',
|
||||||
flexDirection: vertical ? 'column' : 'row',
|
flexDirection: vertical ? 'column' : 'row',
|
||||||
|
|
@ -82,6 +85,7 @@ export const FormItem = ({
|
||||||
extra,
|
extra,
|
||||||
sx,
|
sx,
|
||||||
labelSx,
|
labelSx,
|
||||||
|
permission,
|
||||||
}: {
|
}: {
|
||||||
label?: string | React.ReactNode;
|
label?: string | React.ReactNode;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
|
@ -92,10 +96,13 @@ export const FormItem = ({
|
||||||
extra?: React.ReactNode;
|
extra?: React.ReactNode;
|
||||||
sx?: SxProps;
|
sx?: SxProps;
|
||||||
labelSx?: SxProps;
|
labelSx?: SxProps;
|
||||||
|
permission?: number[];
|
||||||
}) => {
|
}) => {
|
||||||
const { vertical: verticalContext, labelWidth: labelWidthContext } =
|
const { vertical: verticalContext, labelWidth: labelWidthContext } =
|
||||||
useContext(FormContext);
|
useContext(FormContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<VersionMask permission={permission}>
|
||||||
<StyledFormItem vertical={vertical || verticalContext} sx={sx}>
|
<StyledFormItem vertical={vertical || verticalContext} sx={sx}>
|
||||||
<StyledFormLabelWrapper
|
<StyledFormLabelWrapper
|
||||||
vertical={vertical || verticalContext}
|
vertical={vertical || verticalContext}
|
||||||
|
|
@ -106,7 +113,9 @@ export const FormItem = ({
|
||||||
<StyledFormLabel required={required}>{label}</StyledFormLabel>
|
<StyledFormLabel required={required}>{label}</StyledFormLabel>
|
||||||
{tooltip && typeof tooltip === 'string' ? (
|
{tooltip && typeof tooltip === 'string' ? (
|
||||||
<Tooltip title={tooltip} placement='top' arrow>
|
<Tooltip title={tooltip} placement='top' arrow>
|
||||||
<InfoIcon sx={{ color: 'text.secondary', fontSize: 14, ml: 1 }} />
|
<InfoIcon
|
||||||
|
sx={{ color: 'text.secondary', fontSize: 14, ml: 1 }}
|
||||||
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
tooltip
|
tooltip
|
||||||
|
|
@ -117,6 +126,7 @@ export const FormItem = ({
|
||||||
</StyledFormLabelWrapper>
|
</StyledFormLabelWrapper>
|
||||||
{children}
|
{children}
|
||||||
</StyledFormItem>
|
</StyledFormItem>
|
||||||
|
</VersionMask>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -142,6 +152,7 @@ export const SettingCard = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledSettingCardItem = styled('div')(({ theme }) => ({
|
const StyledSettingCardItem = styled('div')(({ theme }) => ({
|
||||||
|
position: 'relative',
|
||||||
'&:not(:last-child)': {
|
'&:not(:last-child)': {
|
||||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||||
paddingBottom: theme.spacing(4),
|
paddingBottom: theme.spacing(4),
|
||||||
|
|
@ -204,6 +215,12 @@ export const SettingCardItem = ({
|
||||||
extra,
|
extra,
|
||||||
more,
|
more,
|
||||||
sx,
|
sx,
|
||||||
|
permission = [
|
||||||
|
ConstsLicenseEdition.LicenseEditionFree,
|
||||||
|
ConstsLicenseEdition.LicenseEditionProfession,
|
||||||
|
ConstsLicenseEdition.LicenseEditionBusiness,
|
||||||
|
ConstsLicenseEdition.LicenseEditionEnterprise,
|
||||||
|
],
|
||||||
}: {
|
}: {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
title?: React.ReactNode;
|
title?: React.ReactNode;
|
||||||
|
|
@ -212,6 +229,7 @@ export const SettingCardItem = ({
|
||||||
extra?: React.ReactNode;
|
extra?: React.ReactNode;
|
||||||
more?: SettingCardItemMore;
|
more?: SettingCardItemMore;
|
||||||
sx?: SxProps;
|
sx?: SxProps;
|
||||||
|
permission?: number[];
|
||||||
}) => {
|
}) => {
|
||||||
const renderMore = (more: SettingCardItemMore) => {
|
const renderMore = (more: SettingCardItemMore) => {
|
||||||
if (more && typeof more === 'object' && 'type' in more) {
|
if (more && typeof more === 'object' && 'type' in more) {
|
||||||
|
|
@ -237,7 +255,9 @@ export const SettingCardItem = ({
|
||||||
return more;
|
return more;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<VersionMask permission={permission}>
|
||||||
<StyledSettingCardItem sx={sx}>
|
<StyledSettingCardItem sx={sx}>
|
||||||
<StyledSettingCardItemTitleWrapper>
|
<StyledSettingCardItemTitleWrapper>
|
||||||
<StyledSettingCardItemTitle>
|
<StyledSettingCardItemTitle>
|
||||||
|
|
@ -252,5 +272,6 @@ export const SettingCardItem = ({
|
||||||
</StyledSettingCardItemTitleWrapper>
|
</StyledSettingCardItemTitleWrapper>
|
||||||
<StyledSettingCardItemContent>{children}</StyledSettingCardItemContent>
|
<StyledSettingCardItemContent>{children}</StyledSettingCardItemContent>
|
||||||
</StyledSettingCardItem>
|
</StyledSettingCardItem>
|
||||||
|
</VersionMask>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { SettingCardItem } from '../Common';
|
import { SettingCardItem } from '../Common';
|
||||||
import { Tooltip } from '@mui/material';
|
|
||||||
import InfoIcon from '@mui/icons-material/Info';
|
|
||||||
import { Modal, message } from '@ctzhian/ui';
|
import { Modal, message } from '@ctzhian/ui';
|
||||||
import { Stack, Button } from '@mui/material';
|
|
||||||
import { Box } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import { postApiProV1AuthGroupSync } from '@/request/pro/AuthOrg';
|
import { postApiProV1AuthGroupSync } from '@/request/pro/AuthOrg';
|
||||||
import {
|
import {
|
||||||
|
|
@ -19,6 +16,7 @@ import {
|
||||||
deleteApiProV1AuthGroupDelete,
|
deleteApiProV1AuthGroupDelete,
|
||||||
} from '@/request/pro/AuthGroup';
|
} from '@/request/pro/AuthGroup';
|
||||||
import GroupTree from './GroupTree';
|
import GroupTree from './GroupTree';
|
||||||
|
import { BUSINESS_VERSION_PERMISSION } from '@/constant/version';
|
||||||
|
|
||||||
interface UserGroupProps {
|
interface UserGroupProps {
|
||||||
enabled: string;
|
enabled: string;
|
||||||
|
|
@ -45,10 +43,6 @@ const UserGroup = ({
|
||||||
GithubComChaitinPandaWikiProApiAuthV1AuthGroupTreeItem[]
|
GithubComChaitinPandaWikiProApiAuthV1AuthGroupTreeItem[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
const isEnterprise = useMemo(() => {
|
|
||||||
return license.edition === 2;
|
|
||||||
}, [license]);
|
|
||||||
|
|
||||||
const onDeleteUserGroup = (id: number) => {
|
const onDeleteUserGroup = (id: number) => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '删除用户组',
|
title: '删除用户组',
|
||||||
|
|
@ -74,10 +68,15 @@ const UserGroup = ({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!kb_id || enabled !== '2' || !isEnterprise) return;
|
if (
|
||||||
|
!kb_id ||
|
||||||
|
enabled !== '2' ||
|
||||||
|
!BUSINESS_VERSION_PERMISSION.includes(license.edition!)
|
||||||
|
)
|
||||||
|
return;
|
||||||
getUserGroup();
|
getUserGroup();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [kb_id, enabled, isEnterprise]);
|
}, [kb_id, enabled, license.edition!]);
|
||||||
|
|
||||||
const handleMove = async ({
|
const handleMove = async ({
|
||||||
id,
|
id,
|
||||||
|
|
@ -123,32 +122,7 @@ const UserGroup = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingCardItem
|
<SettingCardItem title='用户组' permission={BUSINESS_VERSION_PERMISSION}>
|
||||||
title='用户组'
|
|
||||||
more={
|
|
||||||
!isEnterprise && (
|
|
||||||
<Tooltip title='企业版可用' placement='top' arrow>
|
|
||||||
<InfoIcon sx={{ color: 'text.secondary', fontSize: 14, ml: 1 }} />
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// extra={
|
|
||||||
// isEnterprise &&
|
|
||||||
// [
|
|
||||||
// ConstsSourceType.SourceTypeWeCom,
|
|
||||||
// ConstsSourceType.SourceTypeDingTalk,
|
|
||||||
// ].includes(sourceType as ConstsSourceType) && (
|
|
||||||
// <Button
|
|
||||||
// color='primary'
|
|
||||||
// size='small'
|
|
||||||
// onClick={handleSync}
|
|
||||||
// loading={syncLoading}
|
|
||||||
// >
|
|
||||||
// 同步组织架构和成员
|
|
||||||
// </Button>
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
>
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
border: '1px dashed',
|
border: '1px dashed',
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,11 @@ import QAReferer from './QAReferer';
|
||||||
import RTVisitor from './RTVisitor';
|
import RTVisitor from './RTVisitor';
|
||||||
import TypeCount from './TypeCount';
|
import TypeCount from './TypeCount';
|
||||||
import { useAppSelector } from '@/store';
|
import { useAppSelector } from '@/store';
|
||||||
|
import { VersionCanUse } from '@/components/VersionMask';
|
||||||
|
import {
|
||||||
|
BUSINESS_VERSION_PERMISSION,
|
||||||
|
PROFESSION_VERSION_PERMISSION,
|
||||||
|
} from '@/constant/version';
|
||||||
|
|
||||||
export const TimeList = [
|
export const TimeList = [
|
||||||
{ label: '近 24 小时', value: 1 },
|
{ label: '近 24 小时', value: 1 },
|
||||||
|
|
@ -25,13 +30,40 @@ const Statistic = () => {
|
||||||
const isWideScreen = useMediaQuery('(min-width:1190px)');
|
const isWideScreen = useMediaQuery('(min-width:1190px)');
|
||||||
|
|
||||||
const timeList = useMemo(() => {
|
const timeList = useMemo(() => {
|
||||||
const isPro = license.edition === 1 || license.edition === 2;
|
const isPro = PROFESSION_VERSION_PERMISSION.includes(license.edition!);
|
||||||
const isEnterprise = license.edition === 2;
|
const isBusiness = BUSINESS_VERSION_PERMISSION.includes(license.edition!);
|
||||||
return [
|
return [
|
||||||
{ label: '近 24 小时', value: 1, disabled: false },
|
{ label: '近 24 小时', value: 1, disabled: false },
|
||||||
{ label: '近 7 天', value: 7, disabled: !isPro },
|
{
|
||||||
{ label: '近 30 天', value: 30, disabled: !isEnterprise },
|
label: (
|
||||||
{ label: '近 90 天', value: 90, disabled: !isEnterprise },
|
<Stack direction={'row'} alignItems={'center'} gap={0.5}>
|
||||||
|
<span>近 7 天</span>
|
||||||
|
<VersionCanUse permission={PROFESSION_VERSION_PERMISSION} />
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
|
value: 7,
|
||||||
|
disabled: !isPro,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: (
|
||||||
|
<Stack direction={'row'} alignItems={'center'} gap={0.5}>
|
||||||
|
<span>近 30 天</span>
|
||||||
|
<VersionCanUse permission={BUSINESS_VERSION_PERMISSION} />
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
|
value: 30,
|
||||||
|
disabled: !isBusiness,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: (
|
||||||
|
<Stack direction={'row'} alignItems={'center'} gap={0.5}>
|
||||||
|
<span>近 90 天</span>
|
||||||
|
<VersionCanUse permission={BUSINESS_VERSION_PERMISSION} />
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
|
value: 90,
|
||||||
|
disabled: !isBusiness,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}, [license]);
|
}, [license]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ import {
|
||||||
GetApiV1NodeListParams,
|
GetApiV1NodeListParams,
|
||||||
GetApiV1NodeRecommendNodesParams,
|
GetApiV1NodeRecommendNodesParams,
|
||||||
V1NodeDetailResp,
|
V1NodeDetailResp,
|
||||||
|
V1NodeRestudyReq,
|
||||||
|
V1NodeRestudyResp,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -263,6 +265,38 @@ export const getApiV1NodeRecommendNodes = (
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 文档重新学习
|
||||||
|
*
|
||||||
|
* @tags Node
|
||||||
|
* @name PostApiV1NodeRestudy
|
||||||
|
* @summary 文档重新学习
|
||||||
|
* @request POST:/api/v1/node/restudy
|
||||||
|
* @secure
|
||||||
|
* @response `200` `(DomainResponse & {
|
||||||
|
data?: V1NodeRestudyResp,
|
||||||
|
|
||||||
|
})` OK
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const postApiV1NodeRestudy = (
|
||||||
|
param: V1NodeRestudyReq,
|
||||||
|
params: RequestParams = {},
|
||||||
|
) =>
|
||||||
|
httpRequest<
|
||||||
|
DomainResponse & {
|
||||||
|
data?: V1NodeRestudyResp;
|
||||||
|
}
|
||||||
|
>({
|
||||||
|
path: `/api/v1/node/restudy`,
|
||||||
|
method: "POST",
|
||||||
|
body: param,
|
||||||
|
secure: true,
|
||||||
|
type: ContentType.Json,
|
||||||
|
format: "json",
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Summary Node
|
* @description Summary Node
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
/* eslint-disable */
|
|
||||||
/* tslint:disable */
|
|
||||||
// @ts-nocheck
|
|
||||||
/*
|
|
||||||
* ---------------------------------------------------------------
|
|
||||||
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
|
||||||
* ## ##
|
|
||||||
* ## AUTHOR: acacode ##
|
|
||||||
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
|
||||||
* ---------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
import httpRequest, { ContentType, RequestParams } from "./httpClient";
|
|
||||||
import { DomainResponse, V1NodeRestudyReq, V1NodeRestudyResp } from "./types";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 文档重新学习
|
|
||||||
*
|
|
||||||
* @tags NodeRestudy
|
|
||||||
* @name PostApiV1NodeRestudy
|
|
||||||
* @summary 文档重新学习
|
|
||||||
* @request POST:/api/v1/node/restudy
|
|
||||||
* @secure
|
|
||||||
* @response `200` `(DomainResponse & {
|
|
||||||
data?: V1NodeRestudyResp,
|
|
||||||
|
|
||||||
})` OK
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const postApiV1NodeRestudy = (
|
|
||||||
param: V1NodeRestudyReq,
|
|
||||||
params: RequestParams = {},
|
|
||||||
) =>
|
|
||||||
httpRequest<
|
|
||||||
DomainResponse & {
|
|
||||||
data?: V1NodeRestudyResp;
|
|
||||||
}
|
|
||||||
>({
|
|
||||||
path: `/api/v1/node/restudy`,
|
|
||||||
method: "POST",
|
|
||||||
body: param,
|
|
||||||
secure: true,
|
|
||||||
type: ContentType.Json,
|
|
||||||
format: "json",
|
|
||||||
...params,
|
|
||||||
});
|
|
||||||
|
|
@ -10,7 +10,6 @@ export * from './Message'
|
||||||
export * from './Model'
|
export * from './Model'
|
||||||
export * from './Node'
|
export * from './Node'
|
||||||
export * from './NodePermission'
|
export * from './NodePermission'
|
||||||
export * from './NodeRestudy'
|
|
||||||
export * from './Stat'
|
export * from './Stat'
|
||||||
export * from './User'
|
export * from './User'
|
||||||
export * from './types'
|
export * from './types'
|
||||||
|
|
|
||||||
|
|
@ -52,10 +52,12 @@ export enum ConstsSourceType {
|
||||||
export enum ConstsLicenseEdition {
|
export enum ConstsLicenseEdition {
|
||||||
/** 开源版 */
|
/** 开源版 */
|
||||||
LicenseEditionFree = 0,
|
LicenseEditionFree = 0,
|
||||||
/** 联创版 */
|
/** 专业版 */
|
||||||
LicenseEditionContributor = 1,
|
LicenseEditionProfession = 1,
|
||||||
/** 企业版 */
|
/** 企业版 */
|
||||||
LicenseEditionEnterprise = 2,
|
LicenseEditionEnterprise = 2,
|
||||||
|
/** 商业版 */
|
||||||
|
LicenseEditionBusiness = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ConstsContributeType {
|
export enum ConstsContributeType {
|
||||||
|
|
@ -465,6 +467,7 @@ export interface GithubComChaitinPandaWikiProApiShareV1AuthOAuthResp {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GithubComChaitinPandaWikiProApiShareV1AuthWecomReq {
|
export interface GithubComChaitinPandaWikiProApiShareV1AuthWecomReq {
|
||||||
|
is_app?: boolean;
|
||||||
kb_id?: string;
|
kb_id?: string;
|
||||||
redirect_url?: string;
|
redirect_url?: string;
|
||||||
}
|
}
|
||||||
|
|
@ -668,8 +671,6 @@ export interface GetApiProV1TokenListParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PostApiV1LicensePayload {
|
export interface PostApiV1LicensePayload {
|
||||||
/** license edition */
|
|
||||||
license_edition: "contributor" | "enterprise";
|
|
||||||
/** license type */
|
/** license type */
|
||||||
license_type: "file" | "code";
|
license_type: "file" | "code";
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -175,10 +175,12 @@ export enum ConstsNodeAccessPerm {
|
||||||
export enum ConstsLicenseEdition {
|
export enum ConstsLicenseEdition {
|
||||||
/** 开源版 */
|
/** 开源版 */
|
||||||
LicenseEditionFree = 0,
|
LicenseEditionFree = 0,
|
||||||
/** 联创版 */
|
/** 专业版 */
|
||||||
LicenseEditionContributor = 1,
|
LicenseEditionProfession = 1,
|
||||||
/** 企业版 */
|
/** 企业版 */
|
||||||
LicenseEditionEnterprise = 2,
|
LicenseEditionEnterprise = 2,
|
||||||
|
/** 商业版 */
|
||||||
|
LicenseEditionBusiness = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ConstsHomePageSetting {
|
export enum ConstsHomePageSetting {
|
||||||
|
|
@ -1645,8 +1647,9 @@ export interface V1NodePermissionResp {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface V1NodeRestudyReq {
|
export interface V1NodeRestudyReq {
|
||||||
kb_id?: string;
|
kb_id: string;
|
||||||
node_ids?: string[];
|
/** @minItems 1 */
|
||||||
|
node_ids: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type V1NodeRestudyResp = Record<string, any>;
|
export type V1NodeRestudyResp = Record<string, any>;
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,8 @@ export interface ConversationItem {
|
||||||
message_id: string;
|
message_id: string;
|
||||||
source: 'history' | 'chat';
|
source: 'history' | 'chat';
|
||||||
chunk_result: ChunkResultItem[];
|
chunk_result: ChunkResultItem[];
|
||||||
|
result_expend: boolean;
|
||||||
|
thinking_expend: boolean;
|
||||||
thinking_content: string;
|
thinking_content: string;
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
@ -382,6 +384,8 @@ const AiQaContent: React.FC<{
|
||||||
const solution = await cap.solve();
|
const solution = await cap.solve();
|
||||||
token = solution.token;
|
token = solution.token;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
|
setThinking(4);
|
||||||
message.error('验证失败');
|
message.error('验证失败');
|
||||||
console.log(error, 'error---------');
|
console.log(error, 'error---------');
|
||||||
return;
|
return;
|
||||||
|
|
@ -465,6 +469,8 @@ const AiQaContent: React.FC<{
|
||||||
if (lastConversation) {
|
if (lastConversation) {
|
||||||
lastConversation.a = answerContent;
|
lastConversation.a = answerContent;
|
||||||
lastConversation.thinking_content = thinkingContent;
|
lastConversation.thinking_content = thinkingContent;
|
||||||
|
lastConversation.result_expend = false;
|
||||||
|
lastConversation.thinking_expend = false;
|
||||||
}
|
}
|
||||||
return newConversation;
|
return newConversation;
|
||||||
});
|
});
|
||||||
|
|
@ -513,6 +519,8 @@ const AiQaContent: React.FC<{
|
||||||
source: 'chat',
|
source: 'chat',
|
||||||
chunk_result: [],
|
chunk_result: [],
|
||||||
thinking_content: '',
|
thinking_content: '',
|
||||||
|
result_expend: true,
|
||||||
|
thinking_expend: true,
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
});
|
});
|
||||||
messageIdRef.current = '';
|
messageIdRef.current = '';
|
||||||
|
|
@ -631,6 +639,8 @@ const AiQaContent: React.FC<{
|
||||||
source: 'history',
|
source: 'history',
|
||||||
chunk_result: [],
|
chunk_result: [],
|
||||||
thinking_content: '',
|
thinking_content: '',
|
||||||
|
result_expend: true,
|
||||||
|
thinking_expend: true,
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -667,6 +677,8 @@ const AiQaContent: React.FC<{
|
||||||
chunk_result: [],
|
chunk_result: [],
|
||||||
thinking_content: '',
|
thinking_content: '',
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
|
result_expend: true,
|
||||||
|
thinking_expend: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -791,7 +803,16 @@ const AiQaContent: React.FC<{
|
||||||
<StyledAiBubble>
|
<StyledAiBubble>
|
||||||
{/* 搜索结果 */}
|
{/* 搜索结果 */}
|
||||||
{item.chunk_result.length > 0 && (
|
{item.chunk_result.length > 0 && (
|
||||||
<StyledChunkAccordion defaultExpanded>
|
<StyledChunkAccordion
|
||||||
|
expanded={item.result_expend}
|
||||||
|
onChange={(event, expanded) => {
|
||||||
|
setConversation(prev => {
|
||||||
|
const newConversation = [...prev];
|
||||||
|
newConversation[index].result_expend = expanded;
|
||||||
|
return newConversation;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
<StyledChunkAccordionSummary
|
<StyledChunkAccordionSummary
|
||||||
expandIcon={<ExpandMoreIcon sx={{ fontSize: 16 }} />}
|
expandIcon={<ExpandMoreIcon sx={{ fontSize: 16 }} />}
|
||||||
>
|
>
|
||||||
|
|
@ -837,7 +858,16 @@ const AiQaContent: React.FC<{
|
||||||
|
|
||||||
{/* 思考过程 */}
|
{/* 思考过程 */}
|
||||||
{!!item.thinking_content && (
|
{!!item.thinking_content && (
|
||||||
<StyledThinkingAccordion defaultExpanded>
|
<StyledThinkingAccordion
|
||||||
|
expanded={item.thinking_expend}
|
||||||
|
onChange={(event, expanded) => {
|
||||||
|
setConversation(prev => {
|
||||||
|
const newConversation = [...prev];
|
||||||
|
newConversation[index].thinking_expend = expanded;
|
||||||
|
return newConversation;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
<StyledThinkingAccordionSummary
|
<StyledThinkingAccordionSummary
|
||||||
expandIcon={<ExpandMoreIcon sx={{ fontSize: 16 }} />}
|
expandIcon={<ExpandMoreIcon sx={{ fontSize: 16 }} />}
|
||||||
>
|
>
|
||||||
|
|
@ -929,6 +959,9 @@ const AiQaContent: React.FC<{
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
<Box>
|
||||||
|
{kbDetail?.settings?.disclaimer_settings?.content}
|
||||||
|
</Box>
|
||||||
</StyledActionStack>
|
</StyledActionStack>
|
||||||
)}
|
)}
|
||||||
</StyledAiBubble>
|
</StyledAiBubble>
|
||||||
|
|
|
||||||
|
|
@ -47,9 +47,9 @@ export const StyledUserBubble = styled(Box)(({ theme }) => ({
|
||||||
|
|
||||||
export const StyledAiBubble = styled(Box)(({ theme }) => ({
|
export const StyledAiBubble = styled(Box)(({ theme }) => ({
|
||||||
alignSelf: 'flex-start',
|
alignSelf: 'flex-start',
|
||||||
maxWidth: '85%',
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
|
width: '100%',
|
||||||
gap: theme.spacing(3),
|
gap: theme.spacing(3),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
import { useState, useCallback, useRef } from 'react';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import { ConversationItem } from '../types';
|
|
||||||
import { ChunkResultItem } from '@/assets/type';
|
|
||||||
|
|
||||||
export const useConversation = () => {
|
|
||||||
const [conversation, setConversation] = useState<ConversationItem[]>([]);
|
|
||||||
const [fullAnswer, setFullAnswer] = useState<string>('');
|
|
||||||
const [chunkResult, setChunkResult] = useState<ChunkResultItem[]>([]);
|
|
||||||
const [thinkingContent, setThinkingContent] = useState<string>('');
|
|
||||||
const [answer, setAnswer] = useState('');
|
|
||||||
const [isChunkResult, setIsChunkResult] = useState(false);
|
|
||||||
const [isThinking, setIsThinking] = useState(false);
|
|
||||||
|
|
||||||
const messageIdRef = useRef('');
|
|
||||||
|
|
||||||
const addQuestion = useCallback(
|
|
||||||
(q: string, reset: boolean = false) => {
|
|
||||||
const newConversation = reset
|
|
||||||
? []
|
|
||||||
: conversation.some(item => item.source === 'history')
|
|
||||||
? []
|
|
||||||
: [...conversation];
|
|
||||||
|
|
||||||
newConversation.push({
|
|
||||||
q,
|
|
||||||
a: '',
|
|
||||||
score: 0,
|
|
||||||
message_id: '',
|
|
||||||
update_time: '',
|
|
||||||
source: 'chat',
|
|
||||||
chunk_result: [],
|
|
||||||
thinking_content: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
messageIdRef.current = '';
|
|
||||||
setConversation(newConversation);
|
|
||||||
setChunkResult([]);
|
|
||||||
setThinkingContent('');
|
|
||||||
setAnswer('');
|
|
||||||
setFullAnswer('');
|
|
||||||
},
|
|
||||||
[conversation],
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateLastConversation = useCallback(() => {
|
|
||||||
setAnswer(prevAnswer => {
|
|
||||||
setThinkingContent(prevThinkingContent => {
|
|
||||||
setChunkResult(prevChunkResult => {
|
|
||||||
setConversation(prev => {
|
|
||||||
const newConversation = [...prev];
|
|
||||||
const lastConversation =
|
|
||||||
newConversation[newConversation.length - 1];
|
|
||||||
if (lastConversation) {
|
|
||||||
lastConversation.a = prevAnswer;
|
|
||||||
lastConversation.update_time = dayjs().format(
|
|
||||||
'YYYY-MM-DD HH:mm:ss',
|
|
||||||
);
|
|
||||||
lastConversation.message_id = messageIdRef.current;
|
|
||||||
lastConversation.source = 'chat';
|
|
||||||
lastConversation.chunk_result = prevChunkResult;
|
|
||||||
lastConversation.thinking_content = prevThinkingContent;
|
|
||||||
}
|
|
||||||
return newConversation;
|
|
||||||
});
|
|
||||||
return prevChunkResult;
|
|
||||||
});
|
|
||||||
return prevThinkingContent;
|
|
||||||
});
|
|
||||||
return '';
|
|
||||||
});
|
|
||||||
|
|
||||||
setFullAnswer('');
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const updateConversationScore = useCallback(
|
|
||||||
(message_id: string, score: number) => {
|
|
||||||
setConversation(prev =>
|
|
||||||
prev.map(item =>
|
|
||||||
item.message_id === message_id ? { ...item, score } : item,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const resetConversation = useCallback(() => {
|
|
||||||
setConversation([]);
|
|
||||||
setChunkResult([]);
|
|
||||||
setAnswer('');
|
|
||||||
setFullAnswer('');
|
|
||||||
setThinkingContent('');
|
|
||||||
messageIdRef.current = '';
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return {
|
|
||||||
conversation,
|
|
||||||
setConversation,
|
|
||||||
fullAnswer,
|
|
||||||
setFullAnswer,
|
|
||||||
chunkResult,
|
|
||||||
setChunkResult,
|
|
||||||
thinkingContent,
|
|
||||||
setThinkingContent,
|
|
||||||
answer,
|
|
||||||
setAnswer,
|
|
||||||
isChunkResult,
|
|
||||||
setIsChunkResult,
|
|
||||||
isThinking,
|
|
||||||
setIsThinking,
|
|
||||||
messageIdRef,
|
|
||||||
addQuestion,
|
|
||||||
updateLastConversation,
|
|
||||||
updateConversationScore,
|
|
||||||
resetConversation,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
import { useState, useRef, useCallback } from 'react';
|
|
||||||
import { message } from '@ctzhian/ui';
|
|
||||||
import { UploadedImage } from '../types';
|
|
||||||
import { MAX_IMAGES, MAX_IMAGE_SIZE } from '../constants';
|
|
||||||
|
|
||||||
export const useImageUpload = () => {
|
|
||||||
const [uploadedImages, setUploadedImages] = useState<UploadedImage[]>([]);
|
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
const cleanupImageUrls = useCallback((images: UploadedImage[]) => {
|
|
||||||
images.forEach(img => {
|
|
||||||
if (img.url.startsWith('blob:')) {
|
|
||||||
URL.revokeObjectURL(img.url);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleImageSelect = useCallback(
|
|
||||||
async (files: FileList | null) => {
|
|
||||||
if (!files || files.length === 0) return;
|
|
||||||
|
|
||||||
const remainingSlots = MAX_IMAGES - uploadedImages.length;
|
|
||||||
if (remainingSlots <= 0) {
|
|
||||||
message.warning(`最多只能上传 ${MAX_IMAGES} 张图片`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const filesToAdd = Array.from(files).slice(0, remainingSlots);
|
|
||||||
const newImages: UploadedImage[] = [];
|
|
||||||
|
|
||||||
for (const file of filesToAdd) {
|
|
||||||
if (!file.type.startsWith('image/')) {
|
|
||||||
message.error('只支持上传图片文件');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file.size > MAX_IMAGE_SIZE) {
|
|
||||||
message.error('图片大小不能超过 10MB');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const localUrl = URL.createObjectURL(file);
|
|
||||||
newImages.push({
|
|
||||||
id: Date.now().toString() + Math.random(),
|
|
||||||
url: localUrl,
|
|
||||||
file,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setUploadedImages(prev => [...prev, ...newImages]);
|
|
||||||
},
|
|
||||||
[uploadedImages.length],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleImageUpload = useCallback(
|
|
||||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
handleImageSelect(event.target.files);
|
|
||||||
if (fileInputRef.current) {
|
|
||||||
fileInputRef.current.value = '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[handleImageSelect],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleRemoveImage = useCallback((id: string) => {
|
|
||||||
setUploadedImages(prev => {
|
|
||||||
const imageToRemove = prev.find(img => img.id === id);
|
|
||||||
if (imageToRemove && imageToRemove.url.startsWith('blob:')) {
|
|
||||||
URL.revokeObjectURL(imageToRemove.url);
|
|
||||||
}
|
|
||||||
return prev.filter(img => img.id !== id);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handlePaste = useCallback(
|
|
||||||
async (e: React.ClipboardEvent<HTMLDivElement>) => {
|
|
||||||
const items = e.clipboardData?.items;
|
|
||||||
if (!items) return;
|
|
||||||
|
|
||||||
const imageFiles: File[] = [];
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
const item = items[i];
|
|
||||||
if (item.type.startsWith('image/')) {
|
|
||||||
const file = item.getAsFile();
|
|
||||||
if (file) imageFiles.push(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageFiles.length > 0) {
|
|
||||||
e.preventDefault();
|
|
||||||
const dataTransfer = new DataTransfer();
|
|
||||||
imageFiles.forEach(file => dataTransfer.items.add(file));
|
|
||||||
await handleImageSelect(dataTransfer.files);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[handleImageSelect],
|
|
||||||
);
|
|
||||||
|
|
||||||
const clearImages = useCallback(() => {
|
|
||||||
cleanupImageUrls(uploadedImages);
|
|
||||||
setUploadedImages([]);
|
|
||||||
}, [uploadedImages, cleanupImageUrls]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
uploadedImages,
|
|
||||||
fileInputRef,
|
|
||||||
handleImageUpload,
|
|
||||||
handleRemoveImage,
|
|
||||||
handlePaste,
|
|
||||||
clearImages,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -1,217 +0,0 @@
|
||||||
import { useEffect, useRef, useState, useCallback } from 'react';
|
|
||||||
import { message } from '@ctzhian/ui';
|
|
||||||
import SSEClient from '@/utils/fetch';
|
|
||||||
import { handleThinkingContent } from '../utils';
|
|
||||||
import { SSEMessageData, ChatRequestData } from '../types';
|
|
||||||
import { AnswerStatusType, CAP_CONFIG, SSE_CONFIG } from '../constants';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
|
|
||||||
interface UseSSEChatProps {
|
|
||||||
conversationId: string;
|
|
||||||
setConversationId: React.Dispatch<React.SetStateAction<string>>;
|
|
||||||
nonce: string;
|
|
||||||
setNonce: React.Dispatch<React.SetStateAction<string>>;
|
|
||||||
messageIdRef: React.MutableRefObject<string>;
|
|
||||||
setFullAnswer: React.Dispatch<React.SetStateAction<string>>;
|
|
||||||
setAnswer: React.Dispatch<React.SetStateAction<string>>;
|
|
||||||
setThinkingContent: React.Dispatch<React.SetStateAction<string>>;
|
|
||||||
setChunkResult: React.Dispatch<React.SetStateAction<any[]>>;
|
|
||||||
setConversation: React.Dispatch<React.SetStateAction<any[]>>;
|
|
||||||
setIsChunkResult: (value: boolean) => void;
|
|
||||||
setIsThinking: (value: boolean) => void;
|
|
||||||
setThinking: (value: AnswerStatusType) => void;
|
|
||||||
setLoading: (value: boolean) => void;
|
|
||||||
scrollToBottom: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useSSEChat = ({
|
|
||||||
conversationId,
|
|
||||||
setConversationId,
|
|
||||||
nonce,
|
|
||||||
setNonce,
|
|
||||||
messageIdRef,
|
|
||||||
setFullAnswer,
|
|
||||||
setAnswer,
|
|
||||||
setThinkingContent,
|
|
||||||
setChunkResult,
|
|
||||||
setConversation,
|
|
||||||
setIsChunkResult,
|
|
||||||
setIsThinking,
|
|
||||||
setThinking,
|
|
||||||
setLoading,
|
|
||||||
scrollToBottom,
|
|
||||||
}: UseSSEChatProps) => {
|
|
||||||
const sseClientRef = useRef<SSEClient<SSEMessageData> | null>(null);
|
|
||||||
|
|
||||||
const initializeSSE = useCallback(() => {
|
|
||||||
sseClientRef.current = new SSEClient({
|
|
||||||
url: SSE_CONFIG.url,
|
|
||||||
headers: SSE_CONFIG.headers,
|
|
||||||
onCancel: () => {
|
|
||||||
setLoading(false);
|
|
||||||
setThinking(4);
|
|
||||||
setAnswer(prev => {
|
|
||||||
let value = '';
|
|
||||||
if (prev) {
|
|
||||||
value = prev + '\n\n<error>Request canceled</error>';
|
|
||||||
}
|
|
||||||
setConversation(prev => {
|
|
||||||
const newConversation = [...prev];
|
|
||||||
if (newConversation[newConversation.length - 1]) {
|
|
||||||
newConversation[newConversation.length - 1].a = value;
|
|
||||||
newConversation[newConversation.length - 1].update_time =
|
|
||||||
dayjs().format('YYYY-MM-DD HH:mm:ss');
|
|
||||||
newConversation[newConversation.length - 1].message_id =
|
|
||||||
messageIdRef.current;
|
|
||||||
}
|
|
||||||
return newConversation;
|
|
||||||
});
|
|
||||||
return '';
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}, [messageIdRef, setAnswer, setConversation, setLoading, setThinking]);
|
|
||||||
|
|
||||||
const chatAnswer = useCallback(
|
|
||||||
async (q: string) => {
|
|
||||||
setLoading(true);
|
|
||||||
setThinking(1);
|
|
||||||
|
|
||||||
let token = '';
|
|
||||||
try {
|
|
||||||
const Cap = (await import('@cap.js/widget')).default;
|
|
||||||
const cap = new Cap({ apiEndpoint: CAP_CONFIG.apiEndpoint });
|
|
||||||
const solution = await cap.solve();
|
|
||||||
token = solution.token;
|
|
||||||
} catch (error) {
|
|
||||||
message.error('验证失败');
|
|
||||||
console.error('Captcha error:', error);
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reqData: ChatRequestData = {
|
|
||||||
message: q,
|
|
||||||
nonce: nonce || '',
|
|
||||||
conversation_id: conversationId || '',
|
|
||||||
app_type: 1,
|
|
||||||
captcha_token: token,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (sseClientRef.current) {
|
|
||||||
sseClientRef.current.subscribe(
|
|
||||||
JSON.stringify(reqData),
|
|
||||||
({ type, content, chunk_result }) => {
|
|
||||||
if (type === 'conversation_id') {
|
|
||||||
setConversationId(prev => prev + content);
|
|
||||||
} else if (type === 'message_id') {
|
|
||||||
messageIdRef.current += content;
|
|
||||||
} else if (type === 'nonce') {
|
|
||||||
setNonce(prev => prev + content);
|
|
||||||
} else if (type === 'error') {
|
|
||||||
setLoading(false);
|
|
||||||
setIsChunkResult(false);
|
|
||||||
setIsThinking(false);
|
|
||||||
setThinking(4);
|
|
||||||
setAnswer(prev => {
|
|
||||||
if (content) {
|
|
||||||
return prev + `\n\n回答出现错误:<error>${content}</error>`;
|
|
||||||
}
|
|
||||||
return prev + '\n\n回答出现错误,请重试';
|
|
||||||
});
|
|
||||||
if (content) message.error(content);
|
|
||||||
} else if (type === 'done') {
|
|
||||||
setAnswer(prevAnswer => {
|
|
||||||
setThinkingContent(prevThinkingContent => {
|
|
||||||
setChunkResult(prevChunkResult => {
|
|
||||||
setConversation(prev => {
|
|
||||||
const newConversation = [...prev];
|
|
||||||
const lastConversation =
|
|
||||||
newConversation[newConversation.length - 1];
|
|
||||||
if (lastConversation) {
|
|
||||||
lastConversation.a = prevAnswer;
|
|
||||||
lastConversation.update_time = dayjs().format(
|
|
||||||
'YYYY-MM-DD HH:mm:ss',
|
|
||||||
);
|
|
||||||
lastConversation.message_id = messageIdRef.current;
|
|
||||||
lastConversation.source = 'chat';
|
|
||||||
lastConversation.chunk_result = prevChunkResult;
|
|
||||||
lastConversation.thinking_content = prevThinkingContent;
|
|
||||||
}
|
|
||||||
return newConversation;
|
|
||||||
});
|
|
||||||
return prevChunkResult;
|
|
||||||
});
|
|
||||||
return prevThinkingContent;
|
|
||||||
});
|
|
||||||
return '';
|
|
||||||
});
|
|
||||||
|
|
||||||
setFullAnswer('');
|
|
||||||
setLoading(false);
|
|
||||||
setIsChunkResult(false);
|
|
||||||
setIsThinking(false);
|
|
||||||
setThinking(4);
|
|
||||||
} else if (type === 'data') {
|
|
||||||
setIsChunkResult(false);
|
|
||||||
setFullAnswer(prevFullAnswer => {
|
|
||||||
const newFullAnswer = prevFullAnswer + content;
|
|
||||||
const { thinkingContent, answerContent } =
|
|
||||||
handleThinkingContent(newFullAnswer);
|
|
||||||
|
|
||||||
setThinkingContent(thinkingContent);
|
|
||||||
setAnswer(answerContent);
|
|
||||||
|
|
||||||
if (newFullAnswer.includes('</think>')) {
|
|
||||||
setIsThinking(false);
|
|
||||||
setThinking(3);
|
|
||||||
} else if (newFullAnswer.includes('<think>')) {
|
|
||||||
setIsThinking(true);
|
|
||||||
setThinking(2);
|
|
||||||
} else {
|
|
||||||
setThinking(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newFullAnswer;
|
|
||||||
});
|
|
||||||
} else if (type === 'chunk_result') {
|
|
||||||
setChunkResult(prev => [...prev, chunk_result]);
|
|
||||||
setIsChunkResult(true);
|
|
||||||
setTimeout(scrollToBottom, 200);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
|
||||||
conversationId,
|
|
||||||
nonce,
|
|
||||||
messageIdRef,
|
|
||||||
setConversationId,
|
|
||||||
setNonce,
|
|
||||||
setLoading,
|
|
||||||
setThinking,
|
|
||||||
setAnswer,
|
|
||||||
setFullAnswer,
|
|
||||||
setThinkingContent,
|
|
||||||
setChunkResult,
|
|
||||||
setConversation,
|
|
||||||
setIsChunkResult,
|
|
||||||
setIsThinking,
|
|
||||||
scrollToBottom,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSearchAbort = useCallback(() => {
|
|
||||||
sseClientRef.current?.unsubscribe();
|
|
||||||
setLoading(false);
|
|
||||||
setThinking(4);
|
|
||||||
}, [setLoading, setThinking]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
sseClientRef,
|
|
||||||
initializeSSE,
|
|
||||||
chatAnswer,
|
|
||||||
handleSearchAbort,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -247,7 +247,9 @@ const QaModal: React.FC<QaModalProps> = () => {
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
px: 3,
|
px: 3,
|
||||||
pt: kbDetail?.settings?.disclaimer_settings?.content ? 2 : 0,
|
pt: kbDetail?.settings?.web_app_custom_style?.show_brand_info
|
||||||
|
? 2
|
||||||
|
: 0,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
|
@ -263,7 +265,10 @@ const QaModal: React.FC<QaModalProps> = () => {
|
||||||
gap: 1,
|
gap: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box>{kbDetail?.settings?.disclaimer_settings?.content}</Box>
|
<Box>
|
||||||
|
{kbDetail?.settings?.web_app_custom_style?.show_brand_info &&
|
||||||
|
'本网站由 PandaWiki 提供技术支持'}
|
||||||
|
</Box>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue