You've already forked DataMate
feat(knowledge-base): 添加知识库统计功能
- 后端服务新增 KnowledgeBaseStatisticsResp 和 RagFileStatistics 数据传输对象 - 在 KnowledgeBaseService 中实现 getStatistics 方法提供统计信息查询 - 为 RagFileRepository 添加 getStatistics 接口及其实现 - 通过 MyBatis Mapper 实现数据库层面的统计查询功能 - 在 KnowledgeBaseController 中暴露 /statistics 接口供前端调用 - 前端页面集成统计卡片组件展示知识库、文件数量及总大小信息 - 实现前后端数据同步机制确保统计数据实时更新
This commit is contained in:
@@ -140,6 +140,18 @@ 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) {
|
||||||
KnowledgeBase knowledgeBase = Optional.ofNullable(knowledgeBaseRepository.getById(request.getKnowledgeBaseId()))
|
KnowledgeBase knowledgeBase = Optional.ofNullable(knowledgeBaseRepository.getById(request.getKnowledgeBaseId()))
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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;
|
||||||
|
|
||||||
@@ -24,4 +25,6 @@ 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,6 +8,7 @@ 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;
|
||||||
|
|
||||||
@@ -60,6 +61,11 @@ 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,7 +3,9 @@ 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文件映射器接口
|
||||||
@@ -13,4 +15,9 @@ import org.apache.ibatis.annotations.Mapper;
|
|||||||
*/
|
*/
|
||||||
@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,6 +80,16 @@ public class KnowledgeBaseController {
|
|||||||
return knowledgeBaseService.list(request);
|
return knowledgeBaseService.list(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取知识库统计信息
|
||||||
|
*
|
||||||
|
* @return 知识库统计信息
|
||||||
|
*/
|
||||||
|
@GetMapping("/statistics")
|
||||||
|
public KnowledgeBaseStatisticsResp statistics() {
|
||||||
|
return knowledgeBaseService.getStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加文件到知识库
|
* 添加文件到知识库
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
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,23 +1,48 @@
|
|||||||
import { useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { Card, Button, Table, Tooltip, message } from "antd";
|
import { Card, Button, Table, Tooltip, message, Statistic } 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 } from "../knowledge-base.model";
|
import { KnowledgeBaseItem, KnowledgeBaseStatistics } 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,
|
||||||
@@ -32,11 +57,43 @@ 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("知识库删除成功");
|
||||||
fetchData();
|
await refreshAll();
|
||||||
} catch {
|
} catch {
|
||||||
message.error("知识库删除失败");
|
message.error("知识库删除失败");
|
||||||
}
|
}
|
||||||
@@ -140,7 +197,7 @@ export default function KnowledgeBasePage() {
|
|||||||
isEdit={isEdit}
|
isEdit={isEdit}
|
||||||
data={currentKB}
|
data={currentKB}
|
||||||
onUpdate={() => {
|
onUpdate={() => {
|
||||||
fetchData();
|
refreshAll();
|
||||||
}}
|
}}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setIsEdit(false);
|
setIsEdit(false);
|
||||||
@@ -150,6 +207,20 @@ 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}
|
||||||
@@ -160,7 +231,7 @@ export default function KnowledgeBasePage() {
|
|||||||
viewMode={viewMode}
|
viewMode={viewMode}
|
||||||
onViewModeChange={setViewMode}
|
onViewModeChange={setViewMode}
|
||||||
showViewToggle
|
showViewToggle
|
||||||
onReload={fetchData}
|
onReload={refreshAll}
|
||||||
/>
|
/>
|
||||||
{viewMode === "card" ? (
|
{viewMode === "card" ? (
|
||||||
<CardView
|
<CardView
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ 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,6 +25,12 @@ 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;
|
||||||
|
|||||||
Reference in New Issue
Block a user