You've already forked DataMate
knowledge base pages (#43)
* feat: Update site name to DataMate and refine text for AI data processing * feat: Refactor settings page and implement model access functionality - Created a new ModelAccess component for managing model configurations. - Removed the old Settings component and replaced it with a new SettingsPage component that integrates ModelAccess, SystemConfig, and WebhookConfig. - Added SystemConfig component for managing system settings. - Implemented WebhookConfig component for managing webhook configurations. - Updated API functions for model management in settings.apis.ts. - Adjusted routing to point to the new SettingsPage component. * feat: Implement Data Collection Page with Task Management and Execution Log - Created DataCollectionPage component to manage data collection tasks. - Added TaskManagement and ExecutionLog components for task handling and logging. - Integrated task operations including start, stop, edit, and delete functionalities. - Implemented filtering and searching capabilities in task management. - Introduced SimpleCronScheduler for scheduling tasks with cron expressions. - Updated CreateTask component to utilize new scheduling and template features. - Enhanced BasicInformation component to conditionally render fields based on visibility settings. - Refactored ImportConfiguration component to remove NAS import section. * feat: Update task creation API endpoint and enhance task creation form with new fields and validation * Refactor file upload and operator management components - Removed unnecessary console logs from file download and export functions. - Added size property to TaskItem interface for better task management. - Simplified TaskUpload component by utilizing useFileSliceUpload hook for file upload logic. - Enhanced OperatorPluginCreate component to handle file uploads and parsing more efficiently. - Updated ConfigureStep component to use Ant Design Form for better data handling and validation. - Improved PreviewStep component to navigate back to the operator market. - Added support for additional file types in UploadStep component. - Implemented delete operator functionality in OperatorMarketPage with confirmation prompts. - Cleaned up unused API functions in operator.api.ts to streamline the codebase. - Fixed number formatting utility to handle zero values correctly. * Refactor Knowledge Generation to Knowledge Base - Created new API service for Knowledge Base operations including querying, creating, updating, and deleting knowledge bases and files. - Added constants for Knowledge Base status and type mappings. - Defined models for Knowledge Base and related files. - Removed obsolete Knowledge Base creation and home components, replacing them with new implementations under the Knowledge Base structure. - Updated routing to reflect the new Knowledge Base paths. - Adjusted menu items to align with the new Knowledge Base terminology. - Modified ModelAccess interface to include modelName and type properties. * feat: Implement Knowledge Base Page with CRUD operations and data management - Added KnowledgeBasePage component for displaying and managing knowledge bases. - Integrated search and filter functionalities with SearchControls component. - Implemented CreateKnowledgeBase component for creating and editing knowledge bases. - Enhanced AddDataDialog for file uploads and dataset selections. - Introduced TableTransfer component for managing data transfers between tables. - Updated API functions for knowledge base operations, including file management. - Refactored knowledge base model to include file status and metadata. - Adjusted routing to point to the new KnowledgeBasePage.
This commit is contained in:
@@ -1,628 +1,236 @@
|
||||
import type React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Table, Badge, Button, Breadcrumb, Tooltip, App } from "antd";
|
||||
import {
|
||||
Plus,
|
||||
Edit,
|
||||
File,
|
||||
Trash2,
|
||||
Save,
|
||||
Layers,
|
||||
RefreshCw,
|
||||
BookOpen,
|
||||
Database,
|
||||
MoreHorizontal,
|
||||
Upload,
|
||||
Zap,
|
||||
StarOff,
|
||||
CheckCircle,
|
||||
VectorSquareIcon,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
Table,
|
||||
Badge,
|
||||
Button,
|
||||
Progress,
|
||||
Input,
|
||||
Modal,
|
||||
message,
|
||||
Card,
|
||||
Breadcrumb,
|
||||
Checkbox,
|
||||
Dropdown,
|
||||
} from "antd";
|
||||
import { useNavigate } from "react-router";
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
ReloadOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useNavigate, useParams } from "react-router";
|
||||
import DetailHeader from "@/components/DetailHeader";
|
||||
import { SearchControls } from "@/components/SearchControls";
|
||||
import { KnowledgeBaseItem } from "../knowledge-base.model";
|
||||
import { KBFile, KnowledgeBaseItem } from "../knowledge-base.model";
|
||||
import { mapFileData, mapKnowledgeBase } from "../knowledge-base.const";
|
||||
import {
|
||||
deleteKnowledgeBaseByIdUsingDelete,
|
||||
deleteKnowledgeBaseFileByIdUsingDelete,
|
||||
queryKnowledgeBaseByIdUsingGet,
|
||||
queryKnowledgeBaseFilesUsingGet,
|
||||
} from "../knowledge-base.api";
|
||||
import useFetchData from "@/hooks/useFetchData";
|
||||
import AddDataDialog from "../components/AddDataDialog";
|
||||
import CreateKnowledgeBase from "../components/CreateKnowledgeBase";
|
||||
|
||||
const KnowledgeBaseDetailPage: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { message } = App.useApp();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const [knowledgeBase, setKnowledgeBase] = useState<KnowledgeBaseItem>(null);
|
||||
const [files, setFiles] = useState([]);
|
||||
const [showEdit, setShowEdit] = useState(false);
|
||||
|
||||
const fetchKnowledgeBaseDetails = async (id: string) => {
|
||||
const { data } = await queryKnowledgeBaseByIdUsingGet(id);
|
||||
setKnowledgeBase(mapKnowledgeBase(data));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
fetchKnowledgeBaseDetails(id);
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
const {
|
||||
loading,
|
||||
tableData: files,
|
||||
searchParams,
|
||||
pagination,
|
||||
fetchData: fetchFiles,
|
||||
setSearchParams,
|
||||
handleFiltersChange,
|
||||
} = useFetchData<KBFile>(
|
||||
(params) => queryKnowledgeBaseFilesUsingGet(knowledgeBase?.id, params),
|
||||
mapFileData
|
||||
);
|
||||
|
||||
// File table logic
|
||||
const handleDeleteFile = (file: KBFile) => {};
|
||||
|
||||
|
||||
const handleDeleteKB = (kb: KnowledgeBase) => {};
|
||||
|
||||
// 状态 Badge 映射
|
||||
function getStatusBadgeVariant(status: string) {
|
||||
switch (status) {
|
||||
case "completed":
|
||||
case "ready":
|
||||
return "success";
|
||||
case "processing":
|
||||
case "vectorizing":
|
||||
return "processing";
|
||||
case "importing":
|
||||
return "warning";
|
||||
case "error":
|
||||
return "error";
|
||||
default:
|
||||
return "default";
|
||||
const handleDeleteFile = async (file: KBFile) => {
|
||||
try {
|
||||
await deleteKnowledgeBaseFileByIdUsingDelete(knowledgeBase.id, file.id);
|
||||
message.success("文件已删除");
|
||||
fetchFiles();
|
||||
} catch (error) {
|
||||
message.error("文件删除失败");
|
||||
}
|
||||
}
|
||||
function getStatusLabel(status: string) {
|
||||
switch (status) {
|
||||
case "completed":
|
||||
case "ready":
|
||||
return "已完成";
|
||||
case "processing":
|
||||
return "处理中";
|
||||
case "vectorizing":
|
||||
return "向量化中";
|
||||
case "importing":
|
||||
return "导入中";
|
||||
case "error":
|
||||
return "错误";
|
||||
case "disabled":
|
||||
return "已禁用";
|
||||
default:
|
||||
return "未知";
|
||||
}
|
||||
}
|
||||
function getStatusIcon(status: string) {
|
||||
switch (status) {
|
||||
case "completed":
|
||||
case "ready":
|
||||
return <CheckCircle className="w-4 h-4 text-green-500" />;
|
||||
case "processing":
|
||||
case "vectorizing":
|
||||
return <RefreshCw className="w-4 h-4 text-blue-500 animate-spin" />;
|
||||
case "importing":
|
||||
return <Upload className="w-4 h-4 text-orange-500" />;
|
||||
case "error":
|
||||
return <Trash2 className="w-4 h-4 text-red-500" />;
|
||||
default:
|
||||
return <File className="w-4 h-4 text-gray-400" />;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteKB = async (kb: KnowledgeBaseItem) => {
|
||||
await deleteKnowledgeBaseByIdUsingDelete(kb.id);
|
||||
message.success("知识库已删除");
|
||||
navigate("/data/knowledge-base");
|
||||
};
|
||||
|
||||
const handleRefreshPage = () => {
|
||||
fetchKnowledgeBaseDetails(knowledgeBase.id);
|
||||
fetchFiles();
|
||||
setShowEdit(false);
|
||||
};
|
||||
|
||||
const operations = [
|
||||
{
|
||||
key: "edit",
|
||||
label: "编辑知识库",
|
||||
icon: <EditOutlined className="w-4 h-4" />,
|
||||
onClick: () => {
|
||||
setShowEdit(true);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "refresh",
|
||||
label: "刷新知识库",
|
||||
icon: <ReloadOutlined className="w-4 h-4" />,
|
||||
onClick: () => {
|
||||
handleRefreshPage();
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
label: "删除知识库",
|
||||
danger: true,
|
||||
confirm: {
|
||||
title: "确认删除该知识库吗?",
|
||||
description: "删除后将无法恢复,请谨慎操作。",
|
||||
cancelText: "取消",
|
||||
okText: "删除",
|
||||
okType: "danger",
|
||||
onConfirm: () => handleDeleteKB(knowledgeBase),
|
||||
},
|
||||
icon: <DeleteOutlined className="w-4 h-4" />,
|
||||
},
|
||||
];
|
||||
|
||||
const fileOps = [
|
||||
{
|
||||
key: "delete",
|
||||
label: "删除文件",
|
||||
icon: <DeleteOutlined className="w-4 h-4" />,
|
||||
danger: true,
|
||||
onClick: handleDeleteFile,
|
||||
},
|
||||
];
|
||||
|
||||
const fileColumns = [
|
||||
{
|
||||
title: "文件名",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
filterDropdown: ({
|
||||
setSelectedKeys,
|
||||
selectedKeys,
|
||||
confirm,
|
||||
clearFilters,
|
||||
}: any) => (
|
||||
<div style={{ padding: 8 }}>
|
||||
<Input
|
||||
placeholder="搜索文件名"
|
||||
value={selectedKeys[0]}
|
||||
onChange={(e) =>
|
||||
setSelectedKeys(e.target.value ? [e.target.value] : [])
|
||||
}
|
||||
onPressEnter={confirm}
|
||||
style={{ width: 188, marginBottom: 8, display: "block" }}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={confirm}
|
||||
size="small"
|
||||
style={{ width: 90, marginRight: 8 }}
|
||||
>
|
||||
搜索
|
||||
</Button>
|
||||
<Button onClick={clearFilters} size="small" style={{ width: 90 }}>
|
||||
重置
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
onFilter: (value: string, record: KBFile) =>
|
||||
record.name.toLowerCase().includes(value.toLowerCase()),
|
||||
render: (text: string, file: KBFile) => (
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() =>
|
||||
navigate("/data/knowledge-base/file-detail/" + file.id)
|
||||
}
|
||||
>
|
||||
{file.name}
|
||||
</Button>
|
||||
),
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
fixed: "left" as const,
|
||||
},
|
||||
{
|
||||
title: "类型",
|
||||
dataIndex: "type",
|
||||
key: "type",
|
||||
filters: allFileTypes.map((type) => ({
|
||||
text: type,
|
||||
value: type,
|
||||
})),
|
||||
onFilter: (value: string, record: KBFile) => record.type === value,
|
||||
},
|
||||
{
|
||||
title: "大小",
|
||||
dataIndex: "size",
|
||||
key: "size",
|
||||
sorter: (a: KBFile, b: KBFile) => parseFloat(a.size) - parseFloat(b.size),
|
||||
sortOrder: fileSortOrder,
|
||||
},
|
||||
{
|
||||
title: "向量化状态",
|
||||
dataIndex: "vectorizationStatus",
|
||||
title: "状态",
|
||||
dataIndex: "status",
|
||||
key: "vectorizationStatus",
|
||||
filters: allVectorizationStatuses
|
||||
.filter((opt) => opt.value !== null)
|
||||
.map((opt) => ({
|
||||
text: opt.label,
|
||||
value: opt.value,
|
||||
})),
|
||||
onFilter: (value: string, record: KBFile) =>
|
||||
record.vectorizationStatus === value,
|
||||
render: (_: any, file: KBFile) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge
|
||||
status={getStatusBadgeVariant(
|
||||
file.vectorizationStatus || "pending"
|
||||
)}
|
||||
text={getStatusLabel(file.vectorizationStatus || "pending")}
|
||||
/>
|
||||
{file.vectorizationStatus === "processing" && (
|
||||
<div className="w-16">
|
||||
<Progress percent={file.progress} size="small" showInfo={false} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "来源",
|
||||
dataIndex: "source",
|
||||
key: "source",
|
||||
render: (_: any, file: KBFile) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge
|
||||
status={file.source === "upload" ? "processing" : "default"}
|
||||
text={file.source === "upload" ? "上传" : "数据集"}
|
||||
/>
|
||||
{file.datasetId && (
|
||||
<span className="text-xs text-gray-500">({file.datasetId})</span>
|
||||
)}
|
||||
</div>
|
||||
width: 120,
|
||||
render: (status: any) => (
|
||||
<Badge color={status?.color} text={status?.label} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "分块数",
|
||||
dataIndex: "chunkCount",
|
||||
key: "chunkCount",
|
||||
render: (chunkCount: number) => (
|
||||
<span className="font-medium text-gray-900">{chunkCount}</span>
|
||||
),
|
||||
width: 100,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: "上传时间",
|
||||
dataIndex: "uploadedAt",
|
||||
key: "uploadedAt",
|
||||
title: "创建时间",
|
||||
dataIndex: "createdAt",
|
||||
key: "createdAt",
|
||||
ellipsis: true,
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: "更新时间",
|
||||
dataIndex: "updatedAt",
|
||||
key: "updatedAt",
|
||||
ellipsis: true,
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "actions",
|
||||
align: "right" as const,
|
||||
width: 100,
|
||||
render: (_: any, file: KBFile) => (
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
label: "重试",
|
||||
key: "retry",
|
||||
onClick: () => handleStartVectorization(file.id),
|
||||
},
|
||||
{
|
||||
label: "删除",
|
||||
key: "delete",
|
||||
onClick: () => handleDeleteFile(file),
|
||||
},
|
||||
],
|
||||
}}
|
||||
>
|
||||
<MoreHorizontal />
|
||||
</Dropdown>
|
||||
<div>
|
||||
{fileOps.map((op) => (
|
||||
<Tooltip key={op.key} title={op.label}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={op.icon}
|
||||
danger={op?.danger}
|
||||
onClick={() => op.onClick(file)}
|
||||
/>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
{/* Breadcrumb */}
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>
|
||||
<a onClick={() => navigate("/data/knowledge-base")}>知识库</a>
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item>{knowledgeBase.name}</Breadcrumb.Item>
|
||||
<Breadcrumb.Item>{knowledgeBase?.name}</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* Knowledge Base Header */}
|
||||
<DetailHeader
|
||||
data={{
|
||||
icon:
|
||||
knowledgeBase.type === "structured" ? (
|
||||
<Database className="w-8 h-8" />
|
||||
) : (
|
||||
<BookOpen className="w-8 h-8" />
|
||||
),
|
||||
status: {
|
||||
label: getStatusLabel(knowledgeBase.status),
|
||||
icon: getStatusIcon(knowledgeBase.status),
|
||||
color: getStatusBadgeVariant(knowledgeBase.status),
|
||||
},
|
||||
name: knowledgeBase.name,
|
||||
description: knowledgeBase.description,
|
||||
createdAt: knowledgeBase.createdAt,
|
||||
lastUpdated: knowledgeBase.lastUpdated,
|
||||
}}
|
||||
statistics={[
|
||||
{
|
||||
icon: <File className="w-4 h-4 text-gray-400" />,
|
||||
label: "文件",
|
||||
value: knowledgeBase.fileCount,
|
||||
},
|
||||
{
|
||||
icon: <Layers className="w-4 h-4 text-gray-400" />,
|
||||
label: "分块",
|
||||
value: knowledgeBase.chunkCount?.toLocaleString?.() ?? 0,
|
||||
},
|
||||
{
|
||||
icon: <StarOff className="w-4 h-4 text-gray-400" />,
|
||||
label: "向量",
|
||||
value: knowledgeBase.vectorCount?.toLocaleString?.() ?? 0,
|
||||
},
|
||||
{
|
||||
icon: <Database className="w-4 h-4 text-gray-400" />,
|
||||
label: "大小",
|
||||
value: knowledgeBase.size,
|
||||
},
|
||||
]}
|
||||
operations={[
|
||||
{
|
||||
key: "edit",
|
||||
label: "修改参数配置",
|
||||
icon: <Edit className="w-4 h-4" />,
|
||||
onClick: () => {
|
||||
setEditForm(knowledgeBase);
|
||||
setCurrentView("config");
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "vector",
|
||||
label: "向量化管理",
|
||||
icon: <VectorSquareIcon className="w-4 h-4" />,
|
||||
onClick: () => setShowVectorizationDialog(true),
|
||||
},
|
||||
...(knowledgeBase.status === "error"
|
||||
? [
|
||||
{
|
||||
key: "retry",
|
||||
label: "重试",
|
||||
onClick: () => {}, // 填写重试逻辑
|
||||
danger: false,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: "more",
|
||||
label: "更多操作",
|
||||
icon: <MoreHorizontal className="w-4 h-4" />,
|
||||
isDropdown: true,
|
||||
items: [
|
||||
{
|
||||
key: "download",
|
||||
label: "导出",
|
||||
},
|
||||
{
|
||||
key: "settings",
|
||||
label: "配置",
|
||||
},
|
||||
{ type: "divider" },
|
||||
{
|
||||
key: "delete",
|
||||
label: "删除",
|
||||
danger: true,
|
||||
onClick: () => handleDeleteKB(knowledgeBase),
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
<DetailHeader
|
||||
data={knowledgeBase}
|
||||
statistics={knowledgeBase?.statistics || []}
|
||||
operations={operations}
|
||||
/>
|
||||
<CreateKnowledgeBase
|
||||
showBtn={false}
|
||||
isEdit={showEdit}
|
||||
data={knowledgeBase}
|
||||
onUpdate={handleRefreshPage}
|
||||
onClose={() => setShowEdit(false)}
|
||||
/>
|
||||
<div className="flex-1 border-card p-6 mt-4">
|
||||
<div className="flex items-center justify-between mb-4 gap-3">
|
||||
<div className="flex-1">
|
||||
<SearchControls
|
||||
searchTerm={searchParams.keyword}
|
||||
onSearchChange={(keyword) =>
|
||||
setSearchParams({ ...searchParams, keyword })
|
||||
}
|
||||
searchPlaceholder="搜索文件名..."
|
||||
filters={[]}
|
||||
onFiltersChange={handleFiltersChange}
|
||||
onClearFilters={() =>
|
||||
setSearchParams({ ...searchParams, filter: {} })
|
||||
}
|
||||
showViewToggle={false}
|
||||
showReload={false}
|
||||
/>
|
||||
</div>
|
||||
<AddDataDialog knowledgeBase={knowledgeBase} />
|
||||
</div>
|
||||
|
||||
<Table
|
||||
loading={loading}
|
||||
columns={fileColumns}
|
||||
dataSource={files}
|
||||
rowKey="id"
|
||||
pagination={pagination}
|
||||
scroll={{ y: "calc(100vh - 30rem)" }}
|
||||
/>
|
||||
{/* Tab Navigation */}
|
||||
<Card>
|
||||
{/* Files Section */}
|
||||
<div className="flex items-center justify-between mb-4 gap-4">
|
||||
<div className="flex-1">
|
||||
<SearchControls
|
||||
searchTerm={fileSearchQuery}
|
||||
onSearchChange={setFileSearchQuery}
|
||||
searchPlaceholder="搜索文件名..."
|
||||
filters={[
|
||||
{
|
||||
key: "status",
|
||||
label: "状态筛选",
|
||||
options: [
|
||||
{ label: "全部状态", value: "all" },
|
||||
{ label: "已完成", value: "completed" },
|
||||
{ label: "处理中", value: "processing" },
|
||||
{ label: "向量化中", value: "vectorizing" },
|
||||
{ label: "错误", value: "error" },
|
||||
{ label: "已禁用", value: "disabled" },
|
||||
],
|
||||
},
|
||||
]}
|
||||
onFiltersChange={(filters) => {
|
||||
setFileStatusFilter(filters.status?.[0] || "all");
|
||||
}}
|
||||
showViewToggle={false}
|
||||
/>
|
||||
</div>
|
||||
<Button type="primary">
|
||||
<Plus className="w-4 h-4 mr-1" />
|
||||
添加文件
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Files Table */}
|
||||
<Table
|
||||
columns={fileColumns}
|
||||
dataSource={files}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
locale={{
|
||||
emptyText: (
|
||||
<div className="text-center py-12">
|
||||
<File className="w-12 h-12 mx-auto mb-4 text-gray-300" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
||||
没有找到文件
|
||||
</h3>
|
||||
<p className="text-gray-500 mb-4">
|
||||
尝试调整搜索条件或添加新文件
|
||||
</p>
|
||||
<Button type="dashed">
|
||||
<Upload className="w-4 h-4 mr-2" />
|
||||
添加文件
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
{/* Vectorization Dialog */}
|
||||
<Modal
|
||||
open={showVectorizationDialog}
|
||||
onCancel={() => setShowVectorizationDialog(false)}
|
||||
footer={null}
|
||||
title="向量化管理"
|
||||
width={700}
|
||||
destroyOnClose
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Card className="p-4">
|
||||
<h4 className="font-medium mb-2">当前状态</h4>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span>已向量化文件:</span>
|
||||
<span>
|
||||
{
|
||||
knowledgeBase.files.filter(
|
||||
(f) => f.vectorizationStatus === "completed"
|
||||
).length
|
||||
}
|
||||
/{knowledgeBase.files.length}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>向量总数:</span>
|
||||
<span>
|
||||
{knowledgeBase.vectorCount?.toLocaleString?.() ?? 0}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>存储大小:</span>
|
||||
<span>{knowledgeBase.size}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="p-4">
|
||||
<h4 className="font-medium mb-2">操作选项</h4>
|
||||
<div className="space-y-2">
|
||||
<Button
|
||||
className="w-full"
|
||||
type="primary"
|
||||
onClick={() => handleStartVectorization()}
|
||||
>
|
||||
<Zap className="w-4 h-4 mr-2" />
|
||||
批量向量化
|
||||
</Button>
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={() => message.info("TODO: 重新向量化全部")}
|
||||
>
|
||||
<RefreshCw className="w-4 h-4 mr-2" />
|
||||
重新向量化全部
|
||||
</Button>
|
||||
<Button
|
||||
className="w-full"
|
||||
danger
|
||||
onClick={() => message.info("TODO: 清空向量数据")}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 mr-2" />
|
||||
清空向量数据
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-medium mb-3">文件向量化状态</h4>
|
||||
<div className="space-y-2 max-h-60 overflow-y-auto">
|
||||
{knowledgeBase.files.map((file: KBFile) => (
|
||||
<div
|
||||
key={file.id}
|
||||
className="flex items-center justify-between p-3 border rounded-lg"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<File className="w-4 h-4 text-gray-400" />
|
||||
<div>
|
||||
<p className="font-medium text-sm">{file.name}</p>
|
||||
<p className="text-xs text-gray-500">
|
||||
{file.chunkCount} 个分块
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge
|
||||
status={getStatusBadgeVariant(
|
||||
file.vectorizationStatus || "pending"
|
||||
)}
|
||||
text={getStatusLabel(
|
||||
file.vectorizationStatus || "pending"
|
||||
)}
|
||||
/>
|
||||
{file.vectorizationStatus === "processing" && (
|
||||
<div className="w-16">
|
||||
<Progress
|
||||
percent={file.progress}
|
||||
size="small"
|
||||
showInfo={false}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{file.vectorizationStatus !== "completed" && (
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => handleStartVectorization(file.id)}
|
||||
>
|
||||
<StarOff className="w-3 h-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end pt-2">
|
||||
<Button onClick={() => setShowVectorizationDialog(false)}>
|
||||
关闭
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
{/* Edit File Dialog */}
|
||||
<Modal
|
||||
open={!!showEditFileDialog}
|
||||
onCancel={() => setShowEditFileDialog(null)}
|
||||
title="编辑文件"
|
||||
width={600}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={() => setShowEditFileDialog(null)}>
|
||||
取消
|
||||
</Button>,
|
||||
<Button
|
||||
key="save"
|
||||
type="primary"
|
||||
onClick={() => setShowEditFileDialog(null)}
|
||||
>
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
保存更改
|
||||
</Button>,
|
||||
]}
|
||||
destroyOnClose
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block mb-1">文件名</label>
|
||||
<Input value={showEditFileDialog?.name} readOnly />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block mb-1">文件来源</label>
|
||||
<Input
|
||||
value={
|
||||
showEditFileDialog?.source === "upload"
|
||||
? "上传文件"
|
||||
: "数据集文件"
|
||||
}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showEditFileDialog?.source === "upload" ? (
|
||||
<div className="space-y-3">
|
||||
<label className="block mb-1">更新文件</label>
|
||||
<div className="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center">
|
||||
<Upload className="w-8 h-8 mx-auto mb-2 text-gray-400" />
|
||||
<p className="text-sm text-gray-600">
|
||||
拖拽或点击上传新版本文件
|
||||
</p>
|
||||
<Button className="mt-2 bg-transparent" disabled>
|
||||
选择文件
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
<label className="block mb-1">数据集文件管理</label>
|
||||
<div className="p-4 border rounded-lg bg-gray-50">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium">
|
||||
当前数据集: {showEditFileDialog?.datasetId}
|
||||
</span>
|
||||
<Button size="small">
|
||||
<RefreshCw className="w-4 h-4 mr-1" />
|
||||
更新数据集文件
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-600">
|
||||
此文件来自数据集,可以选择更新数据集中的对应文件或切换到其他数据集文件
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-3">
|
||||
<label className="block mb-1">处理选项</label>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="reprocess" />
|
||||
<span className="text-sm">更新后重新处理分块</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="revectorize" />
|
||||
<span className="text-sm">重新生成向量</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user