You've already forked DataMate
feat: Update site name to DataMate and refine text for AI data processing (#48)
* refactor: remove unused components and clean up API logging in KnowledgeBase
This commit is contained in:
@@ -1,582 +0,0 @@
|
|||||||
import {
|
|
||||||
sliceOperators,
|
|
||||||
vectorDatabases,
|
|
||||||
} from "@/mock/knowledgeBase";
|
|
||||||
import { useState } from "react";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
Input,
|
|
||||||
Select,
|
|
||||||
Checkbox,
|
|
||||||
Switch,
|
|
||||||
Tabs,
|
|
||||||
Divider,
|
|
||||||
Upload,
|
|
||||||
message,
|
|
||||||
Form,
|
|
||||||
} from "antd";
|
|
||||||
import {
|
|
||||||
BookOpen,
|
|
||||||
Database,
|
|
||||||
Brain,
|
|
||||||
Scissors,
|
|
||||||
Split,
|
|
||||||
Upload as UploadIcon,
|
|
||||||
Folder,
|
|
||||||
CheckCircle,
|
|
||||||
File,
|
|
||||||
ArrowLeft,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { useNavigate } from "react-router";
|
|
||||||
import type { Dataset } from "@/pages/DataManagement/dataset.model";
|
|
||||||
import RadioCard from "@/components/RadioCard";
|
|
||||||
import { KBTypeMap } from "../knowledge-base.const";
|
|
||||||
import { KBType, KnowledgeBaseItem } from "../knowledge-base.model";
|
|
||||||
import { createKnowledgeBaseUsingPost } from "../knowledge-base.api";
|
|
||||||
|
|
||||||
const { TextArea } = Input;
|
|
||||||
const { Option } = Select;
|
|
||||||
|
|
||||||
const KnowledgeBaseCreatePage: React.FC = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const [datasetSearchQuery, setDatasetSearchQuery] = useState("");
|
|
||||||
const [selectedDatasetId, setSelectedDatasetId] = useState<string | null>(
|
|
||||||
null
|
|
||||||
);
|
|
||||||
const [uploadedFiles, setUploadedFiles] = useState<File[]>([]);
|
|
||||||
const [datasets, setDatasets] = useState<Dataset[]>([]);
|
|
||||||
const [selectedDatasetFiles, setSelectedDatasetFiles] = useState<
|
|
||||||
{
|
|
||||||
datasetId: string;
|
|
||||||
fileId: string;
|
|
||||||
name: string;
|
|
||||||
size: string;
|
|
||||||
type: string;
|
|
||||||
}[]
|
|
||||||
>([]);
|
|
||||||
const [selectedSliceOperators, setSelectedSliceOperators] = useState<
|
|
||||||
string[]
|
|
||||||
>(["semantic-split", "paragraph-split"]);
|
|
||||||
|
|
||||||
// Form initial values
|
|
||||||
const [newKB, setNewKB] = useState<Partial<KnowledgeBaseItem>>({
|
|
||||||
name: "",
|
|
||||||
description: "",
|
|
||||||
type: KBType.STRUCTURED,
|
|
||||||
embeddingModel: "text-embedding-3-large",
|
|
||||||
llmModel: "gpt-4o",
|
|
||||||
chunkSize: 512,
|
|
||||||
overlap: 50,
|
|
||||||
sliceMethod: "semantic" as
|
|
||||||
| "paragraph"
|
|
||||||
| "length"
|
|
||||||
| "delimiter"
|
|
||||||
| "semantic",
|
|
||||||
delimiter: "",
|
|
||||||
enableQA: true,
|
|
||||||
vectorDatabase: "pinecone",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Dataset file selection helpers
|
|
||||||
const handleDatasetFileToggle = (
|
|
||||||
datasetId: string,
|
|
||||||
file: MockDataset["files"][0]
|
|
||||||
) => {
|
|
||||||
setSelectedDatasetFiles((prev) => {
|
|
||||||
const isSelected = prev.some(
|
|
||||||
(f) => f.datasetId === datasetId && f.fileId === file.id
|
|
||||||
);
|
|
||||||
if (isSelected) {
|
|
||||||
return prev.filter(
|
|
||||||
(f) => !(f.datasetId === datasetId && f.fileId === file.id)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return [...prev, { datasetId, ...file }];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelectAllDatasetFiles = (
|
|
||||||
dataset: MockDataset,
|
|
||||||
checked: boolean
|
|
||||||
) => {
|
|
||||||
setSelectedDatasetFiles((prev) => {
|
|
||||||
let newSelectedFiles = [...prev];
|
|
||||||
if (checked) {
|
|
||||||
dataset.files.forEach((file) => {
|
|
||||||
if (
|
|
||||||
!newSelectedFiles.some(
|
|
||||||
(f) => f.datasetId === dataset.id && f.fileId === file.id
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
newSelectedFiles.push({ datasetId: dataset.id, ...file });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
newSelectedFiles = newSelectedFiles.filter(
|
|
||||||
(f) => f.datasetId !== dataset.id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return newSelectedFiles;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const isDatasetFileSelected = (datasetId: string, fileId: string) => {
|
|
||||||
return selectedDatasetFiles.some(
|
|
||||||
(f) => f.datasetId === datasetId && f.fileId === fileId
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isAllDatasetFilesSelected = (dataset: MockDataset) => {
|
|
||||||
return dataset.files.every((file) =>
|
|
||||||
isDatasetFileSelected(dataset.id, file.id)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSliceOperatorToggle = (operatorId: string) => {
|
|
||||||
setSelectedSliceOperators((prev) =>
|
|
||||||
prev.includes(operatorId)
|
|
||||||
? prev.filter((id) => id !== operatorId)
|
|
||||||
: [...prev, operatorId]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 文件上传
|
|
||||||
const handleFileChange = (info: any) => {
|
|
||||||
setUploadedFiles(info.fileList.map((f: any) => f.originFileObj));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 提交表单
|
|
||||||
const handleCreateKnowledgeBase = async (values: any) => {
|
|
||||||
await createKnowledgeBaseUsingPost(values);
|
|
||||||
message.success("知识库创建成功!");
|
|
||||||
navigate("/data/knowledge-base");
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="h-full flex flex-col gap-4">
|
|
||||||
{/* Header */}
|
|
||||||
<div className="flex items-center justify-between mb-2">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
onClick={() => navigate("/data/knowledge-base")}
|
|
||||||
>
|
|
||||||
<ArrowLeft className="w-4 h-4 mr-1" />
|
|
||||||
</Button>
|
|
||||||
<h1 className="text-xl font-bold bg-clip-text">创建知识库</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="border-card flex-overflow-auto">
|
|
||||||
<div className="overflow-auto p-6">
|
|
||||||
<Form
|
|
||||||
form={form}
|
|
||||||
layout="vertical"
|
|
||||||
initialValues={newKB}
|
|
||||||
onValuesChange={(_, allValues) => setNewKB(allValues)}
|
|
||||||
>
|
|
||||||
{/* 基本信息 */}
|
|
||||||
<h2 className="font-medium text-gray-900 text-lg mb-2">基本信息</h2>
|
|
||||||
<Form.Item
|
|
||||||
label="知识库名称"
|
|
||||||
name="name"
|
|
||||||
required
|
|
||||||
rules={[{ required: true, message: "请输入知识库名称" }]}
|
|
||||||
>
|
|
||||||
<Input placeholder="输入知识库名称" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="描述" name="description">
|
|
||||||
<TextArea placeholder="描述知识库的用途和内容" rows={3} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="知识库类型" name="type" required>
|
|
||||||
<RadioCard
|
|
||||||
options={Object.values(KBTypeMap)}
|
|
||||||
value={newKB.type}
|
|
||||||
onChange={(value) => setNewKB({ ...newKB, type: value })}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
{/* 模型配置 */}
|
|
||||||
<h2 className="font-medium text-gray-900 text-lg mb-2 flex items-center gap-2">
|
|
||||||
<Brain className="w-5 h-5" />
|
|
||||||
模型配置
|
|
||||||
</h2>
|
|
||||||
<Form.Item label="嵌入模型" name="embeddingModel">
|
|
||||||
<Select>
|
|
||||||
<Option value="text-embedding-3-large">
|
|
||||||
text-embedding-3-large (推荐)
|
|
||||||
</Option>
|
|
||||||
<Option value="text-embedding-3-small">
|
|
||||||
text-embedding-3-small
|
|
||||||
</Option>
|
|
||||||
<Option value="text-embedding-ada-002">
|
|
||||||
text-embedding-ada-002
|
|
||||||
</Option>
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
shouldUpdate={(prev, curr) =>
|
|
||||||
prev.type !== curr.type || prev.enableQA !== curr.enableQA
|
|
||||||
}
|
|
||||||
noStyle
|
|
||||||
>
|
|
||||||
{() =>
|
|
||||||
form.getFieldValue("type") === "unstructured" &&
|
|
||||||
form.getFieldValue("enableQA") && (
|
|
||||||
<Form.Item label="LLM模型 (用于Q&A生成)" name="llmModel">
|
|
||||||
<Select>
|
|
||||||
<Option value="gpt-4o">GPT-4o (推荐)</Option>
|
|
||||||
<Option value="gpt-4o-mini">GPT-4o Mini</Option>
|
|
||||||
<Option value="gpt-3.5-turbo">GPT-3.5 Turbo</Option>
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="向量数据库" name="vectorDatabase">
|
|
||||||
<Select>
|
|
||||||
{vectorDatabases.map((db) => (
|
|
||||||
<Option key={db.id} value={db.id}>
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<span className="font-medium">{db.name}</span>
|
|
||||||
<span className="text-xs text-gray-500">
|
|
||||||
{db.description}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
{/* 切片算子配置 */}
|
|
||||||
<Form.Item
|
|
||||||
shouldUpdate={(prev, curr) => prev.type !== curr.type}
|
|
||||||
noStyle
|
|
||||||
>
|
|
||||||
{() =>
|
|
||||||
form.getFieldValue("type") === "unstructured" && (
|
|
||||||
<>
|
|
||||||
<h2 className="font-medium text-gray-900 text-lg mb-2 flex items-center gap-2">
|
|
||||||
<Scissors className="w-5 h-5" />
|
|
||||||
切片算子配置
|
|
||||||
</h2>
|
|
||||||
<div className="grid grid-cols-2 gap-3 mb-4">
|
|
||||||
{sliceOperators.map((operator) => (
|
|
||||||
<div
|
|
||||||
key={operator.id}
|
|
||||||
className={`border rounded-lg p-3 cursor-pointer transition-all ${
|
|
||||||
selectedSliceOperators.includes(operator.id)
|
|
||||||
? "border-blue-500 bg-blue-50"
|
|
||||||
: "border-gray-200 hover:border-gray-300"
|
|
||||||
}`}
|
|
||||||
onClick={() => handleSliceOperatorToggle(operator.id)}
|
|
||||||
>
|
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<Checkbox
|
|
||||||
checked={selectedSliceOperators.includes(
|
|
||||||
operator.id
|
|
||||||
)}
|
|
||||||
onChange={() =>
|
|
||||||
handleSliceOperatorToggle(operator.id)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="flex items-center gap-2 mb-1">
|
|
||||||
<span className="text-lg">{operator.icon}</span>
|
|
||||||
<span className="font-medium text-sm">
|
|
||||||
{operator.name}
|
|
||||||
</span>
|
|
||||||
<span className="ant-badge text-xs">
|
|
||||||
{operator.type}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-gray-600">
|
|
||||||
{operator.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
{/* 文档分割配置 */}
|
|
||||||
<h2 className="font-medium text-gray-900 text-lg mb-2 flex items-center gap-2">
|
|
||||||
<Split className="w-5 h-5" />
|
|
||||||
文档分割配置
|
|
||||||
</h2>
|
|
||||||
<Form.Item label="分割方式" name="sliceMethod">
|
|
||||||
<Select>
|
|
||||||
<Option value="semantic">语义分割 (推荐)</Option>
|
|
||||||
<Option value="paragraph">段落分割</Option>
|
|
||||||
<Option value="length">长度分割</Option>
|
|
||||||
<Option value="delimiter">分隔符分割</Option>
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
noStyle
|
|
||||||
shouldUpdate={(prev, curr) =>
|
|
||||||
prev.sliceMethod !== curr.sliceMethod
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{() =>
|
|
||||||
form.getFieldValue("sliceMethod") === "delimiter" && (
|
|
||||||
<Form.Item
|
|
||||||
label="分隔符"
|
|
||||||
name="delimiter"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: "请输入分隔符",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input placeholder="输入分隔符,如 \\n\\n" />
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</Form.Item>
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
|
||||||
<Form.Item
|
|
||||||
label="分块大小"
|
|
||||||
name="chunkSize"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: "请输入分块大小",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input type="number" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="重叠长度"
|
|
||||||
name="overlap"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: "请输入重叠长度",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input type="number" />
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
<Form.Item
|
|
||||||
label="启用Q&A生成"
|
|
||||||
name="enableQA"
|
|
||||||
valuePropName="checked"
|
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
<Divider />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
{/* 数据源选择 */}
|
|
||||||
<h2 className="font-medium text-gray-900 text-lg mb-2 flex items-center gap-2">
|
|
||||||
<UploadIcon className="w-5 h-5" />
|
|
||||||
{form.getFieldValue("type") === "structured"
|
|
||||||
? "导入模板文件"
|
|
||||||
: "选择数据源"}
|
|
||||||
</h2>
|
|
||||||
<Tabs
|
|
||||||
defaultActiveKey="upload"
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
key: "upload",
|
|
||||||
label: "上传文件",
|
|
||||||
children: (
|
|
||||||
<div className="space-y-3">
|
|
||||||
<Upload
|
|
||||||
multiple
|
|
||||||
beforeUpload={() => false}
|
|
||||||
onChange={handleFileChange}
|
|
||||||
fileList={uploadedFiles.map((file, idx) => ({
|
|
||||||
uid: String(idx),
|
|
||||||
name: file.name,
|
|
||||||
status: "done",
|
|
||||||
originFileObj: file,
|
|
||||||
}))}
|
|
||||||
showUploadList={false}
|
|
||||||
>
|
|
||||||
<div className="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center relative cursor-pointer">
|
|
||||||
<UploadIcon className="w-8 h-8 mx-auto mb-2 text-gray-400" />
|
|
||||||
<p className="text-sm text-gray-600">
|
|
||||||
{form.getFieldValue("type") === "structured"
|
|
||||||
? "拖拽或点击上传Excel/CSV模板文件"
|
|
||||||
: "拖拽或点击上传文档文件"}
|
|
||||||
</p>
|
|
||||||
<Button
|
|
||||||
className="mt-2 bg-transparent pointer-events-none"
|
|
||||||
disabled
|
|
||||||
>
|
|
||||||
选择文件
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Upload>
|
|
||||||
{uploadedFiles.length > 0 && (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<p className="text-sm font-medium">已选择文件:</p>
|
|
||||||
<ul className="list-disc pl-5 text-sm text-gray-700">
|
|
||||||
{uploadedFiles.map((file, index) => (
|
|
||||||
<li key={index}>{file.name}</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "dataset",
|
|
||||||
label: "从数据集选择",
|
|
||||||
children: (
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="flex gap-2 mb-4">
|
|
||||||
<Input
|
|
||||||
placeholder="搜索数据集..."
|
|
||||||
value={datasetSearchQuery}
|
|
||||||
onChange={(e) =>
|
|
||||||
setDatasetSearchQuery(e.target.value)
|
|
||||||
}
|
|
||||||
className="flex-1"
|
|
||||||
/>
|
|
||||||
<Button onClick={() => setSelectedDatasetId(null)}>
|
|
||||||
重置选择
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-3 gap-4 h-80">
|
|
||||||
<div className="col-span-1 border rounded-lg overflow-y-auto p-2 space-y-2">
|
|
||||||
{datasets.length === 0 && (
|
|
||||||
<p className="text-center text-gray-500 py-4 text-sm">
|
|
||||||
无匹配数据集
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{datasets.map((dataset) => (
|
|
||||||
<div
|
|
||||||
key={dataset.id}
|
|
||||||
className={`flex items-center justify-between p-3 border rounded-lg cursor-pointer ${
|
|
||||||
selectedDatasetId === dataset.id
|
|
||||||
? "bg-blue-50 border-blue-500"
|
|
||||||
: "hover:bg-gray-50"
|
|
||||||
}`}
|
|
||||||
onClick={() => setSelectedDatasetId(dataset.id)}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Folder className="w-5 h-5 text-blue-400" />
|
|
||||||
<div>
|
|
||||||
<p className="font-medium">{dataset.name}</p>
|
|
||||||
<p className="text-xs text-gray-500">
|
|
||||||
{dataset.files.length} 个文件
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{selectedDatasetId === dataset.id && (
|
|
||||||
<CheckCircle className="w-5 h-5 text-blue-600" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="col-span-2 border rounded-lg overflow-y-auto p-2 space-y-2">
|
|
||||||
{!selectedDatasetId ? (
|
|
||||||
<div className="text-center py-8 text-gray-500">
|
|
||||||
<Folder className="w-8 h-8 mx-auto mb-2 text-gray-300" />
|
|
||||||
<p className="text-sm">请选择一个数据集</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div className="flex items-center gap-2 p-2 border-b pb-2">
|
|
||||||
<Checkbox
|
|
||||||
checked={isAllDatasetFilesSelected(
|
|
||||||
datasets.find(
|
|
||||||
(d) => d.id === selectedDatasetId
|
|
||||||
)!
|
|
||||||
)}
|
|
||||||
onChange={(e) =>
|
|
||||||
handleSelectAllDatasetFiles(
|
|
||||||
datasets.find(
|
|
||||||
(d) => d.id === selectedDatasetId
|
|
||||||
)!,
|
|
||||||
e.target.checked
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<span className="font-medium">
|
|
||||||
全选 (
|
|
||||||
{
|
|
||||||
datasets.find(
|
|
||||||
(d) => d.id === selectedDatasetId
|
|
||||||
)?.files.length
|
|
||||||
}{" "}
|
|
||||||
个文件)
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{datasets
|
|
||||||
.find((d) => d.id === selectedDatasetId)
|
|
||||||
?.files.map((file) => (
|
|
||||||
<div
|
|
||||||
key={file.id}
|
|
||||||
className="flex items-center justify-between p-3 border rounded-lg hover:bg-gray-50"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Checkbox
|
|
||||||
checked={isDatasetFileSelected(
|
|
||||||
selectedDatasetId!,
|
|
||||||
file.id
|
|
||||||
)}
|
|
||||||
onChange={() =>
|
|
||||||
handleDatasetFileToggle(
|
|
||||||
selectedDatasetId!,
|
|
||||||
file
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<File className="w-5 h-5 text-gray-400" />
|
|
||||||
<div>
|
|
||||||
<p className="font-medium">
|
|
||||||
{file.name}
|
|
||||||
</p>
|
|
||||||
<p className="text-sm text-gray-500">
|
|
||||||
{file.size} • {file.type}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{selectedDatasetFiles.length > 0 && (
|
|
||||||
<div className="mt-4 text-sm font-medium text-gray-700">
|
|
||||||
已选择数据集文件总数: {selectedDatasetFiles.length}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2 justify-end p-6 border-top">
|
|
||||||
<Button onClick={() => navigate("/data/knowledge-base")}>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
<Button type="primary" onClick={handleCreateKnowledgeBase}>
|
|
||||||
创建知识库
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default KnowledgeBaseCreatePage;
|
|
||||||
@@ -49,7 +49,6 @@ const getStatusColor = (status: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const KnowledgeBaseFileDetail: React.FC = () => {
|
const KnowledgeBaseFileDetail: React.FC = () => {
|
||||||
return <DevelopmentInProgress showTime="2025.10.30" />;
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
// 假设通过 props 或路由参数获取 selectedFile/selectedKB
|
// 假设通过 props 或路由参数获取 selectedFile/selectedKB
|
||||||
const [selectedFile] = useState<KBFile>(
|
const [selectedFile] = useState<KBFile>(
|
||||||
|
|||||||
@@ -1,189 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { Card, Button, Table, Tooltip, message } from "antd";
|
|
||||||
import { DeleteOutlined, EditOutlined } from "@ant-design/icons";
|
|
||||||
import { SearchControls } from "@/components/SearchControls";
|
|
||||||
import { useNavigate } from "react-router";
|
|
||||||
import CardView from "@/components/CardView";
|
|
||||||
import {
|
|
||||||
deleteKnowledgeBaseByIdUsingDelete,
|
|
||||||
queryKnowledgeBasesUsingPost,
|
|
||||||
} from "../knowledge-base.api";
|
|
||||||
import useFetchData from "@/hooks/useFetchData";
|
|
||||||
import { KnowledgeBaseItem } from "../knowledge-base.model";
|
|
||||||
import CreateKnowledgeBase from "../components/CreateKnowledgeBase";
|
|
||||||
import { mapKnowledgeBase } from "../knowledge-base.const";
|
|
||||||
|
|
||||||
export default function KnowledgeGenerationPage() {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [viewMode, setViewMode] = useState<"card" | "list">("card");
|
|
||||||
const [isEdit, setIsEdit] = useState(false);
|
|
||||||
const [currentKB, setCurrentKB] = useState<KnowledgeBaseItem | null>(null);
|
|
||||||
const {
|
|
||||||
loading,
|
|
||||||
tableData,
|
|
||||||
searchParams,
|
|
||||||
pagination,
|
|
||||||
fetchData,
|
|
||||||
setSearchParams,
|
|
||||||
handleFiltersChange,
|
|
||||||
} = useFetchData<KnowledgeBaseItem>(
|
|
||||||
queryKnowledgeBasesUsingPost,
|
|
||||||
mapKnowledgeBase
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleDeleteKB = async (kb: KnowledgeBaseItem) => {
|
|
||||||
try {
|
|
||||||
await deleteKnowledgeBaseByIdUsingDelete(kb.id);
|
|
||||||
message.success("知识库删除成功");
|
|
||||||
fetchData();
|
|
||||||
} catch (error) {
|
|
||||||
message.error("知识库删除失败");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const operations = [
|
|
||||||
{
|
|
||||||
key: "edit",
|
|
||||||
label: "编辑",
|
|
||||||
icon: <EditOutlined />,
|
|
||||||
onClick: (item) => {
|
|
||||||
setIsEdit(true);
|
|
||||||
setCurrentKB(item);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "delete",
|
|
||||||
label: "删除",
|
|
||||||
danger: true,
|
|
||||||
icon: <DeleteOutlined />,
|
|
||||||
confirm: {
|
|
||||||
title: "确认删除",
|
|
||||||
description: "此操作不可撤销,是否继续?",
|
|
||||||
okText: "删除",
|
|
||||||
okType: "danger",
|
|
||||||
cancelText: "取消",
|
|
||||||
},
|
|
||||||
onClick: (item) => handleDeleteKB(item),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: "知识库",
|
|
||||||
dataIndex: "name",
|
|
||||||
key: "name",
|
|
||||||
fixed: "left" as const,
|
|
||||||
width: 200,
|
|
||||||
ellipsis: true,
|
|
||||||
// render: (_: any, kb: KnowledgeBaseItem) => (
|
|
||||||
// <Button
|
|
||||||
// type="link"
|
|
||||||
// onClick={() => navigate(`/data/knowledge-base/detail/${kb.id}`)}
|
|
||||||
// >
|
|
||||||
// {kb.name}
|
|
||||||
// </Button>
|
|
||||||
// ),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "向量数据库",
|
|
||||||
dataIndex: "embeddingModel",
|
|
||||||
key: "embeddingModel",
|
|
||||||
width: 150,
|
|
||||||
ellipsis: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "大语言模型",
|
|
||||||
dataIndex: "chatModel",
|
|
||||||
key: "chatModel",
|
|
||||||
width: 150,
|
|
||||||
ellipsis: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "创建时间",
|
|
||||||
dataIndex: "createdAt",
|
|
||||||
key: "createdAt",
|
|
||||||
ellipsis: true,
|
|
||||||
width: 150,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "更新时间",
|
|
||||||
dataIndex: "updatedAt",
|
|
||||||
key: "updatedAt",
|
|
||||||
ellipsis: true,
|
|
||||||
width: 150,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "描述",
|
|
||||||
dataIndex: "description",
|
|
||||||
key: "description",
|
|
||||||
width: 120,
|
|
||||||
ellipsis: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "操作",
|
|
||||||
key: "actions",
|
|
||||||
fixed: "right" as const,
|
|
||||||
width: 150,
|
|
||||||
render: (_: any, kb: KnowledgeBaseItem) => (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{operations.map((op) => (
|
|
||||||
<Tooltip key={op.key} title={op.label}>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
icon={op.icon}
|
|
||||||
danger={op.danger}
|
|
||||||
onClick={() => op.onClick(kb)}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
// Main list view
|
|
||||||
return (
|
|
||||||
<div className="h-full flex flex-col gap-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<h1 className="text-xl font-bold">知识生成</h1>
|
|
||||||
<CreateKnowledgeBase
|
|
||||||
isEdit={isEdit}
|
|
||||||
data={currentKB}
|
|
||||||
onUpdate={fetchData}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SearchControls
|
|
||||||
searchTerm={searchParams.keyword}
|
|
||||||
onSearchChange={(keyword) =>
|
|
||||||
setSearchParams({ ...searchParams, keyword })
|
|
||||||
}
|
|
||||||
searchPlaceholder="搜索知识库..."
|
|
||||||
filters={[]}
|
|
||||||
onFiltersChange={handleFiltersChange}
|
|
||||||
onClearFilters={() => setSearchParams({ ...searchParams, filter: {} })}
|
|
||||||
viewMode={viewMode}
|
|
||||||
onViewModeChange={setViewMode}
|
|
||||||
showViewToggle
|
|
||||||
onReload={fetchData}
|
|
||||||
/>
|
|
||||||
{viewMode === "card" ? (
|
|
||||||
<CardView
|
|
||||||
data={tableData}
|
|
||||||
operations={operations}
|
|
||||||
// onView={(item) => navigate(`/data/knowledge-base/detail/${item.id}`)}
|
|
||||||
pagination={pagination}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Card>
|
|
||||||
<Table
|
|
||||||
loading={loading}
|
|
||||||
scroll={{ x: "max-content", y: "calc(100vh - 20rem)" }}
|
|
||||||
columns={columns}
|
|
||||||
dataSource={tableData}
|
|
||||||
rowKey="id"
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -2,8 +2,6 @@ import { get, post, put, del } from "@/utils/request";
|
|||||||
|
|
||||||
// 获取知识库列表
|
// 获取知识库列表
|
||||||
export function queryKnowledgeBasesUsingPost(params: any) {
|
export function queryKnowledgeBasesUsingPost(params: any) {
|
||||||
console.log("get tk", params);
|
|
||||||
|
|
||||||
return post("/api/knowledge-base/list", params);
|
return post("/api/knowledge-base/list", params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user