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,685 @@
import React, { useState } from "react";
import {
Plus,
Eye,
Edit,
Clock,
Trash2,
Scissors,
VideoIcon as Vector,
Server,
FileText,
Download,
} from "lucide-react";
import { Card, Button, Badge, Input, Tabs, Modal, Breadcrumb, Tag } from "antd";
import { mockChunks, mockQAPairs, sliceOperators } from "@/mock/knowledgeBase";
import type {
KnowledgeBase,
KBFile,
} from "@/pages/KnowledgeBase/knowledge-base.model";
import { Link, useNavigate } from "react-router";
import DetailHeader from "@/components/DetailHeader";
import DevelopmentInProgress from "@/components/DevelopmentInProgress";
// 状态标签
const getStatusLabel = (status: string) => {
const labels: Record<string, string> = {
ready: "就绪",
processing: "处理中",
vectorizing: "向量化中",
importing: "导入中",
error: "错误",
disabled: "已禁用",
completed: "已完成",
};
return labels[status] || status;
};
const getStatusColor = (status: string) => {
const colors: Record<string, string> = {
ready: "green",
processing: "blue",
vectorizing: "purple",
importing: "orange",
error: "blue",
disabled: "gray",
completed: "green",
};
return colors[status] || "default";
};
const KnowledgeBaseFileDetail: React.FC = () => {
return <DevelopmentInProgress showTime="2025.10.30" />;
const navigate = useNavigate();
// 假设通过 props 或路由参数获取 selectedFile/selectedKB
const [selectedFile] = useState<KBFile>(
mockChunks.length
? {
id: 1,
name: "API文档.pdf",
type: "pdf",
size: "2.5 MB",
status: "completed",
chunkCount: mockChunks.length,
progress: 100,
uploadedAt: "2024-01-22 10:30",
source: "upload",
vectorizationStatus: "completed",
}
: ({} as KBFile)
);
const [selectedKB] = useState<KnowledgeBase>({
id: 1,
name: "API知识库",
description: "",
type: "unstructured",
status: "ready",
fileCount: 1,
chunkCount: mockChunks.length,
vectorCount: mockChunks.length,
size: "2.5 MB",
progress: 100,
createdAt: "2024-01-22",
lastUpdated: "2024-01-22",
vectorDatabase: "pinecone",
config: {
embeddingModel: "text-embedding-3-large",
chunkSize: 512,
overlap: 50,
sliceMethod: "semantic",
enableQA: true,
vectorDimension: 1536,
sliceOperators: ["semantic-split", "paragraph-split"],
},
files: [],
vectorizationHistory: [],
});
const [currentChunkPage, setCurrentChunkPage] = useState(1);
const chunksPerPage = 5;
const totalPages = Math.ceil(mockChunks.length / chunksPerPage);
const startIndex = (currentChunkPage - 1) * chunksPerPage;
const currentChunks = mockChunks.slice(
startIndex,
startIndex + chunksPerPage
);
const [editingChunk, setEditingChunk] = useState<number | null>(null);
const [editChunkContent, setEditChunkContent] = useState("");
const [chunkDetailModal, setChunkDetailModal] = useState<number | null>(null);
const [showSliceTraceDialog, setShowSliceTraceDialog] = useState<
number | null
>(null);
const handleEditChunk = (chunkId: number, content: string) => {
setEditingChunk(chunkId);
setEditChunkContent(content);
};
const handleSaveChunk = (chunkId: number) => {
// 实际保存逻辑
setEditingChunk(null);
setEditChunkContent("");
};
const handleDeleteChunk = (chunkId: number) => {
// 实际删除逻辑
setEditingChunk(null);
setEditChunkContent("");
};
const handleViewChunkDetail = (chunkId: number) => {
setChunkDetailModal(chunkId);
};
const renderChunks = () => (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="text-sm text-gray-600">
{mockChunks.length} {startIndex + 1}-
{Math.min(startIndex + chunksPerPage, mockChunks.length)}
</div>
<div className="flex items-center gap-2">
<Button
size="small"
onClick={() =>
setCurrentChunkPage(Math.max(1, currentChunkPage - 1))
}
disabled={currentChunkPage === 1}
>
</Button>
<span className="text-sm text-gray-600">
{currentChunkPage} / {totalPages}
</span>
<Button
size="small"
onClick={() =>
setCurrentChunkPage(Math.min(totalPages, currentChunkPage + 1))
}
disabled={currentChunkPage === totalPages}
>
</Button>
</div>
</div>
<div className="space-y-4">
{currentChunks.map((chunk) => (
<Card key={chunk.id} className="p-4">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<div className="flex-1 flex items-center gap-2">
<h4 className="text-sm font-semibold"> {chunk.id}</h4>
<Tag className="text-xs">
{sliceOperators.find(
(op) => op.id === chunk.sliceOperator
)?.name || chunk.sliceOperator}
</Tag>
</div>
<div className="flex items-center gap-2 ml-4">
{editingChunk === chunk.id ? (
<>
<Button
type="primary"
size="small"
onClick={() => handleSaveChunk(chunk.id)}
>
</Button>
<Button
size="small"
onClick={() => {
setEditingChunk(null);
setEditChunkContent("");
}}
>
</Button>
</>
) : (
<>
<Button
size="small"
onClick={() => handleViewChunkDetail(chunk.id)}
>
<Eye className="w-4 h-4" />
</Button>
<Button
size="small"
onClick={() =>
handleEditChunk(chunk.id, chunk.content)
}
>
<Edit className="w-4 h-4" />
</Button>
<Button
size="small"
danger
onClick={() => handleDeleteChunk(chunk.id)}
>
<Trash2 className="w-4 h-4" />
</Button>
</>
)}
</div>
</div>
<div className="text-sm leading-relaxed text-gray-700">
{editingChunk === chunk.id ? (
<Input.TextArea
value={editChunkContent}
onChange={(e) => setEditChunkContent(e.target.value)}
rows={3}
/>
) : (
chunk.content
)}
</div>
<div className="flex items-center gap-4 mt-2 text-xs text-gray-500">
<span>: {chunk.position}</span>
<span>Token: {chunk.tokens}</span>
{chunk.metadata?.page && (
<span>: {chunk.metadata.page}</span>
)}
{chunk.metadata?.section && (
<span>: {chunk.metadata.section}</span>
)}
</div>
</div>
</div>
</Card>
))}
</div>
</div>
);
return (
<div className="flex flex-col gap-4">
<Breadcrumb
items={[
{
title: <Link to="/data/knowledge-base"></Link>,
},
{
title: (
<Link to="/data/knowledge-base/detail/1">
{selectedKB?.name}
</Link>
),
},
{
title: selectedFile.name,
},
]}
/>
<DetailHeader
data={{
id: selectedFile.id,
icon: <FileText className="w-8 h-8" />,
iconColor: "bg-blue-500 text-blue-600",
status: {
label: getStatusLabel(selectedFile.status),
color: getStatusColor(selectedFile.status),
},
name: selectedFile.name,
description: `${selectedFile.size}${
selectedFile.chunkCount
} 个分块${
selectedFile.source === "dataset"
? ` • 数据集: ${selectedFile.datasetId}`
: ""
}`,
createdAt: selectedFile.uploadedAt,
lastUpdated: selectedFile.uploadedAt,
}}
statistics={[
{
icon: <Scissors className="w-4 h-4 text-blue-500" />,
label: "分块",
value: selectedFile.chunkCount,
},
{
icon: <Vector className="w-4 h-4 text-purple-500" />,
label: "向量化状态",
value: getStatusLabel(
selectedFile.vectorizationStatus || "pending"
),
},
{
icon: <Server className="w-4 h-4 text-green-500" />,
label: "文件大小",
value: selectedFile.size,
},
{
icon: <Clock className="w-4 h-4 text-gray-500" />,
label: "上传时间",
value: selectedFile.uploadedAt,
},
]}
operations={[
{
key: "download",
label: "下载",
icon: <Download className="w-4 h-4" />,
onClick: () => {
// 下载逻辑
},
},
{
key: "delete",
label: "删除",
icon: <Trash2 className="w-4 h-4" />,
danger: true,
onClick: () => {
// 删除逻辑
},
},
]}
/>
<Card>{renderChunks()}</Card>
{/* Slice Trace Modal */}
<Modal
open={!!showSliceTraceDialog}
onCancel={() => setShowSliceTraceDialog(null)}
footer={null}
title="知识切片回溯"
width={800}
destroyOnClose
>
<div className="space-y-4">
<div className="bg-gray-50 rounded-lg p-4">
<h4 className="font-medium mb-3"></h4>
<div className="space-y-3">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-blue-500 text-white rounded-full flex items-center justify-center text-sm font-medium">
1
</div>
<div className="flex-1">
<p className="font-medium"></p>
<p className="text-sm text-gray-600">
: {selectedFile.name}
</p>
</div>
<Badge></Badge>
</div>
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-green-500 text-white rounded-full flex items-center justify-center text-sm font-medium">
2
</div>
<div className="flex-1">
<p className="font-medium"></p>
<p className="text-sm text-gray-600">
阈值: 0.7
</p>
</div>
<Badge></Badge>
</div>
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-green-500 text-white rounded-full flex items-center justify-center text-sm font-medium">
3
</div>
<div className="flex-1">
<p className="font-medium"></p>
<p className="text-sm text-gray-600"></p>
</div>
<Badge></Badge>
</div>
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-purple-500 text-white rounded-full flex items-center justify-center text-sm font-medium">
4
</div>
<div className="flex-1">
<p className="font-medium"></p>
<p className="text-sm text-gray-600">
使 {selectedKB?.config.embeddingModel}
</p>
</div>
<Badge>
{selectedFile.vectorizationStatus === "completed"
? "完成"
: "处理中"}
</Badge>
</div>
</div>
</div>
<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 className="text-gray-600">ID:</span>
<span>{showSliceTraceDialog}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span>
{mockChunks.find((c) => c.id === showSliceTraceDialog)
?.parentChunkId || "无"}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Token数:</span>
<span>
{
mockChunks.find((c) => c.id === showSliceTraceDialog)
?.tokens
}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span>
{
mockChunks.find((c) => c.id === showSliceTraceDialog)
?.createdAt
}
</span>
</div>
</div>
</Card>
<Card className="p-4">
<h4 className="font-medium mb-2"></h4>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-600">ID:</span>
<span className="font-mono text-xs">
{
mockChunks.find((c) => c.id === showSliceTraceDialog)
?.vectorId
}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span>{selectedKB?.config.vectorDimension}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span>
{
mockChunks.find((c) => c.id === showSliceTraceDialog)
?.similarity
}
</span>
</div>
</div>
</Card>
</div>
</div>
</Modal>
{/* Chunk Detail Modal */}
<Modal
open={!!chunkDetailModal}
onCancel={() => setChunkDetailModal(null)}
footer={null}
title={`分块详细信息 - 分块 ${chunkDetailModal}`}
width={900}
destroyOnClose
>
<Tabs
defaultActiveKey="content"
items={[
{
key: "content",
label: "内容详情",
children: (
<div>
<div className="font-medium mb-1"></div>
<Input.TextArea
value={
mockChunks.find((c) => c.id === chunkDetailModal)
?.content || ""
}
rows={8}
readOnly
className="mt-2"
/>
</div>
),
},
{
key: "metadata",
label: "元数据",
children: (
<div className="grid grid-cols-2 gap-4">
<div>
<div className="font-medium mb-1"></div>
<Input
value={
mockChunks.find((c) => c.id === chunkDetailModal)
?.position || ""
}
readOnly
/>
</div>
<div>
<div className="font-medium mb-1">Token数量</div>
<Input
value={
mockChunks.find((c) => c.id === chunkDetailModal)
?.tokens || ""
}
readOnly
/>
</div>
<div>
<div className="font-medium mb-1"></div>
<Input
value={
mockChunks.find((c) => c.id === chunkDetailModal)
?.similarity || ""
}
readOnly
/>
</div>
<div>
<div className="font-medium mb-1"></div>
<Input
value={
mockChunks.find((c) => c.id === chunkDetailModal)
?.embedding?.length || ""
}
readOnly
/>
</div>
<div>
<div className="font-medium mb-1"></div>
<Input
value={
mockChunks.find((c) => c.id === chunkDetailModal)
?.createdAt || ""
}
readOnly
/>
</div>
<div>
<div className="font-medium mb-1"></div>
<Input
value={
mockChunks.find((c) => c.id === chunkDetailModal)
?.updatedAt || ""
}
readOnly
/>
</div>
<div>
<div className="font-medium mb-1">ID</div>
<Input
value={
mockChunks.find((c) => c.id === chunkDetailModal)
?.vectorId || ""
}
readOnly
/>
</div>
<div>
<div className="font-medium mb-1"></div>
<Input
value={
mockChunks.find((c) => c.id === chunkDetailModal)
?.sliceOperator || ""
}
readOnly
/>
</div>
</div>
),
},
{
key: "qa",
label: "Q&A对",
children: (
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="font-medium"></span>
<Button size="small">
<Plus className="w-4 h-4 mr-1" />
Q&A
</Button>
</div>
{mockQAPairs.map((qa) => (
<Card key={qa.id} className="p-4">
<div className="space-y-2">
<div>
<span className="text-sm font-medium text-blue-600">
{qa.id}
</span>
<p className="text-sm mt-1">{qa.question}</p>
</div>
<div>
<span className="text-sm font-medium text-green-600">
</span>
<p className="text-sm mt-1">{qa.answer}</p>
</div>
<div className="flex justify-end gap-2">
<Button type="text" size="small">
<Edit className="w-3 h-3 mr-1" />
</Button>
<Button type="text" size="small" danger>
<Trash2 className="w-3 h-3 mr-1" />
</Button>
</div>
</div>
</Card>
))}
</div>
),
},
{
key: "trace",
label: "切片回溯",
children: (
<div className="space-y-3">
<div className="flex items-center gap-3 p-3 bg-blue-50 rounded-lg">
<FileText className="w-5 h-5 text-blue-600" />
<div className="flex-1">
<p className="font-medium"></p>
<p className="text-sm text-gray-600">
{selectedFile.name}
</p>
</div>
</div>
<div className="flex items-center gap-3 p-3 bg-green-50 rounded-lg">
<Scissors className="w-5 h-5 text-green-600" />
<div className="flex-1">
<p className="font-medium"></p>
<p className="text-sm text-gray-600">
:{" "}
{
sliceOperators.find(
(op) =>
op.id ===
mockChunks.find((c) => c.id === chunkDetailModal)
?.sliceOperator
)?.name
}
</p>
</div>
</div>
<div className="flex items-center gap-3 p-3 bg-purple-50 rounded-lg">
<Vector className="w-5 h-5 text-purple-600" />
<div className="flex-1">
<p className="font-medium"></p>
<p className="text-sm text-gray-600">
{selectedKB?.config.vectorDimension}
</p>
</div>
</div>
</div>
),
},
]}
/>
</Modal>
</div>
);
};
export default KnowledgeBaseFileDetail;