You've already forked DataMate
add select dataset files component (#94)
* feat: Refactor AddDataDialog and introduce DatasetFileTransfer component for improved file selection and management * feat: Refactor SynthesisTask and InstructionTemplate components for improved UI and functionality; integrate DatasetFileTransfer for file management * feat: Enhance CollectionTaskCreate form with additional fields for MYSQL configuration and prefix input
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,253 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { Input, Table } from "antd";
|
||||||
|
import { RightOutlined } from "@ant-design/icons";
|
||||||
|
import { mapDataset } from "@/pages/DataManagement/dataset.const";
|
||||||
|
import {
|
||||||
|
Dataset,
|
||||||
|
DatasetFile,
|
||||||
|
DatasetType,
|
||||||
|
} from "@/pages/DataManagement/dataset.model";
|
||||||
|
import {
|
||||||
|
queryDatasetFilesUsingGet,
|
||||||
|
queryDatasetsUsingGet,
|
||||||
|
} from "@/pages/DataManagement/dataset.api";
|
||||||
|
import { formatBytes } from "@/utils/unit";
|
||||||
|
|
||||||
|
interface DatasetFileTransferProps
|
||||||
|
extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
|
open: boolean;
|
||||||
|
selectedMap: Record<string, DatasetFile[]>;
|
||||||
|
onSelectedChange: (filesMap: Record<string, DatasetFile[]>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Customize Table Transfer
|
||||||
|
const DatasetFileTransfer: React.FC<DatasetFileTransferProps> = ({
|
||||||
|
open,
|
||||||
|
selectedMap,
|
||||||
|
onSelectedChange,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const [datasets, setDatasets] = React.useState<Dataset[]>([]);
|
||||||
|
const [datasetSearch, setDatasetSearch] = React.useState<string>("");
|
||||||
|
const [datasetPagination, setDatasetPagination] = React.useState<{
|
||||||
|
current: number;
|
||||||
|
pageSize: number;
|
||||||
|
total: number;
|
||||||
|
}>({ current: 1, pageSize: 1000, total: 0 });
|
||||||
|
|
||||||
|
const [expandedRowKeys, setExpandedRowKeys] = React.useState<React.Key[]>([]);
|
||||||
|
|
||||||
|
const [loadedFiles, setLoadedFiles] = React.useState<
|
||||||
|
Record<string, DatasetFile[]>
|
||||||
|
>({});
|
||||||
|
const [filesSearch, setFilesSearch] = React.useState<string>("");
|
||||||
|
const [filesPagination, setFilesPagination] = React.useState<{
|
||||||
|
current: number;
|
||||||
|
pageSize: number;
|
||||||
|
total: number;
|
||||||
|
}>({ current: 1, pageSize: 10, total: 0 });
|
||||||
|
|
||||||
|
const selectedFiles = React.useMemo(() => {
|
||||||
|
const files: DatasetFile[] = [];
|
||||||
|
Object.values(selectedMap).forEach((fileList) => {
|
||||||
|
files.push(...fileList);
|
||||||
|
});
|
||||||
|
return files;
|
||||||
|
}, [selectedMap]);
|
||||||
|
|
||||||
|
const fetchDatasets = async () => {
|
||||||
|
const { data } = await queryDatasetsUsingGet({
|
||||||
|
page: datasetPagination.current - 1,
|
||||||
|
size: datasetPagination.pageSize,
|
||||||
|
keyword: datasetSearch,
|
||||||
|
type: DatasetType.TEXT,
|
||||||
|
});
|
||||||
|
setDatasets(data.content.map(mapDataset) || []);
|
||||||
|
setDatasetPagination((prev) => ({
|
||||||
|
...prev,
|
||||||
|
total: data.totalElements,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
fetchDatasets();
|
||||||
|
}
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
const fetchFiles = async (dataset: Dataset) => {
|
||||||
|
if (!dataset || loadedFiles[dataset.id]) return;
|
||||||
|
const { data } = await queryDatasetFilesUsingGet(dataset.id, {
|
||||||
|
page: filesPagination.current - 1,
|
||||||
|
size: 1000,
|
||||||
|
keyword: filesSearch,
|
||||||
|
});
|
||||||
|
setLoadedFiles((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[dataset.id]: data.content,
|
||||||
|
}));
|
||||||
|
setFilesPagination((prev) => ({
|
||||||
|
...prev,
|
||||||
|
total: data.totalElements,
|
||||||
|
}));
|
||||||
|
return data.content;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onExpand = (expanded: boolean, record: Dataset) => {
|
||||||
|
if (expanded) {
|
||||||
|
fetchFiles(record);
|
||||||
|
setExpandedRowKeys([...expandedRowKeys, record.id]);
|
||||||
|
} else {
|
||||||
|
setExpandedRowKeys(expandedRowKeys.filter((key) => key !== record.id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleSelectFile = (dataset: Dataset, record: DatasetFile) => {
|
||||||
|
const datasetFiles = selectedMap[dataset.id] || [];
|
||||||
|
const hasSelected = datasetFiles.find((file) => file.id === record.id);
|
||||||
|
let files = [...datasetFiles];
|
||||||
|
if (!hasSelected) {
|
||||||
|
files.push(record);
|
||||||
|
} else {
|
||||||
|
files = datasetFiles.filter((file) => file.id !== record.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newMap = { ...selectedMap, [dataset.id]: files };
|
||||||
|
if (files.length === 0) {
|
||||||
|
delete newMap[dataset.id];
|
||||||
|
}
|
||||||
|
onSelectedChange(newMap);
|
||||||
|
};
|
||||||
|
|
||||||
|
const datasetCols = [
|
||||||
|
{
|
||||||
|
title: "数据集名称",
|
||||||
|
dataIndex: "name",
|
||||||
|
key: "name",
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "文件数",
|
||||||
|
dataIndex: "fileCount",
|
||||||
|
key: "fileCount",
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "大小",
|
||||||
|
dataIndex: "totalSize",
|
||||||
|
key: "totalSize",
|
||||||
|
ellipsis: true,
|
||||||
|
render: formatBytes,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const fileCols = [
|
||||||
|
{
|
||||||
|
title: "文件名",
|
||||||
|
dataIndex: "fileName",
|
||||||
|
key: "fileName",
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "大小",
|
||||||
|
dataIndex: "size",
|
||||||
|
key: "size",
|
||||||
|
ellipsis: true,
|
||||||
|
render: formatBytes,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-25 gap-4 w-full" {...props}>
|
||||||
|
<div className="border-card flex flex-col col-span-12">
|
||||||
|
<div className="border-bottom p-2 font-bold">选择数据集文件</div>
|
||||||
|
<div className="p-2">
|
||||||
|
<Input
|
||||||
|
placeholder="搜索数据集名称..."
|
||||||
|
value={datasetSearch}
|
||||||
|
onChange={(e) => setDatasetSearch(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
scroll={{ y: 400 }}
|
||||||
|
rowKey="id"
|
||||||
|
size="small"
|
||||||
|
onRow={(record: Dataset) => ({
|
||||||
|
onClick: () => {
|
||||||
|
const isExpanded = expandedRowKeys.includes(record.id);
|
||||||
|
onExpand(!isExpanded, record);
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
dataSource={datasets}
|
||||||
|
columns={datasetCols}
|
||||||
|
pagination={datasetPagination}
|
||||||
|
rowSelection={{
|
||||||
|
type: "checkbox",
|
||||||
|
selectedRowKeys: Object.keys(selectedMap),
|
||||||
|
onSelect: async (record, isSelected) => {
|
||||||
|
let files = [];
|
||||||
|
if (!loadedFiles[record.id]) {
|
||||||
|
files = await fetchFiles(record);
|
||||||
|
} else {
|
||||||
|
files = loadedFiles[record.id];
|
||||||
|
}
|
||||||
|
|
||||||
|
const newMap = { ...selectedMap };
|
||||||
|
if (isSelected) {
|
||||||
|
newMap[record.id] = files;
|
||||||
|
} else {
|
||||||
|
delete newMap[record.id];
|
||||||
|
}
|
||||||
|
onSelectedChange(newMap);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
expandable={{
|
||||||
|
expandedRowKeys,
|
||||||
|
onExpand,
|
||||||
|
expandedRowRender: (dataset) => (
|
||||||
|
<Table
|
||||||
|
scroll={{ y: 400 }}
|
||||||
|
rowKey="id"
|
||||||
|
size="small"
|
||||||
|
dataSource={loadedFiles[dataset.id] || []}
|
||||||
|
columns={fileCols}
|
||||||
|
pagination={filesPagination}
|
||||||
|
onRow={(record: DatasetFile) => ({
|
||||||
|
onClick: () => toggleSelectFile(dataset, record),
|
||||||
|
})}
|
||||||
|
rowSelection={{
|
||||||
|
type: "checkbox",
|
||||||
|
selectedRowKeys: Object.values(
|
||||||
|
selectedMap[dataset.id] || {}
|
||||||
|
).map((file) => file.id),
|
||||||
|
onSelect: (record) => toggleSelectFile(dataset, record),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<RightOutlined />
|
||||||
|
<div className="border-card flex flex-col col-span-12">
|
||||||
|
<div className="border-bottom p-2 font-bold">
|
||||||
|
已选文件({selectedFiles.length})
|
||||||
|
</div>
|
||||||
|
<div className="p-2">
|
||||||
|
<Input
|
||||||
|
placeholder="搜索文件名称..."
|
||||||
|
value={filesSearch}
|
||||||
|
onChange={(e) => setFilesSearch(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
size="small"
|
||||||
|
scroll={{ y: 400 }}
|
||||||
|
rowKey="id"
|
||||||
|
dataSource={selectedFiles}
|
||||||
|
columns={fileCols}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DatasetFileTransfer;
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { Table, Transfer } from "antd";
|
|
||||||
import type {
|
|
||||||
GetProp,
|
|
||||||
TableColumnsType,
|
|
||||||
TableProps,
|
|
||||||
TransferProps,
|
|
||||||
} from "antd";
|
|
||||||
|
|
||||||
type TransferItem = GetProp<TransferProps, "dataSource">[number];
|
|
||||||
type TableRowSelection<T extends object> = TableProps<T>["rowSelection"];
|
|
||||||
|
|
||||||
interface DataType {
|
|
||||||
key: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TableTransferProps extends TransferProps<TransferItem> {
|
|
||||||
dataSource: DataType[];
|
|
||||||
leftColumns: TableColumnsType<DataType>;
|
|
||||||
rightColumns: TableColumnsType<DataType>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Customize Table Transfer
|
|
||||||
const TableTransfer: React.FC<TableTransferProps> = (props) => {
|
|
||||||
const { leftColumns, rightColumns, ...restProps } = props;
|
|
||||||
return (
|
|
||||||
<Transfer style={{ width: "100%" }} {...restProps}>
|
|
||||||
{({
|
|
||||||
direction,
|
|
||||||
filteredItems,
|
|
||||||
onItemSelect,
|
|
||||||
onItemSelectAll,
|
|
||||||
selectedKeys: listSelectedKeys,
|
|
||||||
disabled: listDisabled,
|
|
||||||
}) => {
|
|
||||||
const columns = direction === "left" ? leftColumns : rightColumns;
|
|
||||||
const rowSelection: TableRowSelection<TransferItem> = {
|
|
||||||
getCheckboxProps: () => ({ disabled: listDisabled }),
|
|
||||||
onChange(selectedRowKeys) {
|
|
||||||
onItemSelectAll(selectedRowKeys, "replace");
|
|
||||||
},
|
|
||||||
selectedRowKeys: listSelectedKeys,
|
|
||||||
selections: [
|
|
||||||
Table.SELECTION_ALL,
|
|
||||||
Table.SELECTION_INVERT,
|
|
||||||
Table.SELECTION_NONE,
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Table
|
|
||||||
rowSelection={rowSelection}
|
|
||||||
columns={columns}
|
|
||||||
dataSource={filteredItems}
|
|
||||||
size="small"
|
|
||||||
scroll={{ y: 300 }}
|
|
||||||
style={{ pointerEvents: listDisabled ? "none" : undefined }}
|
|
||||||
onRow={({ key, disabled: itemDisabled }) => ({
|
|
||||||
onClick: () => {
|
|
||||||
if (itemDisabled || listDisabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onItemSelect(key, !listSelectedKeys.includes(key));
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Transfer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TableTransfer;
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import type { Dataset } from "@/pages/DataManagement/dataset.model";
|
import type { Dataset } from "@/pages/DataManagement/dataset.model";
|
||||||
import {
|
import {
|
||||||
Steps,
|
Steps,
|
||||||
@@ -36,18 +36,21 @@ import {
|
|||||||
Brain,
|
Brain,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Link, useNavigate } from "react-router";
|
import { Link, useNavigate } from "react-router";
|
||||||
import DevelopmentInProgress from "@/components/DevelopmentInProgress";
|
import { queryDatasetsUsingGet } from "../DataManagement/dataset.api";
|
||||||
|
import { formatBytes } from "@/utils/unit";
|
||||||
|
import DatasetFileTransfer from "../KnowledgeBase/components/DatasetFileTransfer";
|
||||||
|
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
|
|
||||||
export default function SynthesisTaskCreate() {
|
export default function SynthesisTaskCreate() {
|
||||||
return <DevelopmentInProgress showTime="2025.11.30" />;
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [createStep, setCreateStep] = useState(1);
|
const [createStep, setCreateStep] = useState(1);
|
||||||
const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
|
const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
|
||||||
const [datasets] = useState<Dataset[]>([]);
|
const [selectedMap, setSelectedMap] = useState<Record<string, DatasetFile[]>>(
|
||||||
|
{}
|
||||||
|
);
|
||||||
const [files] = useState<File[]>([]);
|
const [files] = useState<File[]>([]);
|
||||||
const [selectedSynthesisTypes, setSelectedSynthesisTypes] = useState<
|
const [selectedSynthesisTypes, setSelectedSynthesisTypes] = useState<
|
||||||
string[]
|
string[]
|
||||||
@@ -59,6 +62,15 @@ export default function SynthesisTaskCreate() {
|
|||||||
"distillation",
|
"distillation",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const fetchDatasets = async () => {
|
||||||
|
const { data } = await queryDatasetsUsingGet({ page: 1, size: 1000 });
|
||||||
|
setDatasets(data.content || []);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchDatasets();
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const [formValues, setFormValues] = useState({
|
const [formValues, setFormValues] = useState({
|
||||||
name: "",
|
name: "",
|
||||||
@@ -270,7 +282,7 @@ export default function SynthesisTaskCreate() {
|
|||||||
const renderCreateTaskPage = () => {
|
const renderCreateTaskPage = () => {
|
||||||
if (createStep === 1) {
|
if (createStep === 1) {
|
||||||
return (
|
return (
|
||||||
<Card className="overflow-y-auto p-2">
|
<div className="flex-1 p-4 overflow-auto">
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
@@ -305,152 +317,11 @@ export default function SynthesisTaskCreate() {
|
|||||||
className="resize-none text-sm"
|
className="resize-none text-sm"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<DatasetFileTransfer
|
||||||
label="源数据集"
|
open
|
||||||
name="sourceDataset"
|
selectedMap={selectedMap}
|
||||||
rules={[{ required: true, message: "请选择数据集" }]}
|
onSelectedChange={setSelectedMap}
|
||||||
>
|
/>
|
||||||
<Select
|
|
||||||
className="w-full"
|
|
||||||
placeholder="选择数据集"
|
|
||||||
options={datasets.map((dataset) => ({
|
|
||||||
label: (
|
|
||||||
<div key={dataset.id}>
|
|
||||||
<div className="flex flex-col py-1">
|
|
||||||
<span className="font-medium text-sm">
|
|
||||||
{dataset.name}
|
|
||||||
</span>
|
|
||||||
<span className="text-xs text-gray-500">
|
|
||||||
{dataset.type} • {dataset.total}条 • {dataset.size}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
value: dataset.id,
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
{form.getFieldValue("sourceDataset") && (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<span className="text-sm font-semibold text-gray-700">
|
|
||||||
选择文件
|
|
||||||
</span>
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
{/* 文件选择区域 */}
|
|
||||||
<Card className="border-gray-200">
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="relative flex-1">
|
|
||||||
<Search className="w-3 h-3 absolute left-2 top-1/2 transform -translate-y-1/2 text-gray-400" />
|
|
||||||
<Input
|
|
||||||
placeholder="搜索文件..."
|
|
||||||
className="pl-7 h-8 text-sm"
|
|
||||||
value={searchQuery}
|
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
onClick={handleSelectAllFiles}
|
|
||||||
className="ml-2 text-xs"
|
|
||||||
type="default"
|
|
||||||
>
|
|
||||||
{selectedFiles.length ===
|
|
||||||
files.filter((file) =>
|
|
||||||
file.name
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchQuery.toLowerCase())
|
|
||||||
).length
|
|
||||||
? "取消全选"
|
|
||||||
: "全选"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
{files
|
|
||||||
.filter((file) =>
|
|
||||||
file.name
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchQuery.toLowerCase())
|
|
||||||
)
|
|
||||||
.map((file) => (
|
|
||||||
<div
|
|
||||||
key={file.id}
|
|
||||||
className="flex items-center space-x-2 p-2 hover:bg-gray-50 rounded"
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
checked={selectedFiles.includes(file.id)}
|
|
||||||
onChange={(e) => {
|
|
||||||
if (e.target.checked) {
|
|
||||||
setSelectedFiles([
|
|
||||||
...selectedFiles,
|
|
||||||
file.id,
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
setSelectedFiles(
|
|
||||||
selectedFiles.filter(
|
|
||||||
(id) => id !== file.id
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<p className="text-sm font-medium text-gray-900 truncate">
|
|
||||||
{file.name}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-gray-500">
|
|
||||||
{file.size} • {file.type}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
{/* 已选文件列表 */}
|
|
||||||
<Card className="border-gray-200">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="text-sm font-medium">已选文件</span>
|
|
||||||
<Badge count={selectedFiles.length} className="text-xs" />
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
{selectedFiles.length === 0 ? (
|
|
||||||
<div className="text-center py-4 text-xs text-gray-500">
|
|
||||||
暂未选择文件
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
selectedFiles.map((fileId) => {
|
|
||||||
const file = files.find((f) => f.id === fileId);
|
|
||||||
if (!file) return null;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={fileId}
|
|
||||||
className="flex items-center justify-between p-2 bg-blue-50 rounded border border-blue-200"
|
|
||||||
>
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<p className="text-sm font-medium text-blue-900 truncate">
|
|
||||||
{file.name}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-blue-600">
|
|
||||||
{file.size} • {file.type}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
onClick={() => handleRemoveSelectedFile(fileId)}
|
|
||||||
className="p-1 h-6 w-6 hover:bg-blue-100"
|
|
||||||
>
|
|
||||||
<X className="w-3 h-3" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<h2 className="font-medium text-gray-900 text-lg mt-6 mb-2">
|
<h2 className="font-medium text-gray-900 text-lg mt-6 mb-2">
|
||||||
任务配置
|
任务配置
|
||||||
</h2>
|
</h2>
|
||||||
@@ -514,32 +385,8 @@ export default function SynthesisTaskCreate() {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Divider />
|
|
||||||
<div className="flex gap-2 justify-end">
|
|
||||||
<Button onClick={() => navigate("/data/synthesis/task")}>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={() => {
|
|
||||||
form
|
|
||||||
.validateFields()
|
|
||||||
.then(() => setCreateStep(2))
|
|
||||||
.catch(() => {});
|
|
||||||
}}
|
|
||||||
disabled={
|
|
||||||
!form.getFieldValue("name") ||
|
|
||||||
!form.getFieldValue("sourceDataset") ||
|
|
||||||
selectedFiles.length === 0 ||
|
|
||||||
!form.getFieldValue("targetCount")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
下一步
|
|
||||||
<ArrowRight className="w-4 h-4 ml-2" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Form>
|
</Form>
|
||||||
</Card>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1224,26 +1071,47 @@ export default function SynthesisTaskCreate() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="h-full flex flex-col">
|
||||||
<div className="p-6">
|
{/* Header */}
|
||||||
{/* Header */}
|
<div className="flex justify-between items-center mb-2">
|
||||||
<div className="flex justify-between items-center mb-2">
|
<div className="flex items-center">
|
||||||
<div className="flex items-center">
|
<Link to="/data/synthesis/task">
|
||||||
<Link to="/data/synthesis/task">
|
<Button type="text">
|
||||||
<Button type="text">
|
<ArrowLeft className="w-4 h-4 mr-1" />
|
||||||
<ArrowLeft className="w-4 h-4 mr-1" />
|
</Button>
|
||||||
</Button>
|
</Link>
|
||||||
</Link>
|
<h1 className="text-xl font-bold bg-clip-text">创建合成任务</h1>
|
||||||
<h1 className="text-xl font-bold bg-clip-text">创建合成任务</h1>
|
|
||||||
</div>
|
|
||||||
<Steps
|
|
||||||
current={createStep - 1}
|
|
||||||
size="small"
|
|
||||||
items={[{ title: "基本信息" }, { title: "算子编排" }]}
|
|
||||||
style={{ width: "50%", marginLeft: "auto" }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<Steps
|
||||||
|
current={createStep - 1}
|
||||||
|
size="small"
|
||||||
|
items={[{ title: "基本信息" }, { title: "算子编排" }]}
|
||||||
|
style={{ width: "50%", marginLeft: "auto" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="border-card flex-overflow-auto">
|
||||||
{renderCreateTaskPage()}
|
{renderCreateTaskPage()}
|
||||||
|
<div className="flex gap-2 justify-end p-4 border-top">
|
||||||
|
<Button onClick={() => navigate("/data/synthesis/task")}>取消</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
form
|
||||||
|
.validateFields()
|
||||||
|
.then(() => setCreateStep(2))
|
||||||
|
.catch(() => {});
|
||||||
|
}}
|
||||||
|
disabled={
|
||||||
|
!form.getFieldValue("name") ||
|
||||||
|
!form.getFieldValue("sourceDataset") ||
|
||||||
|
selectedFiles.length === 0 ||
|
||||||
|
!form.getFieldValue("targetCount")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
下一步
|
||||||
|
<ArrowRight className="w-4 h-4 ml-2" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,12 +12,10 @@ import {
|
|||||||
import { Plus, ArrowLeft, Play, Save, RefreshCw, Code, X } from "lucide-react";
|
import { Plus, ArrowLeft, Play, Save, RefreshCw, Code, X } from "lucide-react";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import { mockTemplates } from "@/mock/annotation";
|
import { mockTemplates } from "@/mock/annotation";
|
||||||
import DevelopmentInProgress from "@/components/DevelopmentInProgress";
|
|
||||||
|
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
|
|
||||||
export default function InstructionTemplateCreate() {
|
export default function InstructionTemplateCreate() {
|
||||||
return <DevelopmentInProgress showTime="2025.11.30" />;
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(
|
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(
|
||||||
null
|
null
|
||||||
@@ -129,7 +127,7 @@ export default function InstructionTemplateCreate() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen">
|
<div className="h-full flex flex-col gap-4">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
@@ -141,171 +139,170 @@ export default function InstructionTemplateCreate() {
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Card className="overflow-y-auto p-2">
|
<div className="flex-overflow-auto border-card p-4">
|
||||||
<Form
|
<div className="flex-1 overflow-auto">
|
||||||
form={form}
|
<Form
|
||||||
layout="vertical"
|
form={form}
|
||||||
initialValues={initialValues}
|
layout="vertical"
|
||||||
autoComplete="off"
|
initialValues={initialValues}
|
||||||
>
|
autoComplete="off"
|
||||||
<h2 className="font-medium text-gray-900 text-lg mb-2">基本信息</h2>
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<Form.Item
|
|
||||||
label="模板名称"
|
|
||||||
name="name"
|
|
||||||
rules={[{ required: true, message: "请输入模板名称" }]}
|
|
||||||
>
|
|
||||||
<Input placeholder="输入模板名称" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="分类"
|
|
||||||
name="category"
|
|
||||||
rules={[{ required: true, message: "请选择分类" }]}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
placeholder="选择分类"
|
|
||||||
options={[
|
|
||||||
{ label: "问答对生成", value: "问答对生成" },
|
|
||||||
{ label: "蒸馏数据集", value: "蒸馏数据集" },
|
|
||||||
{ label: "文本生成", value: "文本生成" },
|
|
||||||
{ label: "多模态生成", value: "多模态生成" },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
<Form.Item label="模板描述" name="description">
|
|
||||||
<Input placeholder="简要描述模板的用途和特点" />
|
|
||||||
</Form.Item>
|
|
||||||
<h2 className="font-medium text-gray-900 text-lg mt-6 mb-2">
|
|
||||||
Prompt内容
|
|
||||||
</h2>
|
|
||||||
<Form.Item
|
|
||||||
label="Prompt内容"
|
|
||||||
name="prompt"
|
|
||||||
rules={[{ required: true, message: "请输入Prompt内容" }]}
|
|
||||||
>
|
>
|
||||||
<TextArea
|
<h2 className="font-medium text-gray-900 text-lg mb-2">基本信息</h2>
|
||||||
placeholder="输入prompt内容,使用 {变量名} 格式定义变量"
|
<div className="grid grid-cols-2 gap-4">
|
||||||
rows={10}
|
<Form.Item
|
||||||
className="font-mono text-xs resize-none"
|
label="模板名称"
|
||||||
onChange={handlePromptChange}
|
name="name"
|
||||||
/>
|
rules={[{ required: true, message: "请输入模板名称" }]}
|
||||||
</Form.Item>
|
|
||||||
<p className="text-xs text-gray-500 mb-2">
|
|
||||||
提示:使用 {"{变量名}"} 格式定义变量,例如 {"{text}"} 或 {"{input}"}
|
|
||||||
</p>
|
|
||||||
<div className="mb-4">
|
|
||||||
<span className="text-sm font-semibold text-gray-700">
|
|
||||||
变量管理
|
|
||||||
</span>
|
|
||||||
<div className="flex flex-wrap gap-2 min-h-[50px] p-3 border rounded-xl bg-gray-50 mt-2">
|
|
||||||
{variables.map((variable, index) => (
|
|
||||||
<Badge
|
|
||||||
key={index}
|
|
||||||
count={
|
|
||||||
<X
|
|
||||||
className="w-3 h-3 cursor-pointer"
|
|
||||||
onClick={() => handleRemoveVariable(index)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
style={{ backgroundColor: "#fff" }}
|
|
||||||
>
|
|
||||||
<span className="flex items-center gap-1 px-2 py-1 text-xs">
|
|
||||||
<Code className="w-3 h-3" />
|
|
||||||
{variable}
|
|
||||||
</span>
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
{variables.length === 0 && (
|
|
||||||
<span className="text-xs text-gray-400">
|
|
||||||
暂无变量,在Prompt中使用 {"{变量名}"} 格式定义
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2 mt-2">
|
|
||||||
<Input
|
|
||||||
ref={variableInputRef}
|
|
||||||
placeholder="添加变量名(如:text, input, question)"
|
|
||||||
className="h-8 text-sm"
|
|
||||||
onPressEnter={handleAddVariable}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
onClick={handleAddVariable}
|
|
||||||
type="default"
|
|
||||||
className="px-4 text-sm"
|
|
||||||
>
|
>
|
||||||
<Plus className="w-3 h-3 mr-1" />
|
<Input placeholder="输入模板名称" />
|
||||||
添加
|
</Form.Item>
|
||||||
</Button>
|
<Form.Item
|
||||||
|
label="分类"
|
||||||
|
name="category"
|
||||||
|
rules={[{ required: true, message: "请选择分类" }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
placeholder="选择分类"
|
||||||
|
options={[
|
||||||
|
{ label: "问答对生成", value: "问答对生成" },
|
||||||
|
{ label: "蒸馏数据集", value: "蒸馏数据集" },
|
||||||
|
{ label: "文本生成", value: "文本生成" },
|
||||||
|
{ label: "多模态生成", value: "多模态生成" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<Form.Item label="模板描述" name="description">
|
||||||
<h2 className="font-medium text-gray-900 text-lg mb-2 pt-2">
|
<Input placeholder="简要描述模板的用途和特点" />
|
||||||
模板测试
|
|
||||||
</h2>
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<Form.Item label="测试输入" name="testInput">
|
|
||||||
<TextArea
|
|
||||||
placeholder="输入测试数据"
|
|
||||||
rows={5}
|
|
||||||
className="resize-none text-sm"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="测试输出" name="testOutput">
|
<h2 className="font-medium text-gray-900 text-lg mt-6 mb-2">
|
||||||
<TextArea
|
Prompt内容
|
||||||
readOnly
|
</h2>
|
||||||
placeholder="点击测试按钮查看输出结果"
|
<Form.Item
|
||||||
rows={5}
|
label="Prompt内容"
|
||||||
className="resize-none bg-gray-50 text-sm"
|
name="prompt"
|
||||||
/>
|
rules={[{ required: true, message: "请输入Prompt内容" }]}
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
onClick={handleTestTemplate}
|
|
||||||
disabled={
|
|
||||||
!form.getFieldValue("prompt") ||
|
|
||||||
!form.getFieldValue("testInput") ||
|
|
||||||
isTestingTemplate
|
|
||||||
}
|
|
||||||
type="default"
|
|
||||||
className="px-4 py-2 text-sm"
|
|
||||||
>
|
|
||||||
{isTestingTemplate ? (
|
|
||||||
<>
|
|
||||||
<RefreshCw className="w-3 h-3 mr-1 animate-spin" />
|
|
||||||
测试中...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Play className="w-3 h-3 mr-1" />
|
|
||||||
测试模板
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
<Divider />
|
|
||||||
<div className="flex gap-2 justify-end">
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={handleSaveTemplate}
|
|
||||||
disabled={
|
|
||||||
!form.getFieldValue("name") ||
|
|
||||||
!form.getFieldValue("prompt") ||
|
|
||||||
!form.getFieldValue("category")
|
|
||||||
}
|
|
||||||
className="px-6 py-2 text-sm font-semibold bg-purple-600 hover:bg-purple-700 shadow-lg"
|
|
||||||
>
|
>
|
||||||
<Save className="w-3 h-3 mr-1" />
|
<TextArea
|
||||||
保存模板
|
placeholder="输入prompt内容,使用 {变量名} 格式定义变量"
|
||||||
</Button>
|
rows={10}
|
||||||
|
className="font-mono text-xs resize-none"
|
||||||
|
onChange={handlePromptChange}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<p className="text-xs text-gray-500 mb-2">
|
||||||
|
提示:使用 {"{变量名}"} 格式定义变量,例如 {"{text}"} 或{" "}
|
||||||
|
{"{input}"}
|
||||||
|
</p>
|
||||||
|
<div className="mb-4">
|
||||||
|
<span className="text-sm font-semibold text-gray-700">
|
||||||
|
变量管理
|
||||||
|
</span>
|
||||||
|
<div className="flex flex-wrap gap-2 min-h-[50px] p-3 border rounded-xl bg-gray-50 mt-2">
|
||||||
|
{variables.map((variable, index) => (
|
||||||
|
<Badge
|
||||||
|
key={index}
|
||||||
|
count={
|
||||||
|
<X
|
||||||
|
className="w-3 h-3 cursor-pointer"
|
||||||
|
onClick={() => handleRemoveVariable(index)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
style={{ backgroundColor: "#fff" }}
|
||||||
|
>
|
||||||
|
<span className="flex items-center gap-1 px-2 py-1 text-xs">
|
||||||
|
<Code className="w-3 h-3" />
|
||||||
|
{variable}
|
||||||
|
</span>
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
{variables.length === 0 && (
|
||||||
|
<span className="text-xs text-gray-400">
|
||||||
|
暂无变量,在Prompt中使用 {"{变量名}"} 格式定义
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 mt-2">
|
||||||
|
<Input
|
||||||
|
ref={variableInputRef}
|
||||||
|
placeholder="添加变量名(如:text, input, question)"
|
||||||
|
className="h-8 text-sm"
|
||||||
|
onPressEnter={handleAddVariable}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={handleAddVariable}
|
||||||
|
type="default"
|
||||||
|
className="px-4 text-sm"
|
||||||
|
>
|
||||||
|
<Plus className="w-3 h-3 mr-1" />
|
||||||
|
添加
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h2 className="font-medium text-gray-900 text-lg mb-2 pt-2">
|
||||||
|
模板测试
|
||||||
|
</h2>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<Form.Item label="测试输入" name="testInput">
|
||||||
|
<TextArea
|
||||||
|
placeholder="输入测试数据"
|
||||||
|
rows={5}
|
||||||
|
className="resize-none text-sm"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="测试输出" name="testOutput">
|
||||||
|
<TextArea
|
||||||
|
readOnly
|
||||||
|
placeholder="点击测试按钮查看输出结果"
|
||||||
|
rows={5}
|
||||||
|
className="resize-none bg-gray-50 text-sm"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => navigate("/data/synthesis/task")}
|
onClick={handleTestTemplate}
|
||||||
|
disabled={
|
||||||
|
!form.getFieldValue("prompt") ||
|
||||||
|
!form.getFieldValue("testInput") ||
|
||||||
|
isTestingTemplate
|
||||||
|
}
|
||||||
type="default"
|
type="default"
|
||||||
className="px-4 py-2 text-sm"
|
className="px-4 py-2 text-sm"
|
||||||
>
|
>
|
||||||
取消
|
{isTestingTemplate ? (
|
||||||
|
<>
|
||||||
|
<RefreshCw className="w-3 h-3 mr-1 animate-spin" />
|
||||||
|
测试中...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>测试模板</>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</Form>
|
||||||
</Form>
|
</div>
|
||||||
</Card>
|
<div className="flex gap-2 justify-end p-4 border-top">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={handleSaveTemplate}
|
||||||
|
disabled={
|
||||||
|
!form.getFieldValue("name") ||
|
||||||
|
!form.getFieldValue("prompt") ||
|
||||||
|
!form.getFieldValue("category")
|
||||||
|
}
|
||||||
|
className="px-6 py-2 text-sm font-semibold bg-purple-600 hover:bg-purple-700 shadow-lg"
|
||||||
|
>
|
||||||
|
<Save className="w-3 h-3 mr-1" />
|
||||||
|
保存模板
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => navigate("/data/synthesis/task")}
|
||||||
|
type="default"
|
||||||
|
className="px-4 py-2 text-sm"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Tabs, Button } from "antd";
|
import { Tabs, Button } from "antd";
|
||||||
|
import { PlusOutlined } from "@ant-design/icons";
|
||||||
import { Plus, ArrowRight } from "lucide-react";
|
import { Plus, ArrowRight } from "lucide-react";
|
||||||
import DataAnnotation from "../DataAnnotation/Annotate/components/TextAnnotation";
|
import DataAnnotation from "../DataAnnotation/Annotate/components/TextAnnotation";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import InstructionTemplateTab from "./components/InstructionTemplateTab";
|
import InstructionTemplateTab from "./components/InstructionTemplateTab";
|
||||||
import SynthesisTaskTab from "./components/SynthesisTaskTab";
|
import SynthesisTaskTab from "./components/SynthesisTaskTab";
|
||||||
import DevelopmentInProgress from "@/components/DevelopmentInProgress";
|
|
||||||
|
|
||||||
export default function DataSynthesisPage() {
|
export default function DataSynthesisPage() {
|
||||||
return <DevelopmentInProgress showTime="2025.11.30" />;
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState("tasks");
|
const [activeTab, setActiveTab] = useState("tasks");
|
||||||
@@ -40,45 +39,42 @@ export default function DataSynthesisPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="h-full flex flex-col">
|
||||||
<div className=" p-6">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center justify-between">
|
<div className="space-y-1">
|
||||||
<div className="space-y-1">
|
<h2 className="text-xl font-bold text-gray-900">数据合成</h2>
|
||||||
<h2 className="text-xl font-bold text-gray-900">数据合成</h2>
|
</div>
|
||||||
</div>
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
<div className="flex items-center gap-2">
|
onClick={() => {
|
||||||
<Button
|
navigate("/data/synthesis/task/create-template");
|
||||||
onClick={() => {
|
}}
|
||||||
navigate("/data/synthesis/task/create-template");
|
icon={<PlusOutlined />}
|
||||||
}}
|
>
|
||||||
>
|
创建模板
|
||||||
<Plus className="w-3 h-3 mr-1" />
|
</Button>
|
||||||
创建模板
|
<Button
|
||||||
</Button>
|
type="primary"
|
||||||
<Button
|
onClick={() => navigate("/data/synthesis/task/create")}
|
||||||
type="primary"
|
icon={<PlusOutlined />}
|
||||||
onClick={() => navigate("/data/synthesis/task/create")}
|
>
|
||||||
>
|
创建合成任务
|
||||||
<Plus className="w-3 h-3 mr-1" />
|
</Button>
|
||||||
创建合成任务
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs
|
|
||||||
items={[
|
|
||||||
{ key: "tasks", label: "合成任务", children: <SynthesisTaskTab /> },
|
|
||||||
{
|
|
||||||
key: "templates",
|
|
||||||
label: "指令模板",
|
|
||||||
children: <InstructionTemplateTab />,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
activeKey={activeTab}
|
|
||||||
onChange={setActiveTab}
|
|
||||||
></Tabs>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Tabs
|
||||||
|
items={[
|
||||||
|
{ key: "tasks", label: "合成任务", children: <SynthesisTaskTab /> },
|
||||||
|
{
|
||||||
|
key: "templates",
|
||||||
|
label: "指令模板",
|
||||||
|
children: <InstructionTemplateTab />,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
activeKey={activeTab}
|
||||||
|
onChange={setActiveTab}
|
||||||
|
></Tabs>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Card, Table, Badge, Button } from "antd";
|
import { Card, Table, Badge, Button } from "antd";
|
||||||
import {
|
import { EditOutlined, DeleteOutlined } from "@ant-design/icons";
|
||||||
Plus,
|
import { Plus, FileText } from "lucide-react";
|
||||||
FileText,
|
|
||||||
Search,
|
|
||||||
Edit,
|
|
||||||
Copy,
|
|
||||||
Trash2,
|
|
||||||
MoreHorizontal,
|
|
||||||
} from "lucide-react";
|
|
||||||
import type { Template } from "@/pages/SynthesisTask/synthesis";
|
import type { Template } from "@/pages/SynthesisTask/synthesis";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import { mockTemplates } from "@/mock/synthesis";
|
import { mockTemplates } from "@/mock/synthesis";
|
||||||
@@ -45,6 +38,7 @@ export default function InstructionTemplateTab() {
|
|||||||
title: "模板名称",
|
title: "模板名称",
|
||||||
dataIndex: "name",
|
dataIndex: "name",
|
||||||
key: "name",
|
key: "name",
|
||||||
|
fixed: "left",
|
||||||
render: (text: string, template: Template) => (
|
render: (text: string, template: Template) => (
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-8 h-8 bg-purple-500 rounded-lg flex items-center justify-center shadow-sm">
|
<div className="w-8 h-8 bg-purple-500 rounded-lg flex items-center justify-center shadow-sm">
|
||||||
@@ -103,14 +97,7 @@ export default function InstructionTemplateTab() {
|
|||||||
title: "质量评分",
|
title: "质量评分",
|
||||||
dataIndex: "quality",
|
dataIndex: "quality",
|
||||||
key: "quality",
|
key: "quality",
|
||||||
render: (quality: number) =>
|
render: (quality: number) => (quality ? `${quality}%` : "-"),
|
||||||
quality ? (
|
|
||||||
<Badge className={`font-medium text-xs ${getQualityColor(quality)}`}>
|
|
||||||
{quality}%
|
|
||||||
</Badge>
|
|
||||||
) : (
|
|
||||||
<span className="text-sm text-gray-400">-</span>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "最后使用",
|
title: "最后使用",
|
||||||
@@ -123,25 +110,14 @@ export default function InstructionTemplateTab() {
|
|||||||
{
|
{
|
||||||
title: "操作",
|
title: "操作",
|
||||||
key: "actions",
|
key: "actions",
|
||||||
align: "center" as const,
|
fixed: "right",
|
||||||
render: (_: any, template: Template) => (
|
render: (_: any, template: Template) => (
|
||||||
<div className="flex items-center justify-center gap-1">
|
<div className="flex items-center justify-center">
|
||||||
<Button
|
|
||||||
onClick={() =>
|
|
||||||
navigate(`/data/synthesis/task/create-template/${template.id}`)
|
|
||||||
}
|
|
||||||
type="text"
|
|
||||||
>
|
|
||||||
<Edit className="w-3 h-3" />
|
|
||||||
</Button>
|
|
||||||
<Button type="text">
|
<Button type="text">
|
||||||
<Copy className="w-3 h-3" />
|
<EditOutlined />
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="text">
|
<Button type="text" danger>
|
||||||
<Trash2 className="w-3 h-3" />
|
<DeleteOutlined />
|
||||||
</Button>
|
|
||||||
<Button type="text">
|
|
||||||
<MoreHorizontal className="w-3 h-3" />
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -9,11 +9,14 @@ import {
|
|||||||
Play,
|
Play,
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
|
Check,
|
||||||
|
StopCircle,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import type { SynthesisTask } from "@/pages/SynthesisTask/synthesis";
|
import type { SynthesisTask } from "@/pages/SynthesisTask/synthesis";
|
||||||
import { mockSynthesisTasks } from "@/mock/synthesis";
|
import { mockSynthesisTasks } from "@/mock/synthesis";
|
||||||
import { useNavigate } from "react-router";
|
import { Link, useNavigate } from "react-router";
|
||||||
import { SearchControls } from "@/components/SearchControls";
|
import { SearchControls } from "@/components/SearchControls";
|
||||||
|
import { formatDateTime } from "@/utils/unit";
|
||||||
|
|
||||||
export default function SynthesisTaskTab() {
|
export default function SynthesisTaskTab() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -73,28 +76,28 @@ export default function SynthesisTaskTab() {
|
|||||||
const getStatusBadge = (status: string) => {
|
const getStatusBadge = (status: string) => {
|
||||||
const statusConfig = {
|
const statusConfig = {
|
||||||
pending: {
|
pending: {
|
||||||
span: "等待中",
|
label: "等待中",
|
||||||
color: "bg-yellow-50 text-yellow-700 border-yellow-200",
|
color: "#F59E0B",
|
||||||
icon: Pause,
|
icon: Pause,
|
||||||
},
|
},
|
||||||
running: {
|
running: {
|
||||||
span: "运行中",
|
label: "运行中",
|
||||||
color: "bg-blue-50 text-blue-700 border-blue-200",
|
color: "#3B82F6",
|
||||||
icon: Play,
|
icon: Play,
|
||||||
},
|
},
|
||||||
completed: {
|
completed: {
|
||||||
span: "已完成",
|
label: "已完成",
|
||||||
color: "bg-green-50 text-green-700 border-green-200",
|
color: "#10B981",
|
||||||
icon: CheckCircle,
|
icon: CheckCircle,
|
||||||
},
|
},
|
||||||
failed: {
|
failed: {
|
||||||
span: "失败",
|
label: "失败",
|
||||||
color: "bg-red-50 text-red-700 border-red-200",
|
color: "#EF4444",
|
||||||
icon: Pause,
|
icon: Pause,
|
||||||
},
|
},
|
||||||
paused: {
|
paused: {
|
||||||
span: "已暂停",
|
label: "已暂停",
|
||||||
color: "bg-gray-50 text-gray-700 border-gray-200",
|
color: "#E5E7EB",
|
||||||
icon: Pause,
|
icon: Pause,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -130,6 +133,7 @@ export default function SynthesisTaskTab() {
|
|||||||
),
|
),
|
||||||
dataIndex: "name",
|
dataIndex: "name",
|
||||||
key: "name",
|
key: "name",
|
||||||
|
fixed: "left" as const,
|
||||||
render: (text: string, task: SynthesisTask) => (
|
render: (text: string, task: SynthesisTask) => (
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-8 h-8 bg-blue-500 rounded-lg flex items-center justify-center shadow-sm">
|
<div className="w-8 h-8 bg-blue-500 rounded-lg flex items-center justify-center shadow-sm">
|
||||||
@@ -139,7 +143,7 @@ export default function SynthesisTaskTab() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium text-gray-900 text-sm">{task.name}</div>
|
<Link to={`/data/synthesis/task/${task.id}`}>{task.name}</Link>
|
||||||
<div className="text-xs text-gray-500">{task.template}</div>
|
<div className="text-xs text-gray-500">{task.template}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -149,11 +153,7 @@ export default function SynthesisTaskTab() {
|
|||||||
title: "类型",
|
title: "类型",
|
||||||
dataIndex: "type",
|
dataIndex: "type",
|
||||||
key: "type",
|
key: "type",
|
||||||
render: (type: string) => (
|
render: (type: string) => type.toUpperCase(),
|
||||||
<Badge className="bg-blue-50 text-blue-700 border-blue-200 text-xs">
|
|
||||||
{type?.toUpperCase()}
|
|
||||||
</Badge>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "状态",
|
title: "状态",
|
||||||
@@ -161,38 +161,17 @@ export default function SynthesisTaskTab() {
|
|||||||
key: "status",
|
key: "status",
|
||||||
render: (status: string) => {
|
render: (status: string) => {
|
||||||
const statusConfig = getStatusBadge(status);
|
const statusConfig = getStatusBadge(status);
|
||||||
const StatusIcon = statusConfig.icon;
|
return <Badge color={statusConfig.color} text={statusConfig.label} />;
|
||||||
return (
|
|
||||||
<Badge
|
|
||||||
className={`${statusConfig.color} flex items-center gap-1 w-fit text-xs`}
|
|
||||||
>
|
|
||||||
<StatusIcon className="w-3 h-3" />
|
|
||||||
{statusConfig.span}
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "进度",
|
title: "进度",
|
||||||
dataIndex: "progress",
|
dataIndex: "progress",
|
||||||
key: "progress",
|
key: "progress",
|
||||||
render: (_: any, task: SynthesisTask) =>
|
width: 150,
|
||||||
task.status === "running" ? (
|
render: (_: any, task: SynthesisTask) => (
|
||||||
<div className="space-y-1">
|
<Progress percent={task.progress} size="small" />
|
||||||
<Progress percent={task.progress} size="small" showInfo={false} />
|
),
|
||||||
<div className="text-xs text-gray-500">
|
|
||||||
{Math.round(task.progress)}%
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="text-sm text-gray-600">
|
|
||||||
{task.status === "completed"
|
|
||||||
? "100%"
|
|
||||||
: task.status === "failed"
|
|
||||||
? `${Math.round(task.progress)}%`
|
|
||||||
: "-"}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "源数据集",
|
title: "源数据集",
|
||||||
@@ -217,48 +196,18 @@ export default function SynthesisTaskTab() {
|
|||||||
title: "质量评分",
|
title: "质量评分",
|
||||||
dataIndex: "quality",
|
dataIndex: "quality",
|
||||||
key: "quality",
|
key: "quality",
|
||||||
render: (quality: number) =>
|
render: (quality: number) => (quality ? `${quality}%` : "-"),
|
||||||
quality ? (
|
|
||||||
<Badge className="font-medium text-xs text-green-600 bg-green-50 border-green-200">
|
|
||||||
{quality}%
|
|
||||||
</Badge>
|
|
||||||
) : (
|
|
||||||
<span className="text-sm text-gray-400">-</span>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: (
|
title: "创建时间",
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
onClick={() => {
|
|
||||||
if (sortBy === "createdAt") {
|
|
||||||
setSortOrder(sortOrder === "asc" ? "desc" : "asc");
|
|
||||||
} else {
|
|
||||||
setSortBy("createdAt");
|
|
||||||
setSortOrder("desc");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="h-auto p-0 font-semibold text-gray-700 hover:bg-transparent"
|
|
||||||
>
|
|
||||||
创建时间
|
|
||||||
{sortBy === "createdAt" &&
|
|
||||||
(sortOrder === "asc" ? (
|
|
||||||
<ArrowUp className="w-3 h-3 ml-1" />
|
|
||||||
) : (
|
|
||||||
<ArrowDown className="w-3 h-3 ml-1" />
|
|
||||||
))}
|
|
||||||
</Button>
|
|
||||||
),
|
|
||||||
dataIndex: "createdAt",
|
dataIndex: "createdAt",
|
||||||
key: "createdAt",
|
key: "createdAt",
|
||||||
render: (createdAt: string) => (
|
render: formatDateTime,
|
||||||
<div className="text-sm text-gray-600">{createdAt}</div>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "操作",
|
title: "操作",
|
||||||
key: "actions",
|
key: "actions",
|
||||||
align: "center" as const,
|
fixed: "right" as const,
|
||||||
render: (_: any, task: SynthesisTask) => (
|
render: (_: any, task: SynthesisTask) => (
|
||||||
<div className="flex items-center justify-center gap-1">
|
<div className="flex items-center justify-center gap-1">
|
||||||
{task.status === "running" && (
|
{task.status === "running" && (
|
||||||
@@ -266,36 +215,24 @@ export default function SynthesisTaskTab() {
|
|||||||
onClick={() => handleTaskAction(task.id, "pause")}
|
onClick={() => handleTaskAction(task.id, "pause")}
|
||||||
className="hover:bg-orange-50 p-1 h-7 w-7"
|
className="hover:bg-orange-50 p-1 h-7 w-7"
|
||||||
type="text"
|
type="text"
|
||||||
>
|
icon={<Pause className="w-4 h-4" />}
|
||||||
<Pause className="w-3 h-3" />
|
></Button>
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
{task.status === "paused" && (
|
{task.status === "paused" && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleTaskAction(task.id, "resume")}
|
onClick={() => handleTaskAction(task.id, "resume")}
|
||||||
className="hover:bg-green-50 p-1 h-7 w-7"
|
className="hover:bg-green-50 p-1 h-7 w-7"
|
||||||
type="text"
|
type="text"
|
||||||
>
|
icon={<Play className="w-4 h-4" />}
|
||||||
<Play className="w-3 h-3" />
|
></Button>
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
<Button
|
|
||||||
className="hover:bg-blue-50 p-2 h-7 w-7"
|
|
||||||
type="text"
|
|
||||||
onClick={() => navigate(`/data/synthesis/task/${task.id}`)}
|
|
||||||
>
|
|
||||||
审核
|
|
||||||
</Button>
|
|
||||||
<Button className="hover:bg-green-50 p-1 h-7 w-7" type="text">
|
|
||||||
<DownloadIcon className="w-3 h-3" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<div className="space-y-4">
|
||||||
{/* 搜索和筛选 */}
|
{/* 搜索和筛选 */}
|
||||||
<SearchControls
|
<SearchControls
|
||||||
searchTerm={searchQuery}
|
searchTerm={searchQuery}
|
||||||
|
|||||||
Reference in New Issue
Block a user