完善知识库容量权限与界面

This commit is contained in:
baiyanyun
2026-07-14 16:39:40 +08:00
parent 5b14b3f48d
commit 9002abfe61
16 changed files with 669 additions and 160 deletions

View File

@@ -42,6 +42,9 @@ public class ComponentDto implements Serializable {
@Schema(description = "创建时间") @Schema(description = "创建时间")
private Date created; private Date created;
@Schema(description = "资源占用空间,单位字节,知识库有效")
private Long fileSize;
private Long creatorId; private Long creatorId;
@Schema(description = "创建者信息") @Schema(description = "创建者信息")

View File

@@ -105,6 +105,8 @@ public class ComponentApplicationServiceImpl implements ComponentApplicationServ
componentDto.setType(ComponentTypeEnum.Knowledge); componentDto.setType(ComponentTypeEnum.Knowledge);
componentDto.setExt(knowledgeConfigModel.getDataType()); componentDto.setExt(knowledgeConfigModel.getDataType());
componentDto.setIcon(knowledgeConfigModel.getIcon()); componentDto.setIcon(knowledgeConfigModel.getIcon());
Long fileSize = iKnowledgeConfigApplicationService.queryTotalFileSize(knowledgeConfigModel.getId());
componentDto.setFileSize(fileSize == null ? 0L : fileSize);
componentDto.setCreatorId(knowledgeConfigModel.getCreatorId()); componentDto.setCreatorId(knowledgeConfigModel.getCreatorId());
UserDto userDto = userDtoMap.get(knowledgeConfigModel.getCreatorId()); UserDto userDto = userDtoMap.get(knowledgeConfigModel.getCreatorId());
if (userDto != null) { if (userDto != null) {

View File

@@ -103,7 +103,13 @@
</select> </select>
<select id="queryTotalFileSize" resultType="java.lang.Long"> <select id="queryTotalFileSize" resultType="java.lang.Long">
select sum(file_size) select coalesce(sum(
case
when coalesce(file_size, 0) > 0 then file_size
when data_type = 2 then octet_length(coalesce(file_content, ''))
else 0
end
), 0)
from knowledge_document from knowledge_document
where kb_id = #{kbId} where kb_id = #{kbId}
and yn = 1 and yn = 1

View File

@@ -8,6 +8,7 @@ import com.xspaceagi.knowledge.domain.repository.IKnowledgeConfigRepository;
import com.xspaceagi.knowledge.domain.vectordb.VectorDBService; import com.xspaceagi.knowledge.domain.vectordb.VectorDBService;
import com.xspaceagi.knowledge.man.ui.web.base.BaseController; import com.xspaceagi.knowledge.man.ui.web.base.BaseController;
import com.xspaceagi.knowledge.man.ui.web.dto.config.*; import com.xspaceagi.knowledge.man.ui.web.dto.config.*;
import com.xspaceagi.knowledge.man.ui.web.permission.KnowledgeWritePermissionChecker;
import com.xspaceagi.system.application.dto.SpaceDto; import com.xspaceagi.system.application.dto.SpaceDto;
import com.xspaceagi.system.application.service.SpaceApplicationService; import com.xspaceagi.system.application.service.SpaceApplicationService;
import com.xspaceagi.system.application.service.SysDataPermissionApplicationService; import com.xspaceagi.system.application.service.SysDataPermissionApplicationService;
@@ -71,6 +72,9 @@ public class KnowledgeConfigController extends BaseController {
@Resource @Resource
private SysDataPermissionApplicationService sysDataPermissionApplicationService; private SysDataPermissionApplicationService sysDataPermissionApplicationService;
@Resource
private KnowledgeWritePermissionChecker knowledgeWritePermissionChecker;
/** /**
* 知识库的列表查询接口,合并为详情的权限(需要权限) * 知识库的列表查询接口,合并为详情的权限(需要权限)
* *
@@ -132,6 +136,7 @@ public class KnowledgeConfigController extends BaseController {
var id = request.getId(); var id = request.getId();
var userContext = this.getUser(); var userContext = this.getUser();
knowledgeWritePermissionChecker.checkKnowledge(id);
knowledgeConfigApplicationService.deleteById(id, userContext); knowledgeConfigApplicationService.deleteById(id, userContext);
return ReqResult.success(); return ReqResult.success();
@@ -151,6 +156,9 @@ public class KnowledgeConfigController extends BaseController {
public ReqResult<KnowledgeConfigVo> detailById(@Schema(description = "知识库ID") Long id) { public ReqResult<KnowledgeConfigVo> detailById(@Schema(description = "知识库ID") Long id) {
var model = knowledgeConfigApplicationService.queryOneInfoById(id); var model = knowledgeConfigApplicationService.queryOneInfoById(id);
if (model != null) {
model.setFileSize(knowledgeConfigApplicationService.queryTotalFileSize(id));
}
var knowledgeConfigVo = KnowledgeConfigVo.convert2Dto(model); var knowledgeConfigVo = KnowledgeConfigVo.convert2Dto(model);
if (knowledgeConfigVo != null) { if (knowledgeConfigVo != null) {
knowledgeConfigVo.setIcon(DefaultIconUrlUtil.setDefaultIconUrl(knowledgeConfigVo.getIcon(), knowledgeConfigVo.getName(), "knowledge")); knowledgeConfigVo.setIcon(DefaultIconUrlUtil.setDefaultIconUrl(knowledgeConfigVo.getIcon(), knowledgeConfigVo.getName(), "knowledge"));
@@ -185,6 +193,7 @@ public class KnowledgeConfigController extends BaseController {
public ReqResult<Long> update(@RequestBody KnowledgeConfigUpdateRequest updateDto) { public ReqResult<Long> update(@RequestBody KnowledgeConfigUpdateRequest updateDto) {
var userContext = this.getUser(); var userContext = this.getUser();
knowledgeWritePermissionChecker.checkKnowledge(updateDto.getId());
var model = KnowledgeConfigUpdateRequest.convert2Model(updateDto); var model = KnowledgeConfigUpdateRequest.convert2Model(updateDto);
var id = knowledgeConfigApplicationService.updateInfo(model, userContext); var id = knowledgeConfigApplicationService.updateInfo(model, userContext);
return ReqResult.success(id); return ReqResult.success(id);
@@ -239,6 +248,7 @@ public class KnowledgeConfigController extends BaseController {
@RequestMapping(path = "/initVectorDb", method = RequestMethod.POST) @RequestMapping(path = "/initVectorDb", method = RequestMethod.POST)
public ReqResult<Long> add(Long id) { public ReqResult<Long> add(Long id) {
knowledgeWritePermissionChecker.checkKnowledge(id);
var model = this.knowledgeConfigApplicationService.queryOneInfoById(id); var model = this.knowledgeConfigApplicationService.queryOneInfoById(id);
Long embeddingModelId = null; Long embeddingModelId = null;
if (model != null) { if (model != null) {

View File

@@ -10,6 +10,7 @@ import com.xspaceagi.knowledge.domain.repository.IKnowledgeDocumentRepository;
import com.xspaceagi.knowledge.man.ui.web.base.BaseController; import com.xspaceagi.knowledge.man.ui.web.base.BaseController;
import com.xspaceagi.knowledge.man.ui.web.dto.config.KnowledgeConfigDeleteRequest; import com.xspaceagi.knowledge.man.ui.web.dto.config.KnowledgeConfigDeleteRequest;
import com.xspaceagi.knowledge.man.ui.web.dto.document.*; import com.xspaceagi.knowledge.man.ui.web.dto.document.*;
import com.xspaceagi.knowledge.man.ui.web.permission.KnowledgeWritePermissionChecker;
import com.xspaceagi.system.application.dto.SpaceDto; import com.xspaceagi.system.application.dto.SpaceDto;
import com.xspaceagi.system.application.dto.UserDto; import com.xspaceagi.system.application.dto.UserDto;
import com.xspaceagi.system.application.service.SpaceApplicationService; import com.xspaceagi.system.application.service.SpaceApplicationService;
@@ -78,6 +79,9 @@ public class KnowledgeDocumentController extends BaseController {
@Resource @Resource
private IKnowledgeConfigApplicationService knowledgeConfigApplicationService; private IKnowledgeConfigApplicationService knowledgeConfigApplicationService;
@Resource
private KnowledgeWritePermissionChecker knowledgeWritePermissionChecker;
@RequireResource(COMPONENT_LIB_QUERY_DETAIL) @RequireResource(COMPONENT_LIB_QUERY_DETAIL)
@OperationLogReporter(actionType = ActionType.QUERY, action = "数据列表查询", objectName = "知识库文档", systemCode = SystemEnum.KNOWLEDGE_CONFIG) @OperationLogReporter(actionType = ActionType.QUERY, action = "数据列表查询", objectName = "知识库文档", systemCode = SystemEnum.KNOWLEDGE_CONFIG)
@LogPrint(step = "知识库[知识库文档]-数据列表查询") @LogPrint(step = "知识库[知识库文档]-数据列表查询")
@@ -161,6 +165,7 @@ public class KnowledgeDocumentController extends BaseController {
var id = request.getId(); var id = request.getId();
var userContext = getUser(); var userContext = getUser();
knowledgeWritePermissionChecker.checkDocument(id);
knowledgeDocumentApplicationService.deleteById(id, userContext); knowledgeDocumentApplicationService.deleteById(id, userContext);
return ReqResult.success(); return ReqResult.success();
@@ -200,6 +205,7 @@ public class KnowledgeDocumentController extends BaseController {
} }
var userContext = this.getUser(); var userContext = this.getUser();
knowledgeWritePermissionChecker.checkDocument(updateDto.getDocId());
var model = KnowledgeDocumentUpdateRequest.convert2Model(updateDto); var model = KnowledgeDocumentUpdateRequest.convert2Model(updateDto);
@@ -218,6 +224,7 @@ public class KnowledgeDocumentController extends BaseController {
var userContext = this.getUser(); var userContext = this.getUser();
var docId = updateDto.getDocId(); var docId = updateDto.getDocId();
knowledgeWritePermissionChecker.checkDocument(docId);
var name = updateDto.getName(); var name = updateDto.getName();
var id = knowledgeDocumentApplicationService.updateDocName(docId, name, userContext); var id = knowledgeDocumentApplicationService.updateDocName(docId, name, userContext);
return ReqResult.success(id); return ReqResult.success(id);
@@ -236,6 +243,7 @@ public class KnowledgeDocumentController extends BaseController {
} }
var userContext = this.getUser(); var userContext = this.getUser();
knowledgeWritePermissionChecker.checkKnowledge(addDto.getKbId());
//新增权限内容 //新增权限内容
//知识库大小验证 //知识库大小验证
@@ -291,6 +299,7 @@ public class KnowledgeDocumentController extends BaseController {
} }
var userContext = this.getUser(); var userContext = this.getUser();
knowledgeWritePermissionChecker.checkKnowledge(addDto.getKbId());
var model = KnowledgeDocumentCustomAddRequest.convert2ModelForCustom(addDto); var model = KnowledgeDocumentCustomAddRequest.convert2ModelForCustom(addDto);
var ids = knowledgeDocumentApplicationService.customAddInfo(model, userContext); var ids = knowledgeDocumentApplicationService.customAddInfo(model, userContext);
@@ -306,6 +315,7 @@ public class KnowledgeDocumentController extends BaseController {
public ReqResult<Void> generateQAs(@PathVariable Long docId) { public ReqResult<Void> generateQAs(@PathVariable Long docId) {
var userContext = this.getUser(); var userContext = this.getUser();
knowledgeWritePermissionChecker.checkDocument(docId);
knowledgeDocumentApplicationService.generateForQa(docId, userContext); knowledgeDocumentApplicationService.generateForQa(docId, userContext);
return ReqResult.success(); return ReqResult.success();
} }
@@ -318,6 +328,7 @@ public class KnowledgeDocumentController extends BaseController {
public ReqResult<Void> generateEmbeddings(@PathVariable Long docId) { public ReqResult<Void> generateEmbeddings(@PathVariable Long docId) {
var userContext = this.getUser(); var userContext = this.getUser();
knowledgeWritePermissionChecker.checkDocument(docId);
knowledgeDocumentApplicationService.generateEmbeddings(docId, userContext); knowledgeDocumentApplicationService.generateEmbeddings(docId, userContext);
return ReqResult.success(); return ReqResult.success();
} }
@@ -340,6 +351,7 @@ public class KnowledgeDocumentController extends BaseController {
public ReqResult<Void> autoRetryTaskByDocId(@PathVariable Long docId) { public ReqResult<Void> autoRetryTaskByDocId(@PathVariable Long docId) {
var userContext = this.getUser(); var userContext = this.getUser();
var userId = userContext.getUserId(); var userId = userContext.getUserId();
knowledgeWritePermissionChecker.checkDocument(docId);
UserDto userDto = (UserDto) RequestContext.get().getUser(); UserDto userDto = (UserDto) RequestContext.get().getUser();
TenantRunnable tenantRunnable = new TenantRunnable(() -> { TenantRunnable tenantRunnable = new TenantRunnable(() -> {

View File

@@ -33,6 +33,7 @@ import com.xspaceagi.knowledge.man.ui.web.dto.qa.KnowledgeQaSegmentAddRequest;
import com.xspaceagi.knowledge.man.ui.web.dto.qa.KnowledgeQaSegmentQueryRequest; import com.xspaceagi.knowledge.man.ui.web.dto.qa.KnowledgeQaSegmentQueryRequest;
import com.xspaceagi.knowledge.man.ui.web.dto.qa.KnowledgeQaSegmentUpdateRequest; import com.xspaceagi.knowledge.man.ui.web.dto.qa.KnowledgeQaSegmentUpdateRequest;
import com.xspaceagi.knowledge.man.ui.web.dto.qa.KnowledgeQaSegmentVo; import com.xspaceagi.knowledge.man.ui.web.dto.qa.KnowledgeQaSegmentVo;
import com.xspaceagi.knowledge.man.ui.web.permission.KnowledgeWritePermissionChecker;
import com.xspaceagi.system.sdk.operate.ActionType; import com.xspaceagi.system.sdk.operate.ActionType;
import com.xspaceagi.system.sdk.operate.OperationLogReporter; import com.xspaceagi.system.sdk.operate.OperationLogReporter;
import com.xspaceagi.system.sdk.operate.SystemEnum; import com.xspaceagi.system.sdk.operate.SystemEnum;
@@ -68,6 +69,9 @@ public class KnowledgeQaController extends BaseController {
@Resource @Resource
private SpaceApplicationService spaceApplicationService; private SpaceApplicationService spaceApplicationService;
@Resource
private KnowledgeWritePermissionChecker knowledgeWritePermissionChecker;
@RequireResource(COMPONENT_LIB_QUERY_DETAIL) @RequireResource(COMPONENT_LIB_QUERY_DETAIL)
@OperationLogReporter(actionType = ActionType.QUERY, action = "数据列表查询", objectName = "知识库问答", systemCode = SystemEnum.KNOWLEDGE_CONFIG) @OperationLogReporter(actionType = ActionType.QUERY, action = "数据列表查询", objectName = "知识库问答", systemCode = SystemEnum.KNOWLEDGE_CONFIG)
@LogPrint(step = "知识库[知识库问答]-数据列表查询") @LogPrint(step = "知识库[知识库问答]-数据列表查询")
@@ -114,6 +118,7 @@ public class KnowledgeQaController extends BaseController {
throws IntrospectionException, InvocationTargetException, IllegalAccessException { throws IntrospectionException, InvocationTargetException, IllegalAccessException {
var id = request.getId(); var id = request.getId();
knowledgeWritePermissionChecker.checkQa(id);
knowledgeQaSegmentApplicationService.deleteById(id); knowledgeQaSegmentApplicationService.deleteById(id);
return ReqResult.success(); return ReqResult.success();
@@ -141,6 +146,7 @@ public class KnowledgeQaController extends BaseController {
public ReqResult<Long> update(@RequestBody KnowledgeQaSegmentUpdateRequest updateDto) public ReqResult<Long> update(@RequestBody KnowledgeQaSegmentUpdateRequest updateDto)
throws IntrospectionException, InvocationTargetException, IllegalAccessException { throws IntrospectionException, InvocationTargetException, IllegalAccessException {
var userContext = this.getUser(); var userContext = this.getUser();
knowledgeWritePermissionChecker.checkQa(updateDto.getId());
var model = KnowledgeQaSegmentUpdateRequest.convert2Model(updateDto); var model = KnowledgeQaSegmentUpdateRequest.convert2Model(updateDto);
var id = knowledgeQaSegmentApplicationService.updateInfo(model, userContext); var id = knowledgeQaSegmentApplicationService.updateInfo(model, userContext);
return ReqResult.success(id); return ReqResult.success(id);
@@ -156,6 +162,7 @@ public class KnowledgeQaController extends BaseController {
throws IntrospectionException, InvocationTargetException, IllegalAccessException { throws IntrospectionException, InvocationTargetException, IllegalAccessException {
var userContext = this.getUser(); var userContext = this.getUser();
knowledgeWritePermissionChecker.checkKnowledge(addDto.getKbId());
var model = KnowledgeQaSegmentAddRequest.convert2Model(addDto); var model = KnowledgeQaSegmentAddRequest.convert2Model(addDto);
var id = knowledgeQaSegmentApplicationService.addInfo(model, userContext); var id = knowledgeQaSegmentApplicationService.addInfo(model, userContext);
return ReqResult.success(id); return ReqResult.success(id);
@@ -195,6 +202,7 @@ public class KnowledgeQaController extends BaseController {
@RequestParam("kbId") Long kbId) { @RequestParam("kbId") Long kbId) {
var userContext = this.getUser(); var userContext = this.getUser();
knowledgeWritePermissionChecker.checkKnowledge(kbId);
knowledgeQaSegmentApplicationService.importQaFromExcel(kbId, file, userContext); knowledgeQaSegmentApplicationService.importQaFromExcel(kbId, file, userContext);

View File

@@ -10,6 +10,7 @@ import com.xspaceagi.knowledge.man.ui.web.dto.segment.KnowledgeRawSegmentAddRequ
import com.xspaceagi.knowledge.man.ui.web.dto.segment.KnowledgeRawSegmentQueryRequest; import com.xspaceagi.knowledge.man.ui.web.dto.segment.KnowledgeRawSegmentQueryRequest;
import com.xspaceagi.knowledge.man.ui.web.dto.segment.KnowledgeRawSegmentUpdateRequest; import com.xspaceagi.knowledge.man.ui.web.dto.segment.KnowledgeRawSegmentUpdateRequest;
import com.xspaceagi.knowledge.man.ui.web.dto.segment.KnowledgeRawSegmentVo; import com.xspaceagi.knowledge.man.ui.web.dto.segment.KnowledgeRawSegmentVo;
import com.xspaceagi.knowledge.man.ui.web.permission.KnowledgeWritePermissionChecker;
import com.xspaceagi.system.application.dto.SpaceDto; import com.xspaceagi.system.application.dto.SpaceDto;
import com.xspaceagi.system.application.service.SpaceApplicationService; import com.xspaceagi.system.application.service.SpaceApplicationService;
import com.xspaceagi.system.domain.log.LogPrint; import com.xspaceagi.system.domain.log.LogPrint;
@@ -56,6 +57,9 @@ public class KnowledgeRawSegmentController extends BaseController {
@Resource @Resource
private SpaceApplicationService spaceApplicationService; private SpaceApplicationService spaceApplicationService;
@Resource
private KnowledgeWritePermissionChecker knowledgeWritePermissionChecker;
@RequireResource(COMPONENT_LIB_QUERY_DETAIL) @RequireResource(COMPONENT_LIB_QUERY_DETAIL)
@OperationLogReporter(actionType = ActionType.QUERY, action = "数据列表查询", objectName = "知识库分段", systemCode = SystemEnum.KNOWLEDGE_CONFIG) @OperationLogReporter(actionType = ActionType.QUERY, action = "数据列表查询", objectName = "知识库分段", systemCode = SystemEnum.KNOWLEDGE_CONFIG)
@LogPrint(step = "知识库[知识库分段]-数据列表查询") @LogPrint(step = "知识库[知识库分段]-数据列表查询")
@@ -104,6 +108,7 @@ public class KnowledgeRawSegmentController extends BaseController {
var id = request.getId(); var id = request.getId();
var userContext = this.getUser(); var userContext = this.getUser();
knowledgeWritePermissionChecker.checkRawSegment(id);
knowledgeRawSegmentApplicationService.deleteById(id, userContext); knowledgeRawSegmentApplicationService.deleteById(id, userContext);
return ReqResult.success(); return ReqResult.success();
@@ -132,6 +137,7 @@ public class KnowledgeRawSegmentController extends BaseController {
throws IntrospectionException, InvocationTargetException, IllegalAccessException { throws IntrospectionException, InvocationTargetException, IllegalAccessException {
var userContext = this.getUser(); var userContext = this.getUser();
knowledgeWritePermissionChecker.checkRawSegment(updateDto.getRawSegmentId());
var model = KnowledgeRawSegmentUpdateRequest.convert2Model(updateDto); var model = KnowledgeRawSegmentUpdateRequest.convert2Model(updateDto);
var id = knowledgeRawSegmentApplicationService.updateInfo(model, userContext); var id = knowledgeRawSegmentApplicationService.updateInfo(model, userContext);
return ReqResult.success(id); return ReqResult.success(id);
@@ -147,6 +153,7 @@ public class KnowledgeRawSegmentController extends BaseController {
throws IntrospectionException, InvocationTargetException, IllegalAccessException { throws IntrospectionException, InvocationTargetException, IllegalAccessException {
var userContext = this.getUser(); var userContext = this.getUser();
knowledgeWritePermissionChecker.checkDocument(addDto.getDocId());
var model = KnowledgeRawSegmentAddRequest.convert2Model(addDto); var model = KnowledgeRawSegmentAddRequest.convert2Model(addDto);
var id = knowledgeRawSegmentApplicationService.addInfo(model, userContext); var id = knowledgeRawSegmentApplicationService.addInfo(model, userContext);

View File

@@ -9,6 +9,7 @@ import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.io.Serializable; import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.Objects; import java.util.Objects;
@Schema(description = "知识库文档-手动自定义新增") @Schema(description = "知识库文档-手动自定义新增")
@@ -51,6 +52,9 @@ public class KnowledgeDocumentCustomAddRequest implements Serializable {
knowledgeDocumentModel.setName(addDto.getName()); knowledgeDocumentModel.setName(addDto.getName());
//dataType = 2,自定义文本,才生效 //dataType = 2,自定义文本,才生效
knowledgeDocumentModel.setFileContent(addDto.getFileContent()); knowledgeDocumentModel.setFileContent(addDto.getFileContent());
knowledgeDocumentModel.setFileSize(addDto.getFileContent() == null
? 0L
: (long) addDto.getFileContent().getBytes(StandardCharsets.UTF_8).length);
//docUrl 给个空字符串,满足数据库非空要求 //docUrl 给个空字符串,满足数据库非空要求
knowledgeDocumentModel.setDocUrl(""); knowledgeDocumentModel.setDocUrl("");
knowledgeDocumentModel.setPubStatus(KnowledgePubStatusEnum.Waiting); knowledgeDocumentModel.setPubStatus(KnowledgePubStatusEnum.Waiting);

View File

@@ -0,0 +1,70 @@
package com.xspaceagi.knowledge.man.ui.web.permission;
import com.xspaceagi.knowledge.core.application.service.IKnowledgeConfigApplicationService;
import com.xspaceagi.knowledge.core.application.service.IKnowledgeDocumentApplicationService;
import com.xspaceagi.knowledge.core.application.service.IKnowledgeQaSegmentApplicationService;
import com.xspaceagi.knowledge.core.application.service.IKnowledgeRawSegmentApplicationService;
import com.xspaceagi.knowledge.domain.model.KnowledgeConfigModel;
import com.xspaceagi.system.spec.common.RequestContext;
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
import com.xspaceagi.system.spec.exception.BizException;
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
import com.xspaceagi.system.spec.exception.KnowledgeException;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
import java.util.Objects;
@Component
public class KnowledgeWritePermissionChecker {
@Resource
private IKnowledgeConfigApplicationService knowledgeConfigApplicationService;
@Resource
private IKnowledgeDocumentApplicationService knowledgeDocumentApplicationService;
@Resource
private IKnowledgeRawSegmentApplicationService knowledgeRawSegmentApplicationService;
@Resource
private IKnowledgeQaSegmentApplicationService knowledgeQaSegmentApplicationService;
public void checkKnowledge(Long knowledgeId) {
KnowledgeConfigModel knowledge = knowledgeConfigApplicationService.queryOneInfoById(knowledgeId);
if (knowledge == null) {
throw KnowledgeException.build(BizExceptionCodeEnum.resourceDataNotFound);
}
RequestContext<?> requestContext = RequestContext.get();
Long currentUserId = requestContext == null ? null : requestContext.getUserId();
if (knowledge.getCreatorId() == null
|| currentUserId == null
|| !Objects.equals(knowledge.getCreatorId(), currentUserId)) {
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied);
}
}
public void checkDocument(Long documentId) {
var document = knowledgeDocumentApplicationService.queryOneInfoById(documentId);
if (document == null) {
throw KnowledgeException.build(BizExceptionCodeEnum.resourceDataNotFound);
}
checkKnowledge(document.getKbId());
}
public void checkRawSegment(Long rawSegmentId) {
var segment = knowledgeRawSegmentApplicationService.queryOneInfoById(rawSegmentId);
if (segment == null) {
throw KnowledgeException.build(BizExceptionCodeEnum.resourceDataNotFound);
}
checkKnowledge(segment.getKbId());
}
public void checkQa(Long qaId) {
var qa = knowledgeQaSegmentApplicationService.queryOneInfoById(qaId);
if (qa == null) {
throw KnowledgeException.build(BizExceptionCodeEnum.resourceDataNotFound);
}
checkKnowledge(qa.getKbId());
}
}

View File

@@ -0,0 +1,34 @@
package com.xspaceagi.knowledge.man.ui.web.dto.document;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import static org.assertj.core.api.Assertions.assertThat;
class KnowledgeDocumentCustomAddRequestTest {
@Test
void calculatesUtf8ByteSizeForCustomText() {
KnowledgeDocumentCustomAddRequest request = new KnowledgeDocumentCustomAddRequest();
request.setKbId(1L);
request.setName("测试文档");
request.setFileContent("知识库 text");
var model = KnowledgeDocumentCustomAddRequest.convert2ModelForCustom(request);
assertThat(model.getFileSize())
.isEqualTo((long) request.getFileContent().getBytes(StandardCharsets.UTF_8).length);
}
@Test
void usesZeroSizeWhenCustomTextIsEmpty() {
KnowledgeDocumentCustomAddRequest request = new KnowledgeDocumentCustomAddRequest();
request.setKbId(1L);
request.setName("空文档");
var model = KnowledgeDocumentCustomAddRequest.convert2ModelForCustom(request);
assertThat(model.getFileSize()).isZero();
}
}

View File

@@ -0,0 +1,116 @@
package com.xspaceagi.knowledge.man.ui.web.permission;
import com.xspaceagi.knowledge.core.application.service.IKnowledgeConfigApplicationService;
import com.xspaceagi.knowledge.core.application.service.IKnowledgeDocumentApplicationService;
import com.xspaceagi.knowledge.core.application.service.IKnowledgeQaSegmentApplicationService;
import com.xspaceagi.knowledge.core.application.service.IKnowledgeRawSegmentApplicationService;
import com.xspaceagi.knowledge.domain.model.KnowledgeConfigModel;
import com.xspaceagi.knowledge.domain.model.KnowledgeDocumentModel;
import com.xspaceagi.knowledge.domain.model.KnowledgeQaSegmentModel;
import com.xspaceagi.knowledge.domain.model.KnowledgeRawSegmentModel;
import com.xspaceagi.system.spec.common.RequestContext;
import com.xspaceagi.system.spec.exception.BizException;
import com.xspaceagi.system.spec.exception.KnowledgeException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class KnowledgeWritePermissionCheckerTest {
private static final Long CURRENT_USER_ID = 10L;
private static final Long KNOWLEDGE_ID = 20L;
@Mock
private IKnowledgeConfigApplicationService knowledgeConfigApplicationService;
@Mock
private IKnowledgeDocumentApplicationService knowledgeDocumentApplicationService;
@Mock
private IKnowledgeRawSegmentApplicationService knowledgeRawSegmentApplicationService;
@Mock
private IKnowledgeQaSegmentApplicationService knowledgeQaSegmentApplicationService;
@InjectMocks
private KnowledgeWritePermissionChecker permissionChecker;
@BeforeEach
void setUpRequestContext() {
RequestContext<Object> requestContext = new RequestContext<>();
requestContext.setUserId(CURRENT_USER_ID);
RequestContext.set(requestContext);
}
@AfterEach
void clearRequestContext() {
RequestContext.remove();
}
@Test
void allowsKnowledgeCreatorToWrite() {
when(knowledgeConfigApplicationService.queryOneInfoById(KNOWLEDGE_ID))
.thenReturn(knowledge(CURRENT_USER_ID));
assertDoesNotThrow(() -> permissionChecker.checkKnowledge(KNOWLEDGE_ID));
}
@Test
void deniesOtherUsersEvenWhenKnowledgeIsAccessible() {
when(knowledgeConfigApplicationService.queryOneInfoById(KNOWLEDGE_ID))
.thenReturn(knowledge(99L));
assertThrows(BizException.class, () -> permissionChecker.checkKnowledge(KNOWLEDGE_ID));
}
@Test
void deniesWriteWhenOwnershipCannotBeConfirmed() {
when(knowledgeConfigApplicationService.queryOneInfoById(KNOWLEDGE_ID))
.thenReturn(knowledge(null));
assertThrows(BizException.class, () -> permissionChecker.checkKnowledge(KNOWLEDGE_ID));
}
@Test
void rejectsMissingKnowledge() {
when(knowledgeConfigApplicationService.queryOneInfoById(KNOWLEDGE_ID)).thenReturn(null);
assertThrows(KnowledgeException.class, () -> permissionChecker.checkKnowledge(KNOWLEDGE_ID));
}
@Test
void resolvesDocumentSegmentAndQaToOwningKnowledge() {
KnowledgeDocumentModel document = new KnowledgeDocumentModel();
document.setKbId(KNOWLEDGE_ID);
KnowledgeRawSegmentModel segment = new KnowledgeRawSegmentModel();
segment.setKbId(KNOWLEDGE_ID);
KnowledgeQaSegmentModel qa = new KnowledgeQaSegmentModel();
qa.setKbId(KNOWLEDGE_ID);
when(knowledgeDocumentApplicationService.queryOneInfoById(1L)).thenReturn(document);
when(knowledgeRawSegmentApplicationService.queryOneInfoById(2L)).thenReturn(segment);
when(knowledgeQaSegmentApplicationService.queryOneInfoById(3L)).thenReturn(qa);
when(knowledgeConfigApplicationService.queryOneInfoById(KNOWLEDGE_ID))
.thenReturn(knowledge(CURRENT_USER_ID));
assertDoesNotThrow(() -> permissionChecker.checkDocument(1L));
assertDoesNotThrow(() -> permissionChecker.checkRawSegment(2L));
assertDoesNotThrow(() -> permissionChecker.checkQa(3L));
}
private KnowledgeConfigModel knowledge(Long creatorId) {
KnowledgeConfigModel knowledge = new KnowledgeConfigModel();
knowledge.setId(KNOWLEDGE_ID);
knowledge.setCreatorId(creatorId);
return knowledge;
}
}

View File

@@ -21,7 +21,6 @@ import {
} from "antd"; } from "antd";
import { import {
ArrowLeftOutlined, ArrowLeftOutlined,
BookOutlined,
CheckOutlined, CheckOutlined,
CloudUploadOutlined, CloudUploadOutlined,
DatabaseOutlined, DatabaseOutlined,
@@ -39,6 +38,7 @@ import {
} from "@ant-design/icons"; } from "@ant-design/icons";
import type { ColumnsType } from "antd/es/table"; import type { ColumnsType } from "antd/es/table";
import ImageWithFallback from "../common/ImageWithFallback"; import ImageWithFallback from "../common/ImageWithFallback";
import { getCurrentAuth } from "../../services/core/auth";
import { import {
AiAgentContext, AiAgentContext,
AiManualComponent, AiManualComponent,
@@ -183,6 +183,7 @@ function getPublishedWorkflowId(workflow: PublishedWorkflow): number {
} }
export default function KnowledgePage() { export default function KnowledgePage() {
const [currentUserId, setCurrentUserId] = useState<number | null>(null);
const [spaces, setSpaces] = useState<SpaceInfo[]>([]); const [spaces, setSpaces] = useState<SpaceInfo[]>([]);
const [selectedSpaceId, setSelectedSpaceId] = useState<number | null>(null); const [selectedSpaceId, setSelectedSpaceId] = useState<number | null>(null);
const [knowledges, setKnowledges] = useState<KnowledgeComponent[]>([]); const [knowledges, setKnowledges] = useState<KnowledgeComponent[]>([]);
@@ -280,6 +281,26 @@ export default function KnowledgePage() {
[knowledges, selectedKnowledgeId], [knowledges, selectedKnowledgeId],
); );
const isKnowledgeOwner = useCallback(
(knowledge?: KnowledgeComponent | KnowledgeInfo | null) => {
if (!knowledge) return false;
const creator = (knowledge as KnowledgeComponent).creator;
const creatorId = Number(knowledge.creatorId || creator?.id || 0);
return Boolean(currentUserId && creatorId && currentUserId === creatorId);
},
[currentUserId],
);
const canManageSelectedKnowledge = isKnowledgeOwner(
knowledgeInfo || selectedKnowledgeComponent,
);
const ensureCanManageSelectedKnowledge = useCallback(() => {
if (canManageSelectedKnowledge) return true;
message.warning("该知识库由其他用户创建,仅支持查看和加入对话");
return false;
}, [canManageSelectedKnowledge]);
const loadAgentComponents = useCallback(async () => { const loadAgentComponents = useCallback(async () => {
try { try {
const context = await resolveAiAgentContext(); const context = await resolveAiAgentContext();
@@ -489,6 +510,9 @@ export default function KnowledgePage() {
useEffect(() => { useEffect(() => {
loadSpaces(); loadSpaces();
loadAgentComponents(); loadAgentComponents();
void getCurrentAuth().then((auth) => {
setCurrentUserId(auth.userInfo?.userId || null);
});
}, [loadAgentComponents, loadSpaces]); }, [loadAgentComponents, loadSpaces]);
useEffect(() => { useEffect(() => {
@@ -530,6 +554,10 @@ export default function KnowledgePage() {
const openKnowledgeForm = async ( const openKnowledgeForm = async (
knowledge?: KnowledgeComponent | KnowledgeInfo, knowledge?: KnowledgeComponent | KnowledgeInfo,
) => { ) => {
if (knowledge && !isKnowledgeOwner(knowledge)) {
message.warning("该知识库由其他用户创建,不能编辑");
return;
}
setKnowledgeModalOpen(true); setKnowledgeModalOpen(true);
void (async () => { void (async () => {
const spaceId = const spaceId =
@@ -591,6 +619,10 @@ export default function KnowledgePage() {
}; };
const saveKnowledge = async () => { const saveKnowledge = async () => {
if (editingKnowledge && !isKnowledgeOwner(editingKnowledge)) {
message.warning("该知识库由其他用户创建,不能编辑");
return;
}
let values: KnowledgeFormValues; let values: KnowledgeFormValues;
try { try {
values = await knowledgeForm.validateFields(); values = await knowledgeForm.validateFields();
@@ -643,6 +675,10 @@ export default function KnowledgePage() {
}; };
const confirmDeleteKnowledge = (knowledge: KnowledgeComponent) => { const confirmDeleteKnowledge = (knowledge: KnowledgeComponent) => {
if (!isKnowledgeOwner(knowledge)) {
message.warning("该知识库由其他用户创建,不能删除");
return;
}
const knowledgeId = getKnowledgeId(knowledge); const knowledgeId = getKnowledgeId(knowledge);
Modal.confirm({ Modal.confirm({
title: "删除知识库", title: "删除知识库",
@@ -767,6 +803,7 @@ export default function KnowledgePage() {
const files = Array.from(event.target.files || []); const files = Array.from(event.target.files || []);
event.target.value = ""; event.target.value = "";
if (!files.length || !selectedKnowledgeId) return; if (!files.length || !selectedKnowledgeId) return;
if (!ensureCanManageSelectedKnowledge()) return;
const validationError = files.map(validateKnowledgeFile).find(Boolean); const validationError = files.map(validateKnowledgeFile).find(Boolean);
if (validationError) { if (validationError) {
@@ -792,6 +829,7 @@ export default function KnowledgePage() {
const saveCustomDocument = async () => { const saveCustomDocument = async () => {
if (!selectedKnowledgeId) return; if (!selectedKnowledgeId) return;
if (!ensureCanManageSelectedKnowledge()) return;
const values = await customDocForm.validateFields(); const values = await customDocForm.validateFields();
setSavingCustomDoc(true); setSavingCustomDoc(true);
try { try {
@@ -812,6 +850,7 @@ export default function KnowledgePage() {
}; };
const openRenameDocument = (doc: KnowledgeDocumentInfo) => { const openRenameDocument = (doc: KnowledgeDocumentInfo) => {
if (!ensureCanManageSelectedKnowledge()) return;
setRenamingDoc(doc); setRenamingDoc(doc);
renameDocForm.setFieldsValue({ name: doc.name }); renameDocForm.setFieldsValue({ name: doc.name });
setRenameDocModalOpen(true); setRenameDocModalOpen(true);
@@ -819,6 +858,7 @@ export default function KnowledgePage() {
const saveDocumentName = async () => { const saveDocumentName = async () => {
if (!renamingDoc) return; if (!renamingDoc) return;
if (!ensureCanManageSelectedKnowledge()) return;
const values = await renameDocForm.validateFields(); const values = await renameDocForm.validateFields();
setSavingRename(true); setSavingRename(true);
try { try {
@@ -856,6 +896,7 @@ export default function KnowledgePage() {
}; };
const confirmDeleteDocument = (doc: KnowledgeDocumentInfo) => { const confirmDeleteDocument = (doc: KnowledgeDocumentInfo) => {
if (!ensureCanManageSelectedKnowledge()) return;
Modal.confirm({ Modal.confirm({
title: "删除文档", title: "删除文档",
content: `确认删除「${doc.name}」吗?`, content: `确认删除「${doc.name}」吗?`,
@@ -874,6 +915,7 @@ export default function KnowledgePage() {
doc: KnowledgeDocumentInfo, doc: KnowledgeDocumentInfo,
action: "qa" | "embedding" | "retry", action: "qa" | "embedding" | "retry",
) => { ) => {
if (!ensureCanManageSelectedKnowledge()) return;
try { try {
if (action === "qa") { if (action === "qa") {
await generateKnowledgeDocumentQa(doc.id); await generateKnowledgeDocumentQa(doc.id);
@@ -892,6 +934,7 @@ export default function KnowledgePage() {
}; };
const openSegmentModal = (segment?: KnowledgeRawSegmentInfo) => { const openSegmentModal = (segment?: KnowledgeRawSegmentInfo) => {
if (!ensureCanManageSelectedKnowledge()) return;
setEditingSegment(segment || null); setEditingSegment(segment || null);
segmentForm.setFieldsValue({ segmentForm.setFieldsValue({
rawTxt: segment?.rawTxt || "", rawTxt: segment?.rawTxt || "",
@@ -901,6 +944,7 @@ export default function KnowledgePage() {
}; };
const saveSegment = async () => { const saveSegment = async () => {
if (!ensureCanManageSelectedKnowledge()) return;
const spaceId = const spaceId =
selectedDocument?.spaceId || knowledgeInfo?.spaceId || selectedSpaceId; selectedDocument?.spaceId || knowledgeInfo?.spaceId || selectedSpaceId;
if (!selectedDocument || !spaceId) return; if (!selectedDocument || !spaceId) return;
@@ -932,6 +976,7 @@ export default function KnowledgePage() {
}; };
const confirmDeleteSegment = (segment: KnowledgeRawSegmentInfo) => { const confirmDeleteSegment = (segment: KnowledgeRawSegmentInfo) => {
if (!ensureCanManageSelectedKnowledge()) return;
Modal.confirm({ Modal.confirm({
title: "删除分段", title: "删除分段",
content: "确认删除该分段吗?", content: "确认删除该分段吗?",
@@ -947,6 +992,7 @@ export default function KnowledgePage() {
}; };
const openQaModal = (qa?: KnowledgeQaInfo) => { const openQaModal = (qa?: KnowledgeQaInfo) => {
if (!ensureCanManageSelectedKnowledge()) return;
setEditingQa(qa || null); setEditingQa(qa || null);
qaForm.setFieldsValue({ qaForm.setFieldsValue({
question: qa?.question || "", question: qa?.question || "",
@@ -957,6 +1003,7 @@ export default function KnowledgePage() {
const saveQa = async () => { const saveQa = async () => {
if (!selectedKnowledgeId) return; if (!selectedKnowledgeId) return;
if (!ensureCanManageSelectedKnowledge()) return;
const values = await qaForm.validateFields(); const values = await qaForm.validateFields();
try { try {
if (editingQa) { if (editingQa) {
@@ -983,6 +1030,7 @@ export default function KnowledgePage() {
}; };
const confirmDeleteQa = (qa: KnowledgeQaInfo) => { const confirmDeleteQa = (qa: KnowledgeQaInfo) => {
if (!ensureCanManageSelectedKnowledge()) return;
Modal.confirm({ Modal.confirm({
title: "删除 QA", title: "删除 QA",
content: "确认删除该问答吗?", content: "确认删除该问答吗?",
@@ -1001,6 +1049,7 @@ export default function KnowledgePage() {
const file = event.target.files?.[0]; const file = event.target.files?.[0];
event.target.value = ""; event.target.value = "";
if (!file || !selectedKnowledgeId) return; if (!file || !selectedKnowledgeId) return;
if (!ensureCanManageSelectedKnowledge()) return;
try { try {
await importKnowledgeQaExcel({ kbId: selectedKnowledgeId, file }); await importKnowledgeQaExcel({ kbId: selectedKnowledgeId, file });
message.success("QA 已导入"); message.success("QA 已导入");
@@ -1068,7 +1117,7 @@ export default function KnowledgePage() {
}, },
{ {
title: "操作", title: "操作",
width: 260, width: canManageSelectedKnowledge ? 260 : 120,
fixed: "right", fixed: "right",
render: (_, record) => ( render: (_, record) => (
<div className={styles.tableActions}> <div className={styles.tableActions}>
@@ -1082,6 +1131,8 @@ export default function KnowledgePage() {
> >
</Button> </Button>
{canManageSelectedKnowledge && (
<>
<Button <Button
type="link" type="link"
size="small" size="small"
@@ -1110,6 +1161,8 @@ export default function KnowledgePage() {
> >
</Button> </Button>
</>
)}
{record.docUrl && ( {record.docUrl && (
<Button <Button
type="link" type="link"
@@ -1119,6 +1172,7 @@ export default function KnowledgePage() {
</Button> </Button>
)} )}
{canManageSelectedKnowledge && (
<Button <Button
type="link" type="link"
size="small" size="small"
@@ -1127,6 +1181,7 @@ export default function KnowledgePage() {
> >
</Button> </Button>
)}
</div> </div>
), ),
}, },
@@ -1149,10 +1204,12 @@ export default function KnowledgePage() {
width: 170, width: 170,
render: (_, record) => formatDateTime(record.modified || record.created), render: (_, record) => formatDateTime(record.modified || record.created),
}, },
...(canManageSelectedKnowledge
? [
{ {
title: "操作", title: "操作",
width: 120, width: 120,
render: (_, record) => ( render: (_: unknown, record: KnowledgeRawSegmentInfo) => (
<div className={styles.tableActions}> <div className={styles.tableActions}>
<Button <Button
type="link" type="link"
@@ -1172,6 +1229,8 @@ export default function KnowledgePage() {
</div> </div>
), ),
}, },
]
: []),
]; ];
const qaColumns: ColumnsType<KnowledgeQaInfo> = [ const qaColumns: ColumnsType<KnowledgeQaInfo> = [
@@ -1191,12 +1250,18 @@ export default function KnowledgePage() {
width: 170, width: 170,
render: (_, record) => formatDateTime(record.modified || record.created), render: (_, record) => formatDateTime(record.modified || record.created),
}, },
...(canManageSelectedKnowledge
? [
{ {
title: "操作", title: "操作",
width: 120, width: 120,
render: (_, record) => ( render: (_: unknown, record: KnowledgeQaInfo) => (
<div className={styles.tableActions}> <div className={styles.tableActions}>
<Button type="link" size="small" onClick={() => openQaModal(record)}> <Button
type="link"
size="small"
onClick={() => openQaModal(record)}
>
</Button> </Button>
<Button <Button
@@ -1210,6 +1275,8 @@ export default function KnowledgePage() {
</div> </div>
), ),
}, },
]
: []),
]; ];
const renderKnowledgeIcon = ( const renderKnowledgeIcon = (
@@ -1295,6 +1362,12 @@ export default function KnowledgePage() {
<div className={styles.grid}> <div className={styles.grid}>
{filteredKnowledges.map((knowledge) => { {filteredKnowledges.map((knowledge) => {
const knowledgeId = getKnowledgeId(knowledge); const knowledgeId = getKnowledgeId(knowledge);
const owned = isKnowledgeOwner(knowledge);
const creatorName =
knowledge.creator?.nickName ||
knowledge.creator?.userName ||
knowledge.creatorName ||
"未知创建人";
const joinedComponent = joinedKnowledgeMap.get(knowledgeId); const joinedComponent = joinedKnowledgeMap.get(knowledgeId);
const joined = const joined =
!!joinedComponent && !!joinedComponent &&
@@ -1311,7 +1384,19 @@ export default function KnowledgePage() {
</div> </div>
<div className={styles.cardTitle}> <div className={styles.cardTitle}>
<h3>{knowledge.name}</h3> <h3>{knowledge.name}</h3>
<span></span> <div className={styles.cardOwnerRow}>
<Tag
color={owned ? "cyan" : "default"}
className={styles.ownerTag}
>
{owned ? "我的知识库" : "只读"}
</Tag>
{!owned && (
<span title={creatorName}>
{creatorName}
</span>
)}
</div>
</div> </div>
</div> </div>
<p className={styles.cardDescription}> <p className={styles.cardDescription}>
@@ -1319,9 +1404,14 @@ export default function KnowledgePage() {
</p> </p>
<div className={styles.cardMeta}> <div className={styles.cardMeta}>
<span> <span>
{formatBytes(Number(knowledge.fileSize || 0))} {formatBytes(Number(knowledge.fileSize || 0))}
</span> </span>
<span> <span
title={formatDateTime(
knowledge.modified || knowledge.created,
)}
>
{" "}
{formatDateTime( {formatDateTime(
knowledge.modified || knowledge.created, knowledge.modified || knowledge.created,
)} )}
@@ -1329,6 +1419,7 @@ export default function KnowledgePage() {
</div> </div>
<div className={styles.cardActions}> <div className={styles.cardActions}>
<Button <Button
type="primary"
size="small" size="small"
icon={joined ? <CheckOutlined /> : <TeamOutlined />} icon={joined ? <CheckOutlined /> : <TeamOutlined />}
loading={joiningIds.has(knowledgeId)} loading={joiningIds.has(knowledgeId)}
@@ -1339,25 +1430,33 @@ export default function KnowledgePage() {
> >
{joined ? "已加入" : "加入对话"} {joined ? "已加入" : "加入对话"}
</Button> </Button>
{owned && (
<div className={styles.cardOwnerActions}>
<Tooltip title="编辑知识库">
<Button <Button
size="small" size="small"
icon={<EditOutlined />} icon={<EditOutlined />}
aria-label="编辑知识库"
onClick={(event) => { onClick={(event) => {
event.stopPropagation(); event.stopPropagation();
openKnowledgeForm(knowledge); openKnowledgeForm(knowledge);
}} }}
> />
</Tooltip>
</Button> <Tooltip title="删除知识库">
<Button <Button
size="small" size="small"
danger danger
icon={<DeleteOutlined />} icon={<DeleteOutlined />}
aria-label="删除知识库"
onClick={(event) => { onClick={(event) => {
event.stopPropagation(); event.stopPropagation();
confirmDeleteKnowledge(knowledge); confirmDeleteKnowledge(knowledge);
}} }}
/> />
</Tooltip>
</div>
)}
</div> </div>
</article> </article>
); );
@@ -1376,7 +1475,11 @@ export default function KnowledgePage() {
allowClear allowClear
placeholder="搜索文档" placeholder="搜索文档"
value={documentKeyword} value={documentKeyword}
onChange={(event) => setDocumentKeyword(event.target.value)} onChange={(event) => {
const value = event.target.value;
setDocumentKeyword(value);
if (!value) void loadDocuments("");
}}
onSearch={(value) => loadDocuments(value)} onSearch={(value) => loadDocuments(value)}
className={styles.innerSearch} className={styles.innerSearch}
/> />
@@ -1387,6 +1490,8 @@ export default function KnowledgePage() {
className={styles.hiddenInput} className={styles.hiddenInput}
onChange={handleDocumentFiles} onChange={handleDocumentFiles}
/> />
{canManageSelectedKnowledge && (
<>
<Button <Button
icon={<CloudUploadOutlined />} icon={<CloudUploadOutlined />}
loading={uploadingDocs} loading={uploadingDocs}
@@ -1403,6 +1508,8 @@ export default function KnowledgePage() {
> >
</Button> </Button>
</>
)}
<Tooltip title="刷新文档状态"> <Tooltip title="刷新文档状态">
<Button icon={<ReloadOutlined />} onClick={refreshDocumentStatus} /> <Button icon={<ReloadOutlined />} onClick={refreshDocumentStatus} />
</Tooltip> </Tooltip>
@@ -1443,6 +1550,7 @@ export default function KnowledgePage() {
}} }}
options={documents.map((doc) => ({ label: doc.name, value: doc.id }))} options={documents.map((doc) => ({ label: doc.name, value: doc.id }))}
/> />
{canManageSelectedKnowledge && (
<Button <Button
icon={<PlusOutlined />} icon={<PlusOutlined />}
disabled={!selectedDocument} disabled={!selectedDocument}
@@ -1450,6 +1558,7 @@ export default function KnowledgePage() {
> >
</Button> </Button>
)}
<Button <Button
icon={<ReloadOutlined />} icon={<ReloadOutlined />}
disabled={!selectedDocument} disabled={!selectedDocument}
@@ -1498,13 +1607,19 @@ export default function KnowledgePage() {
allowClear allowClear
placeholder="搜索问题" placeholder="搜索问题"
value={qaKeyword} value={qaKeyword}
onChange={(event) => setQaKeyword(event.target.value)} onChange={(event) => {
const value = event.target.value;
setQaKeyword(value);
if (!value) void loadQaList("");
}}
onSearch={(value) => loadQaList(value)} onSearch={(value) => loadQaList(value)}
className={styles.innerSearch} className={styles.innerSearch}
/> />
{canManageSelectedKnowledge && (
<Button icon={<PlusOutlined />} onClick={() => openQaModal()}> <Button icon={<PlusOutlined />} onClick={() => openQaModal()}>
QA QA
</Button> </Button>
)}
<input <input
ref={qaFileInputRef} ref={qaFileInputRef}
type="file" type="file"
@@ -1512,12 +1627,14 @@ export default function KnowledgePage() {
className={styles.hiddenInput} className={styles.hiddenInput}
onChange={handleQaImport} onChange={handleQaImport}
/> />
{canManageSelectedKnowledge && (
<Button <Button
icon={<CloudUploadOutlined />} icon={<CloudUploadOutlined />}
onClick={() => qaFileInputRef.current?.click()} onClick={() => qaFileInputRef.current?.click()}
> >
Excel Excel
</Button> </Button>
)}
<Button icon={<DownloadOutlined />} onClick={handleDownloadQaTemplate}> <Button icon={<DownloadOutlined />} onClick={handleDownloadQaTemplate}>
</Button> </Button>
@@ -1563,6 +1680,13 @@ export default function KnowledgePage() {
const joining = const joining =
selectedKnowledgeId !== null && joiningIds.has(selectedKnowledgeId); selectedKnowledgeId !== null && joiningIds.has(selectedKnowledgeId);
const knowledgeForAction = knowledgeInfo || selectedKnowledgeComponent; const knowledgeForAction = knowledgeInfo || selectedKnowledgeComponent;
const owned = isKnowledgeOwner(knowledgeForAction);
const creator = (knowledgeForAction as KnowledgeComponent | null)?.creator;
const creatorName =
creator?.nickName ||
creator?.userName ||
knowledgeForAction?.creatorName ||
"未知创建人";
return ( return (
<> <>
@@ -1584,6 +1708,12 @@ export default function KnowledgePage() {
<strong>{knowledgeForAction?.name || "知识库详情"}</strong> <strong>{knowledgeForAction?.name || "知识库详情"}</strong>
</div> </div>
<h1>{knowledgeForAction?.name || "知识库详情"}</h1> <h1>{knowledgeForAction?.name || "知识库详情"}</h1>
<div className={styles.detailOwnerRow}>
<Tag color={owned ? "cyan" : "default"}>
{owned ? "我的知识库" : "只读知识库"}
</Tag>
{!owned && <span>{creatorName}</span>}
</div>
<p>{knowledgeForAction?.description || "暂无描述"}</p> <p>{knowledgeForAction?.description || "暂无描述"}</p>
</div> </div>
</div> </div>
@@ -1598,6 +1728,7 @@ export default function KnowledgePage() {
> >
{joined ? "已加入对话" : "加入对话"} {joined ? "已加入对话" : "加入对话"}
</Button> </Button>
{owned && (
<Button <Button
icon={<EditOutlined />} icon={<EditOutlined />}
disabled={!knowledgeForAction} disabled={!knowledgeForAction}
@@ -1607,6 +1738,7 @@ export default function KnowledgePage() {
> >
</Button> </Button>
)}
</div> </div>
</header> </header>
<main className={styles.detailContent}> <main className={styles.detailContent}>

View File

@@ -30,6 +30,7 @@ import { t } from "./i18n";
// ========== 类型定义 === // ========== 类型定义 ===
export interface AuthUserInfo { export interface AuthUserInfo {
id?: number; id?: number;
userId?: number;
username: string; username: string;
displayName?: string; displayName?: string;
avatar?: string; avatar?: string;
@@ -333,6 +334,7 @@ export async function loginAndRegister(
await setUserInfo({ await setUserInfo({
id: response.id, id: response.id,
userId: response.userId,
username, username,
displayName: response.name, displayName: response.name,
currentDomain: domain, currentDomain: domain,
@@ -509,6 +511,7 @@ export async function reRegisterClient(): Promise<ClientRegisterResponse | null>
await setUserInfo({ await setUserInfo({
...currentUserInfo, ...currentUserInfo,
id: response.id, id: response.id,
userId: response.userId,
username: username || "", username: username || "",
displayName: response.name, displayName: response.name,
currentDomain: domain || currentUserInfo?.currentDomain, currentDomain: domain || currentUserInfo?.currentDomain,
@@ -675,6 +678,7 @@ export async function syncConfigToServer(options?: {
await setUserInfo({ await setUserInfo({
...currentUserInfo, ...currentUserInfo,
id: response.id, id: response.id,
userId: response.userId,
username: username || "", username: username || "",
displayName: response.name, displayName: response.name,
currentDomain: preservedCurrentDomain, currentDomain: preservedCurrentDomain,

View File

@@ -74,7 +74,15 @@ export interface KnowledgeComponent {
spaceId?: number; spaceId?: number;
created?: string; created?: string;
modified?: string; modified?: string;
creatorId?: number;
creatorName?: string; creatorName?: string;
creator?: {
id?: number;
userName?: string;
nickName?: string;
avatar?: string;
};
permissions?: string[];
[key: string]: unknown; [key: string]: unknown;
} }
@@ -91,6 +99,7 @@ export interface KnowledgeInfo {
pubStatus?: string; pubStatus?: string;
created?: string; created?: string;
modified?: string; modified?: string;
creatorId?: number;
creatorName?: string; creatorName?: string;
workflowId?: string | number; workflowId?: string | number;
workflowName?: string; workflowName?: string;

View File

@@ -37,7 +37,7 @@ export interface AuthUserInfo {
username: string; username: string;
displayName?: string; displayName?: string;
token?: string; token?: string;
userId?: string; userId?: number;
email?: string; email?: string;
currentDomain?: string; currentDomain?: string;
agentId?: number; agentId?: number;

View File

@@ -82,19 +82,20 @@
flex-wrap: wrap; flex-wrap: wrap;
} }
.search, .search {
.innerSearch {
width: min(420px, 100%); width: min(420px, 100%);
} }
.innerSearch {
width: min(360px, 100%);
}
.toolbarCreateButton { .toolbarCreateButton {
flex: 0 0 auto; flex: 0 0 auto;
} }
.search :global(.ant-input-affix-wrapper), .search :global(.ant-input-affix-wrapper),
.search:global(.ant-input-affix-wrapper), .search:global(.ant-input-affix-wrapper) {
.innerSearch :global(.ant-input-affix-wrapper),
.innerSearch:global(.ant-input-affix-wrapper) {
height: 42px; height: 42px;
border-radius: 8px; border-radius: 8px;
border-color: #dcd9d1; border-color: #dcd9d1;
@@ -102,6 +103,35 @@
box-shadow: none; box-shadow: none;
} }
.innerSearch :global(.ant-input-group) {
display: flex;
}
.innerSearch :global(.ant-input-affix-wrapper) {
height: 38px;
border-color: #dcd9d1;
border-radius: 7px 0 0 7px;
background: #ffffff;
box-shadow: none;
}
.innerSearch :global(.ant-input-search-button) {
width: 42px;
height: 38px;
padding: 0;
border-color: #dcd9d1;
border-radius: 0 7px 7px 0 !important;
color: #5e625c;
background: #ffffff;
box-shadow: none;
}
.innerSearch :global(.ant-input-affix-wrapper-focused),
.innerSearch :global(.ant-input-affix-wrapper:focus-within),
.innerSearch :global(.ant-input-search-button:hover) {
border-color: #74aaa5;
}
.count { .count {
margin-left: auto; margin-left: auto;
color: #888b85; color: #888b85;
@@ -132,15 +162,17 @@
.grid { .grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); grid-template-columns: repeat(3, minmax(0, 340px));
gap: 16px; gap: 16px;
justify-content: start;
} }
.card { .card {
min-height: 202px; min-width: 0;
min-height: 218px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 18px; padding: 16px;
background: #ffffff; background: #ffffff;
border: 1px solid #e8e4dc; border: 1px solid #e8e4dc;
border-radius: 8px; border-radius: 8px;
@@ -210,11 +242,28 @@
white-space: nowrap; white-space: nowrap;
} }
.cardTitle span { .cardOwnerRow {
display: block; min-width: 0;
margin-top: 4px; display: flex;
align-items: center;
gap: 6px;
margin-top: 5px;
}
.cardOwnerRow span {
min-width: 0;
overflow: hidden;
color: #8a8d89; color: #8a8d89;
font-size: 12px; font-size: 12px;
line-height: 18px;
text-overflow: ellipsis;
white-space: nowrap;
}
.ownerTag {
flex: 0 0 auto;
margin-inline-end: 0;
line-height: 18px;
} }
.cardDescription { .cardDescription {
@@ -235,7 +284,7 @@
justify-content: space-between; justify-content: space-between;
gap: 12px; gap: 12px;
margin-top: auto; margin-top: auto;
padding-top: 14px; padding-top: 12px;
color: #8a8d89; color: #8a8d89;
font-size: 12px; font-size: 12px;
} }
@@ -251,10 +300,19 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
margin-top: 14px; margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #efede7;
flex-wrap: wrap; flex-wrap: wrap;
} }
.cardOwnerActions {
display: flex;
align-items: center;
gap: 6px;
margin-left: auto;
}
.detailHeader { .detailHeader {
align-items: flex-start; align-items: flex-start;
} }
@@ -274,6 +332,20 @@
white-space: nowrap; white-space: nowrap;
} }
.detailOwnerRow {
min-width: 0;
display: flex;
align-items: center;
gap: 8px;
margin-top: 7px;
color: #777b75;
font-size: 12px;
}
.detailOwnerRow :global(.ant-tag) {
margin-inline-end: 0;
}
.detailActions { .detailActions {
flex: 0 0 auto; flex: 0 0 auto;
display: flex; display: flex;
@@ -504,6 +576,10 @@
} }
@media (max-width: 1180px) { @media (max-width: 1180px) {
.grid {
grid-template-columns: repeat(2, minmax(0, 340px));
}
.statRow { .statRow {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
} }
@@ -533,4 +609,20 @@
.statRow { .statRow {
grid-template-columns: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr);
} }
.grid {
grid-template-columns: minmax(0, 340px);
}
}
@media (max-width: 420px) {
.grid {
grid-template-columns: minmax(0, 1fr);
}
.cardMeta {
align-items: flex-start;
flex-direction: column;
gap: 4px;
}
} }