feat(knowledge-base): 添加知识库文件全库检索功能

- 新增相对路径字段替代原有的metadata存储方式
- 实现跨知识库文件检索接口searchFiles
- 添加前端全库检索页面和相关API调用
- 优化文件路径处理和数据库索引配置
- 统一请求参数类型定义为RequestPayload和RequestParams
- 简化RagFile模型中的元数据结构设计
This commit is contained in:
2026-01-30 22:23:52 +08:00
parent cbad129ce4
commit 76f70a6847
14 changed files with 403 additions and 83 deletions

View File

@@ -35,10 +35,10 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
/** /**
* 知识库服务类 * 知识库服务类
@@ -49,7 +49,6 @@ import java.util.Optional;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class KnowledgeBaseService { public class KnowledgeBaseService {
private static final String RELATIVE_PATH_KEY = "relativePath";
private static final String PATH_SEPARATOR = "/"; private static final String PATH_SEPARATOR = "/";
private final KnowledgeBaseRepository knowledgeBaseRepository; private final KnowledgeBaseRepository knowledgeBaseRepository;
private final RagFileRepository ragFileRepository; private final RagFileRepository ragFileRepository;
@@ -150,12 +149,7 @@ public class KnowledgeBaseService {
ragFile.setKnowledgeBaseId(knowledgeBase.getId()); ragFile.setKnowledgeBaseId(knowledgeBase.getId());
ragFile.setFileId(fileInfo.id()); ragFile.setFileId(fileInfo.id());
ragFile.setFileName(fileInfo.fileName()); ragFile.setFileName(fileInfo.fileName());
String relativePath = normalizeRelativePath(fileInfo.relativePath()); ragFile.setRelativePath(normalizeRelativePath(fileInfo.relativePath()));
if (StringUtils.hasText(relativePath)) {
Map<String, Object> metadata = new HashMap<>();
metadata.put(RELATIVE_PATH_KEY, relativePath);
ragFile.setMetadata(metadata);
}
ragFile.setStatus(FileStatus.UNPROCESSED); ragFile.setStatus(FileStatus.UNPROCESSED);
return ragFile; return ragFile;
}).toList(); }).toList();
@@ -181,6 +175,41 @@ public class KnowledgeBaseService {
return PagedResponse.of(page.getRecords(), page.getCurrent(), page.getTotal(), page.getPages()); return PagedResponse.of(page.getRecords(), page.getCurrent(), page.getTotal(), page.getPages());
} }
public PagedResponse<KnowledgeBaseFileSearchResp> searchFiles(KnowledgeBaseFileSearchReq request) {
IPage<RagFile> page = new Page<>(request.getPage(), request.getSize());
page = ragFileRepository.searchPage(page, request);
List<RagFile> records = page.getRecords();
if (records.isEmpty()) {
return PagedResponse.of(Collections.emptyList(), page.getCurrent(), page.getTotal(), page.getPages());
}
List<String> knowledgeBaseIds = records.stream()
.map(RagFile::getKnowledgeBaseId)
.filter(StringUtils::hasText)
.distinct()
.toList();
Map<String, String> knowledgeBaseNameMap = knowledgeBaseRepository.listByIds(knowledgeBaseIds).stream()
.collect(Collectors.toMap(KnowledgeBase::getId, KnowledgeBase::getName));
List<KnowledgeBaseFileSearchResp> responses = records.stream()
.map(file -> {
KnowledgeBaseFileSearchResp resp = new KnowledgeBaseFileSearchResp();
resp.setId(file.getId());
resp.setKnowledgeBaseId(file.getKnowledgeBaseId());
resp.setKnowledgeBaseName(knowledgeBaseNameMap.getOrDefault(file.getKnowledgeBaseId(), ""));
resp.setFileName(file.getFileName());
resp.setRelativePath(file.getRelativePath());
resp.setChunkCount(file.getChunkCount());
resp.setStatus(file.getStatus());
resp.setCreatedAt(file.getCreatedAt());
resp.setUpdatedAt(file.getUpdatedAt());
return resp;
})
.toList();
return PagedResponse.of(responses, page.getCurrent(), page.getTotal(), page.getPages());
}
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void deleteFiles(String knowledgeBaseId, DeleteFilesReq request) { public void deleteFiles(String knowledgeBaseId, DeleteFilesReq request) {
KnowledgeBase knowledgeBase = Optional.ofNullable(knowledgeBaseRepository.getById(knowledgeBaseId)) KnowledgeBase knowledgeBase = Optional.ofNullable(knowledgeBaseRepository.getById(knowledgeBaseId))

View File

@@ -28,6 +28,10 @@ public class RagFile extends BaseEntity<String> {
* 文件名 * 文件名
*/ */
private String fileName; private String fileName;
/**
* 相对路径
*/
private String relativePath;
/** /**
* 文件ID * 文件ID
*/ */

