feat(rag): 添加相对路径搜索功能并优化文件显示

- 在RagFileRepositoryImpl中新增relativePath字段和路径模式构建方法
- 实现buildRelativePathPattern方法用于构建相对路径搜索模式
- 修改page方法添加相对路径模糊查询支持
- 在RagFileReq DTO中添加relativePath参数字段
- 优化KnowledgeBaseDetail页面中的文件名显示逻辑
- 添加normalizePath函数处理文件路径规范化显示
This commit is contained in:
2026-01-30 21:50:53 +08:00
parent ca7ff56610
commit cbad129ce4
3 changed files with 39 additions and 30 deletions

View File

@@ -20,6 +20,8 @@ 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 = "/";
@Override @Override
public void removeByKnowledgeBaseId(String knowledgeBaseId) { public void removeByKnowledgeBaseId(String knowledgeBaseId) {
lambdaUpdate().eq(RagFile::getKnowledgeBaseId, knowledgeBaseId).remove(); lambdaUpdate().eq(RagFile::getKnowledgeBaseId, knowledgeBaseId).remove();
@@ -42,9 +44,25 @@ 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)
.page(page); .page(page);
} }
private String buildRelativePathPattern(String relativePath) {
if (!StringUtils.hasText(relativePath)) {
return "";
}
String normalized = relativePath.replace("\\", PATH_SEPARATOR).trim();
while (normalized.startsWith(PATH_SEPARATOR)) {
normalized = normalized.substring(1);
}
if (!StringUtils.hasText(normalized)) {
return "";
}
return RELATIVE_PATH_KEY + normalized;
}
} }

View File

@@ -14,5 +14,6 @@ import lombok.Setter;
@Getter @Getter
public class RagFileReq extends PagingQuery { public class RagFileReq extends PagingQuery {
private String fileName; private String fileName;
private String relativePath;
private String knowledgeBaseId; private String knowledgeBaseId;
} }

View File

@@ -124,10 +124,14 @@ const KnowledgeBaseDetailPage: React.FC = () => {
const pageSize = 200; const pageSize = 200;
let page = 0; let page = 0;
let combined: KBFile[] = []; let combined: KBFile[] = [];
const currentPrefix = normalizePrefix(filePrefix);
const keyword = fileKeyword.trim();
while (true) { while (true) {
const { data } = await queryKnowledgeBaseFilesUsingGet(id, { const { data } = await queryKnowledgeBaseFilesUsingGet(id, {
page, page,
size: pageSize, size: pageSize,
...(currentPrefix ? { relativePath: currentPrefix } : {}),
...(keyword ? { fileName: keyword } : {}),
}); });
const content = Array.isArray(data?.content) ? data.content : []; const content = Array.isArray(data?.content) ? data.content : [];
combined = combined.concat(content.map(mapFileData)); combined = combined.concat(content.map(mapFileData));
@@ -146,14 +150,19 @@ const KnowledgeBaseDetailPage: React.FC = () => {
} finally { } finally {
setFilesLoading(false); setFilesLoading(false);
} }
}, [id, message]); }, [id, filePrefix, fileKeyword, message]);
useEffect(() => { useEffect(() => {
if (id) { if (id) {
fetchKnowledgeBaseDetails(id); fetchKnowledgeBaseDetails(id);
}
}, [id, fetchKnowledgeBaseDetails]);
useEffect(() => {
if (id) {
fetchFiles(); fetchFiles();
} }
}, [id, fetchKnowledgeBaseDetails, fetchFiles]); }, [id, fetchFiles]);
// File table logic // File table logic
const handleDeleteFile = async (file: KBFileRow) => { const handleDeleteFile = async (file: KBFileRow) => {
@@ -257,8 +266,7 @@ const KnowledgeBaseDetailPage: React.FC = () => {
const normalizedPrefix = useMemo(() => normalizePrefix(filePrefix), [filePrefix]); const normalizedPrefix = useMemo(() => normalizePrefix(filePrefix), [filePrefix]);
const { rows: fileRows, total: fileTotal } = useMemo(() => { const { rows: fileRows, total: fileTotal } = useMemo(() => {
const keyword = fileKeyword.trim().toLowerCase(); const folderMap = new Map<string, { name: string; fileCount: number }>();
const folderMap = new Map<string, { name: string; fileCount: number; hasMatch: boolean }>();
const fileItems: KBFileRow[] = []; const fileItems: KBFileRow[] = [];
allFiles.forEach((file) => { allFiles.forEach((file) => {
@@ -271,31 +279,22 @@ const KnowledgeBaseDetailPage: React.FC = () => {
return; return;
} }
const leafName = segments[0]; const leafName = segments[0];
const fileMatches =
!keyword ||
leafName.toLowerCase().includes(keyword) ||
fullPath.toLowerCase().includes(keyword);
if (segments.length > 1) { if (segments.length > 1) {
const folderName = leafName; const folderName = leafName;
const entry = folderMap.get(folderName) || { const entry = folderMap.get(folderName) || {
name: folderName, name: folderName,
fileCount: 0, fileCount: 0,
hasMatch: false,
}; };
entry.fileCount += 1; entry.fileCount += 1;
if (fileMatches) {
entry.hasMatch = true;
}
folderMap.set(folderName, entry); folderMap.set(folderName, entry);
return; return;
} }
if (!fileMatches) { const normalizedFileName = normalizePath(file.fileName);
return; const displayName = normalizedFileName.includes(PATH_SEPARATOR)
} ? leafName
: file.fileName || leafName;
const displayName = file.fileName || leafName;
fileItems.push({ fileItems.push({
...file, ...file,
name: displayName, name: displayName,
@@ -304,17 +303,8 @@ const KnowledgeBaseDetailPage: React.FC = () => {
}); });
}); });
const folderItems: KBFileRow[] = Array.from(folderMap.values()) const folderItems: KBFileRow[] = Array.from(folderMap.values()).map(
.filter((entry) => { (entry) =>
if (!keyword) {
return true;
}
return (
entry.hasMatch ||
entry.name.toLowerCase().includes(keyword)
);
})
.map((entry) =>
({ ({
id: `directory-${normalizedPrefix}${entry.name}`, id: `directory-${normalizedPrefix}${entry.name}`,
fileName: entry.name, fileName: entry.name,
@@ -346,7 +336,7 @@ const KnowledgeBaseDetailPage: React.FC = () => {
const combined = [...folderItems, ...fileItems]; const combined = [...folderItems, ...fileItems];
return { rows: combined, total: combined.length }; return { rows: combined, total: combined.length };
}, [allFiles, fileKeyword, knowledgeBase?.id, normalizedPrefix]); }, [allFiles, knowledgeBase?.id, normalizedPrefix]);
const filePageCurrent = filePagination.current; const filePageCurrent = filePagination.current;
const filePageSize = filePagination.pageSize; const filePageSize = filePagination.pageSize;