mirror of https://github.com/chaitin/PandaWiki.git
Compare commits
1 Commits
5231b61db4
...
aa1ad4cb64
| Author | SHA1 | Date |
|---|---|---|
|
|
aa1ad4cb64 |
|
|
@ -1,6 +1,8 @@
|
||||||
package consts
|
package consts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -12,12 +14,27 @@ type LicenseEdition int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LicenseEditionFree LicenseEdition = 0 // 开源版
|
LicenseEditionFree LicenseEdition = 0 // 开源版
|
||||||
LicenseEditionProfession LicenseEdition = 1 // 专业版
|
LicenseEditionContributor 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,26 +4067,22 @@ const docTemplate = `{
|
||||||
"enum": [
|
"enum": [
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
2,
|
2
|
||||||
3
|
|
||||||
],
|
],
|
||||||
"x-enum-comments": {
|
"x-enum-comments": {
|
||||||
"LicenseEditionBusiness": "商业版",
|
"LicenseEditionContributor": "联创版",
|
||||||
"LicenseEditionEnterprise": "企业版",
|
"LicenseEditionEnterprise": "企业版",
|
||||||
"LicenseEditionFree": "开源版",
|
"LicenseEditionFree": "开源版"
|
||||||
"LicenseEditionProfession": "专业版"
|
|
||||||
},
|
},
|
||||||
"x-enum-descriptions": [
|
"x-enum-descriptions": [
|
||||||
"开源版",
|
"开源版",
|
||||||
"专业版",
|
"联创版",
|
||||||
"企业版",
|
"企业版"
|
||||||
"商业版"
|
|
||||||
],
|
],
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
"LicenseEditionFree",
|
"LicenseEditionFree",
|
||||||
"LicenseEditionProfession",
|
"LicenseEditionContributor",
|
||||||
"LicenseEditionEnterprise",
|
"LicenseEditionEnterprise"
|
||||||
"LicenseEditionBusiness"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"consts.ModelSettingMode": {
|
"consts.ModelSettingMode": {
|
||||||
|
|
|
||||||
|
|
@ -4060,26 +4060,22 @@
|
||||||
"enum": [
|
"enum": [
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
2,
|
2
|
||||||
3
|
|
||||||
],
|
],
|
||||||
"x-enum-comments": {
|
"x-enum-comments": {
|
||||||
"LicenseEditionBusiness": "商业版",
|
"LicenseEditionContributor": "联创版",
|
||||||
"LicenseEditionEnterprise": "企业版",
|
"LicenseEditionEnterprise": "企业版",
|
||||||
"LicenseEditionFree": "开源版",
|
"LicenseEditionFree": "开源版"
|
||||||
"LicenseEditionProfession": "专业版"
|
|
||||||
},
|
},
|
||||||
"x-enum-descriptions": [
|
"x-enum-descriptions": [
|
||||||
"开源版",
|
"开源版",
|
||||||
"专业版",
|
"联创版",
|
||||||
"企业版",
|
"企业版"
|
||||||
"商业版"
|
|
||||||
],
|
],
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
"LicenseEditionFree",
|
"LicenseEditionFree",
|
||||||
"LicenseEditionProfession",
|
"LicenseEditionContributor",
|
||||||
"LicenseEditionEnterprise",
|
"LicenseEditionEnterprise"
|
||||||
"LicenseEditionBusiness"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"consts.ModelSettingMode": {
|
"consts.ModelSettingMode": {
|
||||||
|
|
|
||||||
|
|
@ -117,24 +117,20 @@ definitions:
|
||||||
- 0
|
- 0
|
||||||
- 1
|
- 1
|
||||||
- 2
|
- 2
|
||||||
- 3
|
|
||||||
format: int32
|
format: int32
|
||||||
type: integer
|
type: integer
|
||||||
x-enum-comments:
|
x-enum-comments:
|
||||||
LicenseEditionBusiness: 商业版
|
LicenseEditionContributor: 联创版
|
||||||
LicenseEditionEnterprise: 企业版
|
LicenseEditionEnterprise: 企业版
|
||||||
LicenseEditionFree: 开源版
|
LicenseEditionFree: 开源版
|
||||||
LicenseEditionProfession: 专业版
|
|
||||||
x-enum-descriptions:
|
x-enum-descriptions:
|
||||||
- 开源版
|
- 开源版
|
||||||
- 专业版
|
- 联创版
|
||||||
- 企业版
|
- 企业版
|
||||||
- 商业版
|
|
||||||
x-enum-varnames:
|
x-enum-varnames:
|
||||||
- LicenseEditionFree
|
- LicenseEditionFree
|
||||||
- LicenseEditionProfession
|
- LicenseEditionContributor
|
||||||
- LicenseEditionEnterprise
|
- LicenseEditionEnterprise
|
||||||
- LicenseEditionBusiness
|
|
||||||
consts.ModelSettingMode:
|
consts.ModelSettingMode:
|
||||||
enum:
|
enum:
|
||||||
- manual
|
- manual
|
||||||
|
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
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,6 +6,7 @@ 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"
|
||||||
|
|
@ -156,7 +157,7 @@ func (h *ShareCommentHandler) GetCommentList(c echo.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询数据库获取所有评论-->0 所有, 1,2 为需要审核的评论
|
// 查询数据库获取所有评论-->0 所有, 1,2 为需要审核的评论
|
||||||
commentsList, err := h.usecase.GetCommentListByNodeID(ctx, nodeID)
|
commentsList, err := h.usecase.GetCommentListByNodeID(ctx, nodeID, consts.GetLicenseEdition(c))
|
||||||
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,7 +5,6 @@ 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
|
||||||
|
|
@ -56,8 +55,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 !domain.GetBaseEditionLimitation(c.Request().Context()).AllowAdminPerm && req.Perm != consts.UserKBPermissionFullControl {
|
if consts.GetLicenseEdition(c) != consts.LicenseEditionEnterprise && 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)
|
||||||
|
|
@ -88,8 +87,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 !domain.GetBaseEditionLimitation(c.Request().Context()).AllowAdminPerm && req.Perm != consts.UserKBPermissionFullControl {
|
if consts.GetLicenseEdition(c) != consts.LicenseEditionEnterprise && 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,7 +91,11 @@ 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 = domain.GetBaseEditionLimitation(c.Request().Context()).MaxKb
|
req.MaxKB = 1
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetModelList
|
// get model list
|
||||||
//
|
//
|
||||||
// @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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateModel
|
// create model
|
||||||
//
|
//
|
||||||
// @Summary create model
|
// @Summary create model
|
||||||
// @Description create model
|
// @Description create model
|
||||||
|
|
@ -85,6 +85,9 @@ 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{}
|
||||||
|
|
@ -109,7 +112,7 @@ func (h *ModelHandler) CreateModel(c echo.Context) error {
|
||||||
return h.NewResponseWithData(c, model)
|
return h.NewResponseWithData(c, model)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateModel
|
// update model
|
||||||
//
|
//
|
||||||
// @Description update model
|
// @Description update model
|
||||||
// @Tags model
|
// @Tags model
|
||||||
|
|
@ -127,6 +130,9 @@ 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)
|
||||||
|
|
@ -134,7 +140,7 @@ func (h *ModelHandler) UpdateModel(c echo.Context) error {
|
||||||
return h.NewResponseWithData(c, nil)
|
return h.NewResponseWithData(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckModel
|
// check model
|
||||||
//
|
//
|
||||||
// @Summary check model
|
// @Summary check model
|
||||||
// @Description check model
|
// @Description check model
|
||||||
|
|
|
||||||
|
|
@ -81,13 +81,15 @@ 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
|
||||||
req.MaxNode = domain.GetBaseEditionLimitation(ctx).MaxNode
|
if maxNode := c.Get("max_node"); maxNode != nil {
|
||||||
|
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,7 +6,6 @@ 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"
|
||||||
|
|
@ -195,7 +194,7 @@ func (m *JWTMiddleware) ValidateKBUserPerm(perm consts.UserKBPermission) echo.Mi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *JWTMiddleware) ValidateLicenseEdition(needEditions ...consts.LicenseEdition) echo.MiddlewareFunc {
|
func (m *JWTMiddleware) ValidateLicenseEdition(needEdition 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 {
|
||||||
|
|
||||||
|
|
@ -207,7 +206,7 @@ func (m *JWTMiddleware) ValidateLicenseEdition(needEditions ...consts.LicenseEdi
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if !slices.Contains(needEditions, edition) {
|
if edition < needEdition {
|
||||||
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 bcf7e0f0bedb18f43cf36463ddb45ace6c1dbab9
|
Subproject commit c4dc498df094cb617d31c95580db8239a445d652
|
||||||
|
|
@ -300,8 +300,8 @@ func (r *AuthRepo) GetOrCreateAuth(ctx context.Context, auth *domain.Auth, sourc
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if int(count) >= domain.GetBaseEditionLimitation(ctx).MaxSSOUser {
|
if int(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)
|
return fmt.Errorf("exceed max auth limit for kb %s, current count: %d, max limit: %d", auth.KBID, count, licenseEdition.GetMaxAuth(sourceType))
|
||||||
}
|
}
|
||||||
|
|
||||||
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) ([]*domain.ShareCommentListItem, int64, error) {
|
func (r *CommentRepository) GetCommentList(ctx context.Context, nodeID string, edition consts.LicenseEdition) ([]*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 domain.GetBaseEditionLimitation(ctx).AllowCommentAudit {
|
if edition == consts.LicenseEditionContributor || edition == consts.LicenseEditionEnterprise {
|
||||||
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) (
|
||||||
|
|
||||||
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.WithContext(ctx).Model(&domain.Comment{}).Where("comments.kb_id = ?", req.KbID)
|
query := r.db.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 domain.GetBaseEditionLimitation(ctx).AllowCommentAudit {
|
if edition == consts.LicenseEditionContributor || edition == consts.LicenseEditionEnterprise {
|
||||||
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.WithContext(ctx).Model(&domain.Comment{}).Where("id IN (?)", commentID)
|
query := r.db.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,7 +344,6 @@ 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, nodes.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, node_releases.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,14 +60,18 @@ 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 count >= domain.GetBaseEditionLimitation(ctx).MaxAdmin {
|
if edition == consts.LicenseEditionFree && count >= 1 {
|
||||||
return fmt.Errorf("exceed max admin limit, current count: %d, max limit: %d", count, domain.GetBaseEditionLimitation(ctx).MaxAdmin)
|
return errors.New("free edition only allows 1 user")
|
||||||
|
}
|
||||||
|
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,38 +88,34 @@ 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 ||
|
||||||
limitation := domain.GetBaseEditionLimitation(ctx)
|
app.Settings.WatermarkSetting != req.Settings.WatermarkSetting ||
|
||||||
if !limitation.AllowCopyProtection && app.Settings.CopySetting != req.Settings.CopySetting {
|
app.Settings.ContributeSettings != req.Settings.ContributeSettings ||
|
||||||
|
app.Settings.CopySetting != req.Settings.CopySetting {
|
||||||
return domain.ErrPermissionDenied
|
return domain.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
case consts.LicenseEditionContributor:
|
||||||
if !limitation.AllowWatermark {
|
app, err := u.repo.GetAppDetail(ctx, id)
|
||||||
if app.Settings.WatermarkSetting != req.Settings.WatermarkSetting || app.Settings.WatermarkContent != req.Settings.WatermarkContent {
|
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
|
return domain.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
case consts.LicenseEditionEnterprise:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported license type: %d", edition)
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -622,8 +618,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 !domain.GetBaseEditionLimitation(ctx).AllowCustomCopyright {
|
if licenseEdition < consts.LicenseEditionEnterprise {
|
||||||
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) (*domain.PaginatedResult[[]*domain.ShareCommentListItem], error) {
|
func (u *CommentUsecase) GetCommentListByNodeID(ctx context.Context, nodeID string, edition consts.LicenseEdition) (*domain.PaginatedResult[[]*domain.ShareCommentListItem], error) {
|
||||||
comments, total, err := u.CommentRepo.GetCommentList(ctx, nodeID)
|
comments, total, err := u.CommentRepo.GetCommentList(ctx, nodeID, edition)
|
||||||
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 !slices.Contains([]consts.LicenseEdition{consts.LicenseEditionBusiness, consts.LicenseEditionEnterprise}, edition) {
|
if edition != consts.LicenseEditionEnterprise {
|
||||||
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,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
|
@ -68,12 +67,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.LicenseEditionFree {
|
if edition < consts.LicenseEditionContributor {
|
||||||
return domain.ErrPermissionDenied
|
return domain.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case consts.StatDay30, consts.StatDay90:
|
case consts.StatDay30, consts.StatDay90:
|
||||||
if !slices.Contains([]consts.LicenseEdition{consts.LicenseEditionBusiness, consts.LicenseEditionEnterprise}, edition) {
|
if edition < consts.LicenseEditionEnterprise {
|
||||||
return domain.ErrPermissionDenied
|
return domain.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
Before 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, appPreviewData, id]);
|
}, [subscribe]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledCommonWrapper>
|
<StyledCommonWrapper>
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ 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;
|
||||||
|
|
@ -77,6 +75,9 @@ 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(
|
||||||
|
|
@ -505,7 +506,7 @@ const FooterConfig = ({ data, setIsEdit, isEdit }: FooterConfigProps) => {
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
{isEnterprise && (
|
||||||
<Stack direction={'column'} gap={2}>
|
<Stack direction={'column'} gap={2}>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
|
@ -528,10 +529,6 @@ 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'
|
||||||
|
|
@ -551,6 +548,7 @@ 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);
|
||||||
|
|
@ -560,8 +558,8 @@ const FooterConfig = ({ data, setIsEdit, isEdit }: FooterConfigProps) => {
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</VersionMask>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
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';
|
||||||
|
|
@ -24,14 +23,14 @@ const KBSelect = () => {
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [_, setSearchParams] = useURLSearchParams();
|
const [_, setSearchParams] = useURLSearchParams();
|
||||||
const { kb_id, kbList, user } = useAppSelector(state => state.config);
|
const { kb_id, kbList, license, user } = useAppSelector(
|
||||||
|
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 && (
|
||||||
|
|
@ -122,7 +121,8 @@ const KBSelect = () => {
|
||||||
}}
|
}}
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={
|
disabled={
|
||||||
(kbList || []).length >= wikiCount ||
|
(license.edition === 0 && (kbList || []).length >= 1) ||
|
||||||
|
(license.edition === 1 && (kbList || []).length >= 3) ||
|
||||||
user.role === ConstsUserRole.UserRoleUser
|
user.role === ConstsUserRole.UserRoleUser
|
||||||
}
|
}
|
||||||
onClick={event => {
|
onClick={event => {
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,27 @@ 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 { useVersionInfo } from '@/hooks';
|
import { EditionType } from '@/constant/enums';
|
||||||
import { useAppDispatch, useAppSelector } from '@/store';
|
import { useAppDispatch, useAppSelector } from '@/store';
|
||||||
import { setLicense } from '@/store/slices/config';
|
import { setLicense } from '@/store/slices/config';
|
||||||
import { Box, Button, IconButton, Stack, TextField } from '@mui/material';
|
import {
|
||||||
|
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;
|
||||||
|
|
@ -35,9 +42,10 @@ 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 === ConstsLicenseEdition.LicenseEditionEnterprise
|
license.edition === 2 ? 'file' : 'code',
|
||||||
? 'file'
|
);
|
||||||
: 'code',
|
const [authVersion, setAuthVersion] = useState<'contributor' | 'enterprise'>(
|
||||||
|
license.edition === 2 ? 'enterprise' : 'contributor',
|
||||||
);
|
);
|
||||||
const [updateOpen, setUpdateOpen] = useState(false);
|
const [updateOpen, setUpdateOpen] = useState(false);
|
||||||
const [code, setCode] = useState('');
|
const [code, setCode] = useState('');
|
||||||
|
|
@ -45,15 +53,16 @@ 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 = () => {
|
||||||
setLoading(true);
|
const params: PostApiV1LicensePayload = {
|
||||||
postApiV1License({
|
license_edition: authVersion,
|
||||||
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);
|
||||||
|
|
@ -139,8 +148,10 @@ 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 }}>{versionInfo.label}</Box>
|
<Box sx={{ minWidth: 50 }}>
|
||||||
{license.edition === ConstsLicenseEdition.LicenseEditionFree ? (
|
{EditionType[license.edition as keyof typeof EditionType].text}
|
||||||
|
</Box>
|
||||||
|
{license.edition === 0 ? (
|
||||||
<Stack direction={'row'} gap={2}>
|
<Stack direction={'row'} gap={2}>
|
||||||
<Button
|
<Button
|
||||||
size='small'
|
size='small'
|
||||||
|
|
@ -229,7 +240,7 @@ const AuthTypeModal = ({
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
{license.edition! !== ConstsLicenseEdition.LicenseEditionFree && (
|
{license.edition! > 0 && (
|
||||||
<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>
|
||||||
|
|
@ -277,6 +288,18 @@ 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,14 +1,24 @@
|
||||||
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 { useVersionInfo } from '@/hooks';
|
import freeVersion from '@/assets/images/free-version.png';
|
||||||
|
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 versionInfo = useVersionInfo();
|
const { license } = useAppSelector(state => state.config);
|
||||||
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,
|
||||||
|
|
@ -47,8 +57,11 @@ 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 src={versionInfo.image} style={{ height: 13, marginTop: -1 }} />
|
<img
|
||||||
{versionInfo.label}
|
src={versionMap[license.edition!]}
|
||||||
|
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,8 +9,7 @@ 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';
|
||||||
|
|
||||||
|
|
@ -27,13 +26,9 @@ const VERSION_MAP = {
|
||||||
message: '开源版只支持 1 个管理员',
|
message: '开源版只支持 1 个管理员',
|
||||||
max: 1,
|
max: 1,
|
||||||
},
|
},
|
||||||
[ConstsLicenseEdition.LicenseEditionProfession]: {
|
[ConstsLicenseEdition.LicenseEditionContributor]: {
|
||||||
message: '专业版最多支持 20 个管理员',
|
message: '联创版最多支持 3 个管理员',
|
||||||
max: 20,
|
max: 3,
|
||||||
},
|
|
||||||
[ConstsLicenseEdition.LicenseEditionBusiness]: {
|
|
||||||
message: '商业版最多支持 50 个管理员',
|
|
||||||
max: 50,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -50,6 +45,9 @@ 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,
|
||||||
|
|
@ -120,10 +118,6 @@ const MemberAdd = ({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const isPro = useMemo(() => {
|
|
||||||
return PROFESSION_VERSION_PERMISSION.includes(license.edition!);
|
|
||||||
}, [license.edition]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -259,14 +253,6 @@ 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]
|
||||||
|
|
@ -280,25 +266,17 @@ 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>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
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,6 +797,21 @@ 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: '全屏',
|
||||||
|
|
|
||||||
|
|
@ -1,293 +0,0 @@
|
||||||
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,8 +1,3 @@
|
||||||
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';
|
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
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,8 +8,6 @@ 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 {
|
||||||
|
|
@ -48,7 +46,7 @@ const statusColorMap = {
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default function ContributionPage() {
|
export default function ContributionPage() {
|
||||||
const { kb_id = '', license } = useAppSelector(state => state.config);
|
const { kb_id = '', kbDetail } = 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');
|
||||||
|
|
@ -285,14 +283,12 @@ export default function ContributionPage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (kb_id && PROFESSION_VERSION_PERMISSION.includes(license.edition!))
|
if (kb_id) getData();
|
||||||
getData();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [page, pageSize, nodeNameParam, authNameParam, kb_id, license.edition]);
|
}, [page, pageSize, nodeNameParam, authNameParam, kb_id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<VersionMask permission={PROFESSION_VERSION_PERMISSION}>
|
|
||||||
<Stack
|
<Stack
|
||||||
direction='row'
|
direction='row'
|
||||||
alignItems={'center'}
|
alignItems={'center'}
|
||||||
|
|
@ -392,7 +388,6 @@ export default function ContributionPage() {
|
||||||
onClose={() => setDocModalOpen(false)}
|
onClose={() => setDocModalOpen(false)}
|
||||||
onOk={handleDocModalOk}
|
onOk={handleDocModalOk}
|
||||||
/>
|
/>
|
||||||
</VersionMask>
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,6 @@ 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;
|
||||||
|
|
@ -42,6 +40,8 @@ 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,12 +53,7 @@ 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,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -133,13 +128,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: isBusiness
|
answerable_groups: isEnterprise
|
||||||
? values.answerable_groups.map(item => item.id!)
|
? values.answerable_groups.map(item => item.id!)
|
||||||
: undefined,
|
: undefined,
|
||||||
visitable_groups: isBusiness
|
visitable_groups: isEnterprise
|
||||||
? values.visitable_groups.map(item => item.id!)
|
? values.visitable_groups.map(item => item.id!)
|
||||||
: undefined,
|
: undefined,
|
||||||
visible_groups: isBusiness
|
visible_groups: isEnterprise
|
||||||
? values.visible_groups.map(item => item.id!)
|
? values.visible_groups.map(item => item.id!)
|
||||||
: undefined,
|
: undefined,
|
||||||
}),
|
}),
|
||||||
|
|
@ -158,15 +153,15 @@ const DocPropertiesModal = ({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const isBusiness = useMemo(() => {
|
const isEnterprise = useMemo(() => {
|
||||||
return BUSINESS_VERSION_PERMISSION.includes(license.edition!);
|
return license.edition === 2;
|
||||||
}, [license]);
|
}, [license]);
|
||||||
|
|
||||||
const tree = filterEmptyFolders(convertToTree(data));
|
const tree = filterEmptyFolders(convertToTree(data));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open && data) {
|
if (open && data) {
|
||||||
if (isBusiness) {
|
if (isEnterprise) {
|
||||||
getApiProV1AuthGroupList({
|
getApiProV1AuthGroupList({
|
||||||
kb_id: kb_id!,
|
kb_id: kb_id!,
|
||||||
page: 1,
|
page: 1,
|
||||||
|
|
@ -211,7 +206,7 @@ const DocPropertiesModal = ({
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [open, data, isBusiness]);
|
}, [open, data, isEnterprise]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
|
|
@ -307,15 +302,22 @@ const DocPropertiesModal = ({
|
||||||
name='answerable'
|
name='answerable'
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<RadioGroup row {...field} sx={{ gap: 2 }}>
|
<RadioGroup row {...field}>
|
||||||
{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={option.label}
|
label={
|
||||||
|
option.label +
|
||||||
|
(!isEnterprise &&
|
||||||
|
option.value ===
|
||||||
|
ConstsNodeAccessPerm.NodeAccessPermPartial
|
||||||
|
? tips
|
||||||
|
: '')
|
||||||
|
}
|
||||||
disabled={
|
disabled={
|
||||||
!isBusiness &&
|
!isEnterprise &&
|
||||||
option.value ===
|
option.value ===
|
||||||
ConstsNodeAccessPerm.NodeAccessPermPartial
|
ConstsNodeAccessPerm.NodeAccessPermPartial
|
||||||
}
|
}
|
||||||
|
|
@ -357,15 +359,22 @@ const DocPropertiesModal = ({
|
||||||
name='visitable'
|
name='visitable'
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<RadioGroup row {...field} sx={{ gap: 2 }}>
|
<RadioGroup row {...field}>
|
||||||
{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={option.label}
|
label={
|
||||||
|
option.label +
|
||||||
|
(!isEnterprise &&
|
||||||
|
option.value ===
|
||||||
|
ConstsNodeAccessPerm.NodeAccessPermPartial
|
||||||
|
? tips
|
||||||
|
: '')
|
||||||
|
}
|
||||||
disabled={
|
disabled={
|
||||||
!isBusiness &&
|
!isEnterprise &&
|
||||||
option.value ===
|
option.value ===
|
||||||
ConstsNodeAccessPerm.NodeAccessPermPartial
|
ConstsNodeAccessPerm.NodeAccessPermPartial
|
||||||
}
|
}
|
||||||
|
|
@ -407,15 +416,22 @@ const DocPropertiesModal = ({
|
||||||
name='visible'
|
name='visible'
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<RadioGroup row {...field} sx={{ gap: 2 }}>
|
<RadioGroup row {...field}>
|
||||||
{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={option.label}
|
label={
|
||||||
|
option.label +
|
||||||
|
(!isEnterprise &&
|
||||||
|
option.value ===
|
||||||
|
ConstsNodeAccessPerm.NodeAccessPermPartial
|
||||||
|
? tips
|
||||||
|
: '')
|
||||||
|
}
|
||||||
disabled={
|
disabled={
|
||||||
!isBusiness &&
|
!isEnterprise &&
|
||||||
option.value ===
|
option.value ===
|
||||||
ConstsNodeAccessPerm.NodeAccessPermPartial
|
ConstsNodeAccessPerm.NodeAccessPermPartial
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,6 @@ 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;
|
||||||
|
|
@ -54,8 +52,8 @@ const Header = ({
|
||||||
|
|
||||||
const [showSaveTip, setShowSaveTip] = useState(false);
|
const [showSaveTip, setShowSaveTip] = useState(false);
|
||||||
|
|
||||||
const isBusiness = useMemo(() => {
|
const isEnterprise = useMemo(() => {
|
||||||
return BUSINESS_VERSION_PERMISSION.includes(license.edition!);
|
return license.edition === 2;
|
||||||
}, [license]);
|
}, [license]);
|
||||||
|
|
||||||
const handlePublish = useCallback(() => {
|
const handlePublish = useCallback(() => {
|
||||||
|
|
@ -311,7 +309,6 @@ const Header = ({
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
key: 'copy',
|
key: 'copy',
|
||||||
textSx: { flex: 1 },
|
|
||||||
label: <StyledMenuSelect>复制</StyledMenuSelect>,
|
label: <StyledMenuSelect>复制</StyledMenuSelect>,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (kb_id) {
|
if (kb_id) {
|
||||||
|
|
@ -331,22 +328,26 @@ const Header = ({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'version',
|
key: 'version',
|
||||||
textSx: { flex: 1 },
|
|
||||||
label: (
|
label: (
|
||||||
<StyledMenuSelect disabled={!isBusiness}>
|
<StyledMenuSelect disabled={!isEnterprise}>
|
||||||
历史版本
|
历史版本{' '}
|
||||||
<VersionCanUse permission={BUSINESS_VERSION_PERMISSION} />
|
{!isEnterprise && (
|
||||||
|
<Tooltip title='企业版可用' placement='top' arrow>
|
||||||
|
<InfoIcon
|
||||||
|
sx={{ color: 'text.secondary', fontSize: 14 }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</StyledMenuSelect>
|
</StyledMenuSelect>
|
||||||
),
|
),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (isBusiness) {
|
if (isEnterprise) {
|
||||||
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);
|
||||||
|
|
@ -354,7 +355,6 @@ 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,
|
||||||
minWidth: 106,
|
width: 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,7 +29,6 @@ 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;
|
||||||
|
|
@ -73,8 +72,8 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
||||||
emoji: defaultDetail.meta?.emoji || '',
|
emoji: defaultDetail.meta?.emoji || '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const isBusiness = useMemo(() => {
|
const isEnterprise = useMemo(() => {
|
||||||
return BUSINESS_VERSION_PERMISSION.includes(license.edition!);
|
return license.edition === 2;
|
||||||
}, [license]);
|
}, [license]);
|
||||||
|
|
||||||
const debouncedUpdateSummary = useCallback(
|
const debouncedUpdateSummary = useCallback(
|
||||||
|
|
@ -384,7 +383,7 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
||||||
</Stack>
|
</Stack>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<Tooltip arrow title={isBusiness ? '查看历史版本' : ''}>
|
<Tooltip arrow title={isEnterprise ? '查看历史版本' : ''}>
|
||||||
<Stack
|
<Stack
|
||||||
direction={'row'}
|
direction={'row'}
|
||||||
alignItems={'center'}
|
alignItems={'center'}
|
||||||
|
|
@ -392,13 +391,13 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: 'text.tertiary',
|
color: 'text.tertiary',
|
||||||
cursor: isBusiness ? 'pointer' : 'text',
|
cursor: isEnterprise ? 'pointer' : 'text',
|
||||||
':hover': {
|
':hover': {
|
||||||
color: isBusiness ? 'primary.main' : 'text.tertiary',
|
color: isEnterprise ? 'primary.main' : 'text.tertiary',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isBusiness) {
|
if (isEnterprise) {
|
||||||
navigate(`/doc/editor/history/${defaultDetail.id}`);
|
navigate(`/doc/editor/history/${defaultDetail.id}`);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ 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';
|
||||||
|
|
@ -419,19 +418,15 @@ const Content = () => {
|
||||||
>
|
>
|
||||||
{publish.unpublished} 个 文档/文件夹未发布,
|
{publish.unpublished} 个 文档/文件夹未发布,
|
||||||
</Box>
|
</Box>
|
||||||
<ButtonBase
|
<Button
|
||||||
disableRipple
|
size='small'
|
||||||
sx={{
|
sx={{ minWidth: 0, p: 0, fontSize: 12 }}
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: 400,
|
|
||||||
color: 'primary.main',
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPublishOpen(true);
|
setPublishOpen(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
去发布
|
去发布
|
||||||
</ButtonBase>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{ragReStartCount > 0 && (
|
{ragReStartCount > 0 && (
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ 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';
|
||||||
|
|
||||||
|
|
@ -163,8 +162,8 @@ const Comments = ({
|
||||||
useState<DomainWebAppCommentSettings | null>(null);
|
useState<DomainWebAppCommentSettings | null>(null);
|
||||||
|
|
||||||
const isEnableReview = useMemo(() => {
|
const isEnableReview = useMemo(() => {
|
||||||
return PROFESSION_VERSION_PERMISSION.includes(license.edition!);
|
return !!(license.edition === 1 || license.edition === 2);
|
||||||
}, [license.edition]);
|
}, [license]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShowCommentsFilter(isEnableReview);
|
setShowCommentsFilter(isEnableReview);
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@ 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;
|
||||||
|
|
@ -25,8 +23,7 @@ interface AddRoleProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddRole = ({ open, onCancel, onOk, selectedIds }: AddRoleProps) => {
|
const AddRole = ({ open, onCancel, onOk, selectedIds }: AddRoleProps) => {
|
||||||
const { kb_id } = useAppSelector(state => state.config);
|
const { kb_id, license } = 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>('');
|
||||||
|
|
@ -34,6 +31,10 @@ 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: '',
|
||||||
|
|
@ -118,10 +119,6 @@ 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 站管理员'
|
||||||
|
|
@ -212,33 +209,22 @@ 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,6 +1,5 @@
|
||||||
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';
|
||||||
|
|
@ -34,12 +33,11 @@ const CardAI = ({ kb }: CardAIProps) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const isPro = useMemo(() => {
|
const isPro = useMemo(() => {
|
||||||
return PROFESSION_VERSION_PERMISSION.includes(license.edition!);
|
return license.edition === 1 || license.edition === 2;
|
||||||
}, [license]);
|
}, [license]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!kb.id || !PROFESSION_VERSION_PERMISSION.includes(license.edition!))
|
if (!kb.id || !isPro) return;
|
||||||
return;
|
|
||||||
getApiProV1Prompt({ kb_id: kb.id! }).then(res => {
|
getApiProV1Prompt({ kb_id: kb.id! }).then(res => {
|
||||||
setValue('content', res.content || '');
|
setValue('content', res.content || '');
|
||||||
});
|
});
|
||||||
|
|
@ -56,7 +54,7 @@ const CardAI = ({ kb }: CardAIProps) => {
|
||||||
<SettingCardItem title='智能问答' isEdit={isEdit} onSubmit={onSubmit}>
|
<SettingCardItem title='智能问答' isEdit={isEdit} onSubmit={onSubmit}>
|
||||||
<FormItem
|
<FormItem
|
||||||
vertical
|
vertical
|
||||||
permission={PROFESSION_VERSION_PERMISSION}
|
tooltip={!isPro && '联创版和企业版可用'}
|
||||||
extra={
|
extra={
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,6 @@ 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 {
|
||||||
|
|
@ -116,7 +114,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
|
||||||
? isBusiness
|
? isPro
|
||||||
? postApiProV1AuthSet({
|
? postApiProV1AuthSet({
|
||||||
kb_id,
|
kb_id,
|
||||||
source_type: value.source_type as ConstsSourceType,
|
source_type: value.source_type as ConstsSourceType,
|
||||||
|
|
@ -159,18 +157,25 @@ const CardAuth = ({ kb, refresh }: CardAuthProps) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const isBusiness = useMemo(() => {
|
const isPro = useMemo(() => {
|
||||||
return BUSINESS_VERSION_PERMISSION.includes(license.edition!);
|
return license.edition === 1 || license.edition === 2;
|
||||||
|
}, [license]);
|
||||||
|
|
||||||
|
const isEnterprise = useMemo(() => {
|
||||||
|
return license.edition === 2;
|
||||||
}, [license]);
|
}, [license]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const source_type = isBusiness
|
const source_type = isPro
|
||||||
? 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, isBusiness]);
|
}, [kb, isPro]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (kb.access_settings?.simple_auth) {
|
if (kb.access_settings?.simple_auth) {
|
||||||
|
|
@ -186,7 +191,7 @@ const CardAuth = ({ kb, refresh }: CardAuthProps) => {
|
||||||
}, [kb]);
|
}, [kb]);
|
||||||
|
|
||||||
const getAuth = () => {
|
const getAuth = () => {
|
||||||
if (isBusiness) {
|
if (isPro) {
|
||||||
getApiProV1AuthGet({
|
getApiProV1AuthGet({
|
||||||
kb_id,
|
kb_id,
|
||||||
source_type: source_type as ConstsSourceType,
|
source_type: source_type as ConstsSourceType,
|
||||||
|
|
@ -231,7 +236,7 @@ const CardAuth = ({ kb, refresh }: CardAuthProps) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!kb_id || enabled !== '2') return;
|
if (!kb_id || enabled !== '2') return;
|
||||||
getAuth();
|
getAuth();
|
||||||
}, [kb_id, isBusiness, source_type, enabled]);
|
}, [kb_id, isPro, source_type, enabled]);
|
||||||
|
|
||||||
const columns: ColumnType<GithubComChaitinPandaWikiProApiAuthV1AuthItem>[] = [
|
const columns: ColumnType<GithubComChaitinPandaWikiProApiAuthV1AuthItem>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -870,18 +875,8 @@ 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={{
|
sx={{ height: 52 }}
|
||||||
height: 52,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypePassword}
|
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypePassword}
|
||||||
|
|
@ -890,52 +885,44 @@ const CardAuth = ({ kb, refresh }: CardAuthProps) => {
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeDingTalk}
|
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeDingTalk}
|
||||||
disabled={!isBusiness}
|
disabled={!isPro}
|
||||||
>
|
>
|
||||||
钉钉登录{' '}
|
钉钉登录 {isPro ? '' : tips}
|
||||||
<VersionCanUse permission={BUSINESS_VERSION_PERMISSION} />
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeFeishu}
|
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeFeishu}
|
||||||
disabled={!isBusiness}
|
disabled={!isPro}
|
||||||
>
|
>
|
||||||
飞书登录{' '}
|
飞书登录 {isPro ? '' : tips}
|
||||||
<VersionCanUse permission={BUSINESS_VERSION_PERMISSION} />
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeWeCom}
|
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeWeCom}
|
||||||
disabled={!isBusiness}
|
disabled={!isPro}
|
||||||
>
|
>
|
||||||
企业微信登录{' '}
|
企业微信登录 {isPro ? '' : tips}
|
||||||
<VersionCanUse permission={BUSINESS_VERSION_PERMISSION} />
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeOAuth}
|
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeOAuth}
|
||||||
disabled={!isBusiness}
|
disabled={!isPro}
|
||||||
>
|
>
|
||||||
OAuth 登录{' '}
|
OAuth 登录 {isPro ? '' : tips}
|
||||||
<VersionCanUse permission={BUSINESS_VERSION_PERMISSION} />
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeCAS}
|
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeCAS}
|
||||||
disabled={!isBusiness}
|
disabled={!isPro}
|
||||||
>
|
>
|
||||||
CAS 登录{' '}
|
CAS 登录 {isPro ? '' : tips}
|
||||||
<VersionCanUse permission={BUSINESS_VERSION_PERMISSION} />
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeLDAP}
|
value={EXTEND_CONSTS_SOURCE_TYPE.SourceTypeLDAP}
|
||||||
disabled={!isBusiness}
|
disabled={!isPro}
|
||||||
>
|
>
|
||||||
LDAP 登录{' '}
|
LDAP 登录 {isPro ? '' : tips}
|
||||||
<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 { PROFESSION_VERSION_PERMISSION } from '@/constant/version';
|
import InfoIcon from '@mui/icons-material/Info';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Chip,
|
Chip,
|
||||||
|
|
@ -12,6 +12,7 @@ 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';
|
||||||
|
|
@ -36,7 +37,7 @@ const DocumentComments = ({
|
||||||
data: DomainAppDetailResp;
|
data: DomainAppDetailResp;
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { kb_id } = useAppSelector(state => state.config);
|
const { license, 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: {
|
||||||
|
|
@ -56,6 +57,8 @@ 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! },
|
||||||
|
|
@ -105,7 +108,7 @@ const DocumentComments = ({
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem label='评论审核' permission={PROFESSION_VERSION_PERMISSION}>
|
<FormItem label='评论审核' tooltip={!isPro && '联创版和企业版可用'}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name='moderation_enable'
|
name='moderation_enable'
|
||||||
|
|
@ -113,6 +116,7 @@ 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);
|
||||||
|
|
@ -120,12 +124,12 @@ const DocumentComments = ({
|
||||||
>
|
>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={1}
|
value={1}
|
||||||
control={<Radio size='small' />}
|
control={<Radio size='small' disabled={!isPro} />}
|
||||||
label={<StyledRadioLabel>启用</StyledRadioLabel>}
|
label={<StyledRadioLabel>启用</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={0}
|
value={0}
|
||||||
control={<Radio size='small' />}
|
control={<Radio size='small' disabled={!isPro} />}
|
||||||
label={<StyledRadioLabel>禁用</StyledRadioLabel>}
|
label={<StyledRadioLabel>禁用</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
@ -146,7 +150,7 @@ const AIQuestion = ({
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [isEdit, setIsEdit] = useState(false);
|
const [isEdit, setIsEdit] = useState(false);
|
||||||
const { kb_id } = useAppSelector(state => state.config);
|
const { kb_id, license } = useAppSelector(state => state.config);
|
||||||
const { control, handleSubmit, setValue } = useForm({
|
const { control, handleSubmit, setValue } = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
is_enabled: true,
|
is_enabled: true,
|
||||||
|
|
@ -155,6 +159,7 @@ 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(
|
||||||
|
|
@ -268,7 +273,7 @@ const AIQuestion = ({
|
||||||
)}
|
)}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem label='免责声明' permission={PROFESSION_VERSION_PERMISSION}>
|
<FormItem label='免责声明' tooltip={!isEnterprise && '企业版可用'}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name='disclaimer'
|
name='disclaimer'
|
||||||
|
|
@ -277,6 +282,7 @@ const AIQuestion = ({
|
||||||
{...field}
|
{...field}
|
||||||
fullWidth
|
fullWidth
|
||||||
value={field.value || ''}
|
value={field.value || ''}
|
||||||
|
disabled={!isEnterprise}
|
||||||
placeholder='请输入免责声明'
|
placeholder='请输入免责声明'
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
setIsEdit(true);
|
setIsEdit(true);
|
||||||
|
|
@ -298,7 +304,7 @@ const DocumentContribution = ({
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [isEdit, setIsEdit] = useState(false);
|
const [isEdit, setIsEdit] = useState(false);
|
||||||
const { kb_id } = useAppSelector(state => state.config);
|
const { license, kb_id } = useAppSelector(state => state.config);
|
||||||
const { control, handleSubmit, setValue } = useForm({
|
const { control, handleSubmit, setValue } = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
is_enable: false,
|
is_enable: false,
|
||||||
|
|
@ -324,6 +330,7 @@ const DocumentContribution = ({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isPro = license.edition === 1 || license.edition === 2;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue(
|
setValue(
|
||||||
'is_enable',
|
'is_enable',
|
||||||
|
|
@ -333,8 +340,21 @@ const DocumentContribution = ({
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingCardItem title='文档贡献' isEdit={isEdit} onSubmit={onSubmit}>
|
<SettingCardItem
|
||||||
<FormItem label='文档贡献' permission={PROFESSION_VERSION_PERMISSION}>
|
title={
|
||||||
|
<>
|
||||||
|
文档贡献
|
||||||
|
{!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'
|
||||||
|
|
@ -342,7 +362,7 @@ const DocumentContribution = ({
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
row
|
row
|
||||||
{...field}
|
{...field}
|
||||||
value={field.value}
|
value={isPro ? field.value : undefined}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
setIsEdit(true);
|
setIsEdit(true);
|
||||||
field.onChange(e.target.value === 'true');
|
field.onChange(e.target.value === 'true');
|
||||||
|
|
@ -350,12 +370,12 @@ const DocumentContribution = ({
|
||||||
>
|
>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={true}
|
value={true}
|
||||||
control={<Radio size='small' />}
|
control={<Radio size='small' disabled={!isPro} />}
|
||||||
label={<StyledRadioLabel>启用</StyledRadioLabel>}
|
label={<StyledRadioLabel>启用</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={false}
|
value={false}
|
||||||
control={<Radio size='small' />}
|
control={<Radio size='small' disabled={!isPro} />}
|
||||||
label={<StyledRadioLabel>禁用</StyledRadioLabel>}
|
label={<StyledRadioLabel>禁用</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,6 @@ 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'];
|
||||||
|
|
@ -73,8 +69,8 @@ const ApiToken = () => {
|
||||||
perm: ConstsUserKBPermission.UserKBPermissionFullControl,
|
perm: ConstsUserKBPermission.UserKBPermissionFullControl,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const isBusiness = useMemo(() => {
|
const isEnterprise = useMemo(() => {
|
||||||
return BUSINESS_VERSION_PERMISSION.includes(license.edition!);
|
return license.edition === 2;
|
||||||
}, [license]);
|
}, [license]);
|
||||||
|
|
||||||
const onDeleteApiToken = (id: string, name: string) => {
|
const onDeleteApiToken = (id: string, name: string) => {
|
||||||
|
|
@ -135,9 +131,9 @@ const ApiToken = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!kb_id || !isBusiness) return;
|
if (!kb_id) return;
|
||||||
getApiTokenList();
|
getApiTokenList();
|
||||||
}, [kb_id, isBusiness]);
|
}, [kb_id]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!addOpen) reset();
|
if (!addOpen) reset();
|
||||||
|
|
@ -146,17 +142,27 @@ 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>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -226,7 +232,7 @@ const ApiToken = () => {
|
||||||
size='small'
|
size='small'
|
||||||
sx={{ width: 120 }}
|
sx={{ width: 120 }}
|
||||||
value={it.permission}
|
value={it.permission}
|
||||||
disabled={!isBusiness || user.role !== 'admin'}
|
disabled={!isEnterprise || user.role !== 'admin'}
|
||||||
onChange={e =>
|
onChange={e =>
|
||||||
onUpdateApiToken(it.id!, e.target.value as ApiTokenPermission)
|
onUpdateApiToken(it.id!, e.target.value as ApiTokenPermission)
|
||||||
}
|
}
|
||||||
|
|
@ -253,7 +259,7 @@ const ApiToken = () => {
|
||||||
kbDetail?.perm !==
|
kbDetail?.perm !==
|
||||||
ConstsUserKBPermission.UserKBPermissionFullControl
|
ConstsUserKBPermission.UserKBPermissionFullControl
|
||||||
? '权限不足'
|
? '权限不足'
|
||||||
: '商业版可用'
|
: '企业版可用'
|
||||||
}
|
}
|
||||||
placement='top'
|
placement='top'
|
||||||
arrow
|
arrow
|
||||||
|
|
@ -264,7 +270,7 @@ const ApiToken = () => {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
ml: 1,
|
ml: 1,
|
||||||
visibility:
|
visibility:
|
||||||
!isBusiness ||
|
!isEnterprise ||
|
||||||
kbDetail?.perm !==
|
kbDetail?.perm !==
|
||||||
ConstsUserKBPermission.UserKBPermissionFullControl
|
ConstsUserKBPermission.UserKBPermissionFullControl
|
||||||
? 'visible'
|
? 'visible'
|
||||||
|
|
@ -279,13 +285,13 @@ const ApiToken = () => {
|
||||||
type='icon-icon_tool_close'
|
type='icon-icon_tool_close'
|
||||||
sx={{
|
sx={{
|
||||||
cursor:
|
cursor:
|
||||||
!isBusiness ||
|
!isEnterprise ||
|
||||||
kbDetail?.perm !==
|
kbDetail?.perm !==
|
||||||
ConstsUserKBPermission.UserKBPermissionFullControl
|
ConstsUserKBPermission.UserKBPermissionFullControl
|
||||||
? 'not-allowed'
|
? 'not-allowed'
|
||||||
: 'pointer',
|
: 'pointer',
|
||||||
color:
|
color:
|
||||||
!isBusiness ||
|
!isEnterprise ||
|
||||||
kbDetail?.perm !==
|
kbDetail?.perm !==
|
||||||
ConstsUserKBPermission.UserKBPermissionFullControl
|
ConstsUserKBPermission.UserKBPermissionFullControl
|
||||||
? 'text.disabled'
|
? 'text.disabled'
|
||||||
|
|
@ -293,7 +299,7 @@ const ApiToken = () => {
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (
|
if (
|
||||||
!isBusiness ||
|
!isEnterprise ||
|
||||||
kbDetail?.perm !==
|
kbDetail?.perm !==
|
||||||
ConstsUserKBPermission.UserKBPermissionFullControl
|
ConstsUserKBPermission.UserKBPermissionFullControl
|
||||||
)
|
)
|
||||||
|
|
@ -361,16 +367,17 @@ 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>
|
||||||
);
|
);
|
||||||
|
|
@ -398,9 +405,9 @@ const CardKB = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const isPro = useMemo(() => {
|
const isEnterprise = useMemo(() => {
|
||||||
return PROFESSION_VERSION_PERMISSION.includes(license.edition!);
|
return license.edition === 2;
|
||||||
}, [license.edition]);
|
}, [license]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!kb_id) return;
|
if (!kb_id) return;
|
||||||
|
|
@ -506,7 +513,7 @@ const CardKB = () => {
|
||||||
size='small'
|
size='small'
|
||||||
sx={{ width: 180 }}
|
sx={{ width: 180 }}
|
||||||
value={it.perms}
|
value={it.perms}
|
||||||
disabled={!isPro || it.role === 'admin'}
|
disabled={!isEnterprise || it.role === 'admin'}
|
||||||
onChange={e =>
|
onChange={e =>
|
||||||
onUpdateUserPermission(
|
onUpdateUserPermission(
|
||||||
it.id!,
|
it.id!,
|
||||||
|
|
@ -535,7 +542,7 @@ const CardKB = () => {
|
||||||
title={
|
title={
|
||||||
it.role === 'admin'
|
it.role === 'admin'
|
||||||
? '超级管理员不可被修改权限'
|
? '超级管理员不可被修改权限'
|
||||||
: '专业版可用'
|
: '企业版可用'
|
||||||
}
|
}
|
||||||
placement='top'
|
placement='top'
|
||||||
arrow
|
arrow
|
||||||
|
|
@ -546,7 +553,9 @@ const CardKB = () => {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
ml: 1,
|
ml: 1,
|
||||||
visibility:
|
visibility:
|
||||||
!isPro || it.role === 'admin' ? 'visible' : 'hidden',
|
!isEnterprise || it.role === 'admin'
|
||||||
|
? 'visible'
|
||||||
|
: 'hidden',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { DomainKnowledgeBaseDetail } from '@/request/types';
|
import { DomainKnowledgeBaseDetail } from '@/request/types';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
|
Button,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
Link,
|
Link,
|
||||||
|
|
@ -12,11 +13,10 @@ 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, useState } from 'react';
|
import { useEffect, useMemo, 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,6 +29,11 @@ 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,
|
||||||
|
|
@ -109,7 +114,10 @@ const CardRobotApi = ({
|
||||||
}
|
}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
>
|
>
|
||||||
<FormItem label='问答机器人 API' permission={BUSINESS_VERSION_PERMISSION}>
|
<FormItem
|
||||||
|
label='问答机器人 API'
|
||||||
|
tooltip={!isEnterprise ? '企业版可用' : undefined}
|
||||||
|
>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
|
|
@ -125,11 +133,13 @@ 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>}
|
||||||
/>
|
/>
|
||||||
|
|
@ -140,7 +150,7 @@ const CardRobotApi = ({
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
{isEnabled && BUSINESS_VERSION_PERMISSION.includes(license.edition!) && (
|
{isEnabled && (
|
||||||
<>
|
<>
|
||||||
<FormItem label='API Token' required>
|
<FormItem label='API Token' required>
|
||||||
<Controller
|
<Controller
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,6 @@ 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,
|
||||||
|
|
@ -264,7 +262,6 @@ 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>
|
||||||
|
|
@ -297,7 +294,6 @@ const CardRobotWecomService = ({
|
||||||
{...equalKeywordsField}
|
{...equalKeywordsField}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</VersionMask>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</SettingCardItem>
|
</SettingCardItem>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ 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,
|
||||||
|
|
@ -18,7 +17,7 @@ import {
|
||||||
TextField,
|
TextField,
|
||||||
styled,
|
styled,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useMemo, 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';
|
||||||
|
|
||||||
|
|
@ -33,9 +32,15 @@ const WatermarkForm = ({
|
||||||
data?: DomainAppDetailResp;
|
data?: DomainAppDetailResp;
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { kb_id } = useAppSelector(state => state.config);
|
const { license, kb_id } = useAppSelector(state => state.config);
|
||||||
const [watermarkIsEdit, setWatermarkIsEdit] = useState(false);
|
const [watermarkIsEdit, setWatermarkIsEdit] = useState(false);
|
||||||
const { control, handleSubmit, setValue, watch } = useForm({
|
const {
|
||||||
|
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 ?? '',
|
||||||
|
|
@ -43,6 +48,9 @@ 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;
|
||||||
|
|
@ -74,9 +82,8 @@ const WatermarkForm = ({
|
||||||
title='水印'
|
title='水印'
|
||||||
isEdit={watermarkIsEdit}
|
isEdit={watermarkIsEdit}
|
||||||
onSubmit={handleSaveWatermark}
|
onSubmit={handleSaveWatermark}
|
||||||
permission={BUSINESS_VERSION_PERMISSION}
|
|
||||||
>
|
>
|
||||||
<FormItem label='水印开关'>
|
<FormItem label='水印开关' tooltip={!isEnterprise && '企业版可用'}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name='watermark_setting'
|
name='watermark_setting'
|
||||||
|
|
@ -91,18 +98,18 @@ const WatermarkForm = ({
|
||||||
>
|
>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={ConstsWatermarkSetting.WatermarkVisible}
|
value={ConstsWatermarkSetting.WatermarkVisible}
|
||||||
control={<Radio size='small' />}
|
control={<Radio size='small' disabled={!isEnterprise} />}
|
||||||
label={<StyledRadioLabel>显性水印</StyledRadioLabel>}
|
label={<StyledRadioLabel>显性水印</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={ConstsWatermarkSetting.WatermarkHidden}
|
value={ConstsWatermarkSetting.WatermarkHidden}
|
||||||
control={<Radio size='small' />}
|
control={<Radio size='small' disabled={!isEnterprise} />}
|
||||||
label={<StyledRadioLabel>隐形水印</StyledRadioLabel>}
|
label={<StyledRadioLabel>隐形水印</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={ConstsWatermarkSetting.WatermarkDisabled}
|
value={ConstsWatermarkSetting.WatermarkDisabled}
|
||||||
control={<Radio size='small' />}
|
control={<Radio size='small' disabled={!isEnterprise} />}
|
||||||
label={<StyledRadioLabel>禁用</StyledRadioLabel>}
|
label={<StyledRadioLabel>禁用</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
@ -121,6 +128,7 @@ 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);
|
||||||
|
|
@ -138,6 +146,9 @@ 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: {
|
||||||
|
|
@ -158,18 +169,17 @@ const KeywordsForm = ({ kb }: { kb: DomainKnowledgeBaseDetail }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!kb.id || !BUSINESS_VERSION_PERMISSION.includes(license.edition!))
|
if (!kb.id || !isEnterprise) return;
|
||||||
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, license.edition]);
|
}, [kb, isEnterprise]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingCardItem title='内容合规' isEdit={isEdit} onSubmit={onSubmit}>
|
<SettingCardItem title='内容合规' isEdit={isEdit} onSubmit={onSubmit}>
|
||||||
<FormItem
|
<FormItem
|
||||||
vertical
|
vertical
|
||||||
permission={BUSINESS_VERSION_PERMISSION}
|
tooltip={!isEnterprise && '企业版可用'}
|
||||||
label='屏蔽 AI 问答中的关键字'
|
label='屏蔽 AI 问答中的关键字'
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
|
|
@ -183,6 +193,7 @@ const KeywordsForm = ({ kb }: { kb: DomainKnowledgeBaseDetail }) => {
|
||||||
inputValue={questionInputValue}
|
inputValue={questionInputValue}
|
||||||
options={[]}
|
options={[]}
|
||||||
fullWidth
|
fullWidth
|
||||||
|
disabled={!isEnterprise}
|
||||||
onInputChange={(_, value) => {
|
onInputChange={(_, value) => {
|
||||||
setQuestionInputValue(value);
|
setQuestionInputValue(value);
|
||||||
}}
|
}}
|
||||||
|
|
@ -223,14 +234,23 @@ const CopyForm = ({
|
||||||
data?: DomainAppDetailResp;
|
data?: DomainAppDetailResp;
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { kb_id } = useAppSelector(state => state.config);
|
const { license, 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,
|
||||||
|
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(
|
||||||
|
|
@ -260,7 +280,7 @@ const CopyForm = ({
|
||||||
isEdit={isEdit}
|
isEdit={isEdit}
|
||||||
onSubmit={handleSaveWatermark}
|
onSubmit={handleSaveWatermark}
|
||||||
>
|
>
|
||||||
<FormItem label='限制复制' permission={BUSINESS_VERSION_PERMISSION}>
|
<FormItem label='限制复制' tooltip={!isEnterprise && '企业版可用'}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name='copy_setting'
|
name='copy_setting'
|
||||||
|
|
@ -275,18 +295,18 @@ const CopyForm = ({
|
||||||
>
|
>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={ConstsCopySetting.CopySettingNone}
|
value={ConstsCopySetting.CopySettingNone}
|
||||||
control={<Radio size='small' />}
|
control={<Radio size='small' disabled={!isEnterprise} />}
|
||||||
label={<StyledRadioLabel>不做限制</StyledRadioLabel>}
|
label={<StyledRadioLabel>不做限制</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={ConstsCopySetting.CopySettingAppend}
|
value={ConstsCopySetting.CopySettingAppend}
|
||||||
control={<Radio size='small' />}
|
control={<Radio size='small' disabled={!isEnterprise} />}
|
||||||
label={<StyledRadioLabel>增加内容尾巴</StyledRadioLabel>}
|
label={<StyledRadioLabel>增加内容尾巴</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={ConstsCopySetting.CopySettingDisabled}
|
value={ConstsCopySetting.CopySettingDisabled}
|
||||||
control={<Radio size='small' />}
|
control={<Radio size='small' disabled={!isEnterprise} />}
|
||||||
label={<StyledRadioLabel>禁止复制内容</StyledRadioLabel>}
|
label={<StyledRadioLabel>禁止复制内容</StyledRadioLabel>}
|
||||||
/>
|
/>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ const CardWebSEO = ({ data, id, refresh }: CardWebSEOProps) => {
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
desc: '',
|
desc: '',
|
||||||
keyword: '',
|
keyword: '',
|
||||||
|
auto_sitemap: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -43,6 +44,7 @@ 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 (
|
||||||
|
|
@ -86,6 +88,25 @@ 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,9 +1,7 @@
|
||||||
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 }) => ({
|
||||||
|
|
@ -42,7 +40,6 @@ 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',
|
||||||
|
|
@ -85,7 +82,6 @@ export const FormItem = ({
|
||||||
extra,
|
extra,
|
||||||
sx,
|
sx,
|
||||||
labelSx,
|
labelSx,
|
||||||
permission,
|
|
||||||
}: {
|
}: {
|
||||||
label?: string | React.ReactNode;
|
label?: string | React.ReactNode;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
|
@ -96,13 +92,10 @@ 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}
|
||||||
|
|
@ -113,9 +106,7 @@ 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
|
<InfoIcon sx={{ color: 'text.secondary', fontSize: 14, ml: 1 }} />
|
||||||
sx={{ color: 'text.secondary', fontSize: 14, ml: 1 }}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
tooltip
|
tooltip
|
||||||
|
|
@ -126,7 +117,6 @@ export const FormItem = ({
|
||||||
</StyledFormLabelWrapper>
|
</StyledFormLabelWrapper>
|
||||||
{children}
|
{children}
|
||||||
</StyledFormItem>
|
</StyledFormItem>
|
||||||
</VersionMask>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -152,7 +142,6 @@ 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),
|
||||||
|
|
@ -215,12 +204,6 @@ 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;
|
||||||
|
|
@ -229,7 +212,6 @@ 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) {
|
||||||
|
|
@ -255,9 +237,7 @@ export const SettingCardItem = ({
|
||||||
return more;
|
return more;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VersionMask permission={permission}>
|
|
||||||
<StyledSettingCardItem sx={sx}>
|
<StyledSettingCardItem sx={sx}>
|
||||||
<StyledSettingCardItemTitleWrapper>
|
<StyledSettingCardItemTitleWrapper>
|
||||||
<StyledSettingCardItemTitle>
|
<StyledSettingCardItemTitle>
|
||||||
|
|
@ -272,6 +252,5 @@ export const SettingCardItem = ({
|
||||||
</StyledSettingCardItemTitleWrapper>
|
</StyledSettingCardItemTitleWrapper>
|
||||||
<StyledSettingCardItemContent>{children}</StyledSettingCardItemContent>
|
<StyledSettingCardItemContent>{children}</StyledSettingCardItemContent>
|
||||||
</StyledSettingCardItem>
|
</StyledSettingCardItem>
|
||||||
</VersionMask>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { useEffect, useState } from 'react';
|
import React, { useEffect, useMemo, 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 {
|
||||||
|
|
@ -16,7 +19,6 @@ 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;
|
||||||
|
|
@ -43,6 +45,10 @@ 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: '删除用户组',
|
||||||
|
|
@ -68,15 +74,10 @@ const UserGroup = ({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (!kb_id || enabled !== '2' || !isEnterprise) return;
|
||||||
!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, license.edition!]);
|
}, [kb_id, enabled, isEnterprise]);
|
||||||
|
|
||||||
const handleMove = async ({
|
const handleMove = async ({
|
||||||
id,
|
id,
|
||||||
|
|
@ -122,7 +123,32 @@ const UserGroup = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingCardItem title='用户组' permission={BUSINESS_VERSION_PERMISSION}>
|
<SettingCardItem
|
||||||
|
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,11 +9,6 @@ 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 },
|
||||||
|
|
@ -30,40 +25,13 @@ const Statistic = () => {
|
||||||
const isWideScreen = useMediaQuery('(min-width:1190px)');
|
const isWideScreen = useMediaQuery('(min-width:1190px)');
|
||||||
|
|
||||||
const timeList = useMemo(() => {
|
const timeList = useMemo(() => {
|
||||||
const isPro = PROFESSION_VERSION_PERMISSION.includes(license.edition!);
|
const isPro = license.edition === 1 || license.edition === 2;
|
||||||
const isBusiness = BUSINESS_VERSION_PERMISSION.includes(license.edition!);
|
const isEnterprise = license.edition === 2;
|
||||||
return [
|
return [
|
||||||
{ label: '近 24 小时', value: 1, disabled: false },
|
{ label: '近 24 小时', value: 1, disabled: false },
|
||||||
{
|
{ label: '近 7 天', value: 7, disabled: !isPro },
|
||||||
label: (
|
{ label: '近 30 天', value: 30, disabled: !isEnterprise },
|
||||||
<Stack direction={'row'} alignItems={'center'} gap={0.5}>
|
{ label: '近 90 天', value: 90, disabled: !isEnterprise },
|
||||||
<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,8 +26,6 @@ import {
|
||||||
GetApiV1NodeListParams,
|
GetApiV1NodeListParams,
|
||||||
GetApiV1NodeRecommendNodesParams,
|
GetApiV1NodeRecommendNodesParams,
|
||||||
V1NodeDetailResp,
|
V1NodeDetailResp,
|
||||||
V1NodeRestudyReq,
|
|
||||||
V1NodeRestudyResp,
|
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -265,38 +263,6 @@ 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
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
/* 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,6 +10,7 @@ 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,12 +52,10 @@ export enum ConstsSourceType {
|
||||||
export enum ConstsLicenseEdition {
|
export enum ConstsLicenseEdition {
|
||||||
/** 开源版 */
|
/** 开源版 */
|
||||||
LicenseEditionFree = 0,
|
LicenseEditionFree = 0,
|
||||||
/** 专业版 */
|
/** 联创版 */
|
||||||
LicenseEditionProfession = 1,
|
LicenseEditionContributor = 1,
|
||||||
/** 企业版 */
|
/** 企业版 */
|
||||||
LicenseEditionEnterprise = 2,
|
LicenseEditionEnterprise = 2,
|
||||||
/** 商业版 */
|
|
||||||
LicenseEditionBusiness = 3,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ConstsContributeType {
|
export enum ConstsContributeType {
|
||||||
|
|
@ -467,7 +465,6 @@ export interface GithubComChaitinPandaWikiProApiShareV1AuthOAuthResp {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GithubComChaitinPandaWikiProApiShareV1AuthWecomReq {
|
export interface GithubComChaitinPandaWikiProApiShareV1AuthWecomReq {
|
||||||
is_app?: boolean;
|
|
||||||
kb_id?: string;
|
kb_id?: string;
|
||||||
redirect_url?: string;
|
redirect_url?: string;
|
||||||
}
|
}
|
||||||
|
|
@ -671,6 +668,8 @@ 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,12 +175,10 @@ export enum ConstsNodeAccessPerm {
|
||||||
export enum ConstsLicenseEdition {
|
export enum ConstsLicenseEdition {
|
||||||
/** 开源版 */
|
/** 开源版 */
|
||||||
LicenseEditionFree = 0,
|
LicenseEditionFree = 0,
|
||||||
/** 专业版 */
|
/** 联创版 */
|
||||||
LicenseEditionProfession = 1,
|
LicenseEditionContributor = 1,
|
||||||
/** 企业版 */
|
/** 企业版 */
|
||||||
LicenseEditionEnterprise = 2,
|
LicenseEditionEnterprise = 2,
|
||||||
/** 商业版 */
|
|
||||||
LicenseEditionBusiness = 3,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ConstsHomePageSetting {
|
export enum ConstsHomePageSetting {
|
||||||
|
|
@ -1647,9 +1645,8 @@ export interface V1NodePermissionResp {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface V1NodeRestudyReq {
|
export interface V1NodeRestudyReq {
|
||||||
kb_id: string;
|
kb_id?: string;
|
||||||
/** @minItems 1 */
|
node_ids?: string[];
|
||||||
node_ids: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type V1NodeRestudyResp = Record<string, any>;
|
export type V1NodeRestudyResp = Record<string, any>;
|
||||||
|
|
|
||||||
|
|
@ -82,8 +82,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
@ -384,8 +382,6 @@ 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;
|
||||||
|
|
@ -469,8 +465,6 @@ 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;
|
||||||
});
|
});
|
||||||
|
|
@ -519,8 +513,6 @@ 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 = '';
|
||||||
|
|
@ -639,8 +631,6 @@ 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(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -677,8 +667,6 @@ const AiQaContent: React.FC<{
|
||||||
chunk_result: [],
|
chunk_result: [],
|
||||||
thinking_content: '',
|
thinking_content: '',
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
result_expend: true,
|
|
||||||
thinking_expend: true,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -803,16 +791,7 @@ const AiQaContent: React.FC<{
|
||||||
<StyledAiBubble>
|
<StyledAiBubble>
|
||||||
{/* 搜索结果 */}
|
{/* 搜索结果 */}
|
||||||
{item.chunk_result.length > 0 && (
|
{item.chunk_result.length > 0 && (
|
||||||
<StyledChunkAccordion
|
<StyledChunkAccordion defaultExpanded>
|
||||||
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 }} />}
|
||||||
>
|
>
|
||||||
|
|
@ -858,16 +837,7 @@ const AiQaContent: React.FC<{
|
||||||
|
|
||||||
{/* 思考过程 */}
|
{/* 思考过程 */}
|
||||||
{!!item.thinking_content && (
|
{!!item.thinking_content && (
|
||||||
<StyledThinkingAccordion
|
<StyledThinkingAccordion defaultExpanded>
|
||||||
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 }} />}
|
||||||
>
|
>
|
||||||
|
|
@ -959,9 +929,6 @@ 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),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,217 @@
|
||||||
|
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,9 +247,7 @@ const QaModal: React.FC<QaModalProps> = () => {
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
px: 3,
|
px: 3,
|
||||||
pt: kbDetail?.settings?.web_app_custom_style?.show_brand_info
|
pt: kbDetail?.settings?.disclaimer_settings?.content ? 2 : 0,
|
||||||
? 2
|
|
||||||
: 0,
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
|
@ -265,10 +263,7 @@ const QaModal: React.FC<QaModalProps> = () => {
|
||||||
gap: 1,
|
gap: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box>
|
<Box>{kbDetail?.settings?.disclaimer_settings?.content}</Box>
|
||||||
{kbDetail?.settings?.web_app_custom_style?.show_brand_info &&
|
|
||||||
'本网站由 PandaWiki 提供技术支持'}
|
|
||||||
</Box>
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue