You've already forked DataMate
feat(knowledge-management): 添加知识管理搜索功能和统计接口
- 新增知识条目搜索查询和响应DTO - 实现知识管理统计功能,包括总数、文件数和总大小 - 添加数据库查询方法支持文件搜索和统计计算 - 创建知识条目搜索控制器提供REST API - 在前端添加知识管理搜索页面和相关组件 - 更新前端路由配置添加搜索页面入口 - 移除RAG索引服务中的重复统计功能 - 优化前端页面统计数据显示和刷新逻辑
This commit is contained in:
@@ -25,6 +25,9 @@ import com.datamate.datamanagement.interfaces.dto.CreateKnowledgeItemRequest;
|
|||||||
import com.datamate.datamanagement.interfaces.dto.ImportKnowledgeItemsRequest;
|
import com.datamate.datamanagement.interfaces.dto.ImportKnowledgeItemsRequest;
|
||||||
import com.datamate.datamanagement.interfaces.dto.KnowledgeItemPagingQuery;
|
import com.datamate.datamanagement.interfaces.dto.KnowledgeItemPagingQuery;
|
||||||
import com.datamate.datamanagement.interfaces.dto.KnowledgeItemResponse;
|
import com.datamate.datamanagement.interfaces.dto.KnowledgeItemResponse;
|
||||||
|
import com.datamate.datamanagement.interfaces.dto.KnowledgeItemSearchQuery;
|
||||||
|
import com.datamate.datamanagement.interfaces.dto.KnowledgeItemSearchResponse;
|
||||||
|
import com.datamate.datamanagement.interfaces.dto.KnowledgeManagementStatisticsResponse;
|
||||||
import com.datamate.datamanagement.interfaces.dto.ReplaceKnowledgeItemFileRequest;
|
import com.datamate.datamanagement.interfaces.dto.ReplaceKnowledgeItemFileRequest;
|
||||||
import com.datamate.datamanagement.interfaces.dto.UpdateKnowledgeItemRequest;
|
import com.datamate.datamanagement.interfaces.dto.UpdateKnowledgeItemRequest;
|
||||||
import com.datamate.datamanagement.interfaces.dto.UploadKnowledgeItemsRequest;
|
import com.datamate.datamanagement.interfaces.dto.UploadKnowledgeItemsRequest;
|
||||||
@@ -196,6 +199,39 @@ public class KnowledgeItemApplicationService {
|
|||||||
return PagedResponse.of(responses, page.getCurrent(), page.getTotal(), page.getPages());
|
return PagedResponse.of(responses, page.getCurrent(), page.getTotal(), page.getPages());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public KnowledgeManagementStatisticsResponse getKnowledgeManagementStatistics() {
|
||||||
|
KnowledgeManagementStatisticsResponse response = new KnowledgeManagementStatisticsResponse();
|
||||||
|
response.setTotalKnowledgeSets(knowledgeSetRepository.count());
|
||||||
|
|
||||||
|
long totalFiles = knowledgeItemRepository.countBySourceTypes(List.of(
|
||||||
|
KnowledgeSourceType.DATASET_FILE,
|
||||||
|
KnowledgeSourceType.FILE_UPLOAD
|
||||||
|
));
|
||||||
|
response.setTotalFiles(totalFiles);
|
||||||
|
|
||||||
|
long datasetFileSize = safeLong(knowledgeItemRepository.sumDatasetFileSize());
|
||||||
|
long uploadFileSize = calculateUploadFileTotalSize();
|
||||||
|
response.setTotalSize(datasetFileSize + uploadFileSize);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public PagedResponse<KnowledgeItemSearchResponse> searchKnowledgeItems(KnowledgeItemSearchQuery query) {
|
||||||
|
BusinessAssert.notNull(query, CommonErrorCode.PARAM_ERROR);
|
||||||
|
String keyword = StringUtils.trimToEmpty(query.getKeyword());
|
||||||
|
BusinessAssert.isTrue(StringUtils.isNotBlank(keyword), CommonErrorCode.PARAM_ERROR);
|
||||||
|
|
||||||
|
IPage<KnowledgeItemSearchResponse> page = new Page<>(query.getPage(), query.getSize());
|
||||||
|
IPage<KnowledgeItemSearchResponse> result = knowledgeItemRepository.searchFileItems(page, keyword);
|
||||||
|
List<KnowledgeItemSearchResponse> responses = result.getRecords()
|
||||||
|
.stream()
|
||||||
|
.map(this::normalizeSearchResponse)
|
||||||
|
.toList();
|
||||||
|
return PagedResponse.of(responses, result.getCurrent(), result.getTotal(), result.getPages());
|
||||||
|
}
|
||||||
|
|
||||||
public List<KnowledgeItem> importKnowledgeItems(String setId, ImportKnowledgeItemsRequest request) {
|
public List<KnowledgeItem> importKnowledgeItems(String setId, ImportKnowledgeItemsRequest request) {
|
||||||
KnowledgeSet knowledgeSet = requireKnowledgeSet(setId);
|
KnowledgeSet knowledgeSet = requireKnowledgeSet(setId);
|
||||||
BusinessAssert.isTrue(!isReadOnlyStatus(knowledgeSet.getStatus()),
|
BusinessAssert.isTrue(!isReadOnlyStatus(knowledgeSet.getStatus()),
|
||||||
@@ -447,6 +483,58 @@ public class KnowledgeItemApplicationService {
|
|||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private KnowledgeItemSearchResponse normalizeSearchResponse(KnowledgeItemSearchResponse item) {
|
||||||
|
BusinessAssert.notNull(item, CommonErrorCode.PARAM_ERROR);
|
||||||
|
if (item.getSourceType() == KnowledgeSourceType.FILE_UPLOAD) {
|
||||||
|
item.setFileSize(resolveUploadFileSize(item.getContent()));
|
||||||
|
if (StringUtils.isBlank(item.getFileName())) {
|
||||||
|
item.setFileName(item.getSourceFileId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.getSourceType() == KnowledgeSourceType.DATASET_FILE) {
|
||||||
|
if (item.getFileSize() == null) {
|
||||||
|
item.setFileSize(0L);
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(item.getFileName())) {
|
||||||
|
item.setFileName(item.getSourceFileId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item.setContent(null);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long calculateUploadFileTotalSize() {
|
||||||
|
List<KnowledgeItem> items = knowledgeItemRepository.findFileUploadItems();
|
||||||
|
if (CollectionUtils.isEmpty(items)) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
long total = 0L;
|
||||||
|
for (KnowledgeItem item : items) {
|
||||||
|
total += resolveUploadFileSize(item.getContent());
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long resolveUploadFileSize(String relativePath) {
|
||||||
|
if (StringUtils.isBlank(relativePath)) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Path filePath = resolveKnowledgeItemStoragePath(relativePath);
|
||||||
|
if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
return Files.size(filePath);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("resolve knowledge item file size error, path: {}", relativePath, e);
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long safeLong(Long value) {
|
||||||
|
return value == null ? 0L : value;
|
||||||
|
}
|
||||||
|
|
||||||
private String buildRelativeFilePath(String setId, String storedName) {
|
private String buildRelativeFilePath(String setId, String storedName) {
|
||||||
String relativePath = Paths.get(KNOWLEDGE_ITEM_UPLOAD_DIR, setId, storedName).toString();
|
String relativePath = Paths.get(KNOWLEDGE_ITEM_UPLOAD_DIR, setId, storedName).toString();
|
||||||
return relativePath.replace(File.separatorChar, '/');
|
return relativePath.replace(File.separatorChar, '/');
|
||||||
|
|||||||
@@ -1,9 +1,49 @@
|
|||||||
package com.datamate.datamanagement.infrastructure.persistence.mapper;
|
package com.datamate.datamanagement.infrastructure.persistence.mapper;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.datamate.datamanagement.domain.model.knowledge.KnowledgeItem;
|
import com.datamate.datamanagement.domain.model.knowledge.KnowledgeItem;
|
||||||
|
import com.datamate.datamanagement.interfaces.dto.KnowledgeItemSearchResponse;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface KnowledgeItemMapper extends BaseMapper<KnowledgeItem> {
|
public interface KnowledgeItemMapper extends BaseMapper<KnowledgeItem> {
|
||||||
|
@Select("""
|
||||||
|
SELECT
|
||||||
|
ki.id AS id,
|
||||||
|
ki.set_id AS setId,
|
||||||
|
ks.name AS setName,
|
||||||
|
ki.content_type AS contentType,
|
||||||
|
ki.source_type AS sourceType,
|
||||||
|
ki.source_dataset_id AS sourceDatasetId,
|
||||||
|
ki.source_file_id AS sourceFileId,
|
||||||
|
CASE
|
||||||
|
WHEN ki.source_type = 'DATASET_FILE' THEN df.file_name
|
||||||
|
ELSE ki.source_file_id
|
||||||
|
END AS fileName,
|
||||||
|
df.file_size AS fileSize,
|
||||||
|
CASE
|
||||||
|
WHEN ki.source_type = 'FILE_UPLOAD' THEN ki.content
|
||||||
|
ELSE NULL
|
||||||
|
END AS content,
|
||||||
|
ki.created_at AS createdAt,
|
||||||
|
ki.updated_at AS updatedAt
|
||||||
|
FROM t_dm_knowledge_items ki
|
||||||
|
LEFT JOIN t_dm_knowledge_sets ks ON ki.set_id = ks.id
|
||||||
|
LEFT JOIN t_dm_dataset_files df ON ki.source_file_id = df.id AND ki.source_type = 'DATASET_FILE'
|
||||||
|
WHERE (ki.source_type = 'FILE_UPLOAD' AND ki.source_file_id LIKE CONCAT('%', #{keyword}, '%'))
|
||||||
|
OR (ki.source_type = 'DATASET_FILE' AND df.file_name LIKE CONCAT('%', #{keyword}, '%'))
|
||||||
|
ORDER BY ki.created_at DESC
|
||||||
|
""")
|
||||||
|
IPage<KnowledgeItemSearchResponse> searchFileItems(IPage<?> page, @Param("keyword") String keyword);
|
||||||
|
|
||||||
|
@Select("""
|
||||||
|
SELECT COALESCE(SUM(df.file_size), 0)
|
||||||
|
FROM t_dm_knowledge_items ki
|
||||||
|
LEFT JOIN t_dm_dataset_files df ON ki.source_file_id = df.id
|
||||||
|
WHERE ki.source_type = 'DATASET_FILE'
|
||||||
|
""")
|
||||||
|
Long sumDatasetFileSize();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ package com.datamate.datamanagement.infrastructure.persistence.repository;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.extension.repository.IRepository;
|
import com.baomidou.mybatisplus.extension.repository.IRepository;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.datamate.datamanagement.common.enums.KnowledgeSourceType;
|
||||||
import com.datamate.datamanagement.domain.model.knowledge.KnowledgeItem;
|
import com.datamate.datamanagement.domain.model.knowledge.KnowledgeItem;
|
||||||
import com.datamate.datamanagement.interfaces.dto.KnowledgeItemPagingQuery;
|
import com.datamate.datamanagement.interfaces.dto.KnowledgeItemPagingQuery;
|
||||||
|
import com.datamate.datamanagement.interfaces.dto.KnowledgeItemSearchResponse;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,4 +18,12 @@ public interface KnowledgeItemRepository extends IRepository<KnowledgeItem> {
|
|||||||
long countBySetId(String setId);
|
long countBySetId(String setId);
|
||||||
|
|
||||||
List<KnowledgeItem> findAllBySetId(String setId);
|
List<KnowledgeItem> findAllBySetId(String setId);
|
||||||
|
|
||||||
|
long countBySourceTypes(List<KnowledgeSourceType> sourceTypes);
|
||||||
|
|
||||||
|
List<KnowledgeItem> findFileUploadItems();
|
||||||
|
|
||||||
|
IPage<KnowledgeItemSearchResponse> searchFileItems(IPage<?> page, String keyword);
|
||||||
|
|
||||||
|
Long sumDatasetFileSize();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ package com.datamate.datamanagement.infrastructure.persistence.repository.impl;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.extension.repository.CrudRepository;
|
import com.baomidou.mybatisplus.extension.repository.CrudRepository;
|
||||||
|
import com.datamate.datamanagement.common.enums.KnowledgeSourceType;
|
||||||
import com.datamate.datamanagement.domain.model.knowledge.KnowledgeItem;
|
import com.datamate.datamanagement.domain.model.knowledge.KnowledgeItem;
|
||||||
import com.datamate.datamanagement.infrastructure.persistence.mapper.KnowledgeItemMapper;
|
import com.datamate.datamanagement.infrastructure.persistence.mapper.KnowledgeItemMapper;
|
||||||
import com.datamate.datamanagement.infrastructure.persistence.repository.KnowledgeItemRepository;
|
import com.datamate.datamanagement.infrastructure.persistence.repository.KnowledgeItemRepository;
|
||||||
import com.datamate.datamanagement.interfaces.dto.KnowledgeItemPagingQuery;
|
import com.datamate.datamanagement.interfaces.dto.KnowledgeItemPagingQuery;
|
||||||
|
import com.datamate.datamanagement.interfaces.dto.KnowledgeItemSearchResponse;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
@@ -52,4 +54,27 @@ public class KnowledgeItemRepositoryImpl extends CrudRepository<KnowledgeItemMap
|
|||||||
.eq(KnowledgeItem::getSetId, setId)
|
.eq(KnowledgeItem::getSetId, setId)
|
||||||
.orderByDesc(KnowledgeItem::getCreatedAt));
|
.orderByDesc(KnowledgeItem::getCreatedAt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long countBySourceTypes(List<KnowledgeSourceType> sourceTypes) {
|
||||||
|
return knowledgeItemMapper.selectCount(new LambdaQueryWrapper<KnowledgeItem>()
|
||||||
|
.in(KnowledgeItem::getSourceType, sourceTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<KnowledgeItem> findFileUploadItems() {
|
||||||
|
return knowledgeItemMapper.selectList(new LambdaQueryWrapper<KnowledgeItem>()
|
||||||
|
.eq(KnowledgeItem::getSourceType, KnowledgeSourceType.FILE_UPLOAD)
|
||||||
|
.select(KnowledgeItem::getId, KnowledgeItem::getContent, KnowledgeItem::getSourceFileId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPage<KnowledgeItemSearchResponse> searchFileItems(IPage<?> page, String keyword) {
|
||||||
|
return knowledgeItemMapper.searchFileItems(page, keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long sumDatasetFileSize() {
|
||||||
|
return knowledgeItemMapper.sumDatasetFileSize();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.datamate.datamanagement.interfaces.dto;
|
||||||
|
|
||||||
|
import com.datamate.common.interfaces.PagingQuery;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 知识条目文件搜索请求
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class KnowledgeItemSearchQuery extends PagingQuery {
|
||||||
|
/**
|
||||||
|
* 文件名关键词
|
||||||
|
*/
|
||||||
|
private String keyword;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.datamate.datamanagement.interfaces.dto;
|
||||||
|
|
||||||
|
import com.datamate.datamanagement.common.enums.KnowledgeContentType;
|
||||||
|
import com.datamate.datamanagement.common.enums.KnowledgeSourceType;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 知识条目文件搜索响应
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class KnowledgeItemSearchResponse {
|
||||||
|
private String id;
|
||||||
|
private String setId;
|
||||||
|
private String setName;
|
||||||
|
private KnowledgeContentType contentType;
|
||||||
|
private KnowledgeSourceType sourceType;
|
||||||
|
private String sourceDatasetId;
|
||||||
|
private String sourceFileId;
|
||||||
|
private String fileName;
|
||||||
|
private Long fileSize;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private String content;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.datamate.datamanagement.interfaces.dto;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 知识管理统计响应
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class KnowledgeManagementStatisticsResponse {
|
||||||
|
private Long totalKnowledgeSets = 0L;
|
||||||
|
private Long totalFiles = 0L;
|
||||||
|
private Long totalSize = 0L;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.datamate.datamanagement.interfaces.rest;
|
||||||
|
|
||||||
|
import com.datamate.common.interfaces.PagedResponse;
|
||||||
|
import com.datamate.datamanagement.application.KnowledgeItemApplicationService;
|
||||||
|
import com.datamate.datamanagement.interfaces.dto.KnowledgeItemSearchQuery;
|
||||||
|
import com.datamate.datamanagement.interfaces.dto.KnowledgeItemSearchResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 知识条目搜索控制器
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/data-management/knowledge-items")
|
||||||
|
public class KnowledgeItemSearchController {
|
||||||
|
private final KnowledgeItemApplicationService knowledgeItemApplicationService;
|
||||||
|
|
||||||
|
@GetMapping("/search")
|
||||||
|
public PagedResponse<KnowledgeItemSearchResponse> search(KnowledgeItemSearchQuery query) {
|
||||||
|
return knowledgeItemApplicationService.searchKnowledgeItems(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
package com.datamate.datamanagement.interfaces.rest;
|
package com.datamate.datamanagement.interfaces.rest;
|
||||||
|
|
||||||
import com.datamate.common.interfaces.PagedResponse;
|
import com.datamate.common.interfaces.PagedResponse;
|
||||||
|
import com.datamate.datamanagement.application.KnowledgeItemApplicationService;
|
||||||
import com.datamate.datamanagement.application.KnowledgeSetApplicationService;
|
import com.datamate.datamanagement.application.KnowledgeSetApplicationService;
|
||||||
import com.datamate.datamanagement.domain.model.knowledge.KnowledgeSet;
|
import com.datamate.datamanagement.domain.model.knowledge.KnowledgeSet;
|
||||||
import com.datamate.datamanagement.interfaces.converter.KnowledgeConverter;
|
import com.datamate.datamanagement.interfaces.converter.KnowledgeConverter;
|
||||||
import com.datamate.datamanagement.interfaces.dto.CreateKnowledgeSetRequest;
|
import com.datamate.datamanagement.interfaces.dto.CreateKnowledgeSetRequest;
|
||||||
|
import com.datamate.datamanagement.interfaces.dto.KnowledgeManagementStatisticsResponse;
|
||||||
import com.datamate.datamanagement.interfaces.dto.KnowledgeSetPagingQuery;
|
import com.datamate.datamanagement.interfaces.dto.KnowledgeSetPagingQuery;
|
||||||
import com.datamate.datamanagement.interfaces.dto.KnowledgeSetResponse;
|
import com.datamate.datamanagement.interfaces.dto.KnowledgeSetResponse;
|
||||||
import com.datamate.datamanagement.interfaces.dto.UpdateKnowledgeSetRequest;
|
import com.datamate.datamanagement.interfaces.dto.UpdateKnowledgeSetRequest;
|
||||||
@@ -22,6 +24,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
@RequestMapping("/data-management/knowledge-sets")
|
@RequestMapping("/data-management/knowledge-sets")
|
||||||
public class KnowledgeSetController {
|
public class KnowledgeSetController {
|
||||||
private final KnowledgeSetApplicationService knowledgeSetApplicationService;
|
private final KnowledgeSetApplicationService knowledgeSetApplicationService;
|
||||||
|
private final KnowledgeItemApplicationService knowledgeItemApplicationService;
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public PagedResponse<KnowledgeSetResponse> getKnowledgeSets(KnowledgeSetPagingQuery query) {
|
public PagedResponse<KnowledgeSetResponse> getKnowledgeSets(KnowledgeSetPagingQuery query) {
|
||||||
@@ -51,4 +54,9 @@ public class KnowledgeSetController {
|
|||||||
public void deleteKnowledgeSet(@PathVariable("setId") String setId) {
|
public void deleteKnowledgeSet(@PathVariable("setId") String setId) {
|
||||||
knowledgeSetApplicationService.deleteKnowledgeSet(setId);
|
knowledgeSetApplicationService.deleteKnowledgeSet(setId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/statistics")
|
||||||
|
public KnowledgeManagementStatisticsResponse getKnowledgeManagementStatistics() {
|
||||||
|
return knowledgeItemApplicationService.getKnowledgeManagementStatistics();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,17 +140,6 @@ public class KnowledgeBaseService {
|
|||||||
return PagedResponse.of(respList, page.getCurrent(), page.getTotal(), page.getPages());
|
return PagedResponse.of(respList, page.getCurrent(), page.getTotal(), page.getPages());
|
||||||
}
|
}
|
||||||
|
|
||||||
public KnowledgeBaseStatisticsResp getStatistics() {
|
|
||||||
KnowledgeBaseStatisticsResp resp = new KnowledgeBaseStatisticsResp();
|
|
||||||
resp.setTotalKnowledgeBases(knowledgeBaseRepository.count());
|
|
||||||
|
|
||||||
RagFileStatistics fileStatistics = ragFileRepository.getStatistics();
|
|
||||||
if (fileStatistics != null) {
|
|
||||||
resp.setTotalFiles(fileStatistics.getTotalFiles() != null ? fileStatistics.getTotalFiles() : 0L);
|
|
||||||
resp.setTotalSize(fileStatistics.getTotalSize() != null ? fileStatistics.getTotalSize() : 0L);
|
|
||||||
}
|
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void addFiles(AddFilesReq request) {
|
public void addFiles(AddFilesReq request) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import com.baomidou.mybatisplus.extension.repository.IRepository;
|
|||||||
import com.datamate.rag.indexer.domain.model.RagFile;
|
import com.datamate.rag.indexer.domain.model.RagFile;
|
||||||
import com.datamate.rag.indexer.interfaces.dto.KnowledgeBaseFileSearchReq;
|
import com.datamate.rag.indexer.interfaces.dto.KnowledgeBaseFileSearchReq;
|
||||||
import com.datamate.rag.indexer.interfaces.dto.RagFileReq;
|
import com.datamate.rag.indexer.interfaces.dto.RagFileReq;
|
||||||
import com.datamate.rag.indexer.interfaces.dto.RagFileStatistics;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -25,6 +24,4 @@ public interface RagFileRepository extends IRepository<RagFile> {
|
|||||||
IPage<RagFile> page(IPage<RagFile> page, RagFileReq request);
|
IPage<RagFile> page(IPage<RagFile> page, RagFileReq request);
|
||||||
|
|
||||||
IPage<RagFile> searchPage(IPage<RagFile> page, KnowledgeBaseFileSearchReq request);
|
IPage<RagFile> searchPage(IPage<RagFile> page, KnowledgeBaseFileSearchReq request);
|
||||||
|
|
||||||
RagFileStatistics getStatistics();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import com.datamate.rag.indexer.domain.repository.RagFileRepository;
|
|||||||
import com.datamate.rag.indexer.infrastructure.persistence.mapper.RagFileMapper;
|
import com.datamate.rag.indexer.infrastructure.persistence.mapper.RagFileMapper;
|
||||||
import com.datamate.rag.indexer.interfaces.dto.KnowledgeBaseFileSearchReq;
|
import com.datamate.rag.indexer.interfaces.dto.KnowledgeBaseFileSearchReq;
|
||||||
import com.datamate.rag.indexer.interfaces.dto.RagFileReq;
|
import com.datamate.rag.indexer.interfaces.dto.RagFileReq;
|
||||||
import com.datamate.rag.indexer.interfaces.dto.RagFileStatistics;
|
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
@@ -61,11 +60,6 @@ public class RagFileRepositoryImpl extends CrudRepository<RagFileMapper, RagFile
|
|||||||
.page(page);
|
.page(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public RagFileStatistics getStatistics() {
|
|
||||||
return baseMapper.getStatistics();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String normalizeRelativePath(String relativePath) {
|
private String normalizeRelativePath(String relativePath) {
|
||||||
if (!StringUtils.hasText(relativePath)) {
|
if (!StringUtils.hasText(relativePath)) {
|
||||||
return "";
|
return "";
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ package com.datamate.rag.indexer.infrastructure.persistence.mapper;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.datamate.rag.indexer.domain.model.RagFile;
|
import com.datamate.rag.indexer.domain.model.RagFile;
|
||||||
import com.datamate.rag.indexer.interfaces.dto.RagFileStatistics;
|
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.apache.ibatis.annotations.Select;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RAG文件映射器接口
|
* RAG文件映射器接口
|
||||||
@@ -15,9 +13,4 @@ import org.apache.ibatis.annotations.Select;
|
|||||||
*/
|
*/
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface RagFileMapper extends BaseMapper<RagFile> {
|
public interface RagFileMapper extends BaseMapper<RagFile> {
|
||||||
@Select("SELECT COUNT(*) AS totalFiles, " +
|
|
||||||
"COALESCE(SUM(df.file_size), 0) AS totalSize " +
|
|
||||||
"FROM t_rag_file rf " +
|
|
||||||
"LEFT JOIN t_dm_dataset_files df ON rf.file_id = df.id")
|
|
||||||
RagFileStatistics getStatistics();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,15 +80,6 @@ public class KnowledgeBaseController {
|
|||||||
return knowledgeBaseService.list(request);
|
return knowledgeBaseService.list(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取知识库统计信息
|
|
||||||
*
|
|
||||||
* @return 知识库统计信息
|
|
||||||
*/
|
|
||||||
@GetMapping("/statistics")
|
|
||||||
public KnowledgeBaseStatisticsResp statistics() {
|
|
||||||
return knowledgeBaseService.getStatistics();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加文件到知识库
|
* 添加文件到知识库
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
package com.datamate.rag.indexer.interfaces.dto;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 知识库统计响应
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public class KnowledgeBaseStatisticsResp {
|
|
||||||
private Long totalKnowledgeBases = 0L;
|
|
||||||
private Long totalFiles = 0L;
|
|
||||||
private Long totalSize = 0L;
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package com.datamate.rag.indexer.interfaces.dto;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 知识库文件统计
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public class RagFileStatistics {
|
|
||||||
private Long totalFiles = 0L;
|
|
||||||
private Long totalSize = 0L;
|
|
||||||
}
|
|
||||||
@@ -1,48 +1,23 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useState } from "react";
|
||||||
import { Card, Button, Table, Tooltip, message, Statistic } from "antd";
|
import { Card, Button, Table, Tooltip, message } from "antd";
|
||||||
import { DeleteOutlined, EditOutlined } from "@ant-design/icons";
|
import { DeleteOutlined, EditOutlined } from "@ant-design/icons";
|
||||||
import { SearchControls } from "@/components/SearchControls";
|
import { SearchControls } from "@/components/SearchControls";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import CardView from "@/components/CardView";
|
import CardView from "@/components/CardView";
|
||||||
import {
|
import {
|
||||||
deleteKnowledgeBaseByIdUsingDelete,
|
deleteKnowledgeBaseByIdUsingDelete,
|
||||||
getKnowledgeBaseStatisticsUsingGet,
|
|
||||||
queryKnowledgeBasesUsingPost,
|
queryKnowledgeBasesUsingPost,
|
||||||
} from "../knowledge-base.api";
|
} from "../knowledge-base.api";
|
||||||
import useFetchData from "@/hooks/useFetchData";
|
import useFetchData from "@/hooks/useFetchData";
|
||||||
import { KnowledgeBaseItem, KnowledgeBaseStatistics } from "../knowledge-base.model";
|
import { KnowledgeBaseItem } from "../knowledge-base.model";
|
||||||
import CreateKnowledgeBase from "../components/CreateKnowledgeBase";
|
import CreateKnowledgeBase from "../components/CreateKnowledgeBase";
|
||||||
import { mapKnowledgeBase } from "../knowledge-base.const";
|
import { mapKnowledgeBase } from "../knowledge-base.const";
|
||||||
import { formatBytes } from "@/utils/unit";
|
|
||||||
|
|
||||||
type StatisticsItem = {
|
|
||||||
title: string;
|
|
||||||
value: number | string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEFAULT_STATISTICS: StatisticsItem[] = [
|
|
||||||
{
|
|
||||||
title: "知识库总数",
|
|
||||||
value: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "文件总数",
|
|
||||||
value: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "总大小",
|
|
||||||
value: "0 B",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function KnowledgeBasePage() {
|
export default function KnowledgeBasePage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [viewMode, setViewMode] = useState<"card" | "list">("card");
|
const [viewMode, setViewMode] = useState<"card" | "list">("card");
|
||||||
const [isEdit, setIsEdit] = useState(false);
|
const [isEdit, setIsEdit] = useState(false);
|
||||||
const [currentKB, setCurrentKB] = useState<KnowledgeBaseItem | null>(null);
|
const [currentKB, setCurrentKB] = useState<KnowledgeBaseItem | null>(null);
|
||||||
const [statisticsData, setStatisticsData] = useState<StatisticsItem[]>(
|
|
||||||
DEFAULT_STATISTICS
|
|
||||||
);
|
|
||||||
const {
|
const {
|
||||||
loading,
|
loading,
|
||||||
tableData,
|
tableData,
|
||||||
@@ -57,43 +32,11 @@ export default function KnowledgeBasePage() {
|
|||||||
(kb) => mapKnowledgeBase(kb, false) // 在首页不显示索引模型和文本理解模型字段
|
(kb) => mapKnowledgeBase(kb, false) // 在首页不显示索引模型和文本理解模型字段
|
||||||
);
|
);
|
||||||
|
|
||||||
const fetchStatistics = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const { data } = await getKnowledgeBaseStatisticsUsingGet();
|
|
||||||
const stats = data as KnowledgeBaseStatistics | undefined;
|
|
||||||
setStatisticsData([
|
|
||||||
{
|
|
||||||
title: "知识库总数",
|
|
||||||
value: stats?.totalKnowledgeBases ?? 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "文件总数",
|
|
||||||
value: stats?.totalFiles ?? 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "总大小",
|
|
||||||
value: formatBytes(stats?.totalSize ?? 0),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
} catch {
|
|
||||||
message.error("统计数据加载失败");
|
|
||||||
setStatisticsData(DEFAULT_STATISTICS);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const refreshAll = useCallback(async () => {
|
|
||||||
await Promise.all([fetchData(), fetchStatistics()]);
|
|
||||||
}, [fetchData, fetchStatistics]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchStatistics();
|
|
||||||
}, [fetchStatistics]);
|
|
||||||
|
|
||||||
const handleDeleteKB = async (kb: KnowledgeBaseItem) => {
|
const handleDeleteKB = async (kb: KnowledgeBaseItem) => {
|
||||||
try {
|
try {
|
||||||
await deleteKnowledgeBaseByIdUsingDelete(kb.id);
|
await deleteKnowledgeBaseByIdUsingDelete(kb.id);
|
||||||
message.success("知识库删除成功");
|
message.success("知识库删除成功");
|
||||||
await refreshAll();
|
fetchData();
|
||||||
} catch {
|
} catch {
|
||||||
message.error("知识库删除失败");
|
message.error("知识库删除失败");
|
||||||
}
|
}
|
||||||
@@ -197,7 +140,7 @@ export default function KnowledgeBasePage() {
|
|||||||
isEdit={isEdit}
|
isEdit={isEdit}
|
||||||
data={currentKB}
|
data={currentKB}
|
||||||
onUpdate={() => {
|
onUpdate={() => {
|
||||||
refreshAll();
|
fetchData();
|
||||||
}}
|
}}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setIsEdit(false);
|
setIsEdit(false);
|
||||||
@@ -207,20 +150,6 @@ export default function KnowledgeBasePage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-4">
|
|
||||||
<Card>
|
|
||||||
<div className="grid grid-cols-3">
|
|
||||||
{statisticsData.map((item) => (
|
|
||||||
<Statistic
|
|
||||||
title={item.title}
|
|
||||||
key={item.title}
|
|
||||||
value={`${item.value}`}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SearchControls
|
<SearchControls
|
||||||
searchTerm={searchParams.keyword}
|
searchTerm={searchParams.keyword}
|
||||||
onSearchChange={handleKeywordChange}
|
onSearchChange={handleKeywordChange}
|
||||||
@@ -231,7 +160,7 @@ export default function KnowledgeBasePage() {
|
|||||||
viewMode={viewMode}
|
viewMode={viewMode}
|
||||||
onViewModeChange={setViewMode}
|
onViewModeChange={setViewMode}
|
||||||
showViewToggle
|
showViewToggle
|
||||||
onReload={refreshAll}
|
onReload={fetchData}
|
||||||
/>
|
/>
|
||||||
{viewMode === "card" ? (
|
{viewMode === "card" ? (
|
||||||
<CardView
|
<CardView
|
||||||
|
|||||||
@@ -38,11 +38,6 @@ export function queryKnowledgeBaseFilesSearchUsingGet(params: RequestParams) {
|
|||||||
return get("/api/knowledge-base/files/search", params);
|
return get("/api/knowledge-base/files/search", params);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取知识库统计
|
|
||||||
export function getKnowledgeBaseStatisticsUsingGet() {
|
|
||||||
return get("/api/knowledge-base/statistics");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加文件到知识库
|
// 添加文件到知识库
|
||||||
export function addKnowledgeBaseFilesUsingPost(baseId: string, data: RequestPayload) {
|
export function addKnowledgeBaseFilesUsingPost(baseId: string, data: RequestPayload) {
|
||||||
return post(`/api/knowledge-base/${baseId}/files`, data);
|
return post(`/api/knowledge-base/${baseId}/files`, data);
|
||||||
|
|||||||
@@ -25,12 +25,6 @@ export interface KnowledgeBaseItem {
|
|||||||
chat: never;
|
chat: never;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KnowledgeBaseStatistics {
|
|
||||||
totalKnowledgeBases: number;
|
|
||||||
totalFiles: number;
|
|
||||||
totalSize: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface KBFile {
|
export interface KBFile {
|
||||||
id: string;
|
id: string;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { Card, Button, Table, Tooltip, Tag, App } from "antd";
|
import { Card, Button, Table, Tooltip, Tag, App, Statistic } from "antd";
|
||||||
import { DeleteOutlined, EditOutlined } from "@ant-design/icons";
|
import { DeleteOutlined, EditOutlined } from "@ant-design/icons";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import CardView from "@/components/CardView";
|
import CardView from "@/components/CardView";
|
||||||
@@ -8,6 +8,7 @@ import TagManager from "@/components/business/TagManagement";
|
|||||||
import useFetchData from "@/hooks/useFetchData";
|
import useFetchData from "@/hooks/useFetchData";
|
||||||
import {
|
import {
|
||||||
deleteKnowledgeSetByIdUsingDelete,
|
deleteKnowledgeSetByIdUsingDelete,
|
||||||
|
getKnowledgeManagementStatisticsUsingGet,
|
||||||
queryKnowledgeSetsUsingGet,
|
queryKnowledgeSetsUsingGet,
|
||||||
} from "../knowledge-management.api";
|
} from "../knowledge-management.api";
|
||||||
import {
|
import {
|
||||||
@@ -15,7 +16,7 @@ import {
|
|||||||
mapKnowledgeSet,
|
mapKnowledgeSet,
|
||||||
KnowledgeSetView,
|
KnowledgeSetView,
|
||||||
} from "../knowledge-management.const";
|
} from "../knowledge-management.const";
|
||||||
import { KnowledgeSet } from "../knowledge-management.model";
|
import { KnowledgeManagementStatistics, KnowledgeSet } from "../knowledge-management.model";
|
||||||
import CreateKnowledgeSet from "../components/CreateKnowledgeSet";
|
import CreateKnowledgeSet from "../components/CreateKnowledgeSet";
|
||||||
import {
|
import {
|
||||||
createDatasetTagUsingPost,
|
createDatasetTagUsingPost,
|
||||||
@@ -23,6 +24,27 @@ import {
|
|||||||
queryDatasetTagsUsingGet,
|
queryDatasetTagsUsingGet,
|
||||||
updateDatasetTagUsingPut,
|
updateDatasetTagUsingPut,
|
||||||
} from "@/pages/DataManagement/dataset.api";
|
} from "@/pages/DataManagement/dataset.api";
|
||||||
|
import { formatBytes } from "@/utils/unit";
|
||||||
|
|
||||||
|
type StatisticsItem = {
|
||||||
|
title: string;
|
||||||
|
value: number | string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_STATISTICS: StatisticsItem[] = [
|
||||||
|
{
|
||||||
|
title: "知识集总数",
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "文件总数",
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "总大小",
|
||||||
|
value: "0 B",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default function KnowledgeManagementPage() {
|
export default function KnowledgeManagementPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -31,6 +53,9 @@ export default function KnowledgeManagementPage() {
|
|||||||
const [isEdit, setIsEdit] = useState(false);
|
const [isEdit, setIsEdit] = useState(false);
|
||||||
const [currentSet, setCurrentSet] = useState<KnowledgeSet | null>(null);
|
const [currentSet, setCurrentSet] = useState<KnowledgeSet | null>(null);
|
||||||
const [tags, setTags] = useState<string[]>([]);
|
const [tags, setTags] = useState<string[]>([]);
|
||||||
|
const [statisticsData, setStatisticsData] = useState<StatisticsItem[]>(
|
||||||
|
DEFAULT_STATISTICS
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchTags = async () => {
|
const fetchTags = async () => {
|
||||||
@@ -75,10 +100,45 @@ export default function KnowledgeManagementPage() {
|
|||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const fetchStatistics = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const { data } = await getKnowledgeManagementStatisticsUsingGet();
|
||||||
|
const stats = data as KnowledgeManagementStatistics | undefined;
|
||||||
|
setStatisticsData([
|
||||||
|
{
|
||||||
|
title: "知识集总数",
|
||||||
|
value: stats?.totalKnowledgeSets ?? 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "文件总数",
|
||||||
|
value: stats?.totalFiles ?? 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "总大小",
|
||||||
|
value: formatBytes(stats?.totalSize ?? 0),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
} catch {
|
||||||
|
message.error("统计数据加载失败");
|
||||||
|
setStatisticsData(DEFAULT_STATISTICS);
|
||||||
|
}
|
||||||
|
}, [message]);
|
||||||
|
|
||||||
|
const refreshAll = useCallback(
|
||||||
|
async (extraParams: Record<string, unknown> = {}) => {
|
||||||
|
await Promise.all([fetchData(extraParams), fetchStatistics()]);
|
||||||
|
},
|
||||||
|
[fetchData, fetchStatistics]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchStatistics();
|
||||||
|
}, [fetchStatistics]);
|
||||||
|
|
||||||
const handleDeleteSet = async (setId: string) => {
|
const handleDeleteSet = async (setId: string) => {
|
||||||
await deleteKnowledgeSetByIdUsingDelete(setId);
|
await deleteKnowledgeSetByIdUsingDelete(setId);
|
||||||
message.success("知识集删除成功");
|
message.success("知识集删除成功");
|
||||||
fetchData({ pageOffset: 0 });
|
await refreshAll({ pageOffset: 0 });
|
||||||
};
|
};
|
||||||
|
|
||||||
const operations = [
|
const operations = [
|
||||||
@@ -191,6 +251,9 @@ export default function KnowledgeManagementPage() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h1 className="text-xl font-bold">知识管理</h1>
|
<h1 className="text-xl font-bold">知识管理</h1>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
|
<Button onClick={() => navigate("/data/knowledge-management/search")}>
|
||||||
|
全库搜索
|
||||||
|
</Button>
|
||||||
<TagManager
|
<TagManager
|
||||||
onCreate={createDatasetTagUsingPost}
|
onCreate={createDatasetTagUsingPost}
|
||||||
onDelete={(ids: string) => deleteDatasetTagUsingDelete({ ids })}
|
onDelete={(ids: string) => deleteDatasetTagUsingDelete({ ids })}
|
||||||
@@ -201,7 +264,7 @@ export default function KnowledgeManagementPage() {
|
|||||||
isEdit={isEdit}
|
isEdit={isEdit}
|
||||||
data={currentSet}
|
data={currentSet}
|
||||||
onUpdate={() => {
|
onUpdate={() => {
|
||||||
fetchData();
|
refreshAll();
|
||||||
}}
|
}}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setIsEdit(false);
|
setIsEdit(false);
|
||||||
@@ -211,6 +274,20 @@ export default function KnowledgeManagementPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-4">
|
||||||
|
<Card>
|
||||||
|
<div className="grid grid-cols-3">
|
||||||
|
{statisticsData.map((item) => (
|
||||||
|
<Statistic
|
||||||
|
title={item.title}
|
||||||
|
key={item.title}
|
||||||
|
value={`${item.value}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
<SearchControls
|
<SearchControls
|
||||||
searchTerm={searchParams.keyword}
|
searchTerm={searchParams.keyword}
|
||||||
onSearchChange={handleKeywordChange}
|
onSearchChange={handleKeywordChange}
|
||||||
@@ -221,7 +298,7 @@ export default function KnowledgeManagementPage() {
|
|||||||
viewMode={viewMode}
|
viewMode={viewMode}
|
||||||
onViewModeChange={setViewMode}
|
onViewModeChange={setViewMode}
|
||||||
showViewToggle
|
showViewToggle
|
||||||
onReload={fetchData}
|
onReload={refreshAll}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{viewMode === "card" ? (
|
{viewMode === "card" ? (
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
import { useCallback, useMemo, useState } from "react";
|
||||||
|
import { App, Breadcrumb, Button, Input, Table } from "antd";
|
||||||
|
import { useNavigate } from "react-router";
|
||||||
|
import {
|
||||||
|
KnowledgeContentType,
|
||||||
|
KnowledgeItemSearchResult,
|
||||||
|
KnowledgeSourceType,
|
||||||
|
} from "../knowledge-management.model";
|
||||||
|
import {
|
||||||
|
knowledgeContentTypeOptions,
|
||||||
|
knowledgeSourceTypeOptions,
|
||||||
|
} from "../knowledge-management.const";
|
||||||
|
import { searchKnowledgeItemsUsingGet } from "../knowledge-management.api";
|
||||||
|
import { formatBytes, formatDateTime } from "@/utils/unit";
|
||||||
|
|
||||||
|
const buildOptionMap = (options: { label: string; value: string }[]) =>
|
||||||
|
options.reduce<Record<string, string>>((acc, option) => {
|
||||||
|
acc[option.value] = option.label;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
export default function KnowledgeManagementSearch() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { message } = App.useApp();
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
const [activeKeyword, setActiveKeyword] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [searched, setSearched] = useState(false);
|
||||||
|
const [results, setResults] = useState<KnowledgeItemSearchResult[]>([]);
|
||||||
|
const [pagination, setPagination] = useState({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const contentTypeMap = useMemo(
|
||||||
|
() => buildOptionMap(knowledgeContentTypeOptions),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const sourceTypeMap = useMemo(
|
||||||
|
() => buildOptionMap(knowledgeSourceTypeOptions),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchResults = useCallback(
|
||||||
|
async (keyword: string, page?: number, pageSize?: number) => {
|
||||||
|
const resolvedPage = page ?? pagination.current;
|
||||||
|
const resolvedPageSize = pageSize ?? pagination.pageSize;
|
||||||
|
if (!keyword) {
|
||||||
|
setResults([]);
|
||||||
|
setPagination((prev) => ({ ...prev, total: 0, current: resolvedPage }));
|
||||||
|
setSearched(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const { data } = await searchKnowledgeItemsUsingGet({
|
||||||
|
keyword,
|
||||||
|
page: Math.max(resolvedPage - 1, 0),
|
||||||
|
size: resolvedPageSize,
|
||||||
|
});
|
||||||
|
const content = Array.isArray(data?.content) ? data.content : [];
|
||||||
|
setResults(content);
|
||||||
|
setPagination({
|
||||||
|
current: resolvedPage,
|
||||||
|
pageSize: resolvedPageSize,
|
||||||
|
total: data?.totalElements ?? 0,
|
||||||
|
});
|
||||||
|
setSearched(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to search knowledge items:", error);
|
||||||
|
message.error("检索失败,请稍后重试");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[message, pagination]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSearch = (value?: string) => {
|
||||||
|
const keyword = (value ?? searchTerm).trim();
|
||||||
|
if (!keyword) {
|
||||||
|
message.warning("请输入文件名");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setActiveKeyword(keyword);
|
||||||
|
fetchResults(keyword, 1, pagination.pageSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
title: "知识集",
|
||||||
|
dataIndex: "setName",
|
||||||
|
key: "setName",
|
||||||
|
width: 220,
|
||||||
|
ellipsis: true,
|
||||||
|
render: (text: string) => text || "-",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "文件名",
|
||||||
|
dataIndex: "fileName",
|
||||||
|
key: "fileName",
|
||||||
|
width: 220,
|
||||||
|
ellipsis: true,
|
||||||
|
render: (_: string, record: KnowledgeItemSearchResult) =>
|
||||||
|
record.fileName || record.sourceFileId || "-",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "来源类型",
|
||||||
|
dataIndex: "sourceType",
|
||||||
|
key: "sourceType",
|
||||||
|
width: 140,
|
||||||
|
render: (value: KnowledgeSourceType) =>
|
||||||
|
sourceTypeMap[value] || value || "-",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "内容类型",
|
||||||
|
dataIndex: "contentType",
|
||||||
|
key: "contentType",
|
||||||
|
width: 120,
|
||||||
|
render: (value: KnowledgeContentType) =>
|
||||||
|
contentTypeMap[value] || value || "-",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "大小",
|
||||||
|
dataIndex: "fileSize",
|
||||||
|
key: "fileSize",
|
||||||
|
width: 120,
|
||||||
|
render: (value?: number) => formatBytes(value ?? 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "更新时间",
|
||||||
|
dataIndex: "updatedAt",
|
||||||
|
key: "updatedAt",
|
||||||
|
width: 180,
|
||||||
|
ellipsis: true,
|
||||||
|
render: (value: string) => formatDateTime(value) || "-",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
key: "action",
|
||||||
|
width: 120,
|
||||||
|
align: "right" as const,
|
||||||
|
render: (_: unknown, record: KnowledgeItemSearchResult) => (
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
onClick={() => {
|
||||||
|
navigate(`/data/knowledge-management/detail/${record.setId}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
定位
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[contentTypeMap, navigate, sourceTypeMap]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full flex flex-col gap-4">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
<a onClick={() => navigate("/data/knowledge-management")}>知识管理</a>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item>全库搜索</Breadcrumb.Item>
|
||||||
|
</Breadcrumb>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h1 className="text-xl font-bold">知识管理全库检索</h1>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Input.Search
|
||||||
|
allowClear
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(event) => setSearchTerm(event.target.value)}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
placeholder="输入文件名,回车或点击搜索"
|
||||||
|
enterButton="搜索"
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
rowKey="id"
|
||||||
|
loading={loading}
|
||||||
|
columns={columns}
|
||||||
|
dataSource={results}
|
||||||
|
pagination={{
|
||||||
|
current: pagination.current,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
total: pagination.total,
|
||||||
|
showTotal: (total) => `共 ${total} 条`,
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
const nextKeyword = activeKeyword.trim();
|
||||||
|
if (!nextKeyword) {
|
||||||
|
message.warning("请输入文件名");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fetchResults(nextKeyword, page, pageSize || pagination.pageSize);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
locale={{
|
||||||
|
emptyText: searched ? "暂无匹配文件" : "请输入文件名开始检索",
|
||||||
|
}}
|
||||||
|
onRow={(record) => ({
|
||||||
|
onClick: () => {
|
||||||
|
navigate(`/data/knowledge-management/detail/${record.setId}`);
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,6 +5,11 @@ export function queryKnowledgeSetsUsingGet(params?: Record<string, unknown>) {
|
|||||||
return get("/api/data-management/knowledge-sets", params);
|
return get("/api/data-management/knowledge-sets", params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 知识管理统计
|
||||||
|
export function getKnowledgeManagementStatisticsUsingGet() {
|
||||||
|
return get("/api/data-management/knowledge-sets/statistics");
|
||||||
|
}
|
||||||
|
|
||||||
// 创建知识集
|
// 创建知识集
|
||||||
export function createKnowledgeSetUsingPost(data: Record<string, unknown>) {
|
export function createKnowledgeSetUsingPost(data: Record<string, unknown>) {
|
||||||
return post("/api/data-management/knowledge-sets", data);
|
return post("/api/data-management/knowledge-sets", data);
|
||||||
@@ -30,6 +35,11 @@ export function queryKnowledgeItemsUsingGet(setId: string, params?: Record<strin
|
|||||||
return get(`/api/data-management/knowledge-sets/${setId}/items`, params);
|
return get(`/api/data-management/knowledge-sets/${setId}/items`, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 知识条目文件搜索
|
||||||
|
export function searchKnowledgeItemsUsingGet(params?: Record<string, unknown>) {
|
||||||
|
return get("/api/data-management/knowledge-items/search", params);
|
||||||
|
}
|
||||||
|
|
||||||
// 创建知识条目
|
// 创建知识条目
|
||||||
export function createKnowledgeItemUsingPost(setId: string, data: Record<string, unknown>) {
|
export function createKnowledgeItemUsingPost(setId: string, data: Record<string, unknown>) {
|
||||||
return post(`/api/data-management/knowledge-sets/${setId}/items`, data);
|
return post(`/api/data-management/knowledge-sets/${setId}/items`, data);
|
||||||
|
|||||||
@@ -67,3 +67,23 @@ export interface KnowledgeItem {
|
|||||||
createdBy?: string;
|
createdBy?: string;
|
||||||
updatedBy?: string;
|
updatedBy?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface KnowledgeManagementStatistics {
|
||||||
|
totalKnowledgeSets: number;
|
||||||
|
totalFiles: number;
|
||||||
|
totalSize: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KnowledgeItemSearchResult {
|
||||||
|
id: string;
|
||||||
|
setId: string;
|
||||||
|
setName: string;
|
||||||
|
contentType: KnowledgeContentType;
|
||||||
|
sourceType: KnowledgeSourceType;
|
||||||
|
sourceDatasetId?: string;
|
||||||
|
sourceFileId?: string;
|
||||||
|
fileName?: string;
|
||||||
|
fileSize?: number;
|
||||||
|
createdAt?: string;
|
||||||
|
updatedAt?: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import DatasetCreate from "@/pages/DataManagement/Create/CreateDataset";
|
|||||||
import DatasetDetail from "@/pages/DataManagement/Detail/DatasetDetail";
|
import DatasetDetail from "@/pages/DataManagement/Detail/DatasetDetail";
|
||||||
import KnowledgeManagementPage from "@/pages/KnowledgeManagement/Home/KnowledgeManagementPage";
|
import KnowledgeManagementPage from "@/pages/KnowledgeManagement/Home/KnowledgeManagementPage";
|
||||||
import KnowledgeSetDetail from "@/pages/KnowledgeManagement/Detail/KnowledgeSetDetail";
|
import KnowledgeSetDetail from "@/pages/KnowledgeManagement/Detail/KnowledgeSetDetail";
|
||||||
|
import KnowledgeManagementSearch from "@/pages/KnowledgeManagement/Search/KnowledgeManagementSearch";
|
||||||
|
|
||||||
import DataCleansing from "@/pages/DataCleansing/Home/DataCleansing";
|
import DataCleansing from "@/pages/DataCleansing/Home/DataCleansing";
|
||||||
import CleansingTaskCreate from "@/pages/DataCleansing/Create/CreateTask";
|
import CleansingTaskCreate from "@/pages/DataCleansing/Create/CreateTask";
|
||||||
@@ -116,6 +117,10 @@ const router = createBrowserRouter([
|
|||||||
index: true,
|
index: true,
|
||||||
Component: KnowledgeManagementPage,
|
Component: KnowledgeManagementPage,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "search",
|
||||||
|
Component: KnowledgeManagementSearch,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "detail/:id",
|
path: "detail/:id",
|
||||||
Component: KnowledgeSetDetail,
|
Component: KnowledgeSetDetail,
|
||||||
|
|||||||
Reference in New Issue
Block a user