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 {
|
||||
Steps,
|
||||
@@ -36,18 +36,21 @@ import {
|
||||
Brain,
|
||||
} from "lucide-react";
|
||||
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;
|
||||
|
||||
export default function SynthesisTaskCreate() {
|
||||
return <DevelopmentInProgress showTime="2025.11.30" />;
|
||||
const navigate = useNavigate();
|
||||
const [form] = Form.useForm();
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [createStep, setCreateStep] = useState(1);
|
||||
const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
|
||||
const [datasets] = useState<Dataset[]>([]);
|
||||
const [selectedMap, setSelectedMap] = useState<Record<string, DatasetFile[]>>(
|
||||
{}
|
||||
);
|
||||
const [files] = useState<File[]>([]);
|
||||
const [selectedSynthesisTypes, setSelectedSynthesisTypes] = useState<
|
||||
string[]
|
||||
@@ -59,6 +62,15 @@ export default function SynthesisTaskCreate() {
|
||||
"distillation",
|
||||
]);
|
||||
|
||||
const fetchDatasets = async () => {
|
||||
const { data } = await queryDatasetsUsingGet({ page: 1, size: 1000 });
|
||||
setDatasets(data.content || []);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchDatasets();
|
||||
}, []);
|
||||
|
||||
// 表单数据
|
||||
const [formValues, setFormValues] = useState({
|
||||
name: "",
|
||||
@@ -270,7 +282,7 @@ export default function SynthesisTaskCreate() {
|
||||
const renderCreateTaskPage = () => {
|
||||
if (createStep === 1) {
|
||||
return (
|
||||
<Card className="overflow-y-auto p-2">
|
||||
<div className="flex-1 p-4 overflow-auto">
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
@@ -305,152 +317,11 @@ export default function SynthesisTaskCreate() {
|
||||
className="resize-none text-sm"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="源数据集"
|
||||
name="sourceDataset"
|
||||
rules={[{ required: true, message: "请选择数据集" }]}
|
||||
>
|
||||
<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>
|
||||
)}
|
||||
|
||||
<DatasetFileTransfer
|
||||
open
|
||||
selectedMap={selectedMap}
|
||||
onSelectedChange={setSelectedMap}
|
||||
/>
|
||||
<h2 className="font-medium text-gray-900 text-lg mt-6 mb-2">
|
||||
任务配置
|
||||
</h2>
|
||||
@@ -514,32 +385,8 @@ export default function SynthesisTaskCreate() {
|
||||
</Form.Item>
|
||||
</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>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1224,26 +1071,47 @@ export default function SynthesisTaskCreate() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className="p-6">
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<div className="flex items-center">
|
||||
<Link to="/data/synthesis/task">
|
||||
<Button type="text">
|
||||
<ArrowLeft className="w-4 h-4 mr-1" />
|
||||
</Button>
|
||||
</Link>
|
||||
<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 className="h-full flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<div className="flex items-center">
|
||||
<Link to="/data/synthesis/task">
|
||||
<Button type="text">
|
||||
<ArrowLeft className="w-4 h-4 mr-1" />
|
||||
</Button>
|
||||
</Link>
|
||||
<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 className="border-card flex-overflow-auto">
|
||||
{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>
|
||||
);
|
||||
|
||||
@@ -12,12 +12,10 @@ import {
|
||||
import { Plus, ArrowLeft, Play, Save, RefreshCw, Code, X } from "lucide-react";
|
||||
import { useNavigate } from "react-router";
|
||||
import { mockTemplates } from "@/mock/annotation";
|
||||
import DevelopmentInProgress from "@/components/DevelopmentInProgress";
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
export default function InstructionTemplateCreate() {
|
||||
return <DevelopmentInProgress showTime="2025.11.30" />;
|
||||
const navigate = useNavigate();
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(
|
||||
null
|
||||
@@ -129,7 +127,7 @@ export default function InstructionTemplateCreate() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<div className="h-full flex flex-col gap-4">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center">
|
||||
@@ -141,171 +139,170 @@ export default function InstructionTemplateCreate() {
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<Card className="overflow-y-auto p-2">
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
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内容" }]}
|
||||
<div className="flex-overflow-auto border-card p-4">
|
||||
<div className="flex-1 overflow-auto">
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={initialValues}
|
||||
autoComplete="off"
|
||||
>
|
||||
<TextArea
|
||||
placeholder="输入prompt内容,使用 {变量名} 格式定义变量"
|
||||
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"
|
||||
<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: "请输入模板名称" }]}
|
||||
>
|
||||
<Plus className="w-3 h-3 mr-1" />
|
||||
添加
|
||||
</Button>
|
||||
<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>
|
||||
</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 label="模板描述" name="description">
|
||||
<Input placeholder="简要描述模板的用途和特点" />
|
||||
</Form.Item>
|
||||
<Form.Item label="测试输出" name="testOutput">
|
||||
<TextArea
|
||||
readOnly
|
||||
placeholder="点击测试按钮查看输出结果"
|
||||
rows={5}
|
||||
className="resize-none bg-gray-50 text-sm"
|
||||
/>
|
||||
</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"
|
||||
<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内容" }]}
|
||||
>
|
||||
<Save className="w-3 h-3 mr-1" />
|
||||
保存模板
|
||||
</Button>
|
||||
<TextArea
|
||||
placeholder="输入prompt内容,使用 {变量名} 格式定义变量"
|
||||
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
|
||||
onClick={() => navigate("/data/synthesis/task")}
|
||||
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" />
|
||||
测试中...
|
||||
</>
|
||||
) : (
|
||||
<>测试模板</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Card>
|
||||
</Form>
|
||||
</div>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { useState } from "react";
|
||||
import { Tabs, Button } from "antd";
|
||||
import { PlusOutlined } from "@ant-design/icons";
|
||||
import { Plus, ArrowRight } from "lucide-react";
|
||||
import DataAnnotation from "../DataAnnotation/Annotate/components/TextAnnotation";
|
||||
import { useNavigate } from "react-router";
|
||||
import InstructionTemplateTab from "./components/InstructionTemplateTab";
|
||||
import SynthesisTaskTab from "./components/SynthesisTaskTab";
|
||||
import DevelopmentInProgress from "@/components/DevelopmentInProgress";
|
||||
|
||||
export default function DataSynthesisPage() {
|
||||
return <DevelopmentInProgress showTime="2025.11.30" />;
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [activeTab, setActiveTab] = useState("tasks");
|
||||
@@ -40,45 +39,42 @@ export default function DataSynthesisPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className=" p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-xl font-bold text-gray-900">数据合成</h2>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
navigate("/data/synthesis/task/create-template");
|
||||
}}
|
||||
>
|
||||
<Plus className="w-3 h-3 mr-1" />
|
||||
创建模板
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => navigate("/data/synthesis/task/create")}
|
||||
>
|
||||
<Plus className="w-3 h-3 mr-1" />
|
||||
创建合成任务
|
||||
</Button>
|
||||
</div>
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-xl font-bold text-gray-900">数据合成</h2>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
navigate("/data/synthesis/task/create-template");
|
||||
}}
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
创建模板
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => navigate("/data/synthesis/task/create")}
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
创建合成任务
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Tabs
|
||||
items={[
|
||||
{ key: "tasks", label: "合成任务", children: <SynthesisTaskTab /> },
|
||||
{
|
||||
key: "templates",
|
||||
label: "指令模板",
|
||||
children: <InstructionTemplateTab />,
|
||||
},
|
||||
]}
|
||||
activeKey={activeTab}
|
||||
onChange={setActiveTab}
|
||||
></Tabs>
|
||||
</div>
|
||||
|
||||
<Tabs
|
||||
items={[
|
||||
{ key: "tasks", label: "合成任务", children: <SynthesisTaskTab /> },
|
||||
{
|
||||
key: "templates",
|
||||
label: "指令模板",
|
||||
children: <InstructionTemplateTab />,
|
||||
},
|
||||
]}
|
||||
activeKey={activeTab}
|
||||
onChange={setActiveTab}
|
||||
></Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { Card, Table, Badge, Button } from "antd";
|
||||
import {
|
||||
Plus,
|
||||
FileText,
|
||||
Search,
|
||||
Edit,
|
||||
Copy,
|
||||
Trash2,
|
||||
MoreHorizontal,
|
||||
} from "lucide-react";
|
||||
import { EditOutlined, DeleteOutlined } from "@ant-design/icons";
|
||||
import { Plus, FileText } from "lucide-react";
|
||||
import type { Template } from "@/pages/SynthesisTask/synthesis";
|
||||
import { useNavigate } from "react-router";
|
||||
import { mockTemplates } from "@/mock/synthesis";
|
||||
@@ -45,6 +38,7 @@ export default function InstructionTemplateTab() {
|
||||
title: "模板名称",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
fixed: "left",
|
||||
render: (text: string, template: Template) => (
|
||||
<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">
|
||||
@@ -103,14 +97,7 @@ export default function InstructionTemplateTab() {
|
||||
title: "质量评分",
|
||||
dataIndex: "quality",
|
||||
key: "quality",
|
||||
render: (quality: number) =>
|
||||
quality ? (
|
||||
<Badge className={`font-medium text-xs ${getQualityColor(quality)}`}>
|
||||
{quality}%
|
||||
</Badge>
|
||||
) : (
|
||||
<span className="text-sm text-gray-400">-</span>
|
||||
),
|
||||
render: (quality: number) => (quality ? `${quality}%` : "-"),
|
||||
},
|
||||
{
|
||||
title: "最后使用",
|
||||
@@ -123,25 +110,14 @@ export default function InstructionTemplateTab() {
|
||||
{
|
||||
title: "操作",
|
||||
key: "actions",
|
||||
align: "center" as const,
|
||||
fixed: "right",
|
||||
render: (_: any, template: Template) => (
|
||||
<div className="flex items-center justify-center gap-1">
|
||||
<Button
|
||||
onClick={() =>
|
||||
navigate(`/data/synthesis/task/create-template/${template.id}`)
|
||||
}
|
||||
type="text"
|
||||
>
|
||||
<Edit className="w-3 h-3" />
|
||||
</Button>
|
||||
<div className="flex items-center justify-center">
|
||||
<Button type="text">
|
||||
<Copy className="w-3 h-3" />
|
||||
<EditOutlined />
|
||||
</Button>
|
||||
<Button type="text">
|
||||
<Trash2 className="w-3 h-3" />
|
||||
</Button>
|
||||
<Button type="text">
|
||||
<MoreHorizontal className="w-3 h-3" />
|
||||
<Button type="text" danger>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
|
||||
@@ -9,11 +9,14 @@ import {
|
||||
Play,
|
||||
DownloadIcon,
|
||||
CheckCircle,
|
||||
Check,
|
||||
StopCircle,
|
||||
} from "lucide-react";
|
||||
import type { SynthesisTask } from "@/pages/SynthesisTask/synthesis";
|
||||
import { mockSynthesisTasks } from "@/mock/synthesis";
|
||||
import { useNavigate } from "react-router";
|
||||
import { Link, useNavigate } from "react-router";
|
||||
import { SearchControls } from "@/components/SearchControls";
|
||||
import { formatDateTime } from "@/utils/unit";
|
||||
|
||||
export default function SynthesisTaskTab() {
|
||||
const navigate = useNavigate();
|
||||
@@ -73,28 +76,28 @@ export default function SynthesisTaskTab() {
|
||||
const getStatusBadge = (status: string) => {
|
||||
const statusConfig = {
|
||||
pending: {
|
||||
span: "等待中",
|
||||
color: "bg-yellow-50 text-yellow-700 border-yellow-200",
|
||||
label: "等待中",
|
||||
color: "#F59E0B",
|
||||
icon: Pause,
|
||||
},
|
||||
running: {
|
||||
span: "运行中",
|
||||
color: "bg-blue-50 text-blue-700 border-blue-200",
|
||||
label: "运行中",
|
||||
color: "#3B82F6",
|
||||
icon: Play,
|
||||
},
|
||||
completed: {
|
||||
span: "已完成",
|
||||
color: "bg-green-50 text-green-700 border-green-200",
|
||||
label: "已完成",
|
||||
color: "#10B981",
|
||||
icon: CheckCircle,
|
||||
},
|
||||
failed: {
|
||||
span: "失败",
|
||||
color: "bg-red-50 text-red-700 border-red-200",
|
||||
label: "失败",
|
||||
color: "#EF4444",
|
||||
icon: Pause,
|
||||
},
|
||||
paused: {
|
||||
span: "已暂停",
|
||||
color: "bg-gray-50 text-gray-700 border-gray-200",
|
||||
label: "已暂停",
|
||||
color: "#E5E7EB",
|
||||
icon: Pause,
|
||||
},
|
||||
};
|
||||
@@ -130,6 +133,7 @@ export default function SynthesisTaskTab() {
|
||||
),
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
fixed: "left" as const,
|
||||
render: (text: string, task: SynthesisTask) => (
|
||||
<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">
|
||||
@@ -139,7 +143,7 @@ export default function SynthesisTaskTab() {
|
||||
</span>
|
||||
</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>
|
||||
</div>
|
||||
@@ -149,11 +153,7 @@ export default function SynthesisTaskTab() {
|
||||
title: "类型",
|
||||
dataIndex: "type",
|
||||
key: "type",
|
||||
render: (type: string) => (
|
||||
<Badge className="bg-blue-50 text-blue-700 border-blue-200 text-xs">
|
||||
{type?.toUpperCase()}
|
||||
</Badge>
|
||||
),
|
||||
render: (type: string) => type.toUpperCase(),
|
||||
},
|
||||
{
|
||||
title: "状态",
|
||||
@@ -161,38 +161,17 @@ export default function SynthesisTaskTab() {
|
||||
key: "status",
|
||||
render: (status: string) => {
|
||||
const statusConfig = getStatusBadge(status);
|
||||
const StatusIcon = statusConfig.icon;
|
||||
return (
|
||||
<Badge
|
||||
className={`${statusConfig.color} flex items-center gap-1 w-fit text-xs`}
|
||||
>
|
||||
<StatusIcon className="w-3 h-3" />
|
||||
{statusConfig.span}
|
||||
</Badge>
|
||||
);
|
||||
return <Badge color={statusConfig.color} text={statusConfig.label} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "进度",
|
||||
dataIndex: "progress",
|
||||
key: "progress",
|
||||
render: (_: any, task: SynthesisTask) =>
|
||||
task.status === "running" ? (
|
||||
<div className="space-y-1">
|
||||
<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>
|
||||
),
|
||||
width: 150,
|
||||
render: (_: any, task: SynthesisTask) => (
|
||||
<Progress percent={task.progress} size="small" />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "源数据集",
|
||||
@@ -217,48 +196,18 @@ export default function SynthesisTaskTab() {
|
||||
title: "质量评分",
|
||||
dataIndex: "quality",
|
||||
key: "quality",
|
||||
render: (quality: number) =>
|
||||
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>
|
||||
),
|
||||
render: (quality: number) => (quality ? `${quality}%` : "-"),
|
||||
},
|
||||
{
|
||||
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>
|
||||
),
|
||||
title: "创建时间",
|
||||
dataIndex: "createdAt",
|
||||
key: "createdAt",
|
||||
render: (createdAt: string) => (
|
||||
<div className="text-sm text-gray-600">{createdAt}</div>
|
||||
),
|
||||
render: formatDateTime,
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "actions",
|
||||
align: "center" as const,
|
||||
fixed: "right" as const,
|
||||
render: (_: any, task: SynthesisTask) => (
|
||||
<div className="flex items-center justify-center gap-1">
|
||||
{task.status === "running" && (
|
||||
@@ -266,36 +215,24 @@ export default function SynthesisTaskTab() {
|
||||
onClick={() => handleTaskAction(task.id, "pause")}
|
||||
className="hover:bg-orange-50 p-1 h-7 w-7"
|
||||
type="text"
|
||||
>
|
||||
<Pause className="w-3 h-3" />
|
||||
</Button>
|
||||
icon={<Pause className="w-4 h-4" />}
|
||||
></Button>
|
||||
)}
|
||||
{task.status === "paused" && (
|
||||
<Button
|
||||
onClick={() => handleTaskAction(task.id, "resume")}
|
||||
className="hover:bg-green-50 p-1 h-7 w-7"
|
||||
type="text"
|
||||
>
|
||||
<Play className="w-3 h-3" />
|
||||
</Button>
|
||||
icon={<Play className="w-4 h-4" />}
|
||||
></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>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<div className="space-y-4">
|
||||
{/* 搜索和筛选 */}
|
||||
<SearchControls
|
||||
searchTerm={searchQuery}
|
||||
|
||||
Reference in New Issue
Block a user