View File

@@ -3,6 +3,7 @@ package com.datamate.rag.indexer.domain.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.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.RagFileReq; import com.datamate.rag.indexer.interfaces.dto.RagFileReq;
import java.util.List; import java.util.List;
@@ -21,4 +22,6 @@ public interface RagFileRepository extends IRepository<RagFile> {
List<RagFile> findAllByKnowledgeBaseId(String knowledgeBaseId); List<RagFile> findAllByKnowledgeBaseId(String knowledgeBaseId);
IPage<RagFile> page(IPage<RagFile> page, RagFileReq request); IPage<RagFile> page(IPage<RagFile> page, RagFileReq request);
IPage<RagFile> searchPage(IPage<RagFile> page, KnowledgeBaseFileSearchReq request);
} }

View File

@@ -6,6 +6,7 @@ import com.datamate.rag.indexer.domain.model.FileStatus;
import com.datamate.rag.indexer.domain.model.RagFile; import com.datamate.rag.indexer.domain.model.RagFile;
import com.datamate.rag.indexer.domain.repository.RagFileRepository; 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.RagFileReq; import com.datamate.rag.indexer.interfaces.dto.RagFileReq;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@@ -20,7 +21,6 @@ import java.util.List;
*/ */
@Repository @Repository
public class RagFileRepositoryImpl extends CrudRepository<RagFileMapper, RagFile> implements RagFileRepository { public class RagFileRepositoryImpl extends CrudRepository<RagFileMapper, RagFile> implements RagFileRepository {
private static final String RELATIVE_PATH_KEY = "\"relativePath\":\"";
private static final String PATH_SEPARATOR = "/"; private static final String PATH_SEPARATOR = "/";
@Override @Override
public void removeByKnowledgeBaseId(String knowledgeBaseId) { public void removeByKnowledgeBaseId(String knowledgeBaseId) {
@@ -44,15 +44,23 @@ public class RagFileRepositoryImpl extends CrudRepository<RagFileMapper, RagFile
@Override @Override
public IPage<RagFile> page(IPage<RagFile> page, RagFileReq request) { public IPage<RagFile> page(IPage<RagFile> page, RagFileReq request) {
String relativePathPattern = buildRelativePathPattern(request.getRelativePath());
return lambdaQuery() return lambdaQuery()
.eq(RagFile::getKnowledgeBaseId, request.getKnowledgeBaseId()) .eq(RagFile::getKnowledgeBaseId, request.getKnowledgeBaseId())
.like(StringUtils.hasText(request.getFileName()), RagFile::getFileName, request.getFileName()) .like(StringUtils.hasText(request.getFileName()), RagFile::getFileName, request.getFileName())
.like(StringUtils.hasText(relativePathPattern), RagFile::getMetadata, relativePathPattern) .likeRight(StringUtils.hasText(request.getRelativePath()), RagFile::getRelativePath, normalizeRelativePath(request.getRelativePath()))
.page(page); .page(page);
} }
private String buildRelativePathPattern(String relativePath) { @Override
public IPage<RagFile> searchPage(IPage<RagFile> page, KnowledgeBaseFileSearchReq request) {
return lambdaQuery()
.eq(StringUtils.hasText(request.getKnowledgeBaseId()), RagFile::getKnowledgeBaseId, request.getKnowledgeBaseId())
.like(StringUtils.hasText(request.getFileName()), RagFile::getFileName, request.getFileName())
.likeRight(StringUtils.hasText(request.getRelativePath()), RagFile::getRelativePath, normalizeRelativePath(request.getRelativePath()))
.page(page);
}
private String normalizeRelativePath(String relativePath) {
if (!StringUtils.hasText(relativePath)) { if (!StringUtils.hasText(relativePath)) {
return ""; return "";
} }
@@ -60,9 +68,6 @@ public class RagFileRepositoryImpl extends CrudRepository<RagFileMapper, RagFile
while (normalized.startsWith(PATH_SEPARATOR)) { while (normalized.startsWith(PATH_SEPARATOR)) {
normalized = normalized.substring(1); normalized = normalized.substring(1);
} }
if (!StringUtils.hasText(normalized)) { return normalized;
return "";
}
return RELATIVE_PATH_KEY + normalized;
} }
} }

View File

@@ -105,6 +105,17 @@ public class KnowledgeBaseController {
return knowledgeBaseService.listFiles(knowledgeBaseId, request); return knowledgeBaseService.listFiles(knowledgeBaseId, request);
} }
/**
* 全库检索知识库文件(跨知识库)
*
* @param request 检索请求
* @return 文件列表
*/
@GetMapping("/files/search")
public PagedResponse<KnowledgeBaseFileSearchResp> searchFiles(KnowledgeBaseFileSearchReq request) {
return knowledgeBaseService.searchFiles(request);
}
/** /**
* 删除知识库文件 * 删除知识库文件
* *

View File

@@ -0,0 +1,19 @@
package com.datamate.rag.indexer.interfaces.dto;
import com.datamate.common.interfaces.PagingQuery;
import lombok.Getter;
import lombok.Setter;
/**
* 知识库文件全库检索请求
*
* @author dallas
* @since 2026-01-30
*/
@Getter
@Setter
public class KnowledgeBaseFileSearchReq extends PagingQuery {
private String fileName;
private String relativePath;
private String knowledgeBaseId;
}

View File

@@ -0,0 +1,27 @@
package com.datamate.rag.indexer.interfaces.dto;
import com.datamate.rag.indexer.domain.model.FileStatus;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
/**
* 知识库文件全库检索响应
*
* @author dallas
* @since 2026-01-30
*/
@Getter
@Setter
public class KnowledgeBaseFileSearchResp {
private String id;
private String knowledgeBaseId;
private String knowledgeBaseName;
private String fileName;
private String relativePath;
private Integer chunkCount;
private FileStatus status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}

View File

@@ -17,7 +17,7 @@ import {
EditOutlined, EditOutlined,
ReloadOutlined, ReloadOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useNavigate, useParams } from "react-router"; import { useNavigate, useParams, useSearchParams } from "react-router";
import DetailHeader from "@/components/DetailHeader"; import DetailHeader from "@/components/DetailHeader";
import { SearchControls } from "@/components/SearchControls"; import { SearchControls } from "@/components/SearchControls";
import { KBFile, KnowledgeBaseItem } from "../knowledge-base.model"; import { KBFile, KnowledgeBaseItem } from "../knowledge-base.model";
@@ -58,7 +58,6 @@ type KBFileRow = KBFile & {
}; };
const PATH_SEPARATOR = "/"; const PATH_SEPARATOR = "/";
const RELATIVE_PATH_KEY = "relativePath";
const normalizePath = (value?: string) => const normalizePath = (value?: string) =>
(value ?? "").replace(/\\/g, PATH_SEPARATOR); (value ?? "").replace(/\\/g, PATH_SEPARATOR);
@@ -81,17 +80,13 @@ const splitRelativePath = (fullPath: string, prefix: string) => {
}; };
const resolveFileRelativePath = (file: KBFile) => { const resolveFileRelativePath = (file: KBFile) => {
const metadata = file?.metadata as Record<string, unknown> | undefined; const rawPath = file.relativePath || file.fileName || file.name || "";
const metadataPath =
metadata && typeof metadata[RELATIVE_PATH_KEY] === "string"
? String(metadata[RELATIVE_PATH_KEY])
: "";
const rawPath = metadataPath || file.fileName || file.name || "";
return normalizePath(rawPath).replace(/^\/+/, ""); return normalizePath(rawPath).replace(/^\/+/, "");
}; };
const KnowledgeBaseDetailPage: React.FC = () => { const KnowledgeBaseDetailPage: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [searchParams] = useSearchParams();
const { message } = App.useApp(); const { message } = App.useApp();
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const [knowledgeBase, setKnowledgeBase] = useState<KnowledgeBaseItem | undefined>(undefined); const [knowledgeBase, setKnowledgeBase] = useState<KnowledgeBaseItem | undefined>(undefined);
@@ -158,6 +153,16 @@ const KnowledgeBaseDetailPage: React.FC = () => {
} }
}, [id, fetchKnowledgeBaseDetails]); }, [id, fetchKnowledgeBaseDetails]);
useEffect(() => {
if (!id) {
return;
}
const prefixParam = searchParams.get("prefix");
const fileNameParam = searchParams.get("fileName");
setFilePrefix(prefixParam ? normalizePrefix(prefixParam) : "");
setFileKeyword(fileNameParam ? fileNameParam : "");
}, [id, searchParams]);
useEffect(() => { useEffect(() => {
if (id) { if (id) {
fetchFiles(); fetchFiles();

View File

@@ -37,7 +37,7 @@ export default function KnowledgeBasePage() {
await deleteKnowledgeBaseByIdUsingDelete(kb.id); await deleteKnowledgeBaseByIdUsingDelete(kb.id);
message.success("知识库删除成功"); message.success("知识库删除成功");
fetchData(); fetchData();
} catch (error) { } catch {
message.error("知识库删除失败"); message.error("知识库删除失败");
} }
}; };
@@ -47,7 +47,7 @@ export default function KnowledgeBasePage() {
key: "edit", key: "edit",
label: "编辑", label: "编辑",
icon: <EditOutlined />, icon: <EditOutlined />,
onClick: (item) => { onClick: (item: KnowledgeBaseItem) => {
setIsEdit(true); setIsEdit(true);
setCurrentKB(item); setCurrentKB(item);
}, },
@@ -64,7 +64,7 @@ export default function KnowledgeBasePage() {
okType: "danger", okType: "danger",
cancelText: "取消", cancelText: "取消",
}, },
onClick: (item) => handleDeleteKB(item), onClick: (item: KnowledgeBaseItem) => handleDeleteKB(item),
}, },
]; ];
@@ -76,7 +76,7 @@ export default function KnowledgeBasePage() {
fixed: "left" as const, fixed: "left" as const,
width: 200, width: 200,
ellipsis: true, ellipsis: true,
render: (_: any, kb: KnowledgeBaseItem) => ( render: (_: unknown, kb: KnowledgeBaseItem) => (
<Button <Button
type="link" type="link"
onClick={() => navigate(`/data/knowledge-base/detail/${kb.id}`)} onClick={() => navigate(`/data/knowledge-base/detail/${kb.id}`)}
@@ -111,7 +111,7 @@ export default function KnowledgeBasePage() {
key: "actions", key: "actions",
fixed: "right" as const, fixed: "right" as const,
width: 150, width: 150,
render: (_: any, kb: KnowledgeBaseItem) => ( render: (_: unknown, kb: KnowledgeBaseItem) => (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{operations.map((op) => ( {operations.map((op) => (
<Tooltip key={op.key} title={op.label}> <Tooltip key={op.key} title={op.label}>
@@ -132,6 +132,10 @@ export default function KnowledgeBasePage() {
<div className="h-full flex flex-col gap-4"> <div className="h-full flex flex-col gap-4">
<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 items-center gap-2">
<Button onClick={() => navigate("/data/knowledge-base/search")}>
</Button>
<CreateKnowledgeBase <CreateKnowledgeBase
isEdit={isEdit} isEdit={isEdit}
data={currentKB} data={currentKB}
@@ -144,6 +148,7 @@ export default function KnowledgeBasePage() {
}} }}
/> />
</div> </div>
</div>
<SearchControls <SearchControls
searchTerm={searchParams.keyword} searchTerm={searchParams.keyword}
@@ -161,7 +166,9 @@ export default function KnowledgeBasePage() {
<CardView <CardView
data={tableData} data={tableData}
operations={operations} operations={operations}
onView={(item) => navigate(`/data/knowledge-base/detail/${item.id}`)} onView={(item: KnowledgeBaseItem) =>
navigate(`/data/knowledge-base/detail/${item.id}`)
}
pagination={pagination} pagination={pagination}
/> />
) : ( ) : (

View File

@@ -0,0 +1,217 @@
import { useCallback, useMemo, useState } from "react";
import { App, Badge, Breadcrumb, Button, Input, Table } from "antd";
import { useNavigate } from "react-router";
import {
KBFileStatus,
KnowledgeBaseFileSearchResult,
} from "../knowledge-base.model";
import { KBFileStatusMap } from "../knowledge-base.const";
import { queryKnowledgeBaseFilesSearchUsingGet } from "../knowledge-base.api";
import { formatDateTime } from "@/utils/unit";
const PATH_SEPARATOR = "/";
const normalizePath = (value?: string) =>
(value ?? "").replace(/\\/g, PATH_SEPARATOR);
const resolvePrefix = (relativePath?: string) => {
const normalized = normalizePath(relativePath);
const parts = normalized.split(PATH_SEPARATOR).filter(Boolean);
if (parts.length <= 1) {
return "";
}
parts.pop();
return `${parts.join(PATH_SEPARATOR)}${PATH_SEPARATOR}`;
};
export default function KnowledgeBaseSearch() {
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<KnowledgeBaseFileSearchResult[]>([]);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
total: 0,
});
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 queryKnowledgeBaseFilesSearchUsingGet({
fileName: 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 base files:", 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: "knowledgeBaseName",
key: "knowledgeBaseName",
width: 220,
ellipsis: true,
render: (text: string) => text || "-",
},
{
title: "文件名",
dataIndex: "fileName",
key: "fileName",
width: 220,
ellipsis: true,
},
{
title: "相对路径",
dataIndex: "relativePath",
key: "relativePath",
ellipsis: true,
render: (value: string) => value || "-",
},
{
title: "状态",
dataIndex: "status",
key: "status",
width: 120,
render: (status?: KBFileStatus) => {
const config = status ? KBFileStatusMap[status] : undefined;
if (!config) {
return <Badge color="default" text={status || "-"} />;
}
return <Badge color={config.color} text={config.label} />;
},
},
{
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: KnowledgeBaseFileSearchResult) => (
<Button
type="link"
onClick={() => {
const prefix = resolvePrefix(record.relativePath);
const searchParams = new URLSearchParams();
if (prefix) {
searchParams.set("prefix", prefix);
}
navigate(
`/data/knowledge-base/detail/${record.knowledgeBaseId}?${searchParams.toString()}`
);
}}
>
</Button>
),
},
],
[navigate]
);
return (
<div className="h-full flex flex-col gap-4">
<Breadcrumb>
<Breadcrumb.Item>
<a onClick={() => navigate("/data/knowledge-base")}></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: () => {
const prefix = resolvePrefix(record.relativePath);
const searchParams = new URLSearchParams();
if (prefix) {
searchParams.set("prefix", prefix);
}
navigate(
`/data/knowledge-base/detail/${record.knowledgeBaseId}?${searchParams.toString()}`
);
},
})}
/>
</div>
);
}

View File

@@ -1,12 +1,15 @@
import { get, post, put, del } from "@/utils/request"; import { get, post, put, del } from "@/utils/request";
type RequestPayload = Record<string, unknown>;
type RequestParams = Record<string, unknown>;
// 获取知识库列表 // 获取知识库列表
export function queryKnowledgeBasesUsingPost(params: any) { export function queryKnowledgeBasesUsingPost(params: RequestPayload) {
return post("/api/knowledge-base/list", params); return post("/api/knowledge-base/list", params);
} }
// 创建知识库 // 创建知识库
export function createKnowledgeBaseUsingPost(data: any) { export function createKnowledgeBaseUsingPost(data: RequestPayload) {
return post("/api/knowledge-base/create", data); return post("/api/knowledge-base/create", data);
} }
@@ -16,7 +19,7 @@ export function queryKnowledgeBaseByIdUsingGet(baseId: string) {
} }
// 更新知识库 // 更新知识库
export function updateKnowledgeBaseByIdUsingPut(baseId: string, data: any) { export function updateKnowledgeBaseByIdUsingPut(baseId: string, data: RequestPayload) {
return put(`/api/knowledge-base/${baseId}`, data); return put(`/api/knowledge-base/${baseId}`, data);
} }
@@ -26,17 +29,22 @@ export function deleteKnowledgeBaseByIdUsingDelete(baseId: string) {
} }
// 获取知识生成文件列表 // 获取知识生成文件列表
export function queryKnowledgeBaseFilesUsingGet(baseId: string, data) { export function queryKnowledgeBaseFilesUsingGet(baseId: string, data: RequestParams) {
return get(`/api/knowledge-base/${baseId}/files`, data); return get(`/api/knowledge-base/${baseId}/files`, data);
} }
// 全库检索知识库文件
export function queryKnowledgeBaseFilesSearchUsingGet(params: RequestParams) {
return get("/api/knowledge-base/files/search", params);
}
// 添加文件到知识库 // 添加文件到知识库
export function addKnowledgeBaseFilesUsingPost(baseId: string, data: any) { export function addKnowledgeBaseFilesUsingPost(baseId: string, data: RequestPayload) {
return post(`/api/knowledge-base/${baseId}/files`, data); return post(`/api/knowledge-base/${baseId}/files`, data);
} }
// 删除知识生成文件 // 删除知识生成文件
export function deleteKnowledgeBaseFileByIdUsingDelete(baseId: string, data: any) { export function deleteKnowledgeBaseFileByIdUsingDelete(baseId: string, data: RequestPayload) {
return del(`/api/knowledge-base/${baseId}/files`, data); return del(`/api/knowledge-base/${baseId}/files`, data);
} }

View File

@@ -29,50 +29,26 @@ export interface KBFile {
id: string; id: string;
fileName: string; fileName: string;
name?: string; name?: string;
relativePath?: string;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
status: KBFileStatus; status: KBFileStatus;
chunkCount: number; chunkCount: number;
metadata: Record<string, any>; metadata: Record<string, unknown>;
knowledgeBaseId: string; knowledgeBaseId: string;
fileId: string; fileId: string;
updatedBy: string; updatedBy: string;
createdBy: string; createdBy: string;
} }
interface Chunk { export interface KnowledgeBaseFileSearchResult {
id: number; id: string;
content: string; knowledgeBaseId: string;
position: number; knowledgeBaseName: string;
tokens: number; fileName: string;
embedding?: number[]; relativePath?: string;
similarity?: string; status?: KBFileStatus;
chunkCount?: number;
createdAt?: string; createdAt?: string;
updatedAt?: string; updatedAt?: string;
vectorId?: string;
sliceOperator?: string;
parentChunkId?: number;
metadata?: {
source: string;
page?: number;
section?: string;
};
}
interface VectorizationRecord {
id: number;
timestamp: string;
operation: "create" | "update" | "delete" | "reprocess";
fileId: number;
fileName: string;
chunksProcessed: number;
vectorsGenerated: number;
status: "success" | "failed" | "partial";
duration: string;
config: {
embeddingModel: string;
chunkSize: number;
sliceMethod: string;
};
error?: string;
} }

View File

@@ -30,6 +30,7 @@ import ManualEvaluatePage from "@/pages/DataEvaluation/Evaluate/ManualEvaluate";
import KnowledgeBasePage from "@/pages/KnowledgeBase/Home/KnowledgeBasePage"; import KnowledgeBasePage from "@/pages/KnowledgeBase/Home/KnowledgeBasePage";
import KnowledgeBaseDetailPage from "@/pages/KnowledgeBase/Detail/KnowledgeBaseDetail"; import KnowledgeBaseDetailPage from "@/pages/KnowledgeBase/Detail/KnowledgeBaseDetail";
import KnowledgeBaseFileDetailPage from "@/pages/KnowledgeBase/FileDetail/KnowledgeBaseFileDetail"; import KnowledgeBaseFileDetailPage from "@/pages/KnowledgeBase/FileDetail/KnowledgeBaseFileDetail";
import KnowledgeBaseSearch from "@/pages/KnowledgeBase/Search/KnowledgeBaseSearch";
import OperatorMarketPage from "@/pages/OperatorMarket/Home/OperatorMarket"; import OperatorMarketPage from "@/pages/OperatorMarket/Home/OperatorMarket";
import OperatorPluginCreate from "@/pages/OperatorMarket/Create/OperatorPluginCreate"; import OperatorPluginCreate from "@/pages/OperatorMarket/Create/OperatorPluginCreate";
@@ -246,6 +247,10 @@ const router = createBrowserRouter([
index: true, index: true,
Component: KnowledgeBasePage, Component: KnowledgeBasePage,
}, },
{
path: "search",
Component: KnowledgeBaseSearch,
},
{ {
path: "detail/:id", path: "detail/:id",
Component: KnowledgeBaseDetailPage, Component: KnowledgeBaseDetailPage,

View File

@@ -18,6 +18,7 @@ create table if not exists t_rag_file
id VARCHAR(36) PRIMARY KEY COMMENT 'UUID', id VARCHAR(36) PRIMARY KEY COMMENT 'UUID',
knowledge_base_id VARCHAR(36) NOT NULL COMMENT '知识库ID', knowledge_base_id VARCHAR(36) NOT NULL COMMENT '知识库ID',
file_name VARCHAR(255) NOT NULL COMMENT '文件名', file_name VARCHAR(255) NOT NULL COMMENT '文件名',
relative_path VARCHAR(512) NULL COMMENT '相对路径',
file_id VARCHAR(255) NOT NULL COMMENT '文件ID', file_id VARCHAR(255) NOT NULL COMMENT '文件ID',
chunk_count INT COMMENT '切片数', chunk_count INT COMMENT '切片数',
metadata JSON COMMENT '元数据', metadata JSON COMMENT '元数据',
@@ -28,3 +29,6 @@ create table if not exists t_rag_file
created_by VARCHAR(255) COMMENT '创建者', created_by VARCHAR(255) COMMENT '创建者',
updated_by VARCHAR(255) COMMENT '更新者' updated_by VARCHAR(255) COMMENT '更新者'
) comment '知识库切片表'; ) comment '知识库切片表';
create index idx_rag_file_kb_name on t_rag_file (knowledge_base_id, file_name);
create index idx_rag_file_kb_path on t_rag_file (knowledge_base_id, relative_path);