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:
Dallas98
2025-10-31 15:36:35 +08:00
committed by GitHub
parent 1797e221c9
commit 452d279195
4 changed files with 0 additions and 774 deletions

View File

@@ -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;

View File

@@ -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>(

View File

@@ -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>
);
}

View File

@@ -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);
} }