You've already forked DataMate
- 新增知识条目搜索查询和响应DTO - 实现知识管理统计功能,包括总数、文件数和总大小 - 添加数据库查询方法支持文件搜索和统计计算 - 创建知识条目搜索控制器提供REST API - 在前端添加知识管理搜索页面和相关组件 - 更新前端路由配置添加搜索页面入口 - 移除RAG索引服务中的重复统计功能 - 优化前端页面统计数据显示和刷新逻辑
213 lines
6.2 KiB
TypeScript
213 lines
6.2 KiB
TypeScript
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>
|
|
);
|
|
}
|