add knowledgebase page (#39)

* 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.
This commit is contained in:
chenghh-9609
2025-10-30 21:33:33 +08:00
committed by GitHub
parent ba6caabbc6
commit d89811f238
16 changed files with 1119 additions and 1166 deletions

View File

@@ -0,0 +1,630 @@
import type React from "react";
import { useEffect, useState } from "react";
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";
import DetailHeader from "@/components/DetailHeader";
import { SearchControls } from "@/components/SearchControls";
import { KnowledgeBaseItem } from "../knowledge-base.model";
const KnowledgeBaseDetailPage: React.FC = () => {
const navigate = useNavigate();
const [knowledgeBase, setKnowledgeBase] = useState<KnowledgeBaseItem>(null);
const [files, setFiles] = useState([]);
// 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";
}
}
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 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>
),
},
{
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",
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>
),
},
{
title: "分块数",
dataIndex: "chunkCount",
key: "chunkCount",
render: (chunkCount: number) => (
<span className="font-medium text-gray-900">{chunkCount}</span>
),
},
{
title: "上传时间",
dataIndex: "uploadedAt",
key: "uploadedAt",
},
{
title: "操作",
key: "actions",
align: "right" as const,
render: (_: any, file: KBFile) => (
<Dropdown
menu={{
items: [
{
label: "重试",
key: "retry",
onClick: () => handleStartVectorization(file.id),
},
{
label: "删除",
key: "delete",
onClick: () => handleDeleteFile(file),
},
],
}}
>
<MoreHorizontal />
</Dropdown>
),
},
];
return (
<div className="">
{/* Breadcrumb */}
<div className="mb-4">
<Breadcrumb>
<Breadcrumb.Item>
<a onClick={() => navigate("/data/knowledge-base")}></a>
</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),
},
],
},
]}
/>
{/* 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>
);
};
export default KnowledgeBaseDetailPage;